def signup_info_from_serialized(cls, serialized_signup_info):
        """
        Takes signup information that has been serialized to JSON and
        reconstitutes into an EnclaveSignupInfo object.

        Args:
            serialized_signup_info: JSON serialized signup info

        Returns:
            An EnclaveSignupInfo object with sealed signup info data
            stripped out as serialized signup info doesn't contain it.
        """
        deserialized_signup_info = json2dict(serialized_signup_info)

        # Note - serialized signup info shouldn't have sealed signup
        # data.
        signup_info = \
            EnclaveSignupInfo(
                poet_public_key=deserialized_signup_info.get(
                    'poet_public_key'),
                proof_data=deserialized_signup_info.get(
                    'proof_data'),
                anti_sybil_id=deserialized_signup_info.get(
                    'anti_sybil_id'),
                serialized_signup_info=serialized_signup_info)

        return signup_info
    def signup_info_from_serialized(cls, serialized_signup_info):
        """
        Takes signup information that has been serialized to JSON and
        reconstitutes into an EnclaveSignupInfo object.

        Args:
            serialized_signup_info: JSON serialized signup info

        Returns:
            An EnclaveSignupInfo object with sealed signup info data
            stripped out as serialized signup info doesn't contain it.
        """
        deserialized_signup_info = json2dict(serialized_signup_info)

        # Note - serialized signup info shouldn't have sealed signup
        # data.
        signup_info = \
            EnclaveSignupInfo(
                poet_public_key=deserialized_signup_info.get(
                    'poet_public_key'),
                proof_data=deserialized_signup_info.get(
                    'proof_data'),
                anti_sybil_id=deserialized_signup_info.get(
                    'anti_sybil_id'),
                serialized_signup_info=serialized_signup_info)

        return signup_info
    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 64 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).decode())

        # Since the signing module uses strings for both private (WIF encoded)
        # and public (hex encoded) key canonical formats, we don't have to
        # decode.
        with cls._lock:
            cls._poet_public_key = str(signup_data.get('poet_public_key'))
            cls._poet_private_key = str(signup_data.get('poet_private_key'))
            cls._active_wait_timer = None

            return signup_data.get('poet_public_key')
    def wait_timer_from_serialized(cls, serialized_timer, signature):
        """
        Takes wait timer that has been serialized to JSON and reconstitutes
        into an EnclaveWaitTimer object.

        Args:
            serialized_timer (str): JSON serialized wait timer
            signature (str): Signature over serialized timer

        Returns:
            An EnclaveWaitTimer object
        """
        deserialized_timer = json2dict(serialized_timer)

        timer = \
            EnclaveWaitTimer(
                validator_address=str(deserialized_timer.get(
                    'validator_address')),
                duration=float(deserialized_timer.get(
                    'duration')),
                previous_certificate_id=str(deserialized_timer.get(
                    'previous_certificate_id')),
                local_mean=float(deserialized_timer.get(
                    'local_mean')),
                signature=signature,
                serialized_timer=serialized_timer)

        timer.request_time = float(deserialized_timer.get('request_time'))

        return timer
    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 64 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.encode()).decode())

        with cls._lock:
            cls._poet_public_key = str(signup_data.get('poet_public_key'))
            cls._poet_private_key = str(signup_data.get('poet_private_key'))
            cls._active_wait_timer = None

            return signup_data.get('poet_public_key')
