Add docs for Client SDK in Python
diff --git a/examples/python/builtin_echo.py b/examples/python/builtin_echo.py
index d9569b2..f60fd9a 100644
--- a/examples/python/builtin_echo.py
+++ b/examples/python/builtin_echo.py
@@ -15,10 +15,9 @@
self.user_password = user_password
def echo(self, message="Hello, Teaclave!"):
- channel = AuthenticationService(AUTHENTICATION_SERVICE_ADDRESS,
- AS_ROOT_CA_CERT_PATH,
- ENCLAVE_INFO_PATH).connect()
- client = AuthenticationClient(channel)
+ client = AuthenticationService(
+ AUTHENTICATION_SERVICE_ADDRESS, AS_ROOT_CA_CERT_PATH,
+ ENCLAVE_INFO_PATH).connect().get_client()
print("[+] registering user")
client.user_register(self.user_id, self.user_password)
@@ -26,11 +25,11 @@
print("[+] login")
token = client.user_login(self.user_id, self.user_password)
- channel = FrontendService(FRONTEND_SERVICE_ADDRESS,
- AS_ROOT_CA_CERT_PATH,
- ENCLAVE_INFO_PATH).connect()
+ client = FrontendService(FRONTEND_SERVICE_ADDRESS,
+ AS_ROOT_CA_CERT_PATH,
+ ENCLAVE_INFO_PATH).connect().get_client()
metadata = {"id": self.user_id, "token": token}
- client = FrontendClient(channel, metadata)
+ client.metadata = metadata
print("[+] registering function")
function_id = client.register_function(
diff --git a/examples/python/builtin_gbdt_train.py b/examples/python/builtin_gbdt_train.py
index 7d72715..e565ba1 100644
--- a/examples/python/builtin_gbdt_train.py
+++ b/examples/python/builtin_gbdt_train.py
@@ -3,46 +3,22 @@
import sys
from teaclave import (AuthenticationService, FrontendService,
- AuthenticationClient, FrontendClient)
+ AuthenticationClient, FrontendClient, FunctionInput,
+ FunctionOutput, OwnerList, DataMap)
from utils import (AUTHENTICATION_SERVICE_ADDRESS, FRONTEND_SERVICE_ADDRESS,
AS_ROOT_CA_CERT_PATH, ENCLAVE_INFO_PATH, USER_ID,
USER_PASSWORD)
-class FunctionInput:
- def __init__(self, name, description):
- self.name = name
- self.description = description
-
-
-class FunctionOutput:
- def __init__(self, name, description):
- self.name = name
- self.description = description
-
-
-class OwnerList:
- def __init__(self, data_name, uids):
- self.data_name = data_name
- self.uids = uids
-
-
-class DataList:
- def __init__(self, data_name, data_id):
- self.data_name = data_name
- self.data_id = data_id
-
-
class BuiltinGbdtExample:
def __init__(self, user_id, user_password):
self.user_id = user_id
self.user_password = user_password
def gbdt(self):
- channel = AuthenticationService(AUTHENTICATION_SERVICE_ADDRESS,
- AS_ROOT_CA_CERT_PATH,
- ENCLAVE_INFO_PATH).connect()
- client = AuthenticationClient(channel)
+ client = AuthenticationService(
+ AUTHENTICATION_SERVICE_ADDRESS, AS_ROOT_CA_CERT_PATH,
+ ENCLAVE_INFO_PATH).connect().get_client()
print("[+] registering user")
client.user_register(self.user_id, self.user_password)
@@ -50,11 +26,11 @@
print("[+] login")
token = client.user_login(self.user_id, self.user_password)
- channel = FrontendService(FRONTEND_SERVICE_ADDRESS,
- AS_ROOT_CA_CERT_PATH,
- ENCLAVE_INFO_PATH).connect()
+ client = FrontendService(FRONTEND_SERVICE_ADDRESS,
+ AS_ROOT_CA_CERT_PATH,
+ ENCLAVE_INFO_PATH).connect().get_client()
metadata = {"id": self.user_id, "token": token}
- client = FrontendClient(channel, metadata)
+ client.metadata = metadata
print("[+] registering function")
function_id = client.register_function(
@@ -110,8 +86,8 @@
print("[+] assigning data to task")
client.assign_data_to_task(
- task_id, [DataList("training_data", training_data_id)],
- [DataList("trained_model", output_model_id)])
+ task_id, [DataMap("training_data", training_data_id)],
+ [DataMap("trained_model", output_model_id)])
print("[+] approving task")
client.approve_task(task_id)
diff --git a/examples/python/builtin_online_decrypt.py b/examples/python/builtin_online_decrypt.py
index d14cf7a..e660e57 100644
--- a/examples/python/builtin_online_decrypt.py
+++ b/examples/python/builtin_online_decrypt.py
@@ -16,10 +16,9 @@
self.user_password = user_password
def decrypt(self, key, nonce, encrypted_data, algorithm):
- channel = AuthenticationService(AUTHENTICATION_SERVICE_ADDRESS,
- AS_ROOT_CA_CERT_PATH,
- ENCLAVE_INFO_PATH).connect()
- client = AuthenticationClient(channel)
+ client = AuthenticationService(
+ AUTHENTICATION_SERVICE_ADDRESS, AS_ROOT_CA_CERT_PATH,
+ ENCLAVE_INFO_PATH).connect().get_client()
print("[+] registering user")
client.user_register(self.user_id, self.user_password)
@@ -27,11 +26,11 @@
print("[+] login")
token = client.user_login(self.user_id, self.user_password)
- channel = FrontendService(FRONTEND_SERVICE_ADDRESS,
- AS_ROOT_CA_CERT_PATH,
- ENCLAVE_INFO_PATH).connect()
+ client = FrontendService(FRONTEND_SERVICE_ADDRESS,
+ AS_ROOT_CA_CERT_PATH,
+ ENCLAVE_INFO_PATH).connect().get_client()
metadata = {"id": self.user_id, "token": token}
- client = FrontendClient(channel, metadata)
+ client.metadata = metadata
print("[+] registering function")
function_id = client.register_function(
diff --git a/examples/python/mesapy_echo.py b/examples/python/mesapy_echo.py
index 570adc7..3f12512 100644
--- a/examples/python/mesapy_echo.py
+++ b/examples/python/mesapy_echo.py
@@ -17,10 +17,9 @@
def echo(self,
payload_file="mesapy_echo_payload.py",
message="Hello, Teaclave!"):
- channel = AuthenticationService(AUTHENTICATION_SERVICE_ADDRESS,
- AS_ROOT_CA_CERT_PATH,
- ENCLAVE_INFO_PATH).connect()
- client = AuthenticationClient(channel)
+ client = AuthenticationService(
+ AUTHENTICATION_SERVICE_ADDRESS, AS_ROOT_CA_CERT_PATH,
+ ENCLAVE_INFO_PATH).connect().get_client()
print("[+] registering user")
client.user_register(self.user_id, self.user_password)
@@ -28,11 +27,11 @@
print("[+] login")
token = client.user_login(self.user_id, self.user_password)
- channel = FrontendService(FRONTEND_SERVICE_ADDRESS,
- AS_ROOT_CA_CERT_PATH,
- ENCLAVE_INFO_PATH).connect()
+ client = FrontendService(FRONTEND_SERVICE_ADDRESS,
+ AS_ROOT_CA_CERT_PATH,
+ ENCLAVE_INFO_PATH).connect().get_client()
metadata = {"id": self.user_id, "token": token}
- client = FrontendClient(channel, metadata)
+ client.metadata = metadata
print("[+] registering function")
diff --git a/sdk/python/teaclave.py b/sdk/python/teaclave.py
index 31d0858..1da3179 100644
--- a/sdk/python/teaclave.py
+++ b/sdk/python/teaclave.py
@@ -1,4 +1,10 @@
#!/usr/bin/env python3
+"""
+Python package `teaclave` is the client SDK for Python developers, providing
+some essential data structures, service, and client classes to establish
+trusted TLS channel and communicate with Teaclave services (e.g., the
+authentication service and frontend service) through RPC protocols.
+"""
import struct
import json
@@ -9,6 +15,8 @@
import ssl
import socket
+from typing import Tuple, Dict, List, Any
+
from cryptography import x509
from cryptography.hazmat.backends import default_backend
@@ -16,83 +24,226 @@
from OpenSSL.crypto import X509Store, X509StoreContext
from OpenSSL import crypto
+__all__ = [
+ 'FrontendClient', 'FrontendService', 'AuthenticationClient',
+ 'AuthenticationService', 'FunctionInput', 'FunctionOutput', 'OwnerList',
+ 'DataMap'
+]
-class RequestEncoder(json.JSONEncoder):
- def default(self, o):
- return o.__dict__
+Metadata = Dict[str, str]
+
+
+class FunctionInput:
+ """Function input for registering.
+
+ Args:
+ name: Name of input data.
+ description: Description of the input data.
+ """
+ def __init__(self, name: str, description: str):
+ self.name = name
+ self.description = description
+
+
+class FunctionOutput:
+ """Function output for registering.
+
+ Args:
+ name: Name of output data.
+ description: Description of the output data.
+ """
+ def __init__(self, name: str, description: str):
+ self.name = name
+ self.description = description
+
+
+class OwnerList:
+ """Defines data ownership.
+
+ Args:
+ data_name: Name of output data.
+ uids: A list of user id which own this data.
+ """
+ def __init__(self, data_name: str, uids: List[str]):
+ self.data_name = data_name
+ self.uids = uids
+
+
+class DataMap:
+ """Assign data id to input or output data.
+
+ Args:
+ data_name: Name of output data.
+ data_id: Id for the data name.
+ """
+ def __init__(self, data_name, data_id):
+ self.data_name = data_name
+ self.data_id = data_id
+
+
+class CryptoInfo:
+ """Cryptographic information for the input/output data.
+
+ Args:
+ schema: Encryption algorithms for the input/output data.
+ key: Key for encryption and decryption, bytes in list.
+ iv: IV, bytes in list.
+ """
+ def __init__(self, schema: str, key: List[int], iv: List[int]):
+ self.schema = schema
+ self.key = key
+ self.iv = iv
class UserRegisterReqeust:
- def __init__(self, user_id, user_password):
+ def __init__(self, user_id: str, user_password: str):
self.request = "user_register"
self.id = user_id
self.password = user_password
class UserLoginRequest:
- def __init__(self, user_id, user_password):
+ def __init__(self, user_id: str, user_password: str):
self.request = "user_login"
self.id = user_id
self.password = user_password
+class AuthenticationService:
+ """
+ Establish trusted channel with the authentication service and provide
+ clients to send request through RPC.
+
+ Args:
+ address: The address of the remote services in tuple.
+ as_root_ca_cert_path: Root CA certification of the attestation services
+ to verify the attestation report.
+ enclave_info_path: Path of enclave info to verify the remote service in
+ the attestation report.
+ """
+ _context = ssl._create_unverified_context()
+ _channel = None
+
+ def __init__(self, address: Tuple[str, int], as_root_ca_cert_path: str,
+ enclave_info_path: str):
+ self.address = address
+ self.as_root_ca_cert_path = as_root_ca_cert_path
+ self.enclave_info_path = enclave_info_path
+
+ def connect(self):
+ """Establish trusted connection and verify remote attestation report.
+
+ Returns:
+ AuthenticationService: The original object which can be chained
+ with other methods.
+ """
+ sock = socket.create_connection(self.address)
+ channel = self._context.wrap_socket(sock,
+ server_hostname=self.address[0])
+ cert = channel.getpeercert(binary_form=True)
+ _verify_report(self.as_root_ca_cert_path, self.enclave_info_path, cert,
+ "authentication")
+
+ self._channel = channel
+
+ return self
+
+ def get_client(self):
+ """Get a client of authentication service to send RPC requests.
+
+ Returns:
+ AuthenticationClient: Used for send/receive RPC requests.
+ """
+ return AuthenticationClient(self._channel)
+
+
class AuthenticationClient:
- def __init__(self, channel):
+ """Client to communicate with the authentication service.
+
+ Args:
+ channel: Trusted TLS socket (verified with remote attestation).
+ """
+ def __init__(self, channel: ssl.SSLSocket):
self.channel = channel
- def user_register(self, user_id, user_password):
- request = UserRegisterReqeust(user_id, user_password)
- write_message(self.channel, request)
- return read_message(self.channel)
+ def user_register(self, user_id: str, user_password: str):
+ """Register a new user.
- def user_login(self, user_id, user_password):
+ Args:
+ user_id: User ID.
+ user_password: Password.
+ """
+ request = UserRegisterReqeust(user_id, user_password)
+ _write_message(self.channel, request)
+ _ = _read_message(self.channel)
+
+ def user_login(self, user_id: str, user_password: str) -> str:
+ """Login and get a session token.
+
+ Args:
+ user_id: User ID.
+ user_password: Password.
+
+ Returns:
+ str: User login token.
+ """
request = UserLoginRequest(user_id, user_password)
- write_message(self.channel, request)
- response = read_message(self.channel)
+ _write_message(self.channel, request)
+ response = _read_message(self.channel)
return response["content"]["token"]
-class AuthenticationService:
- context = ssl._create_unverified_context()
-
- def __init__(self, address, as_root_ca_cert_path, enclave_info_path):
- self.address = address
- self.as_root_ca_cert_path = as_root_ca_cert_path
- self.enclave_info_path = enclave_info_path
-
- def connect(self):
- sock = socket.create_connection(self.address)
- channel = self.context.wrap_socket(sock,
- server_hostname=self.address[0])
- cert = channel.getpeercert(binary_form=True)
- verify_report(self.as_root_ca_cert_path, self.enclave_info_path, cert,
- "authentication")
-
- return channel
-
-
class FrontendService:
- context = ssl._create_unverified_context()
+ """Establish trusted channel with the frontend service and provide
+ clients to send request through RPC.
- def __init__(self, address, as_root_ca_cert_path, enclave_info_path):
+ Args:
+ address: The address of the remote services in tuple.
+ as_root_ca_cert_path: Root CA certification of the attestation services
+ to verify the attestation report.
+ enclave_info_path: Path of enclave info to verify the remote service in
+ the attestation report.
+ """
+ _context = ssl._create_unverified_context()
+ _channel = None
+
+ def __init__(self, address: Tuple[str, int], as_root_ca_cert_path: str,
+ enclave_info_path: str):
self.address = address
self.as_root_ca_cert_path = as_root_ca_cert_path
self.enclave_info_path = enclave_info_path
def connect(self):
- sock = socket.create_connection(self.address)
- channel = self.context.wrap_socket(sock,
- server_hostname=self.address[0])
- cert = channel.getpeercert(binary_form=True)
- verify_report(self.as_root_ca_cert_path, self.enclave_info_path, cert,
- "frontend")
+ """Establish trusted connection and verify remote attestation report.
- return channel
+ Returns:
+ FrontendService: The original object which can be chained
+ with other methods.
+ """
+ sock = socket.create_connection(self.address)
+ channel = self._context.wrap_socket(sock,
+ server_hostname=self.address[0])
+ cert = channel.getpeercert(binary_form=True)
+ _verify_report(self.as_root_ca_cert_path, self.enclave_info_path, cert,
+ "frontend")
+
+ self._channel = channel
+ return self
+
+ def get_client(self):
+ """Get a client of frontend service to send RPC requests.
+
+ Returns:
+ FrontendClient: Used for send/receive RPC requests.
+ """
+ return FrontendClient(self._channel)
class RegisterFunctionRequest:
- def __init__(self, metadata, name, description, executor_type, public,
- payload, arguments, inputs, outputs):
+ def __init__(self, metadata: Metadata, name: str, description: str,
+ executor_type: str, public: bool, payload: List[int],
+ arguments: List[str], inputs: List[FunctionInput],
+ outputs: List[FunctionOutput]):
self.request = "register_function"
self.metadata = metadata
self.name = name
@@ -106,7 +257,8 @@
class RegisterInputFileRequest:
- def __init__(self, metadata, url, cmac, crypto_info):
+ def __init__(self, metadata: Metadata, url: str, cmac: str,
+ crypto_info: CryptoInfo):
self.request = "register_input_file"
self.metadata = metadata
self.url = url
@@ -115,30 +267,34 @@
class RegisterOutputFileRequest:
- def __init__(self, metadata, url, crypto_info):
+ def __init__(self, metadata: Metadata, url: str, crypto_info: CryptoInfo):
self.request = "register_output_file"
self.metadata = metadata
self.url = url
self.crypto_info = crypto_info
+
class UpdateInputFileRequest:
- def __init__(self, metadata, data_id, url):
+ def __init__(self, metadata: Metadata, data_id: str, url: str):
self.request = "update_input_file"
self.metadata = metadata
- self.data_id =data_id
+ self.data_id = data_id
self.url = url
class UpdateOutputFileRequest:
- def __init__(self, metadata, data_id, url):
+ def __init__(self, metadata: Metadata, data_id: str, url: str):
self.request = "update_output_file"
self.metadata = metadata
- self.data_id =data_id
+ self.data_id = data_id
self.url = url
+
class CreateTaskRequest:
- def __init__(self, metadata, function_id, function_arguments, executor,
- inputs_ownership, outputs_ownership):
+ def __init__(self, metadata: Metadata, function_id: str,
+ function_arguments: Dict[str, Any], executor: str,
+ inputs_ownership: List[OwnerList],
+ outputs_ownership: List[OwnerList]):
self.request = "create_task"
self.metadata = metadata
self.function_id = function_id
@@ -149,7 +305,8 @@
class AssignDataRequest:
- def __init__(self, metadata, task_id, inputs, outputs):
+ def __init__(self, metadata: Metadata, task_id: str, inputs: List[DataMap],
+ outputs: List[DataMap]):
self.request = "assign_data"
self.metadata = metadata
self.task_id = task_id
@@ -158,106 +315,102 @@
class ApproveTaskRequest:
- def __init__(self, metadata, task_id):
+ def __init__(self, metadata: Metadata, task_id: str):
self.request = "approve_task"
self.metadata = metadata
self.task_id = task_id
class InvokeTaskRequest:
- def __init__(self, metadata, task_id):
+ def __init__(self, metadata: Metadata, task_id: str):
self.request = "invoke_task"
self.metadata = metadata
self.task_id = task_id
class GetTaskRequest:
- def __init__(self, metadata, task_id):
+ def __init__(self, metadata: Metadata, task_id: str):
self.request = "get_task"
self.metadata = metadata
self.task_id = task_id
-class TeaclaveFile128Key:
- def __init__(self, schema, key, iv):
- self.schema = schema
- self.key = key
- self.iv = iv
-
-
class FrontendClient:
- def __init__(self, channel, metadata):
+ def __init__(self, channel: ssl.SSLSocket, metadata: Metadata = None):
self.channel = channel
self.metadata = metadata
def register_function(self,
- name,
- description,
- executor_type,
- public=True,
- payload=[],
- arguments=[],
- inputs=[],
- outputs=[]):
+ name: str,
+ description: str,
+ executor_type: str,
+ public: bool = True,
+ payload: List[int] = [],
+ arguments: List[str] = [],
+ inputs: List[FunctionInput] = [],
+ outputs: List[FunctionOutput] = []):
request = RegisterFunctionRequest(self.metadata, name, description,
executor_type, public, payload,
arguments, inputs, outputs)
- write_message(self.channel, request)
- response = read_message(self.channel)
+ _write_message(self.channel, request)
+ response = _read_message(self.channel)
return response["content"]["function_id"]
- def register_input_file(self, url, schema, key, iv, cmac):
+ def register_input_file(self, url: str, schema: str, key: List[int],
+ iv: List[int], cmac: str):
request = RegisterInputFileRequest(self.metadata, url, cmac,
- TeaclaveFile128Key(schema, key, iv))
- write_message(self.channel, request)
- response = read_message(self.channel)
+ CryptoInfo(schema, key, iv))
+ _write_message(self.channel, request)
+ response = _read_message(self.channel)
return response["content"]["data_id"]
- def register_output_file(self, url, schema, key, iv):
- request = RegisterOutputFileRequest(
- self.metadata, url, TeaclaveFile128Key(schema, key, iv))
- write_message(self.channel, request)
- response = read_message(self.channel)
+ def register_output_file(self, url: str, schema: str, key: List[int],
+ iv: List[int]):
+ request = RegisterOutputFileRequest(self.metadata, url,
+ CryptoInfo(schema, key, iv))
+ _write_message(self.channel, request)
+ response = _read_message(self.channel)
return response["content"]["data_id"]
def create_task(self,
- function_id,
- function_arguments,
- executor,
- inputs_ownership=[],
- outputs_ownership=[]):
+ function_id: str,
+ function_arguments: Dict[str, Any],
+ executor: str,
+ inputs_ownership: List[OwnerList] = [],
+ outputs_ownership: List[OwnerList] = []):
function_arguments = json.dumps(function_arguments)
request = CreateTaskRequest(self.metadata, function_id,
function_arguments, executor,
inputs_ownership, outputs_ownership)
- write_message(self.channel, request)
- response = read_message(self.channel)
+ _write_message(self.channel, request)
+ response = _read_message(self.channel)
return response["content"]["task_id"]
- def assign_data_to_task(self, task_id, inputs, outputs):
+ def assign_data_to_task(self, task_id: str, inputs: List[DataMap],
+ outputs: List[DataMap]):
request = AssignDataRequest(self.metadata, task_id, inputs, outputs)
- write_message(self.channel, request)
- response = read_message(self.channel)
+ _write_message(self.channel, request)
+ _ = _read_message(self.channel)
return
- def approve_task(self, task_id):
+ def approve_task(self, task_id: str):
request = ApproveTaskRequest(self.metadata, task_id)
- write_message(self.channel, request)
- response = read_message(self.channel)
+ _write_message(self.channel, request)
+ _ = _read_message(self.channel)
return
- def invoke_task(self, task_id):
+ def invoke_task(self, task_id: str):
request = InvokeTaskRequest(self.metadata, task_id)
- write_message(self.channel, request)
- response = read_message(self.channel)
+ _write_message(self.channel, request)
+ response = _read_message(self.channel)
assert (response["result"] == "ok")
- def get_task_result(self, task_id):
+ def get_task_result(self, task_id: str):
request = GetTaskRequest(self.metadata, task_id)
while True:
- write_message(self.channel, request)
- response = read_message(self.channel)
+ _write_message(self.channel, request)
+ response = _read_message(self.channel)
time.sleep(1)
if response["content"]["status"] == 10:
break
@@ -265,21 +418,25 @@
return response["content"]["result"]["result"]["Ok"]["return_value"]
-def write_message(sock, message):
+def _write_message(sock: ssl.SSLSocket, message: Any):
+ class RequestEncoder(json.JSONEncoder):
+ def default(self, o):
+ return o.__dict__
+
message = json.dumps(message, cls=RequestEncoder).encode()
sock.write(struct.pack(">Q", len(message)))
sock.write(message)
-def read_message(sock):
+def _read_message(sock: ssl.SSLSocket):
response_len = struct.unpack(">Q", sock.read(8))
response = sock.read(response_len[0])
response = json.loads(response)
return response
-def verify_report(as_root_ca_cert_path, enclave_info_path, cert,
- endpoint_name):
+def _verify_report(as_root_ca_cert_path: str, enclave_info_path: str,
+ cert: Dict[str, Any], endpoint_name: str):
if os.environ.get('SGX_MODE') == 'SW':
return