def _verify_wo_verification_key_signature(self, wo_response, wo_verification_key, requester_nonce): """ Function to verify the work order response signature Parameters: @param wo_response - dictionary contains work order response as per Trusted Compute EEA API 6.1.2 Work Order Result Payload @param wo_verification_key - ECDSA/SECP256K1 public key used to verify work order verification key signature. @param requester_nonce - requester generated nonce passed in work order request. Required in 2 step verification. Returns enum type SignatureStatus """ if requester_nonce is None: logger.error("Missing requester_nonce argument") return SignatureStatus.FAILED concat_string = wo_response["extVerificationKey"] + requester_nonce v_key_sig = wo_response["extVerificationKeySignature"] v_key_hash = worker_hash.WorkerHash().compute_message_hash( bytes(concat_string, 'UTF-8')) decoded_v_key_sig = crypto_utility.base64_to_byte_array(v_key_sig) return self.verify_signature_from_pubkey(decoded_v_key_sig, v_key_hash, wo_verification_key)
def _encrypt_and_sign_response(self, session_key, session_key_iv, output_json): """ Encrypt outdata and compute worker signature. Parameters : session_key: Session key of the client which submitted this work order session_key_iv: iv corresponding to teh session key output_json: Pre-populated response json Returns : JSON RPC response with worker signature """ # Encrypt outData self.encrypt.encrypt_work_order_data_json( output_json["result"]["outData"], session_key, session_key_iv) # Compute worker signature res_hash = worker_hash.WorkerHash().calculate_response_hash( output_json["result"]) res_hash_sign = self.sign.sign_message(res_hash) res_hash_sign_b64 = crypto_utility.byte_array_to_base64(res_hash_sign) output_json["result"]["workerSignature"] = res_hash_sign_b64 return output_json
def verify_create_receipt_signature(self, input_json): """ Function to verify the signature of work order receipt create Parameters: - input_json is dictionary contains request payload of WorkOrderReceiptRetrieve API as define EEA spec 7.2.2 Returns enum type SignatureStatus """ input_json_params = input_json['params'] concat_string = input_json_params["workOrderId"] + \ input_json_params["workerServiceId"] + \ input_json_params["workerId"] + \ input_json_params["requesterId"] + \ str(input_json_params["receiptCreateStatus"]) + \ input_json_params["workOrderRequestHash"] + \ input_json_params["requesterGeneratedNonce"] concat_hash = bytes(concat_string, "UTF-8") final_hash = bytes( worker_hash.WorkerHash().compute_message_hash(concat_hash)) signature = input_json_params["requesterSignature"] verification_key = \ input_json_params["receiptVerificationKey"].encode("ascii") decoded_signature = crypto_utility.base64_to_byte_array(signature) return self.verify_signature_from_pubkey(decoded_signature, final_hash, verification_key)
def _verfify_uid_signature(self, params): """ Verify uid signature received from KME. Parameters : params: Parameters received in request Returns : JSON RPC response containing uid signature verification result. """ # Public key generated by KME to be used for registration verify_key = params["uniqueVerificationKey"] # Digital signature computed on hash of concatenated # string of unique id and nonce verify_key_sig = params["uniqueVerificationKeySignature"] verify_key_sig_bytes = hex_to_byte_array(verify_key_sig) verify_key_bytes = hex_to_byte_array(verify_key) b64_verify_key = verify_key_bytes.decode("utf-8") concat_str = b64_verify_key + self._nonce str_hash = worker_hash.WorkerHash().compute_message_hash( concat_str.encode("utf-8")) result = self.sign.verify_signature_from_pubkey( verify_key_sig_bytes, str_hash, verify_key_bytes) verification_result = 0 if result is True else -1 return json.dumps({"verification_result": verification_result})
def _verify_work_order_request(self, input_json, session_key, session_key_iv): """ Verify work order request integrity. Verifies work order request hash. Parameters : input_json: JSON work order request session_key: Session key of the client which submitted this work order session_key_iv: iv corresponding to the session key Returns : JSON RPC response containing result or error. """ # Decrypt request hash encrypted_req_hash_hex = input_json["params"]["encryptedRequestHash"] encrypted_req_hash = bytes.fromhex(encrypted_req_hash_hex) try: decrypt_req_hash = self.encrypt.decrypt_data( encrypted_req_hash, session_key, session_key_iv) except Exception as e: logger.error("Decryption of request hash: %s", e) return False # Compute work order request hash req_hash = worker_hash.WorkerHash().calculate_request_hash( input_json["params"]) # Compare decrypted request hash with expected value if decrypt_req_hash.hex() == req_hash.hex(): return True else: return False
def calculate_wo_pre_proc_keys_hash(self, pre_proc_json): """ Computes hash on pre-processed work order keys(by KME worker) Parameters: pre_proc_json: Pre processed JSON(by KME worker) having work order keys needed in encrypted format to process client work order request Returns: Computed hash of pre-processed work order keys in bytes """ concat_hash = crypto_utility.byte_array_to_base64( self.encrypted_sym_key) + \ crypto_utility.byte_array_to_base64(self.encrypted_wo_key) + \ crypto_utility.byte_array_to_base64(self.encrypted_sig_key) hash_1 = worker_hash.WorkerHash().compute_message_hash( concat_hash.encode("utf-8")) hash_1_str = crypto_utility.byte_array_to_base64(hash_1) # Compute hash of input-data-keys in_data_key_hash_str = "" for key in pre_proc_json['input-data-keys']: in_data_key_hash_str += crypto_utility.byte_array_to_base64( worker_hash.WorkerHash().compute_message_hash( crypto_utility.base64_to_byte_array( key['encrypted-data-key']))) # Compute hash on output-data-keys out_data_key_hash_str = "" for key in pre_proc_json['output-data-keys']: out_data_key_hash_str += crypto_utility.byte_array_to_base64( worker_hash.WorkerHash().compute_message_hash( crypto_utility.base64_to_byte_array( key['encrypted-data-key']))) final_hash = hash_1_str.encode("utf-8") + \ in_data_key_hash_str.encode("utf-8") + \ out_data_key_hash_str.encode("utf-8") return worker_hash.WorkerHash().compute_message_hash(final_hash)
def __validate_work_order_receipt_update_req(self, wo_receipt_req): """ Function to validate the work order receipt create request parameters Parameters: - wo_receipt_req is work order receipt request as dictionary Returns - tuple containing validation status(Boolean) and error message(string) """ valid_params = [ "workOrderId", "updaterId", "updateType", "updateData", "updateSignature", "signatureRules", "receiptVerificationKey" ] for key in wo_receipt_req["params"]: if key not in valid_params: return False, "Missing parameter " + key + " in the request" else: if key in ["workOrderId", "updaterId"]: if not is_valid_hex_str(wo_receipt_req[key]): return False, "invalid data parameter for " + key elif key in ["updateData", "updateSignature"]: try: base64.b64decode(wo_receipt_req[key]) except Exception: return False, "Invalid data format for " + key update_type = wo_receipt_req["params"]["updateType"] try: update_enum_value = ReceiptCreateStatus(update_type) except Exception as err: return False, "Invalid receipt update type {}: {}".format( update_enum_value, str(err)) # If update type is completed or processed, # it is a hash value of the Work Order Response if wo_receipt_req["params"]["updateType"] in [ ReceiptCreateStatus.PROCESSED.value, ReceiptCreateStatus.COMPLETED.value ]: wo_id = wo_receipt_req["params"]["workOrderId"] # Load the work order response and calculate it's hash wo_resp = self.kv_helper.get("wo-responses", wo_id) wo_resp_bytes = bytes(wo_resp, "UTF-8") wo_resp_hash = worker_hash.WorkerHash().compute_message_hash( wo_resp_bytes) wo_resp_hash_str = crypto_utility.byte_array_to_hex(wo_resp_hash) if wo_resp_hash_str != wo_receipt_req["params"]["updateData"]: return False, "Invalid Update data in the request" # If all validation is pass return True, ""
def __init__(self, kv_helper): """ Function to perform init activity Parameters: - kv_helper is a object of lmdb database """ self.kv_helper = kv_helper self.__workorder_receipt_on_boot() # Special index 0xFFFFFFFF value to fetch last update to receipt self.LAST_RECEIPT_INDEX = 1 << 32 # Supported hashing and signing algorithms self.SIGNING_ALGORITHM = "SECP256K1" self.HASHING_ALGORITHM = "SHA-256" self.signer = worker_signing.WorkerSign() self.hasher = worker_hash.WorkerHash()
def _generate_worker_keys(self): """ Generates worker signing and encryption keys. """ # Generate worker signing key logger.info("Generate worker signing and encryption keys") self.sign = worker_signing.WorkerSign() self.sign.generate_signing_key() self.worker_public_sign_key = self.sign.get_public_sign_key() # Generate worker encryption key self.encrypt = worker_encryption.WorkerEncrypt() self.encrypt.generate_rsa_key() self.worker_public_enc_key = self.encrypt.get_rsa_public_key() # Sign worker encryption key hash hash_obj = worker_hash.WorkerHash() hash_val = hash_obj.compute_message_hash(self.worker_public_enc_key) self.worker_public_enc_key_sign = self.sign.sign_message(hash_val)
def _create_work_order_request(workload_id, input_data_str, session_key, session_key_iv, worker_signup_json): worker_enc_key_str = worker_signup_json["encryption_key"] encrypt = worker_encryption.WorkerEncrypt() encrypted_session_key = encrypt.encrypt_session_key( session_key, worker_enc_key_str) msg_bytes = input_data_str.encode('UTF-8') input_json = dict() input_json["jsonrpc"] = "2.0" input_json["method"] = "WorkOrderSubmit" input_json["id"] = "1" input_json["params"] = dict() input_json["params"]["encryptedSessionKey"] = encrypted_session_key.hex() if session_key_iv: input_json["params"]["sessionKeyIv"] = session_key_iv.hex() input_json["params"]["workloadId"] = workload_id.encode("UTF-8").hex() input_json["params"]["workOrderId"] = secrets.token_hex(32) worker_id = hashlib.sha256("graphene-hello".encode("UTF-8")).hexdigest() input_json["params"]["workerId"] = worker_id input_json["params"]["requesterId"] = secrets.token_hex(32) input_json["params"]["requesterNonce"] = secrets.token_hex(32) in_data = dict() in_data["index"] = 0 in_data["dataHash"] = "" in_data["data"] = msg_bytes in_data["encryptedDataEncryptionKey"] = "null" in_data["iv"] = "" input_json["params"]["inData"] = [in_data] # Encrypt inData encrypt.encrypt_work_order_data_json(input_json["params"]["inData"], session_key, session_key_iv) req_hash = worker_hash.WorkerHash().calculate_request_hash( input_json["params"]) encrypted_req_hash = encrypt.encrypt_data(req_hash, session_key, session_key_iv) input_json["params"]["encryptedRequestHash"] = encrypted_req_hash.hex() return json.dumps(input_json)
def _create_work_order_response(self, input_json, out_data, session_key, session_key_iv): """ Creates work order response JSON object. Parameters : input_json: JSON work order request out_data: output data dictionary which has data in plain text session_key: Session key of the client which submitted this work order session_key_iv: iv corresponding to teh session key Returns : JSON RPC response containing result. """ output_json = dict() output_json["jsonrpc"] = "2.0" output_json["id"] = input_json["id"] output_json["result"] = dict() output_json["result"]["workloadId"] = \ input_json["params"]["workloadId"] output_json["result"]["workOrderId"] = \ input_json["params"]["workOrderId"] output_json["result"]["workerId"] = \ input_json["params"]["workerId"] output_json["result"]["requesterId"] = \ input_json["params"]["requesterId"] workerNonce = ''.join( random.choices(string.ascii_uppercase + string.digits, k=16)) output_json["result"]["workerNonce"] = \ crypto_utility.byte_array_to_base64( workerNonce.encode("UTF-8")) output_json["result"]["outData"] = [out_data] # Encrypt outData self.encrypt.encrypt_work_order_data_json( output_json["result"]["outData"], session_key, session_key_iv) # Compute worker signature res_hash = worker_hash.WorkerHash().calculate_response_hash( output_json["result"]) res_hash_sign = self.sign.sign_message(res_hash) res_hash_sign_b64 = crypto_utility.byte_array_to_base64(res_hash_sign) output_json["result"]["workerSignature"] = res_hash_sign_b64 return output_json
def EncryptionKeyGet(self, **params): """ Function to process get encryption key request. Parameters: - param is the 'param' object in the a worker request as per TCF API 6.1.10 Get Encryption Key Request Payload """ worker_id = str(params['workerId']) value = self.kv_helper.get("workers", worker_id) if value is None: raise JSONRPCDispatchException( WorkerError.INVALID_PARAMETER_FORMAT_OR_VALUE, "Worker id not found in the database. Hence invalid parameter") worker_type_data = json.loads(value).get("details").get( "workerTypeData") encryptionKey = worker_type_data["encryptionKey"] try: encryptionKeyNonce = worker_type_data["encryptionKeyNonce"] except Exception: encryptionKeyNonce = crypto.generate_random_string(NO_OF_BYTES) tag = "" # calculate signature concat_string = worker_id.encode('UTF-8') + encryptionKey.encode( 'UTF-8') + encryptionKeyNonce.encode('UTF-8') + tag.encode('UTF-8') concat_hash = bytes(concat_string) hash_1 = worker_hash.WorkerHash().compute_message_hash(concat_hash) s1 = crypto.byte_array_to_base64(hash_1) # Requires worker private key to sign. # signature = self.PrivateKey.SignMessage(hash) result = { "workerId": worker_id, "encryptionKey": encryptionKey, "encryptionKeyNonce": encryptionKeyNonce, "tag": "", "signature": s1, } return result
def verify_encryption_key_signature(self, encryption_key_signature, encryption_key, verifying_key): """ Utils function to verify integrity of worker encryption key using worker verification key @params encryption_key_signature - Signature computed on hash of encryption key @params encryption_key - Public encryption key of the worker @params verifying_key - Public signing key or verification key of the worker returns SignatureStatus.PASSED in case of successful verification SignatureStatus.FAILED in case of verification failure """ encrypt_key_sig_bytes = hex_to_byte_array(encryption_key_signature) encrypt_key_bytes = crypto_utility.string_to_byte_array(encryption_key) encryption_key_hash = worker_hash.WorkerHash().compute_message_hash( encrypt_key_bytes) return self.verify_signature_from_pubkey(encrypt_key_sig_bytes, encryption_key_hash, verifying_key)
def _verify_wo_response_signature(self, wo_response, wo_res_verification_key): """ Function to verify the work order response signature Parameters: @param wo_response - dictionary contains work order response as per Trusted Compute EEA API 6.1.2 Work Order Result Payload @param wo_res_verification_key - ECDSA/SECP256K1 public key used to verify work order response signature. Returns enum type SignatureStatus """ if "workerSignature" not in wo_response: logger.error("workerSignature not present in reponse json") return False signature = wo_response['workerSignature'] response_hash = worker_hash.WorkerHash().calculate_response_hash( wo_response) decoded_signature = crypto_utility.base64_to_byte_array(signature) return self.verify_signature_from_pubkey(decoded_signature, response_hash, wo_res_verification_key)
def verify_update_receipt_signature(self, input_json): """ Function to verify the signature of work order receipt update Parameters: - input_json is dictionary contains payload returned by the WorkOrderReceiptUpdateRetrieve API as define EEA spec 7.2.7 Returns enum type SignatureStatus """ input_json_params = input_json concat_string = input_json_params["workOrderId"] + \ str(input_json_params["updateType"]) + \ input_json_params["updateData"] concat_hash = bytes(concat_string, 'UTF-8') final_hash = worker_hash.WorkerHash().compute_message_hash(concat_hash) signature = input_json_params["updateSignature"] verification_key = \ input_json_params["receiptVerificationKey"].encode("ascii") decoded_signature = crypto_utility.base64_to_byte_array(signature) return self.verify_signature_from_pubkey(decoded_signature, final_hash, verification_key)
def __init__(self): self.params_obj = {} self.signer = worker_signing.WorkerSign() self.encrypt = worker_encryption.WorkerEncrypt() self.hasher = worker_hash.WorkerHash()
def __init__(self): self.SIGNING_ALGORITHM = "SECP256K1" self.HASHING_ALGORITHM = "SHA-256" self.signer = worker_signing.WorkerSign() self.signer.generate_signing_key() self.hasher = worker_hash.WorkerHash()
def local_main(config): global input_json_str if not input_json_str and not input_json_dir: LOGGER.error("JSON input file is not provided") exit(1) if not output_json_file_name: LOGGER.error("JSON output file is not provided") exit(1) if not server_uri: LOGGER.error("Server URI is not provided") exit(1) LOGGER.info("Execute work order") uri_client = HttpJrpcClient(server_uri) response = None wo_id = None if input_json_dir: directory = os.fsencode(input_json_dir) files = os.listdir(directory) for file in sorted(files): LOGGER.info("---------------- Input file name: %s -------------\n", file.decode("utf-8")) input_json_str = futils.read_json_file( (directory.decode("utf-8") + file.decode("utf-8"))) # ----------------------------------------------------------------- # If Client request is WorkOrderSubmit, a requester payload's # signature with the requester private signing key is generated. if "WorkOrderSubmit" in input_json_str: # Update workOrderId , workerId and workloadId input_json_obj = json.loads(input_json_str) wo_id = hex(random.randint(1, 2**64 - 1)) input_json_obj["params"]["workOrderId"] = wo_id input_json_obj["params"]["workerId"] = worker_id # Convert workloadId to a hex string and update the request workload_id = input_json_obj["params"]["workloadId"] workload_id_hex = workload_id.encode("UTF-8").hex() input_json_obj["params"]["workloadId"] = workload_id_hex encrypt = worker_encryption.WorkerEncrypt() # Generate session key, session iv and encrypted session key session_key = encrypt.generate_session_key() session_iv = encrypt.generate_iv() encrypted_session_key = encrypt.encrypt_session_key( session_key, worker_obj.encryption_key) input_json_obj["params"]["encryptedSessionKey"] = \ crypto_utility.byte_array_to_hex(encrypted_session_key) input_json_obj["params"]["sessionKeyIv"] = \ crypto_utility.byte_array_to_hex(session_iv) if "requesterNonce" in input_json_obj["params"]: if len(input_json_obj["params"]["requesterNonce"]) == 0: # [NO_OF_BYTES] 16 BYTES for nonce. # This is the recommendation by NIST to # avoid collisions by the "Birthday Paradox". requester_nonce = secrets.token_hex(NO_OF_BYTES) input_json_obj["params"]["requesterNonce"] = \ requester_nonce requester_nonce = input_json_obj["params"]["requesterNonce"] # Encode data in inData for data_obj in input_json_obj["params"]["inData"]: encoded_data = data_obj["data"].encode('UTF-8') data_obj["data"] = encoded_data # Encrypt inData encrypt.encrypt_work_order_data_json( input_json_obj["params"]["inData"], session_key, session_iv) req_hash = worker_hash.WorkerHash().calculate_request_hash( input_json_obj["params"]) encrypted_req_hash = encrypt.encrypt_data( req_hash, session_key, session_iv) input_json_obj["params"]["encryptedRequestHash"] = \ encrypted_req_hash.hex() signer = worker_signing.WorkerSign() signer.generate_signing_key() wo_req_sig = signer.sign_message(req_hash) input_json_obj["params"]["requesterSignature"] = \ crypto_utility.byte_array_to_base64(wo_req_sig) input_json_obj["params"]["verifyingKey"] = \ signer.get_public_sign_key().decode('utf-8') input_json_str = json.dumps(input_json_obj) if input_json_str is None: continue # ----------------------------------------------------------------- # Update the worker ID if response: if "workerId" in input_json_str: # Retrieve the worker id from the "WorkerRetrieve" # response and update the worker id information for # further json requests. if "result" in response and \ "ids" in response["result"].keys() and \ len(response["result"]["ids"]) > 0: input_json_final = json.loads(input_json_str) worker_id = response["result"]["ids"][0] input_json_final["params"]["workerId"] = worker_id input_json_str = json.dumps(input_json_final) LOGGER.info( "********** Worker details Updated with " "Worker ID*********\n%s\n", input_json_str) # ----------------------------------------------------------------- if "WorkOrderGetResult" in input_json_str or \ "WorkOrderReceiptRetrieve" in input_json_str: input_json_obj = json.loads(input_json_str) input_json_obj["params"]["workOrderId"] = wo_id input_json_str = json.dumps(input_json_obj) LOGGER.info("*********Request Json********* \n%s\n", input_json_str) response = uri_client._postmsg(input_json_str) LOGGER.info("**********Received Response*********\n%s\n", response) # ----------------------------------------------------------------- # Worker details are loaded into Worker_Obj if "WorkerRetrieve" in input_json_str and "result" in response: worker_obj.load_worker(response["result"]["details"]) # ----------------------------------------------------------------- # Poll for "WorkOrderGetResult" and break when you get the result while ("WorkOrderGetResult" in input_json_str and "result" not in response): if response["error"]["code"] != WorkOrderStatus.PENDING: break response = uri_client._postmsg(input_json_str) LOGGER.info("Received Response: %s, \n \n ", response) time.sleep(3) # ----------------------------------------------------------------- # Verify the signature if "WorkOrderGetResult" in input_json_str: if "error" in response: # Response has error, hence skip Signature verification LOGGER.info("Work order response has error; " "skipping signature verification") continue sig_bool = signer.verify_signature(response['result'], worker_obj.verification_key, requester_nonce) try: if sig_bool > 0: LOGGER.info("Signature Verified") encrypt.decrypt_work_order_data_json( response['result']['outData'], session_key, session_iv) else: LOGGER.info("Signature verification Failed") exit(1) except Exception as e: LOGGER.error("ERROR: Failed to verify signature of " + "work order response") exit(1) # ----------------------------------------------------------------- else: LOGGER.info("Input Request %s", input_json_str) response = uri_client._postmsg(input_json_str) LOGGER.info("Received Response: %s , \n \n ", response) exit(0)