Example #6
0
    def wait_certificate_from_serialized(cls, serialized_certificate,
                                         signature):
        """
        Takes wait certificate that has been serialized to JSON and
        reconstitutes into an EnclaveWaitCertificate object.

        Args:
            serialized_certificate (str): JSON serialized wait certificate
            signature (str): Signature over serialized certificate

        Returns:
            An EnclaveWaitCertificate object
        """
        deserialized_certificate = json2dict(serialized_certificate)

        certificate = \
            EnclaveWaitCertificate(
                duration=float(deserialized_certificate.get('duration')),
                previous_certificate_id=str(
                    deserialized_certificate.get('previous_certificate_id')),
                local_mean=float(deserialized_certificate.get('local_mean')),
                request_time=float(
                    deserialized_certificate.get('request_time')),
                validator_address=str(
                    deserialized_certificate.get('validator_address')),
                nonce=str(deserialized_certificate.get('nonce')),
                block_hash=str(deserialized_certificate.get(
                    'block_hash')),
                signature=signature,
                serialized_certificate=serialized_certificate)

        return certificate
    def wait_certificate_from_serialized(cls,
                                         serialized_certificate,
                                         signature):
        """
        Takes wait certificate that has been serialized to JSON and
        reconstitutes into an EnclaveWaitCertificate object.

        Args:
            serialized_certificate (str): JSON serialized wait certificate
            signature (str): Signature over serialized certificate

        Returns:
            An EnclaveWaitCertificate object
        """
        deserialized_certificate = json2dict(serialized_certificate)

        certificate = \
            EnclaveWaitCertificate(
                duration=float(deserialized_certificate.get('duration')),
                previous_certificate_id=str(
                    deserialized_certificate.get('previous_certificate_id')),
                local_mean=float(deserialized_certificate.get('local_mean')),
                request_time=float(
                    deserialized_certificate.get('request_time')),
                validator_address=str(
                    deserialized_certificate.get('validator_address')),
                nonce=str(deserialized_certificate.get('nonce')),
                block_hash=str(deserialized_certificate.get(
                    'block_hash')),
                signature=signature,
                serialized_certificate=serialized_certificate)

        return certificate
    def wait_timer_from_serialized(cls, serialized_timer, signature):
        """
        Takes wait timer that has been serialized to JSON and reconstitutes
        into an EnclaveWaitTimer object.

        Args:
            serialized_timer (str): JSON serialized wait timer
            signature (str): Signature over serialized timer

        Returns:
            An EnclaveWaitTimer object
        """
        deserialized_timer = json2dict(serialized_timer)

        timer = \
            EnclaveWaitTimer(
                validator_address=str(deserialized_timer.get(
                    'validator_address')),
                duration=float(deserialized_timer.get(
                    'duration')),
                previous_certificate_id=str(deserialized_timer.get(
                    'previous_certificate_id')),
                local_mean=float(deserialized_timer.get(
                    'local_mean')),
                signature=signature,
                serialized_timer=serialized_timer)

        timer.request_time = float(deserialized_timer.get('request_time'))

        return timer
    def create_wait_timer(cls,
                          sealed_signup_data,
                          validator_address,
                          previous_certificate_id,
                          local_mean):
        with cls._lock:
            # Extract keys from the 'sealed' signup data
            signup_data = \
                json2dict(
                    base64.b64decode(sealed_signup_data.encode()).decode())
            poet_private_key = signup_data['poet_private_key']

            if poet_private_key is None:
                raise \
                    ValueError(
                        'Invalid signup data. No poet private key.')

            try:
                poet_private_key = Secp256k1PrivateKey.from_hex(
                    poet_private_key)
            except ParseError:
                raise \
                    ValueError(
                        'Invalid signup data. Badly formatted poet key(s).')

            # In a TEE implementation we would increment the HW counter here.
            # We can't usefully simulate a HW counter though.

            # 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(
                    cls._context.sign(
                        previous_certificate_id.encode(),
                        cls._seal_private_key))

            tagd = float(struct.unpack('Q', tag[-8:])[0]) / (2**64 - 1)

            # Now compute the duration with a minimum wait time guaranteed
            duration = \
                _PoetEnclaveSimulator.MINIMUM_WAIT_TIME \
                - local_mean * math.log(tagd)

            # Create and sign the wait timer
            wait_timer = \
                EnclaveWaitTimer(
                    validator_address=validator_address,
                    duration=duration,
                    previous_certificate_id=previous_certificate_id,
                    local_mean=local_mean)
            wait_timer.signature = \
                cls._context.sign(
                    wait_timer.serialize().encode(),
                    poet_private_key)

            return wait_timer
    def create_wait_timer(cls,
                          sealed_signup_data,
                          validator_address,
                          previous_certificate_id,
                          local_mean):
        with cls._lock:
            # Extract keys from the 'sealed' signup data
            signup_data = \
                json2dict(
                    base64.b64decode(sealed_signup_data.encode()).decode())
            poet_private_key = signup_data['poet_private_key']

            if poet_private_key is None:
                raise \
                    ValueError(
                        'Invalid signup data. No poet private key.')

            try:
                poet_private_key = Secp256k1PrivateKey.from_hex(
                    poet_private_key)
            except ParseError:
                raise \
                    ValueError(
                        'Invalid signup data. Badly formatted poet key(s).')

            # In a TEE implementation we would increment the HW counter here.
            # We can't usefully simulate a HW counter though.

            # 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(
                    cls._context.sign(
                        previous_certificate_id.encode(),
                        cls._seal_private_key))

            tagd = float(struct.unpack('Q', tag[-8:])[0]) / (2**64 - 1)

            # Now compute the duration with a minimum wait time guaranteed
            duration = \
                _PoetEnclaveSimulator.MINIMUM_WAIT_TIME \
                - local_mean * math.log(tagd)

            # Create and sign the wait timer
            wait_timer = \
                EnclaveWaitTimer(
                    validator_address=validator_address,
                    duration=duration,
                    previous_certificate_id=previous_certificate_id,
                    local_mean=local_mean)
            wait_timer.signature = \
                cls._context.sign(
                    wait_timer.serialize().encode(),
                    poet_private_key)

            return wait_timer
    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 64 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.encode()).decode())
        return signup_data.get('poet_public_key')
    def create_wait_certificate(cls,
                                sealed_signup_data,
                                wait_timer,
                                block_hash):
        with cls._lock:
            # Extract keys from the 'sealed' signup data
            if sealed_signup_data is None:
                raise ValueError('Sealed Signup Data is None')
            signup_data = \
                json2dict(
                    base64.b64decode(sealed_signup_data.encode()).decode())
            poet_private_key = signup_data['poet_private_key']
            poet_public_key = signup_data['poet_public_key']

            if poet_private_key is None or poet_public_key is None:
                raise \
                    ValueError(
                        'Invalid signup data. No poet key(s).')

            try:
                poet_public_key = Secp256k1PublicKey.from_hex(poet_public_key)
                poet_private_key = Secp256k1PrivateKey.from_hex(
                    poet_private_key)
            except ParseError:
                raise \
                    ValueError(
                        'Invalid signup data. Badly formatted poet key(s).')

            # Several criteria need to be met before we can create a wait
            # certificate:
            # 1. This signup data was used to sign this timer.
            #    i.e. the key sealed / unsealed by the TEE signed this
            #    wait timer.
            # 2. This timer has expired
            # 3. This timer has not timed out
            #
            # In a TEE implementation we would check HW counter agreement.
            # We can't usefully simulate a HW counter though.
            # i.e. wait_timer.counter_value == signup_data.counter.value

            #
            # 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 wait_timer is None or \
                    not cls._context.verify(
                        wait_timer.signature,
                        wait_timer.serialize().encode(),
                        poet_public_key):
                raise \
                    ValueError(
                        'Validator is not using the current wait timer')

            is_not_genesis_block = \
                (wait_timer.previous_certificate_id != NULL_BLOCK_IDENTIFIER)

            now = time.time()
            expire_time = \
                wait_timer.request_time + \
                wait_timer.duration

            if is_not_genesis_block and now < expire_time:
                raise \
                    ValueError(
                        'Cannot create wait certificate because timer has '
                        'not expired')

            time_out_time = \
                wait_timer.request_time + \
                wait_timer.duration + \
                TIMER_TIMEOUT_PERIOD

            if is_not_genesis_block and time_out_time < now:
                raise \
                    ValueError(
                        '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': wait_timer.signature,
                    'now': datetime.datetime.utcnow().isoformat()
                })
            nonce = hashlib.sha256(random_string.encode()).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=wait_timer,
                    nonce=nonce,
                    block_hash=block_hash)
            wait_certificate.signature = \
                cls._context.sign(
                    wait_certificate.serialize().encode(),
                    poet_private_key)

            # In a TEE implementation we would increment the HW counter here
            # to prevent replay.
            # We can't usefully simulate a HW counter though.

            return wait_certificate
    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 ValueError('Verification report is missing from proof data')

        signature = proof_data_dict.get('signature')
        if signature is None:
            raise ValueError('Signature is missing from proof data')

        try:
            cls._report_public_key.verify(base64.b64decode(signature.encode()),
                                          verification_report.encode(),
                                          padding.PKCS1v15(), hashes.SHA256())
        except InvalidSignature:
            raise ValueError('Verification report signature is invalid')

        verification_report_dict = json2dict(verification_report)

        # Verify that the verification report contains an ID field
        if 'id' not in verification_report_dict:
            raise ValueError('Verification report does not contain an ID')

        # Verify that the verification report contains an EPID pseudonym and
        # that it matches the anti-Sybil ID
        epid_pseudonym = verification_report_dict.get('epidPseudonym')
        if epid_pseudonym is None:
            raise \
                ValueError(
                    'Verification report does not contain an EPID pseudonym')

        if epid_pseudonym != signup_info.anti_sybil_id:
            raise \
                ValueError(
                    'The anti-Sybil ID in the verification report [{0}] does '
                    'not match the one contained in the signup information '
                    '[{1}]'.format(
                        epid_pseudonym,
                        signup_info.anti_sybil_id))

        # 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 \
                ValueError(
                    'Verification report does not contain a PSE manifest '
                    'status')
        if pse_manifest_status.upper() != 'OK':
            raise \
                ValueError(
                    'PSE manifest status is {} (i.e., not OK)'.format(
                        pse_manifest_status))

        # Verify that the verification report contains a PSE manifest hash
        pse_manifest_hash = \
            verification_report_dict.get('pseManifestHash')
        if pse_manifest_hash is None:
            raise \
                ValueError(
                    'Verification report does not contain a PSE manifest '
                    'hash')

        # Verify that the proof data contains evidence payload
        evidence_payload = proof_data_dict.get('evidence_payload')
        if evidence_payload is None:
            raise ValueError('Evidence payload is missing from proof data')

        # Verify that the evidence payload contains a PSE manifest and then
        # use it to make sure that the PSE manifest hash is what we expect
        pse_manifest = evidence_payload.get('pse_manifest')
        if pse_manifest is None:
            raise ValueError('Evidence payload does not include PSE manifest')

        expected_pse_manifest_hash = \
            base64.b64encode(
                hashlib.sha256(
                    pse_manifest.encode()).hexdigest().encode()).decode()

        if pse_manifest_hash.upper() != expected_pse_manifest_hash.upper():
            raise \
                ValueError(
                    '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 status
        # and the status is OK
        enclave_quote_status = \
            verification_report_dict.get('isvEnclaveQuoteStatus')
        if enclave_quote_status is None:
            raise \
                ValueError(
                    'Verification report does not contain an enclave quote '
                    'status')
        if enclave_quote_status.upper() != 'OK':
            raise \
                ValueError(
                    'Enclave quote status is {} (i.e., not OK)'.format(
                        enclave_quote_status))

        # Verify that the verification report contains an enclave quote
        enclave_quote = verification_report_dict.get('isvEnclaveQuoteBody')
        if enclave_quote is None:
            raise \
                ValueError(
                    'Verification report does not contain an enclave quote')

        # The ISV enclave quote body is base 64 encoded, so decode it and then
        # create an SGX quote structure from it so we can inspect
        sgx_quote = sgx_structs.SgxQuote()
        sgx_quote.parse_from_bytes(base64.b64decode(enclave_quote))

        # The report body should be SHA256(SHA256(OPK)|PPK)
        #
        # NOTE - since the code that created the report data is in the enclave
        # code, this code needs to be kept in sync with it.  Any changes to how
        # the report data is created, needs to be reflected in how we re-create
        # the report data for verification.

        hash_input = \
            '{0}{1}'.format(
                originator_public_key_hash.upper(),
                cls._poet_public_key.upper()).encode()
        hash_value = hashlib.sha256(hash_input).digest()
        expected_report_data = \
            hash_value + \
            (b'\x00' *
             (sgx_structs.SgxReportData.STRUCT_SIZE - len(hash_value)))

        if sgx_quote.report_body.report_data.d != expected_report_data:
            raise \
                ValueError(
                    'AVR report data [{0}] not equal to [{1}]'.format(
                        sgx_quote.report_body.report_data.d.hex(),
                        expected_report_data.hex()))

        # Compare the enclave measurement against the expected valid enclave
        # measurement.
        #
        # NOTE - this is only a temporary check.  Instead of checking against
        # a predefined enclave measurement value, we should be configured with
        # a set of one or more enclave measurement values that we will
        # consider as valid.

        if sgx_quote.report_body.mr_enclave.m != \
                cls.__VALID_ENCLAVE_MEASUREMENT__:
            raise \
                ValueError(
                    'AVR enclave measurement [{0}] not equal to [{1}]'.format(
                        sgx_quote.report_body.mr_enclave.m.hex(),
                        cls.__VALID_ENCLAVE_MEASUREMENT__.hex()))

        # Compare the enclave basename in the verification report against the
        # expected enclave basename.
        #
        # NOTE - this is only a temporary check.  Instead of checking against
        # a predefined enclave basenme value, we should be configured with a
        # set of one or more enclave basenames that we will consider as valid.

        if sgx_quote.basename.name != cls.__VALID_BASENAME__:
            raise \
                ValueError(
                    'AVR enclave basename [{0}] not equal to [{1}]'.format(
                        sgx_quote.basename.name.hex(),
                        cls.__VALID_BASENAME__.hex()))

        # 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 \
                ValueError(
                    'Verification report does not have a nonce')
    def create_wait_certificate(cls,
                                sealed_signup_data,
                                wait_timer,
                                block_hash):
        with cls._lock:
            # Extract keys from the 'sealed' signup data
            if sealed_signup_data is None:
                raise ValueError('Sealed Signup Data is None')
            signup_data = \
                json2dict(
                    base64.b64decode(sealed_signup_data.encode()).decode())
            poet_private_key = signup_data['poet_private_key']
            poet_public_key = signup_data['poet_public_key']

            if poet_private_key is None or poet_public_key is None:
                raise \
                    ValueError(
                        'Invalid signup data. No poet key(s).')

            try:
                poet_public_key = Secp256k1PublicKey.from_hex(poet_public_key)
                poet_private_key = Secp256k1PrivateKey.from_hex(
                    poet_private_key)
            except ParseError:
                raise \
                    ValueError(
                        'Invalid signup data. Badly formatted poet key(s).')

            # Several criteria need to be met before we can create a wait
            # certificate:
            # 1. This signup data was used to sign this timer.
            #    i.e. the key sealed / unsealed by the TEE signed this
            #    wait timer.
            # 2. This timer has expired
            # 3. This timer has not timed out
            #
            # In a TEE implementation we would check HW counter agreement.
            # We can't usefully simulate a HW counter though.
            # i.e. wait_timer.counter_value == signup_data.counter.value

            #
            # 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 wait_timer is None or \
                    not cls._context.verify(
                        wait_timer.signature,
                        wait_timer.serialize().encode(),
                        poet_public_key):
                raise \
                    ValueError(
                        'Validator is not using the current wait timer')

            is_not_genesis_block = \
                (wait_timer.previous_certificate_id !=
                 NULL_BLOCK_IDENTIFIER)

            now = time.time()
            expire_time = \
                wait_timer.request_time + \
                wait_timer.duration

            if is_not_genesis_block and now < expire_time:
                raise \
                    ValueError(
                        'Cannot create wait certificate because timer has '
                        'not expired')

            time_out_time = \
                wait_timer.request_time + \
                wait_timer.duration + \
                TIMER_TIMEOUT_PERIOD

            if is_not_genesis_block and time_out_time < now:
                raise \
                    ValueError(
                        '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': wait_timer.signature,
                    'now': datetime.datetime.utcnow().isoformat()
                })
            nonce = hashlib.sha256(random_string.encode()).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=wait_timer,
                    nonce=nonce,
                    block_hash=block_hash)
            wait_certificate.signature = \
                cls._context.sign(
                    wait_certificate.serialize().encode(),
                    poet_private_key)

            # In a TEE implementation we would increment the HW counter here
            # to prevent replay.
            # We can't usefully simulate a HW counter though.

            return wait_certificate