This is an automated email from the ASF dual-hosted git repository. mssun pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-teaclave.git
commit bea1ec21b9887ac914b05fb942e6ad7089b2d4e2 Author: Mingshen Sun <[email protected]> AuthorDate: Mon Jun 8 16:44:11 2020 -0700 Add docs for Client SDK in Python --- examples/python/builtin_echo.py | 15 +- examples/python/builtin_gbdt_train.py | 46 +--- examples/python/builtin_online_decrypt.py | 15 +- examples/python/mesapy_echo.py | 15 +- sdk/python/teaclave.py | 359 +++++++++++++++++++++--------- 5 files changed, 290 insertions(+), 160 deletions(-) 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 @@ class BuiltinEchoExample: 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 @@ class BuiltinEchoExample: 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 @@ class BuiltinGbdtExample: 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 @@ class BuiltinGbdtExample: 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 @@ class BuiltinOnlineDecryptExample: 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 @@ class BuiltinOnlineDecryptExample: 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 @@ class MesaPyEchoExample: 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 @@ class MesaPyEchoExample: 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 time 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 load_certificate, FILETYPE_PEM, FILETYPE_ASN1 from OpenSSL.crypto import X509Store, X509StoreContext from OpenSSL import crypto +__all__ = [ + 'FrontendClient', 'FrontendService', 'AuthenticationClient', + 'AuthenticationService', 'FunctionInput', 'FunctionOutput', 'OwnerList', + 'DataMap' +] + +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 RequestEncoder(json.JSONEncoder): - def default(self, o): - return o.__dict__ +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 AuthenticationClient: - def __init__(self, channel): - 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_login(self, user_id, user_password): - request = UserLoginRequest(user_id, user_password) - 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): + """ + 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]) + 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") + _verify_report(self.as_root_ca_cert_path, self.enclave_info_path, cert, + "authentication") - return channel + self._channel = channel + return self -class FrontendService: - context = ssl._create_unverified_context() + 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: + """Client to communicate with the authentication service. - def __init__(self, address, as_root_ca_cert_path, enclave_info_path): + Args: + channel: Trusted TLS socket (verified with remote attestation). + """ + def __init__(self, channel: ssl.SSLSocket): + self.channel = channel + + def user_register(self, user_id: str, user_password: str): + """Register a new user. + + 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) + return response["content"]["token"] + + +class FrontendService: + """Establish trusted channel with the frontend 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: + 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]) + 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") + _verify_report(self.as_root_ca_cert_path, self.enclave_info_path, cert, + "frontend") + + self._channel = channel + return self - return channel + 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 RegisterFunctionRequest: 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 RegisterInputFileRequest: 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 CreateTaskRequest: 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 AssignDataRequest: 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 @@ class FrontendClient: 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 --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
