def unseal_signup_data(cls, sealed_signup_data): """ Args: sealed_signup_data: Sealed signup data that was returned previously in a EnclaveSignupInfo object from a call to create_signup_info Returns: A string The hex encoded PoET public key that was extracted from the sealed data """ # Reverse the process we used in creating "sealed" signup info. # Specifically, we will do a base 32 decode, which gives us json # we can convert back to a dictionary we can use to get the # data we need signup_data = \ json2dict(base64.b64decode(sealed_signup_data)) with cls._lock: cls._poet_public_key = \ signing.decode_pubkey( signup_data.get('poet_public_key'), 'hex') cls._poet_private_key = \ signing.decode_privkey( signup_data.get('poet_private_key'), 'hex') cls._active_wait_timer = None return signup_data.get('poet_public_key')
def __init__(self, base_url, store_name=None, name='SawtoothClient', txntype_name=None, msgtype_name=None, keystring=None, keyfile=None, disable_client_validation=False): self._base_url = base_url self._message_type = msgtype_name self._transaction_type = txntype_name # an explicit store name takes precedence over a store name # implied by the transaction type self._store_name = None if store_name is not None: self._store_name = store_name.strip('/') elif txntype_name is not None: self._store_name = txntype_name.strip('/') self._communication = _Communication(base_url) self._last_transaction = None self._signing_key = None self._identifier = None self._update_batch = None self._disable_client_validation = disable_client_validation if keystring: LOGGER.debug("set signing key from string\n%s", keystring) self._signing_key = signing.encode_privkey( signing.decode_privkey(keystring, 'wif'), 'hex') elif keyfile: LOGGER.debug("set signing key from file %s", keyfile) try: self._signing_key = signing.encode_privkey( signing.decode_privkey( open(keyfile, "r").read().strip(), 'wif'), 'hex') except IOError as ex: raise ClientException("Failed to load key file: {}".format( str(ex))) if self._signing_key is not None: self._identifier = signing.generate_identifier( signing.generate_pubkey(self._signing_key))
def generate_signing_key(wifstr=None): """Returns a decoded signing key associated with wifstr or generates a random signing key. Args: wifstr (str): A private key in wif format. Returns: str: a signing key. """ if wifstr: return signing.decode_privkey(wifstr) else: return signing.generate_privkey()
class _PoetEnclaveSimulator(object): # A lock to protect threaded access _lock = threading.Lock() # The WIF-encoded enclave private seal key. From it, we will create # private and public keys we can use for sealing and unsealing signup # info. __SEAL_PRIVATE_KEY_WIF = \ '5KYsbooGBg51Gohakgq45enpXvCXmEBed1JivFfUZskmjLegHBG' _seal_private_key = \ signing.decode_privkey(__SEAL_PRIVATE_KEY_WIF, 'wif') _seal_public_key = signing.generate_pubkey(_seal_private_key) # The WIF-encoded private report key. From it, we will create private # key we can use for signing attestation verification reports. __REPORT_PRIVATE_KEY_WIF = \ '5Jz5Kaiy3kCiHE537uXcQnJuiNJshf2bZZn43CrALMGoCd3zRuo' _report_private_key = \ signing.decode_privkey(__REPORT_PRIVATE_KEY_WIF, 'wif') _report_public_key = signing.generate_pubkey(_report_private_key) # Minimum duration for PoET 1 simulator is 30 seconds __MINIMUM_DURATTION = 30.0 # The anti-sybil ID for this particular validator. This will get set when # the enclave is initialized _anti_sybil_id = None # The PoET keys will remain unset until signup info is either created or # unsealed _poet_public_key = None _poet_private_key = None _active_wait_timer = None @classmethod def initialize(cls, **kwargs): # Create an anti-Sybil ID that is unique for this validator cls._anti_sybil_id = \ hashlib.sha256( kwargs.get('NodeName', 'validator')).hexdigest() @classmethod def create_signup_info(cls, originator_public_key_hash, most_recent_wait_certificate_id): with cls._lock: # First we need to create a public/private key pair for the PoET # enclave to use. cls._poet_private_key = signing.generate_privkey() cls._poet_public_key = \ signing.generate_pubkey(cls._poet_private_key) cls._active_wait_timer = None # We are going to fake out the sealing the signup data. signup_data = { 'poet_public_key': signing.encode_pubkey(cls._poet_public_key, 'hex'), 'poet_private_key': signing.encode_privkey(cls._poet_private_key, 'hex') } sealed_signup_data = \ base64.b64encode(dict2json(signup_data)) # Create a fake report report_data = '{0}{1}'.format( originator_public_key_hash.upper(), signing.encode_pubkey(cls._poet_public_key, 'hex').upper()) quote = { 'report_body': hashlib.sha256(dict2json(report_data)).hexdigest() } # Fake our "proof" data. verification_report = { 'id': base64.b64encode( hashlib.sha256( datetime.datetime.now().isoformat()).hexdigest()), 'isvEnclaveQuoteStatus': 'OK', 'isvEnclaveQuoteBody': base64.b64encode(dict2json(quote)), 'pseManifestStatus': 'OK', 'pseManifestHash': base64.b64encode( hashlib.sha256(b'Do you believe in ' 'manifest destiny?').hexdigest()), 'nonce': most_recent_wait_certificate_id } proof_data_dict = { 'verification_report': dict2json(verification_report), 'signature': signing.sign(dict2json(verification_report), cls._report_private_key) } proof_data = dict2json(proof_data_dict) return \ EnclaveSignupInfo( poet_public_key=signup_data['poet_public_key'], proof_data=proof_data, anti_sybil_id=originator_public_key_hash, sealed_signup_data=sealed_signup_data) @classmethod def deserialize_signup_info(cls, serialized_signup_info): return \ EnclaveSignupInfo.signup_info_from_serialized( serialized_signup_info=serialized_signup_info) @classmethod def unseal_signup_data(cls, sealed_signup_data): """ Args: sealed_signup_data: Sealed signup data that was returned previously in a EnclaveSignupInfo object from a call to create_signup_info Returns: A string The hex encoded PoET public key that was extracted from the sealed data """ # Reverse the process we used in creating "sealed" signup info. # Specifically, we will do a base 32 decode, which gives us json # we can convert back to a dictionary we can use to get the # data we need signup_data = \ json2dict(base64.b64decode(sealed_signup_data)) with cls._lock: cls._poet_public_key = \ signing.decode_pubkey( signup_data.get('poet_public_key'), 'hex') cls._poet_private_key = \ signing.decode_privkey( signup_data.get('poet_private_key'), 'hex') cls._active_wait_timer = None return signup_data.get('poet_public_key') @classmethod def verify_signup_info(cls, signup_info, originator_public_key_hash, most_recent_wait_certificate_id): # Verify the attestation verification report signature proof_data_dict = json2dict(signup_info.proof_data) verification_report = proof_data_dict.get('verification_report') if verification_report is None: raise \ SignupInfoError( 'Verification report is missing from proof data') signature = proof_data_dict.get('signature') if signature is None: raise \ SignupInfoError( 'Signature is missing from proof data') if not signing.verify(verification_report, signature, cls._report_public_key): raise \ SignupInfoError('Verification report signature is invalid') verification_report_dict = json2dict(verification_report) # Verify that the verification report contains a PSE manifest status # and it is OK pse_manifest_status = \ verification_report_dict.get('pseManifestStatus') if pse_manifest_status is None: raise \ SignupInfoError( 'Verification report does not contain a PSE manifest ' 'status') if pse_manifest_status != 'OK': raise \ SignupInfoError( 'PSE manifest status is {} (i.e., not OK)'.format( pse_manifest_status)) # Verify that the verification report contains a PSE manifest hash # and it is the value we expect pse_manifest_hash = \ verification_report_dict.get('pseManifestHash') if pse_manifest_hash is None: raise \ SignupInfoError( 'Verification report does not contain a PSE manifest ' 'hash') expected_pse_manifest_hash = \ base64.b64encode( hashlib.sha256( b'Do you believe in manifest destiny?').hexdigest()) if pse_manifest_hash != expected_pse_manifest_hash: raise \ SignupInfoError( 'PSE manifest hash {0} does not match {1}'.format( pse_manifest_hash, expected_pse_manifest_hash)) # Verify that the verification report contains an enclave quote and # that its status is OK enclave_quote_status = \ verification_report_dict.get('isvEnclaveQuoteStatus') if enclave_quote_status is None: raise \ SignupInfoError( 'Verification report does not contain an enclave quote ' 'status') if enclave_quote_status != 'OK': raise \ SignupInfoError( 'Enclave quote status is {} (i.e., not OK)'.format( enclave_quote_status)) enclave_quote = verification_report_dict.get('isvEnclaveQuoteBody') if enclave_quote is None: raise \ SignupInfoError( 'Verification report does not contain an enclave quote') # Verify that the enclave quote contains a report body with the value # we expect (i.e., SHA256(SHA256(OPK)|PPK) report_data = '{0}{1}'.format(originator_public_key_hash.upper(), signup_info.poet_public_key.upper()) expected_report_body = hashlib.sha256( dict2json(report_data)).hexdigest() enclave_quote_dict = \ json2dict(base64.b64decode(enclave_quote)) report_body = enclave_quote_dict.get('report_body') if report_body is None: raise \ SignupInfoError( 'Enclave quote does not contain a report body') if report_body != expected_report_body: raise \ SignupInfoError( 'Enclave quote report body {0} does not match {1}'.format( report_body, expected_report_body)) # Verify that the wait certificate ID in the verification report # matches the provided wait certificate ID. The wait certificate ID # is stored in the nonce field. nonce = verification_report_dict.get('nonce') if nonce is None: raise \ SignupInfoError( 'Verification report does not have a nonce') # NOTE - this check is currently not performed as a transaction # does not have a good way to obtaining the most recent # wait certificate ID. # # if nonce != most_recent_wait_certificate_id: # raise \ # SignupInfoError( # 'Attestation evidence payload nonce {0} does not match ' # 'most-recently-committed wait certificate ID {1}'.format( # nonce, # most_recent_wait_certificate_id)) @classmethod def create_wait_timer(cls, previous_certificate_id, local_mean): with cls._lock: # If we don't have a PoET private key, then the enclave has not # been properly initialized (either by calling create_signup_info # or unseal_signup_data) if cls._poet_private_key is None: raise \ WaitTimerError( 'Enclave must be initialized before attempting to ' 'create a wait timer') # Create some value from the cert ID. We are just going to use # the seal key to sign the cert ID. We will then use the # low-order 64 bits to change that to a number [0, 1] tag = \ base64.b64decode( signing.sign( previous_certificate_id, cls._seal_private_key)) tagd = float(struct.unpack('L', tag[-8:])[0]) / (2**64 - 1) # Now compute the duration duration = cls.__MINIMUM_DURATTION - local_mean * math.log(tagd) # Create and sign the wait timer wait_timer = \ EnclaveWaitTimer( duration=duration, previous_certificate_id=previous_certificate_id, local_mean=local_mean) wait_timer.signature = \ signing.sign( wait_timer.serialize(), cls._poet_private_key) # Keep track of the active wait timer cls._active_wait_timer = wait_timer return wait_timer @classmethod def deserialize_wait_timer(cls, serialized_timer, signature): with cls._lock: # Verify the signature before trying to deserialize if not signing.verify(serialized_timer, signature, cls._poet_public_key): return None return \ EnclaveWaitTimer.wait_timer_from_serialized( serialized_timer=serialized_timer, signature=signature) @classmethod def create_wait_certificate(cls, block_digest): with cls._lock: # If we don't have a PoET private key, then the enclave has not # been properly initialized (either by calling create_signup_info # or unseal_signup_data) if cls._poet_private_key is None: raise \ WaitCertificateError( 'Enclave must be initialized before attempting to ' 'create a wait certificate') # Several criteria need to be met before we can create a wait # certificate: # 1. We have an active timer # 2. The active timer has expired # 3. The active timer has not timed out # # Note - we make a concession for the genesis block (i.e., a wait # timer for which the previous certificate ID is the Null # identifier) in that we don't require the timer to have expired # and we don't worry about the timer having timed out. if cls._active_wait_timer is None: raise \ WaitCertificateError( 'Enclave active wait timer has not been initialized') is_not_genesis_block = \ (cls._active_wait_timer.previous_certificate_id != NullIdentifier) now = time.time() expire_time = \ cls._active_wait_timer.request_time + \ cls._active_wait_timer.duration if is_not_genesis_block and now < expire_time: raise \ WaitCertificateError( 'Cannot create wait certificate because timer has ' 'not expired') time_out_time = \ cls._active_wait_timer.request_time + \ cls._active_wait_timer.duration + \ TIMER_TIMEOUT_PERIOD if is_not_genesis_block and time_out_time < now: raise \ WaitCertificateError( 'Cannot create wait certificate because timer ' 'has timed out') # Create a random nonce for the certificate. For our "random" # nonce we will take the timer signature, concat that with the # current time, JSON-ize it and create a SHA-256 hash over it. # Probably not considered random by security professional # standards, but it is good enough for the simulator. random_string = \ dict2json({ 'wait_timer_signature': cls._active_wait_timer.signature, 'now': datetime.datetime.utcnow().isoformat() }) nonce = hashlib.sha256(random_string).hexdigest() # First create a new enclave wait certificate using the data # provided and then sign the certificate with the PoET private key wait_certificate = \ EnclaveWaitCertificate.wait_certificate_with_wait_timer( wait_timer=cls._active_wait_timer, nonce=nonce, block_digest=block_digest) wait_certificate.signature = \ signing.sign( wait_certificate.serialize(), cls._poet_private_key) # Now that we have created the certificate, we no longer have an # active timer cls._active_wait_timer = None return wait_certificate @classmethod def deserialize_wait_certificate(cls, serialized_certificate, signature): return \ EnclaveWaitCertificate.wait_certificate_from_serialized( serialized_certificate=serialized_certificate, signature=signature) @classmethod def verify_wait_certificate(cls, certificate, poet_public_key): # Reconstitute the PoET public key and check the signature over the # serialized wait certificate. decoded_poet_public_key = \ signing.decode_pubkey(poet_public_key, 'hex') return \ signing.verify( certificate.serialize(), certificate.signature, decoded_poet_public_key)
def get_address_from_private_key_wif(key): return signing.generate_identifier( signing.generate_pubkey(signing.decode_privkey(key, 'wif')))