def _send_ccl_transaction(self, txn, wait): if self._verbose: PdoDbgDump().dump_ccl_transaction(txn) PdoDbgDump().dump_ccl_transaction_protobuf_message_to_json(txn) return self.connect.send_transaction( txn.SerializeToString(), self.connect.get_ccl_family_name(), wait=wait)
def __init__(self, debug_on, dbg_dump_to_logger=True): self.connect = PdoTpConnectHelper() self._debug_on = debug_on if dbg_dump_to_logger: self.dbg_dump = PdoDbgDump(LOGGER) else: self.dbg_dump = PdoDbgDump() LOGGER.debug("Enclave namespace prefix: %s", self.connect.get_enclave_prefix())
def __init__(self, url, keyfile=None, key_str=None, auto_generate=False, cli=False): super(PdoClientConnectHelper, self).__init__(url) if not cli: self._dbg_dump = PdoDbgDump(LOGGER) else: self._dbg_dump = PdoDbgDump() self._make_signer(keyfile, key_str, auto_generate)
def execute_list_request(self, namespace_name, details, page_size, max_entries, wait=None): pdo_state_type = make_id = dump = None prefix = "" if namespace_name == 'enclave': pdo_state_type = PdoContractEnclaveInfo prefix = self.connect.get_enclave_prefix() make_id = self._make_enclave_id_for_list if details: dump = PdoDbgDump().dump_contract_enclave_state print("\nEnclaves list:") elif namespace_name == 'contract': pdo_state_type = PdoContractInfo prefix = self.connect.get_contract_prefix() make_id = self._make_contract_id_for_list if details: dump = PdoDbgDump().dump_contract_state print("\nContracts list:") elif namespace_name == 'ccl-info': pdo_state_type = CCL_ContractInformation prefix = self.connect.get_ccl_info_prefix() make_id = self._make_ccl_info_id_for_list if details: dump = PdoDbgDump().dump_ccl_info print("\nCCL information list:") elif namespace_name == 'ccl-state': pdo_state_type = CCL_ContractState prefix = self.connect.get_ccl_state_prefix() make_id = self._make_ccl_state_id_for_list if details: dump = PdoDbgDump().dump_ccl_state print("\nCCl states list:") elif namespace_name == 'settings': print("\nSawtooth settings:") self._show_config_setting_list() else: raise PdoCliException( "Invalid PD0 family type '{}'. Must be one of enclave, contract, ccl-info, or ccl-state" .format(namespace_name)) if prefix: self._execute_list_request(namespace_name, prefix, pdo_state_type, make_id, dump, page_size, max_entries) return "\nOk"
def __init__(self, debug_on, dbg_dump_to_logger=True): self.connect = PdoTpConnectHelper() self._debug_on = debug_on if dbg_dump_to_logger: self.dbg_dump = PdoDbgDump(LOGGER) else: self.dbg_dump = PdoDbgDump() LOGGER.debug("CCL state namespace prefix: %s", self.connect.get_ccl_state_prefix()) LOGGER.debug("CCL information namespace prefix: %s", self.connect.get_ccl_info_prefix()) LOGGER.debug("Contract namespace prefix: %s", self.connect.get_contract_prefix()) LOGGER.debug("Enclave namespace prefix: %s", self.connect.get_enclave_prefix())
def generate_test_enclave_info_request(self): public_signer_key = self.connect.get_signer_public_key_as_hex() txn = CreateTestEnclavePayload().create_test_enclave(public_signer_key) PdoDbgDump().dump_enclave_transaction_protobuf_message_to_json( txn, self.connect.get_enclave_registry_family_name()) return "OK"
class PdoClientConnectHelper(PdoRegistryHelper): def __init__(self, url, keyfile=None, key_str=None, auto_generate=False, cli=False): super(PdoClientConnectHelper, self).__init__(url) if not cli: self._dbg_dump = PdoDbgDump(LOGGER) else: self._dbg_dump = PdoDbgDump() self._make_signer(keyfile, key_str, auto_generate) def generate_new_signer_key(self): self._make_signer(auto_generate=True) def get_signer_public_key_as_hex(self): return self._signer.get_public_key_as_hex() def get_signer_private_key_as_hex(self): return self._signer.get_private_key_as_hex() def _make_signer(self, keyfile=None, key_str=None, auto_generate=False): if key_str or auto_generate: self._signer = CreatePdoSawtoothSigner(key_str) elif keyfile: try: with open(keyfile) as fd: key_str = fd.read().strip() fd.close() except OSError as err: raise ClientConnectException( 'Failed to read private key: {}'.format(str(err))) self._signer = CreatePdoSawtoothSigner(key_str) else: raise ClientConnectException('No option to create a signing key') def get_status(self, batch_id, wait): try: result = self.send_request( 'batch_statuses?id={}&wait={}'.format(batch_id, wait), ) return yaml.safe_load(result)['data'][0]['status'] except BaseException as err: raise ClientConnectException(err) def send_transaction(self, payload, family, wait=None, transaction_output_list=None, transaction_input_list=None, verbose=False, exception_type=TimeoutError, transaction_dependency_list=None): if not transaction_output_list: if family == self.get_ccl_family_name(): transaction_output_list = [ self.get_ccl_info_prefix(), self.get_ccl_state_prefix() ] elif family == self.get_contract_registry_family_name(): transaction_output_list = [self.get_contract_prefix()] else: transaction_output_list = [self.get_enclave_prefix()] if not transaction_input_list: transaction_input_list = [ self.get_enclave_prefix(), self.get_contract_prefix(), '000000', self.get_ccl_info_prefix(), self.get_ccl_state_prefix() ] if verbose: self._dbg_dump.dump_str( "output_list: {}".format(transaction_output_list)) self._dbg_dump.dump_str( "intput_list: {}".format(transaction_input_list)) self._dbg_dump.dump_str("family: {}".format(family)) self._dbg_dump.dump_str( "dependency_list: {}".format(transaction_dependency_list)) header = TransactionHeader( signer_public_key=self.get_signer_public_key_as_hex(), family_name=family, family_version="1.0", inputs=transaction_input_list, outputs=transaction_output_list, dependencies=transaction_dependency_list, payload_sha512=self._sha512(payload), batcher_public_key=self.get_signer_public_key_as_hex(), nonce=time.time().hex().encode()).SerializeToString() signature = self._signer.sign(header) transaction = Transaction(header=header, payload=payload, header_signature=signature) batch_list = self._create_batch_list([transaction]) batch_id = batch_list.batches[0].header_signature if wait and wait > 0: wait_time = 0 start_time = time.time() response = self.send_request( "batches", batch_list.SerializeToString(), 'application/octet-stream', ) while wait_time < wait: status = self.get_status( batch_id, wait - int(wait_time), ) wait_time = time.time() - start_time if status != 'PENDING': if verbose: self._dbg_dump.dump_str( "Transaction status: '{}'".format(status)) if status != "COMMITTED" and exception_type: # this is a temporary fix for the fact that Sawtooth may return INVALID status for a short while after submitting batch for commit # FIX: if total wait time < 10 (ad hoc), and we get INVALID, we wait for 0.1s before checking the status again if wait_time < 10 and wait_time < wait: LOGGER.info( "Unexpected status {}. Waiting for 0.1s before rechecking status" .format(status)) time.sleep(0.1) continue else: raise exception_type( "Transaction submission failed with status '{}'" .format(status)) return response, signature if not exception_type: return response, signature else: if verbose: self._dbg_dump.dump_str("Transaction submission timeout") raise exception_type("Transaction submission timeout") response = self.send_request( "batches", batch_list.SerializeToString(), 'application/octet-stream', ) return response, signature def _create_batch_list(self, transactions): transaction_signatures = [t.header_signature for t in transactions] header = BatchHeader( signer_public_key=self.get_signer_public_key_as_hex(), transaction_ids=transaction_signatures).SerializeToString() signature = self._signer.sign(header) batch = Batch(header=header, transactions=transactions, header_signature=signature) return BatchList(batches=[batch]) def execute_json_transaction(self, json_input, address_family, wait, exception_type=None, verbose=False, timeout_exception_type=TimeoutError, transaction_output_list=None, transaction_input_list=None, transaction_dependency_list=None): json_dict = json.loads(json_input) verb = json_dict['verb'] if not verb: if not exception_type: return False raise exception_type("no 'verb' in the json input") if address_family == self.get_enclave_registry_family_name(): txn = PdoContractEnclaveTransaction() txn.verb = verb txn.verifying_key = json_dict.get("verifying_key") if verb == 'register': details = PdoContractEnclaveRegister() proof_data = json_dict.get("proof_data") if proof_data is None or isinstance(proof_data, str): json_format.Parse(json_input, details, ignore_unknown_fields=True) else: if not exception_type: return False raise exception_type("missing or invalid 'proof_data'") elif verb == 'update': details = PdoContractEnclaveUpdate() json_format.Parse(json_input, details, ignore_unknown_fields=True) elif verb == 'delete': details = None else: if not exception_type: return False raise exception_type( "unknown verb in the json input '{}'".format(verb)) if details: txn.transaction_details = txn.transaction_details = details.SerializeToString( ) if verbose: self._dbg_dump.dump_contract_enclave_transaction(txn) self._dbg_dump.dump_enclave_transaction_protobuf_message_to_json( txn) elif address_family == self.get_contract_registry_family_name(): txn = PdoContractTransaction() txn.verb = verb if 'contract_id' in json_dict: txn.contract_id = json_dict.get("contract_id") if verb == 'register': details = PdoContractRegister() elif verb == 'add-enclaves': details = PdoContractAddEnclaves() elif verb == 'remove-enclaves': details = PdoContractRemoveEnclaves() elif verb == 'delete': details = None else: if not exception_type: return False raise exception_type( "unknown verb in the json input '{}'".format(verb)) if details: json_format.Parse(json_input, details, ignore_unknown_fields=True) txn.transaction_details = details.SerializeToString() if verbose: self._dbg_dump.dump_contract_transaction(txn) self._dbg_dump.dump_contract_transaction_protobuf_message_to_json( txn) elif address_family == self.get_ccl_family_name(): if verb not in ['initialize', 'update', 'terminate', 'delete']: if not exception_type: return False raise exception_type( "unknown verb in the json input '{}'".format(verb)) txn = CCL_TransactionPayload() json_format.Parse(json_input, txn, ignore_unknown_fields=True) if verbose: self._dbg_dump.dump_ccl_transaction(txn) self._dbg_dump.dump_ccl_transaction_protobuf_message_to_json( txn) else: if not exception_type: return False raise exception_type( "unknown 'af' (a.k.a. address family) in the json input '{}'". format(address_family)) result, signature = self.send_transaction( txn.SerializeToString(), address_family, wait=wait, exception_type=timeout_exception_type, transaction_output_list=transaction_output_list, transaction_input_list=transaction_input_list, transaction_dependency_list=transaction_dependency_list) if verbose: self._dbg_dump.dump_str("") self._dbg_dump.dump_str(result) self._dbg_dump.dump_str("") self._dbg_dump.dump_str( "Transaction signature: {}".format(signature)) return signature
class ContractEnclaveRegistryTransactionHandler(TransactionHandler): def __init__(self, debug_on, dbg_dump_to_logger=True): self.connect = PdoTpConnectHelper() self._debug_on = debug_on if dbg_dump_to_logger: self.dbg_dump = PdoDbgDump(LOGGER) else: self.dbg_dump = PdoDbgDump() LOGGER.debug("Enclave namespace prefix: %s", self.connect.get_enclave_prefix()) @property def family_name(self): family = self.connect.get_enclave_registry_family_name() LOGGER.debug("Enclave family name: %s", family) return family @property def family_versions(self): return ['1.0'] @property def namespaces(self): return self.connect.get_enclave_prefix() def _get_enclave_state(self, context, enclave_id): address = self.connect.get_enclave_address(enclave_id) return self.connect.get_state(context, address, PdoContractEnclaveInfo) def _delete_enclave_state(self, context, enclave_id): address = self.connect.get_enclave_address(enclave_id) return self.connect.delete_state(context, address) def _set_enclave_state(self, context, enclave_id, data): address = self.connect.get_enclave_address(enclave_id) return self.connect.set_state(context, address, data) def _verify_registration_info(self, payload, details, public_key_hash, context): # TODO: Allowing no proof data should be removed in the production version if not details.proof_data: LOGGER.debug("*** Enclave proof data is empty - simulation mode") if not self._debug_on: raise InvalidTransaction( 'Simulation mode is not allowed when the debug support is OFF') return # Try to get the report key from the configuration setting. # If it is not there, fail verification. try: report_public_key_pem = self.connect.get_report_public_key(context) except KeyError: raise \ ValueError( 'Failed to get report public key configuration setting {}'.format( self.connect.get_report_public_key_setting_name())) # Retrieve the valid enclave measurement values, converting the comma- # delimited list. If it is not there, fail verification. try: valid_measurements = self.connect.get_valid_measurements(context) except KeyError: raise \ ValueError( 'Failed to get enclave measurements setting {}'.format( self.connect.get_valid_measurements_setting_name())) # Retrieve the valid enclave basename value. If it is not there, # fail verification. try: valid_basenames = self.connect.get_valid_basenames(context) except KeyError: raise \ ValueError( 'Failed to get enclave basenames setting {}'.format( self.connect.get_valid_basenames_setting_name())) verify_enclave_registration_info(self.connect, payload, details, public_key_hash, context, report_public_key_pem, valid_measurements, valid_basenames) # def check_address(context, address, key, register_new): def check_address(self, context, key, register_new): try: state = self._get_enclave_state(context, key) if register_new: if state.verifying_key: raise InvalidTransaction( 'Contract enclave already exist with signing_key {}'\ .format(key)) else: return state else: if not state.verifying_key: raise InvalidTransaction( 'Enclave does not exist: {0}'.format(key)) else: return state except InternalError: if not register_new: raise InvalidTransaction( 'Enclave does not exist: {0}'.format(key)) else: return PdoContractEnclaveInfo() def apply(self, transaction, context): txn_header = transaction.header txn_signer_public_key = txn_header.signer_public_key payload = PdoContractEnclaveTransaction() payload.ParseFromString(transaction.payload) self.dbg_dump.dump_contract_enclave_transaction(payload) info = self.check_address(context, payload.verifying_key, payload.verb == 'register') if payload.verb == 'register': public_key_hash = hashlib.sha256(txn_signer_public_key.encode()).hexdigest() details = PdoContractEnclaveRegister() details.ParseFromString(payload.transaction_details) try: self._verify_registration_info(payload, details, public_key_hash, context) except ValueError as error: raise InvalidTransaction\ ('Invalid Signup Info: {}'.format(error)) info.verifying_key = payload.verifying_key info.encryption_key = details.encryption_key info.last_registration_block_context = \ details.registration_block_context info.owner_id = txn_signer_public_key info.registration_transaction_id = transaction.signature self.dbg_dump.dump_contract_enclave_state(info, "Setting new PdoContractEnclaveInfo") self._set_enclave_state(context, payload.verifying_key, info.SerializeToString()) elif payload.verb == 'delete' or payload.verb == 'update': self.dbg_dump.dump_contract_enclave_state(info, "Received PdoContractEnclaveInfo") if payload.verb == 'delete': if not self._debug_on: raise InvalidTransaction('Delete is not allowed, debug support is OFF') LOGGER.info("Deleting PdoContractEnclaveInfo %s", payload.verifying_key) self._delete_enclave_state(context, payload.verifying_key) else: # Check the contract enclave owner matches transaction signer. if info.owner_id != txn_signer_public_key: raise InvalidTransaction( 'Owner signature mismatch signer {}, owner {}' .format(info.verifying_key, txn_signer_public_key)) details = PdoContractEnclaveUpdate() details.ParseFromString(payload.transaction_details) info.last_registration_block_context = \ details.registration_block_context self.dbg_dump.dump_contract_enclave_state(info, "Updating existing PdoContractEnclaveInfo") self._set_enclave_state(context, payload.verifying_key, info.SerializeToString()) else: raise InvalidTransaction('Invalid transaction action {}' .format(payload.verb))
class ContractRegistryTransactionHandler(TransactionHandler): def __init__(self, debug_on, dbg_dump_to_logger=True): self.connect = PdoTpConnectHelper() self._debug_on = debug_on if dbg_dump_to_logger: self.dbg_dump = PdoDbgDump(LOGGER) else: self.dbg_dump = PdoDbgDump() LOGGER.debug("Contract namespace prefix: %s", self.connect.get_contract_prefix()) @property def family_name(self): family = self.connect.get_contract_registry_family_name() LOGGER.debug("Contract family name: %s", family) return family @property def family_versions(self): return ['1.0'] @property def namespaces(self): return self.connect.get_contract_prefix() def _get_enclave_state(self, context, enclave_id): address = self.connect.get_enclave_address(enclave_id) return self.connect.get_state(context, address, PdoContractEnclaveInfo) def _set_contract_state(self, context, contract_id, data): address = self.connect.get_contract_address(contract_id) self.connect.set_state(context, address, data) def _delete_contract_state(self, context, contract_id): address = self.connect.get_contract_address(contract_id) self.connect.delete_state(context, address) def _get_contract_state(self, context, contract_id): address = self.connect.get_contract_address(contract_id) return self.connect.get_state(context, address, PdoContractInfo) def check_address(self, context, contract_id, register_new): LOGGER.debug("check_address.address of: %s", contract_id) try: state = self._get_contract_state(context, contract_id) if register_new: if state.contract_id: raise InvalidTransaction( 'Contract already exists with signing_key {}'\ .format(contract_id)) else: return state else: if not state.contract_id: raise InvalidTransaction( 'Contract does not exist: {0}'.format(contract_id)) else: return state except InternalError as error: if not register_new: raise InvalidTransaction( 'Contract does not exist: {0}'.format(contract_id)) else: return PdoContractInfo() @staticmethod def _verify_register(reg_info, txn_signer_public_key): if not reg_info.contract_code_hash: raise InvalidTransaction('Contract code hash is empty') if len(reg_info.provisioning_service_ids) == 0: raise InvalidTransaction('No provisioning service specified') for provisioning_service_id in reg_info.provisioning_service_ids: if not provisioning_service_id: raise InvalidTransaction('Empty provisioning service id') if not verify_contract_register_signature(reg_info, txn_signer_public_key): raise InvalidTransaction('Contract register signature is invalid') def _verify_enclave_info(self, context, contract_info, enclave_info): # enclave_info.contract_enclave_id is not empty if not enclave_info.contract_enclave_id: raise InvalidTransaction('Empty contract_enclave_id') contract_enclave_info = \ self._get_enclave_state(context, enclave_info.contract_enclave_id) # enclave_info.contract_enclave_id is already in the state list for c in contract_info.enclaves_info: if enclave_info.contract_enclave_id == c.contract_enclave_id: raise InvalidTransaction( 'Contract enclave id is already in the state list') # enclave_info.encrypted_contract_state_encryption_key not empty if not enclave_info.encrypted_contract_state_encryption_key: raise InvalidTransaction( 'Empty encrypted_contract_state_encryption_key') # enclave_info.enclave_signature is not empty if not enclave_info.enclave_signature: raise InvalidTransaction('Empty enclave_signature') # enclave_info.enclaves_map is not empty if len(enclave_info.enclaves_map) == 0: raise InvalidTransaction('Empty enclaves_map') for m in enclave_info.enclaves_map: # m.provisioning_service_public_key is not empty if not m.provisioning_service_public_key: raise InvalidTransaction( 'Empty provisioning_service_public_key') # m.provisioning_service_public_key exists in contract_info if not m.provisioning_service_public_key\ in contract_info.provisioning_service_ids: raise InvalidTransaction( 'Provisioning service id {} not in contract'.format( m.provisioning_service_public_key)) # m.provisioning_contract_state_secret if not m.provisioning_contract_state_secret: raise InvalidTransaction( 'Empty provisioning_contract_state_secret') if not contract_enclave_info.verifying_key: raise InvalidTransaction('Unregisteted enclave with id {}'.format( enclave_info.contract_enclave_id)) # verify signature if not verify_add_enclave_to_contract_signature( enclave_info, contract_info): raise InvalidTransaction('Enclave signature is invalid') def apply(self, transaction, context): txn_header = transaction.header txn_signer_public_key = txn_header.signer_public_key payload = PdoContractTransaction() payload.ParseFromString(transaction.payload) if payload.verb == 'register': sig_unxelified = binascii.unhexlify(transaction.signature) digest = hashlib.sha256(sig_unxelified).digest() sig_base64 = base64.b64encode(digest) contract_id = sig_base64.decode("utf-8", "ignore") else: contract_id = payload.contract_id self.dbg_dump.dump_contract_transaction(payload) info = self.check_address(context, contract_id, payload.verb == 'register') if payload.verb != 'register': self.dbg_dump.dump_contract_state(info, "Received PdoContractInfo") if payload.verb == 'register': details = PdoContractRegister() details.ParseFromString(payload.transaction_details) self._verify_register(details, txn_signer_public_key) info.contract_id = contract_id info.contract_code_hash = details.contract_code_hash info.pdo_contract_creator_pem_key = details.pdo_contract_creator_pem_key for id in details.provisioning_service_ids: info.provisioning_service_ids.append(id) self.dbg_dump.dump_contract_state(info, "Setting new PdoContractInfo") self._set_contract_state(context, contract_id, info.SerializeToString()) LOGGER.info("Contract %s was added to the registry.", payload.contract_id) elif payload.verb == 'delete': if not self._debug_on: raise InvalidTransaction( 'Delete is not allowed, debug support is OFF') LOGGER.info("Contract %s was deleted", payload.contract_id) self._delete_contract_state(context, contract_id) elif payload.verb == 'add-enclaves': details = PdoContractAddEnclaves() details.ParseFromString(payload.transaction_details) if not verify_add_enclave_to_contract_pdo_signature( details, info, txn_signer_public_key): raise InvalidTransaction( 'Overall PDO signature for add enclaves transaction is invalid' ) for enclave_info in details.enclaves_info: self._verify_enclave_info(context, info, enclave_info) for ei in details.enclaves_info: enclave_info = info.enclaves_info.add() enclave_info.CopyFrom(ei) self.dbg_dump.dump_contract_state( info, "PdoContractInfo After adding enclave(s)") self._set_contract_state(context, contract_id, info.SerializeToString()) LOGGER.info("Enclaves were added to contract %s.", payload.contract_id) elif payload.verb == 'remove-enclaves': details = PdoContractRemoveEnclaves() details.ParseFromString(payload.transaction_details) for id in details.contract_enclave_ids: for ei in info.enclaves_info: if ei.contract_enclave_id == id: info.enclaves_info.remove(ei) break self.dbg_dump.dump_contract_state( info, "PdoContractInfo after removing enclave(s)") self._set_contract_state(context, contract_id, info.SerializeToString()) LOGGER.info("Enclaves were removed from contract %s.", payload.contract_id) else: raise InvalidTransaction('Invalid transaction action {}'.format( payload.verb))
def execute_show_request(self, type, value, wait): if type == 'address': prefix = value[0:6] if prefix == self.connect.get_enclave_prefix(): pdo_state_type = PdoContractEnclaveInfo dump = PdoDbgDump().dump_contract_enclave_state prompt = "\nEnclave info for address {}:".format(value) elif prefix == self.connect.get_contract_prefix(): pdo_state_type = PdoContractInfo dump = PdoDbgDump().dump_contract_state prompt = "\nContract info for address {}:".format(value) elif prefix == self.connect.get_ccl_info_prefix(): pdo_state_type = CCL_ContractInformation dump = PdoDbgDump().dump_ccl_info prompt = "\nCCL info for address {}:".format(value) elif prefix == self.connect.get_ccl_state_prefix(): pdo_state_type = CCL_ContractState dump = PdoDbgDump().dump_ccl_state prompt = "\nCCL state for address {}:".format(value) else: raise PdoCliException( "It is not a PD0 namespaces prefix '{}'".format(prefix)) try: info = self.connect.get_state(value, pdo_state_type, value) dump(info, prompt) except BaseException as err: print(err) except ClientConnectException as err: print(err) elif type == 'enclave': if self._verbose: print("Enclave as dictionary:\n{}\n".format( self.connect.get_enclave_dict(value))) state = self._get_enclave_state(value) PdoDbgDump().dump_contract_enclave_state( state, "\nPDO Enclave Info for {}".format( self.connect.get_enclave_address(value))) elif type == 'contract': if self._verbose: print("Contract as dictionary:\n{}\n".format( self.connect.get_contract_dict(value))) state = self._get_contract_state(value) PdoDbgDump().dump_contract_state( state, "\nPDO Contract Info for {}".format( self.connect.get_contract_address(value))) elif type == 'ccl': if self._verbose: print("CCL Info as dictionary:\n{}\n".format( self.connect.get_ccl_info_dict(value))) info = self._get_ccl_info_state(value) PdoDbgDump().dump_ccl_info( info, "\nPDO CCL Info for {}".format( self.connect.get_ccl_info_address(value))) if info.current_state.state_hash and info.current_state.contract_id: if self._verbose: print("CCL State as dictionary:\n{}\n".format( self.connect.get_ccl_state_dict( value, info.current_state.state_hash))) state = self._get_ccl_state_state( value, info.current_state.state_hash) PdoDbgDump().dump_ccl_state( state, "\nPDO CCL State for {}".format( self.connect.get_ccl_state_address( value, info.current_state.state_hash))) elif type == 'ccl-history': info = self._get_ccl_info_state(value) PdoDbgDump().dump_ccl_info( info, "\nPDO CCL Info for {}".format( self.connect.get_ccl_info_address(value))) state_hash = info.current_state.state_hash while state_hash: state = self._get_ccl_state_state(value, state_hash) PdoDbgDump().dump_ccl_state( state, "\nPDO CCL State for {}".format( self.connect.get_ccl_state_address(value, state_hash))) state_hash = state.state_update.previous_state_hash elif type == 'ccl-state': value_list = value.split(':') state = self._get_ccl_state_state(value_list[0], value_list[1]) PdoDbgDump().dump_ccl_state( state, "\nPDO CCL State for {}".format( self.connect.get_ccl_state_address(value_list[0], value_list[1]))) elif type == 'setting': if value == "basenames": setting_name = self.connect.get_valid_basenames_setting_name() elif value == "measurements": setting_name = self.connect.get_valid_measurements_setting_name( ) elif value == "report-public-key": setting_name = self.connect.get_report_public_key_setting_name( ) else: setting_name = value if self._verbose: print("value:", value) self._show_config_setting(setting_name) else: raise PdoCliException("invalid PD0 state type '{}'".format(type)) return "OK"
def execute_json_transaction(self, json_input, address_family, wait): if self._verbose: print("\njson_input:") print(json_input) json_dict = json.loads(json_input) if not address_family: try: address_family = json_dict['af'] except: raise PdoCliException( "Family not defined, use 'af'in the json or --enclave, --contract, or --ccl on the command line" ) # set contract id and signature(s) if they are empty in case of contract add and remove enclaves transactions if address_family == self.connect.get_contract_registry_family_name(): if json_dict['verb'] != 'delete': details = None contract = None if json_dict['verb'] != 'register' and not json_dict[ 'contract_id']: contract = self._find_contract_id() if json_dict['verb'] == 'register': # generate pdo_signature and pdo_contract_creator_pem_key in case of contract register details = PdoContractRegister() json_format.Parse(json_input, details, ignore_unknown_fields=True) details.contract_code_hash = self._to_base64( details.contract_code_hash) # normally it would be done using one time transaction signing key # to simplify test automation we are going to reuse the same key was used for contract registration details.pdo_signature = secp256k1_sign( make_contract_register_hash_input( details, self.connect.get_signer_public_key_as_hex()), self.connect.get_signer_private_key_as_hex()) details.pdo_contract_creator_pem_key = self.connect.get_signer_public_key_as_hex( ) contract = PdoContractInfo() if 'contract_id' in json_dict: contract.contract_id = json_dict.get('contract_id') elif json_dict['verb'] == 'remove-enclaves': details = PdoContractRemoveEnclaves() json_format.Parse(json_input, details, ignore_unknown_fields=True) elif json_dict['verb'] == 'add-enclaves': details = PdoContractAddEnclaves() json_format.Parse(json_input, details, ignore_unknown_fields=True) # generate enclave signatures for each enclave info # if signatures are empty in the json input for enclave_info in details.enclaves_info: if not enclave_info.enclave_signature: if not contract: contract = self._get_contract_state( json_dict['contract_id']) if not not contract or not contract.contract_id: raise PdoCliException( "Cannot load contract to generate signature {}" .format(json_dict['contract_id'])) enclave_info.encrypted_contract_state_encryption_key = \ base64.b64encode(enclave_info.encrypted_contract_state_encryption_key.encode()) hash_input = make_add_enclave_to_contract_hash_input( enclave_info, contract) enclave_info.enclave_signature = secp256k1_sign( hash_input, self._enclave_signing_private_key) contract_creator_private_key = self.connect.get_signer_private_key_as_hex( ) self.connect.generate_new_signer_key() details.pdo_signature = secp256k1_sign( make_add_enclave_to_contract_pdo_hash_input( details, contract, self.connect.get_signer_public_key_as_hex()), contract_creator_private_key) if contract: txn = PdoContractTransaction() txn.verb = json_dict['verb'] txn.contract_id = contract.contract_id if details: txn.transaction_details = details.SerializeToString() if self._verbose: print("Updated Contract Transaction:") PdoDbgDump().dump_contract_transaction(txn) PdoDbgDump( ).dump_contract_transaction_protobuf_message_to_json( txn) result = self.connect.send_transaction( txn.SerializeToString(), address_family, wait=wait) if self._verbose: print(result) print("OK") return # set contract id and signature(s) if they are empty in case of CCL transactions if address_family == self.connect.get_ccl_family_name(): if json_dict['verb'] != 'delete': signature = json_dict['contract_enclave_signature'] contract_id = json_dict['state_update']['contract_id'] if not contract_id or not signature: contract_creator_private_key = self.connect.get_signer_private_key_as_hex( ) self.connect.generate_new_signer_key() txn = CCL_TransactionPayload() json_format.Parse(json_input, txn, ignore_unknown_fields=True) # for testing set channel_id to the one time transaction signer key txn.channel_id = self.connect.get_signer_public_key_as_hex( ) if not contract_id: contract = self._find_contract_id() else: contract = self._get_contract_state(contract_id) txn.state_update.contract_id = contract.contract_id for d in txn.state_update.dependency_list: d.contract_id = contract.contract_id d.state_hash = self._to_base64(d.state_hash) txn.state_update.current_state_hash = self._to_base64( txn.state_update.current_state_hash) txn.state_update.previous_state_hash = self._to_base64( txn.state_update.previous_state_hash) txn.state_update.message_hash = self._to_base64( txn.state_update.message_hash) contract.contract_code_hash = self._to_base64( contract.contract_code_hash) if not signature: txn.contract_enclave_signature = sign_ccl_transaction( txn, contract, self._enclave_signing_private_key) if json_dict['verb'] == 'initialize': # generate PDO signature normally done using one time transaction signing key # to simplify test automation assume reuse signing key used to register the contract txn.pdo_signature = secp256k1_sign( make_ccl_transaction_pdo_hash_input(txn, contract), contract_creator_private_key) if self._verbose: print("Updated CCL Transaction:") PdoDbgDump().dump_ccl_transaction(txn) PdoDbgDump( ).dump_ccl_transaction_protobuf_message_to_json(txn) result = self.connect.send_transaction( txn.SerializeToString(), address_family, wait=wait) if self._verbose: print(result) print("OK") return try: if self.connect.execute_json_transaction(json_input, address_family, wait, PdoCliException, self._verbose): print("OK") else: print("Error") except PdoCliException as e: print(e) print("Error") except TypeError as err: print("missing or invalid key:", err) print("Error")
class ContractCclTransactionHandler(TransactionHandler): def __init__(self, debug_on, dbg_dump_to_logger=True): self.connect = PdoTpConnectHelper() self._debug_on = debug_on if dbg_dump_to_logger: self.dbg_dump = PdoDbgDump(LOGGER) else: self.dbg_dump = PdoDbgDump() LOGGER.debug("CCL state namespace prefix: %s", self.connect.get_ccl_state_prefix()) LOGGER.debug("CCL information namespace prefix: %s", self.connect.get_ccl_info_prefix()) LOGGER.debug("Contract namespace prefix: %s", self.connect.get_contract_prefix()) LOGGER.debug("Enclave namespace prefix: %s", self.connect.get_enclave_prefix()) @property def family_name(self): family = self.connect.get_ccl_family_name() LOGGER.debug("CCL family name: %s", family) return family @property def family_versions(self): return ['1.0'] @property def namespaces(self): return [ self.connect.get_ccl_state_prefix(), self.connect.get_ccl_info_prefix() ] def _set_ccl_state(self, context, ccl_state): self.connect.set_state( context, self.connect.get_ccl_state_address( ccl_state.state_update.contract_id, ccl_state.state_update.current_state_hash), ccl_state.SerializeToString()) def _get_ccl_state(self, context, contract_id, state_hash): address = self.connect.get_ccl_state_address(contract_id, state_hash) return self.connect.get_state(context, address, CCL_ContractState) def _delete_ccl_state(self, context, contract_id, state_hash): address = self.connect.get_ccl_state_address(contract_id, state_hash) self.connect.delete_state(context, address) def _set_ccl_info(self, context, ccl_info): address = self.connect.get_ccl_info_address(ccl_info.contract_id) self.connect.set_state(context, address, ccl_info.SerializeToString()) def _get_ccl_info(self, context, contract_id): address = self.connect.get_ccl_info_address(contract_id) result = self.connect.get_state(context, address, CCL_ContractInformation) return result def _delete_ccl_info(self, context, contract_id): address = self.connect.get_ccl_info_address(contract_id) self.connect.delete_state(context, address) def _get_contract_info(self, context, contract_id): address = self.connect.get_contract_address(contract_id) return self.connect.get_state(context, address, PdoContractInfo) def _get_enclave_info(self, context, enclave_id): address = self.connect.get_enclave_address(enclave_id) return self.connect.get_state(context, address, PdoContractEnclaveInfo) def _verify_common(self, context, payload, signer, initialize=False): # check that signer matches channel_id if payload.channel_id != signer: raise InvalidTransaction( "Payload channel id '{0}' doesn't match signer '{1}'".format( payload.channel_id, signer)) # check that this contract exists contract = self._get_contract_info(context, payload.state_update.contract_id) if payload.state_update.contract_id != contract.contract_id: raise InvalidTransaction('No contract in registry {}'.format( payload.state_update.contract_id)) # check that this enclave exists enclave = self._get_enclave_info(context, payload.contract_enclave_id) if payload.contract_enclave_id != enclave.verifying_key: raise InvalidTransaction('Enclave does not exist for {}'.format( payload.contract_enclave_id)) # check that this enclave is added to the contract enclave_found = False for e in contract.enclaves_info: if e.contract_enclave_id == payload.contract_enclave_id: enclave_found = True break if not enclave_found: raise InvalidTransaction( 'Enclave {0} has not been added to contract {1}'.format( payload.contract_enclave_id, payload.state_update.contract_id)) # check dependencies for d in payload.state_update.dependency_list: state = self._get_ccl_state(context, d.contract_id, d.state_hash) if not state.state_update.contract_id: raise InvalidTransaction( "Dependency doesn't exist for '{0}' '{1}'".format( d.contract_id, d.state_hash)) # check enclave signature if not verify_ccl_transaction_signature(payload, contract): raise InvalidTransaction( 'Contract CCL enclave signature is invalid') # verify PDO signature if initialize: if not verify_ccl_transaction_pdo_signature(payload, contract): raise InvalidTransaction( 'Contract CCL Initialize PDO signature is invalid') def _check_current_ccl_state_and_info(self, context, payload): info = self._get_ccl_info(context, payload.state_update.contract_id) self.dbg_dump.dump_ccl_info(info) if payload.verb == 'initialize': if info.contract_id: raise InvalidTransaction( 'CCL Contract already exists for {}'.format( payload.state_update.contract_id)) else: if not info.contract_id: raise InvalidTransaction( 'CCL Contract does not exist: {0}'.format( payload.state_update.contract_id)) else: if not info.is_active: raise InvalidTransaction( 'CCL Contract has been terminated: {0}'.format( payload.state_update.contract_id)) state = self._get_ccl_state(context, info.current_state.contract_id, info.current_state.state_hash) self.dbg_dump.dump_ccl_state(state) if state.state_update.contract_id !=\ info.current_state.contract_id\ or\ state.state_update.current_state_hash != \ info.current_state.state_hash: raise InvalidTransaction( "CCL Contract state doesn't exist or invalid") return state # return new state in case of "initialize" action return CCL_ContractState() def _verify_initialize(self, context, payload, signer): if payload.state_update.previous_state_hash: raise InvalidTransaction( 'Previous state hash must be empty on initialize') if len(payload.state_update.dependency_list) != 0: raise InvalidTransaction( 'Dependency list must be empty on initialize') self._check_current_ccl_state_and_info(context, payload) self._verify_common(context, payload, signer, True) def _verify_update(self, context, payload, signer): state = self._check_current_ccl_state_and_info(context, payload) if payload.state_update.previous_state_hash !=\ state.state_update.current_state_hash: raise InvalidTransaction( 'Previous state hash in transcation {0}'\ 'mismatches current {1}'.format( payload.state_update.previous_state_hash, state.state_update.current_state_hash)) self._verify_common(context, payload, signer) return state def _verify_terminate(self, context, payload, signer): state = self._check_current_ccl_state_and_info(context, payload) if payload.state_update.previous_state_hash !=\ state.state_update.current_state_hash: raise InvalidTransaction( 'Previous state hash in transcation {0}'\ 'mismatches current {1}'.format( payload.state_update.previous_state_hash, state.state_update.current_state_hash)) if payload.state_update.current_state_hash: raise InvalidTransaction( 'Current state hash must be empty on terminate') self._verify_common(context, payload, signer) return state def _complete_action(self, context, transaction, payload, contract_id): ccl_state = CCL_ContractState() ccl_info = CCL_ContractInformation() ccl_state.transaction_id = transaction.signature ccl_state.state_update.CopyFrom(payload.state_update) ccl_info.contract_id = \ payload.state_update.contract_id ccl_info.is_active = \ True if payload.verb != 'terminate' else False ccl_info.current_state.contract_id = \ ccl_state.state_update.contract_id ccl_info.current_state.state_hash = \ ccl_state.state_update.current_state_hash self.dbg_dump.dump_ccl_info(ccl_info) self.dbg_dump.dump_ccl_state(ccl_state) if payload.verb != 'terminate': self._set_ccl_state(context, ccl_state) else: ccl_info.current_state.state_hash = \ payload.state_update.previous_state_hash self._set_ccl_info(context, ccl_info) def apply(self, transaction, context): txn_header = transaction.header txn_signer_public_key = txn_header.signer_public_key payload = CCL_TransactionPayload() payload.ParseFromString(transaction.payload) self.dbg_dump.dump_ccl_transaction(payload) if payload.verb == 'initialize': self._verify_initialize(context, payload, txn_signer_public_key) self._complete_action(context, transaction, payload, payload.state_update.contract_id) LOGGER.info("Contract CCL initialized for contract %s", payload.state_update.contract_id) elif payload.verb == 'update': contract_id = self._verify_update(context, payload, txn_signer_public_key) self._complete_action(context, transaction, payload, contract_id) LOGGER.info("Contract CCL updated for contract %s", payload.state_update.contract_id) elif payload.verb == 'terminate': contract_id = self._verify_terminate(context, payload, txn_signer_public_key) self._complete_action(context, transaction, payload, contract_id) LOGGER.info("Contract CCL updated for contract %s", payload.state_update.contract_id) elif payload.verb == 'delete': # 'delete' is useful for development/testing # it should be removed from the production # it is for debug only so no verification # 1) delete states listed in state_update.dependencies_list if not self._debug_on: raise InvalidTransaction( 'Delete is not allowed, debug support is OFF') for d in payload.state_update.dependency_list: state = self._get_ccl_state(context, d.contract_id, d.state_hash) if state.state_update.contract_id != d.contract_id: LOGGER.info("CCL state doesn't exist for '%s':'%s'", d.contract_id, d.state_hash) else: self._delete_ccl_state(context, d.contract_id, d.state_hash) LOGGER.info("CCL state deleted for '%s':'%s'", d.contract_id, d.state_hash) # 2) if payload.state_update.contract_id != "" remove info also if payload.state_update.contract_id: info = self._get_ccl_info(context, payload.state_update.contract_id) if info.current_state.contract_id != \ payload.state_update.contract_id: LOGGER.info("CCL info doesn't exist for '%s'", payload.state_update.contract_id) else: self._delete_ccl_info(context, payload.state_update.contract_id) LOGGER.info("CCL info deleted for %s", payload.state_update.contract_id) else: raise InvalidTransaction('Invalid transaction verb {}'.format( payload.verb))