def do_post(self, request): """ Handle two types of HTTP POST requests: - gossip messages. relayed to the gossip network as is - validator command and control (/command) """ # break the path into its component parts components = request.path.split('/') while components and components[0] == '': components.pop(0) try: response = self.render_post(request, components, request.args) encoding = request.getHeader('Content-Type') request.responseHeaders.addRawHeader("content-type", encoding) if encoding == 'application/json': result = dict2json(response) else: result = dict2cbor(response) return result except Exception as e: LOGGER.warn('error processing http request %s; %s; %s', request.path, str(e), traceback.format_exc(20)) return self._encode_error_response( request, http.INTERNAL_SERVER_ERROR, e)
def do_post(self, request): """ Handle two types of HTTP POST requests: - gossip messages. relayed to the gossip network as is - validator command and control (/command) """ # break the path into its component parts components = request.path.split('/') while components and components[0] == '': components.pop(0) try: response = self.render_post(request, components, request.args) encoding = request.getHeader('Content-Type') request.responseHeaders.addRawHeader("content-type", encoding) if encoding == 'application/json': result = dict2json(response) else: result = dict2cbor(response) return result except Exception as e: LOGGER.warn('error processing http request %s; %s; %s', request.path, str(e), traceback.format_exc(20)) return self._encode_error_response(request, http.INTERNAL_SERVER_ERROR, e)
def do_get(self, request): """ Handle a GET request on the HTTP interface. Three paths are accepted: /store[/<storename>[/<key>|*]] /block[/<blockid>] /transaction[/<txnid>] """ # pylint: disable=invalid-name # split the request path removing leading duplicate slashes LOGGER.info("%s.do_get %s", self.__class__.__name__, request.path) components = request.path.split('/') while components and components[0] == '': components.pop(0) if components[0] != self.page_name: return self.error_response(request, http.NOT_FOUND, "Invalid page name: {}", request.path) else: components.pop(0) test_only = (request.method == 'HEAD') try: response = self.render_get(request, components, request.args) if test_only: return '' cbor = (request.getHeader('Accept') == 'application/cbor') if cbor: request.responseHeaders.addRawHeader(b"content-type", b"application/cbor") return dict2cbor(response) request.responseHeaders.addRawHeader(b"content-type", b"application/json") pretty = False if 'p' in request.args: pretty = request.args['p'] == ['1'] if pretty: result = pretty_print_dict(response) + '\n' else: result = dict2json(response) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing http request {0}; {1}', request.path, str(e)) except: LOGGER.warn('error processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response(request, http.BAD_REQUEST, 'error processing http request {0}', request.path)
def render_GET(self, request): """ Handle a GET request on the HTTP interface. Three paths are accepted: /store[/<storename>[/<key>|*]] /block[/<blockid>] /transaction[/<txnid>] """ # pylint: disable=invalid-name # split the request path removing leading duplicate slashes components = request.path.split('/') while components and components[0] == '': components.pop(0) prefix = components.pop(0) if components else 'error' if prefix not in self.GetPageMap: return self.error_response(request, http.BAD_REQUEST, 'unknown request {0}', request.path) testonly = (request.method == 'HEAD') try: response = self.GetPageMap[prefix](components, request.args, testonly) if testonly: return '' cbor = (request.getHeader('Accept') == 'application/cbor') if cbor: request.responseHeaders.addRawHeader(b"content-type", b"application/cbor") return dict2cbor(response) request.responseHeaders.addRawHeader(b"content-type", b"application/json") pretty = 'p' in request.args if pretty: result = pretty_print_dict(response) + '\n' else: result = dict2json(response) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing http request {0}; {1}', request.path, str(e)) except: logger.warn('error processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response(request, http.BAD_REQUEST, 'error processing http request {0}', request.path)
def do_get(self, request): """ Handle a GET request on the HTTP interface. Three paths are accepted: /store[/<storename>[/<key>|*]] /block[/<blockid>] /transaction[/<txnid>] """ # pylint: disable=invalid-name # split the request path removing leading duplicate slashes components = request.path.split('/') while components and components[0] == '': components.pop(0) prefix = components.pop(0) if components else 'error' if prefix not in self.GetPageMap: # attempt to serve static content if present. resource = self.static_content.getChild(request.path[1:], request) return resource.render(request) test_only = (request.method == 'HEAD') try: response = self.GetPageMap[prefix](components, request.args, test_only) if test_only: return '' cbor = (request.getHeader('Accept') == 'application/cbor') if cbor: request.responseHeaders.addRawHeader(b"content-type", b"application/cbor") return dict2cbor(response) request.responseHeaders.addRawHeader(b"content-type", b"application/json") pretty = 'p' in request.args if pretty: result = pretty_print_dict(response) + '\n' else: result = dict2json(response) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing http request {0}; {1}', request.path, str(e)) except: logger.warn('error processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response(request, http.BAD_REQUEST, 'error processing http request {0}', request.path)
def create_signup_info(cls, originator_public_key): # First we need to create a public/private key pair for the PoET # enclave to use. cls._poet_private_key = pybitcointools.random_key() cls._poet_public_key = pybitcointools.privtopub(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': pybitcointools.encode_pubkey(cls._poet_public_key, 'hex'), 'poet_private_key': pybitcointools.encode_privkey(cls._poet_private_key, 'hex') } sealed_signup_data = \ pybitcointools.base64.b32encode(dict2json(signup_data)) # Create a fake report report_data = { 'originator_public_key_hash': pybitcointools.sha256( pybitcointools.encode_pubkey(originator_public_key, 'hex')), 'poet_public_key': pybitcointools.encode_pubkey(cls._poet_public_key, 'hex') } report = {'report_data': pybitcointools.sha256(dict2json(report_data))} report = pybitcointools.base64.b32encode(dict2json(report)) # Fake our "proof" data. proof_data = { 'attestation_evidence_payload': pybitcointools.sha256(report), 'attestation_verification_report': pybitcointools.sha256('Shave and a haircut...Two bits!') } return \ EnclaveSignupInfo( anti_sybil_id='Sally Field', poet_public_key=signup_data['poet_public_key'], proof_data=proof_data, sealed_signup_data=sealed_signup_data)
def do_get(self, request): """ Handle a GET request on the HTTP interface. Three paths are accepted: /store[/<storename>[/<key>|*]] /block[/<blockid>] /transaction[/<txnid>] """ # pylint: disable=invalid-name # split the request path removing leading duplicate slashes LOGGER.info("%s.do_get %s", self.__class__.__name__, request.path) components = request.path.split('/') while components and components[0] == '': components.pop(0) if components[0] != self.page_name: return self._error_response( request, http.NOT_FOUND, "Invalid page name: {}", request.path) else: components.pop(0) test_only = (request.method == 'HEAD') try: response = self.render_get(request, components, request.args) if test_only: return '' cbor = (request.getHeader('Accept') == 'application/cbor') if cbor: request.responseHeaders.addRawHeader(b"content-type", b"application/cbor") result = dict2cbor(response) else: request.responseHeaders.addRawHeader(b"content-type", b"application/json") pretty = False if 'p' in request.args: pretty = request.args['p'] == ['1'] if pretty: result = pretty_print_dict(response) + '\n' else: result = dict2json(response) return result except Exception as e: LOGGER.warn('error processing http request %s; %s', request.path, traceback.format_exc(20)) return self._encode_error_response( request, http.INTERNAL_SERVER_ERROR, e)
def is_valid(self, store): if not super(Register, self).is_valid(store): logger.debug('something weird happened; %s', dict2json(self.dump())) return False if not self.is_permitted(store): logger.debug('failed permission check on offer %s', self.ObjectID) return False if not liability_update.LiabilityObject.is_valid_object( store, self.InputID): logger.debug('input liability %s is not valid in offer %s ', self.InputID, self.ObjectID) return False obj = liability_update.LiabilityObject.get_valid_object( store, self.InputID) if not self.CreatorType.is_valid_creator(store, obj.get('creator'), self.OriginatorID): logger.info('%s does not have permission to modify liability %s', self.OriginatorID, self.InputID) return False if not liability_update.LiabilityObject.is_valid_object( store, self.OutputID): logger.debug('output liability %s is not valid in offer %s ', self.OutputID, self.ObjectID) return False obj = liability_update.LiabilityObject.get_valid_object( store, self.OutputID) if not self.CreatorType.is_valid_creator(store, obj.get('creator'), self.OriginatorID): logger.info('%s does not have permission to modify liability %s', self.OriginatorID, self.OutputID) return False if self.Ratio <= 0: logger.debug('invalid ratio %s in offer %s', self.Ratio, self.ObjectID) return False if self.Minimum < 0 or self.Maximum < 0 or self.Maximum < self.Minimum: logger.debug('inconsistent range %s < %s in offer %s', self.Minimum, self.Maximum, self.ObjectID) return False if self.Execution not in ExchangeOfferObject.ExecutionStyle: logger.debug('invalid execution style %s in offer %s', self.Execution, self.ObjectID) return False return True
def is_valid(self, store): if not super(Register, self).is_valid(store): logger.debug('something weird happened; %s', dict2json(self.dump())) return False if not self.is_permitted(store): logger.debug('failed permission check on offer %s', self.ObjectID) return False if not liability_update.LiabilityObject.is_valid_object(store, self.InputID): logger.debug('input liability %s is not valid in offer %s ', self.InputID, self.ObjectID) return False obj = liability_update.LiabilityObject.get_valid_object(store, self.InputID) if not self.CreatorType.is_valid_creator(store, obj.get('creator'), self.OriginatorID): logger.info('%s does not have permission to modify liability %s', self.OriginatorID, self.InputID) return False if not liability_update.LiabilityObject.is_valid_object(store, self.OutputID): logger.debug('output liability %s is not valid in offer %s ', self.OutputID, self.ObjectID) return False obj = liability_update.LiabilityObject.get_valid_object(store, self.OutputID) if not self.CreatorType.is_valid_creator(store, obj.get('creator'), self.OriginatorID): logger.info('%s does not have permission to modify liability %s', self.OriginatorID, self.OutputID) return False if self.Ratio <= 0: logger.debug('invalid ratio %s in offer %s', self.Ratio, self.ObjectID) return False if self.Minimum < 0 or self.Maximum < 0 or self.Maximum < self.Minimum: logger.debug('inconsistent range %s < %s in offer %s', self.Minimum, self.Maximum, self.ObjectID) return False if self.Execution not in ExchangeOfferObject.ExecutionStyle: logger.debug('invalid execution style %s in offer %s', self.Execution, self.ObjectID) return False return True
def postmsg(self, msgtype, info): """ Post a transaction message to the validator, parse the returning CBOR and return the corresponding dictionary. """ logger.debug(dict2json(info)) data = dict2cbor(info) datalen = len(data) url = self.BaseURL + msgtype logger.debug( 'post transaction to %s with DATALEN=%d, ' 'base64(DATA)=<%s>', url, datalen, base64.b64encode(data)) try: request = urllib2.Request(url, data, { 'Content-Type': 'application/cbor', 'Content-Length': datalen }) opener = urllib2.build_opener(self.ProxyHandler) response = opener.open(request, timeout=10) except urllib2.HTTPError as err: logger.warn('operation failed with response: %s', err.code) raise MessageException( 'operation failed with response: {0}'.format(err.code)) except urllib2.URLError as err: logger.warn('operation failed: %s', err.reason) raise MessageException('operation failed: {0}'.format(err.reason)) except: logger.warn('no response from server') raise MessageException('no response from server') content = response.read() headers = response.info() response.close() encoding = headers.get('Content-Type') if encoding == 'application/json': value = json2dict(content) elif encoding == 'application/cbor': value = cbor2dict(content) else: logger.info('server responds with message %s of type %s', content, encoding) return None logger.debug(pretty_print_dict(value)) return value
def postmsg(self, msgtype, info): """ Post a transaction message to the validator, parse the returning CBOR and return the corresponding dictionary. """ logger.debug(dict2json(info)) data = dict2cbor(info) datalen = len(data) url = self.BaseURL + msgtype logger.debug('post transaction to %s with DATALEN=%d, ' 'base64(DATA)=<%s>', url, datalen, base64.b64encode(data)) try: request = urllib2.Request(url, data, {'Content-Type': 'application/cbor', 'Content-Length': datalen}) opener = urllib2.build_opener(self.ProxyHandler) response = opener.open(request, timeout=10) except urllib2.HTTPError as err: logger.warn('operation failed with response: %s', err.code) raise MessageException( 'operation failed with response: {0}'.format(err.code)) except urllib2.URLError as err: logger.warn('operation failed: %s', err.reason) raise MessageException('operation failed: {0}'.format(err.reason)) except: logger.warn('no response from server') raise MessageException('no response from server') content = response.read() headers = response.info() response.close() encoding = headers.get('Content-Type') if encoding == 'application/json': value = json2dict(content) elif encoding == 'application/cbor': value = cbor2dict(content) else: logger.info('server responds with message %s of type %s', content, encoding) return None logger.debug(pretty_print_dict(value)) return value
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 = pybitcointools.random_key() cls._poet_public_key = \ pybitcointools.privtopub(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': pybitcointools.encode_pubkey(cls._poet_public_key, 'hex'), 'poet_private_key': pybitcointools.encode_privkey(cls._poet_private_key, 'hex') } sealed_signup_data = \ pybitcointools.base64.b64encode(dict2json(signup_data)) # Create a fake report report_data = { 'originator_public_key_hash': originator_public_key_hash.upper(), 'poet_public_key': pybitcointools.encode_pubkey(cls._poet_public_key, 'hex').upper() } report = { 'report_data': pybitcointools.sha256(dict2json(report_data)) } # Fake our "proof" data. verification_report = { 'enclave_quote': pybitcointools.base64.b64encode(dict2json(report)), 'pse_manifest_hash': pybitcointools.base64.b64encode( pybitcointools.sha256('manifest destiny')), 'nonce': most_recent_wait_certificate_id } proof_data = { 'verification_report': dict2json(verification_report), 'signature': pybitcointools.ecdsa_sign(dict2json(verification_report), cls._report_private_key) } proof_data_dict = dict2json(proof_data) return \ EnclaveSignupInfo( poet_public_key=signup_data['poet_public_key'], proof_data=proof_data_dict, anti_sybil_id=originator_public_key_hash, sealed_signup_data=sealed_signup_data)
def render_post(self, request, components, msg): """ Process validator control commands """ encoding = request.getHeader('Content-Type') data = request.content.getvalue() try: if encoding == 'application/json': minfo = json2dict(data) else: raise Error( "", 'unknown message' ' encoding: {0}'.format(encoding)) except Error as e: LOGGER.info('exception while decoding http request %s; %s', request.path, traceback.format_exc(20)) raise Error(http.BAD_REQUEST, 'unable to decode incoming request {0}'.format(str(e))) # process /command try: if minfo['action'] == 'start': if self.Validator.delaystart is True: self.Validator.delaystart = False LOGGER.info("command received : %s", minfo['action']) minfo['action'] = 'started' else: LOGGER.warn("validator startup not delayed") minfo['action'] = 'running' else: LOGGER.warn("unknown command received") minfo['action'] = 'startup failed' request.responseHeaders.addRawHeader("content-type", encoding) result = dict2json(minfo) return result except Error as e: raise Error( int(e.status), 'exception while processing' ' request {0}; {1}'.format(request.path, str(e))) except: LOGGER.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) raise Error( http.BAD_REQUEST, 'error processing http request' ' {0}'.format(request.path)) return msg
def render_post(self, request, components, msg): """ Process validator control commands """ encoding = request.getHeader('Content-Type') data = request.content.getvalue() try: if encoding == 'application/json': minfo = json2dict(data) else: raise Error("", 'unknown message' ' encoding: {0}'.format(encoding)) except Error as e: LOGGER.info('exception while decoding http request %s; %s', request.path, traceback.format_exc(20)) raise Error(http.BAD_REQUEST, 'unable to decode incoming request {0}'.format(str(e))) # process /command try: if minfo['action'] == 'start': if self.Validator.delaystart is True: self.Validator.delaystart = False LOGGER.info("command received : %s", minfo['action']) minfo['action'] = 'started' else: LOGGER.warn("validator startup not delayed") minfo['action'] = 'running' else: LOGGER.warn("unknown command received") minfo['action'] = 'startup failed' request.responseHeaders.addRawHeader("content-type", encoding) result = dict2json(minfo) return result except Error as e: raise Error(int(e.status), 'exception while processing' ' request {0}; {1}'.format(request.path, str(e))) except: LOGGER.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) raise Error(http.BAD_REQUEST, 'error processing http request' ' {0}'.format(request.path)) return msg
def serialize(self): """ Serializes to JSON that can later be reconstituted to an EnclaveWaitTimer object Returns: A JSON string representing the serialized version of the object """ timer_dict = { 'request_time': self.request_time, 'duration': self.duration, 'previous_certificate_id': self.previous_certificate_id, 'local_mean': self.local_mean } return dict2json(timer_dict)
def test_register_validator_key_mismatch(self): key = signed_object.generate_signing_key() key2 = signed_object.generate_signing_key() validator_id = signed_object.generate_identifier(key) name = 'DasValidator' signup_info = dict2json({'poet_public_key': 'fake_key', 'anti_sybil_id': 'some_token', 'proof_data': 'proof'}) store = KeyValueStore() transaction = ValidatorRegistryTransaction.register_validator( name, validator_id, signup_info) transaction.sign_object(key2) with self.assertRaises(InvalidTransactionError): transaction.check_valid(store) transaction.apply(store) self.fail("Failure: Verified an invalid transaction")
def test_register_validator_key_mismatch(self): key = signed_object.generate_signing_key() key2 = signed_object.generate_signing_key() validator_id = signed_object.generate_identifier(key) name = 'DasValidator' signup_info = dict2json({'poet_pubkey': 'fake_key', 'anti_sybil_id': 'some_token', 'proof_data': 'proof'}) store = KeyValueStore() transaction = ValidatorRegistryTransaction.register_validator( name, validator_id, signup_info) transaction.sign_object(key2) with self.assertRaises(InvalidTransactionError): transaction.check_valid(store) transaction.apply(store) self.fail("Failure: Verified an invalid transaction")
def test_register_validator_valid(self): key = signed_object.generate_signing_key() validator_id = signed_object.generate_identifier(key) name = 'DasValidator' signup_info = dict2json({'poet_public_key': 'fake_key', 'anti_sybil_id': 'some_token', 'proof_data': 'proof'}) store = KeyValueStore() transaction = ValidatorRegistryTransaction.register_validator( name, validator_id, signup_info) transaction.sign_object(key) try: transaction.check_valid(store) transaction.apply(store) except InvalidTransactionError: self.fail("Bad: Failed valid transaction")
def test_register_validator_valid(self): key = signed_object.generate_signing_key() validator_id = signed_object.generate_identifier(key) name = 'DasValidator' signup_info = dict2json({'poet_pubkey': 'fake_key', 'anti_sybil_id': 'some_token', 'proof_data': 'proof'}) store = KeyValueStore() transaction = ValidatorRegistryTransaction.register_validator( name, validator_id, signup_info) transaction.sign_object(key) try: transaction.check_valid(store) transaction.apply(store) except InvalidTransactionError: self.fail("Bad: Failed valid transaction")
def serialize(self): """ Serializes to JSON that can later be reconstituted to an EnclaveSignupInfo object Returns: A JSON string representing the serialized version of the object """ signup_info_dict = { 'anti_sybil_id': self.anti_sybil_id, 'poet_public_key': self.poet_public_key, 'proof_data': self.proof_data, 'sealed_signup_data': self.sealed_signup_data } return dict2json(signup_info_dict)
def serialize(self): """ Serializes to JSON that can later be reconstituted to an EnclaveWaitCertificate object Returns: A JSON string representing the serialized version of the object """ certificate_dict = { 'duration': self.duration, 'previous_certificate_id': self.previous_certificate_id, 'local_mean': self.local_mean, 'request_time': self.request_time, 'nonce': self.nonce, 'block_digest': self.block_digest } return dict2json(certificate_dict)
def verify_signup_info(cls, signup_info, originator_public_key_hash, most_recent_wait_certificate_id): # Verify the attestation verification report signature proof_data = json2dict(signup_info.proof_data) verification_report = proof_data.get('verification_report') if verification_report is None: raise \ SignupInfoError( 'Verification report is missing from proof data') if not pybitcointools.ecdsa_verify(verification_report, proof_data.get('signature'), cls._report_public_key): raise \ SignupInfoError('Verification report signature is invalid') # Verify that the report data field in the report contains the SHA256 # digest of the originator's public key SHA 256 digest and the PoET # public key. verification_report_dict = json2dict(verification_report) enclave_quote = verification_report_dict.get('enclave_quote') if enclave_quote is None: raise \ SignupInfoError( 'Verification report does not contain an enclave quote') report = json2dict(pybitcointools.base64.b64decode(enclave_quote)) report_data = report.get('report_data') if report_data is None: raise \ SignupInfoError('Enclave quote does not contain report data') target_report_data = { 'originator_public_key_hash': originator_public_key_hash.upper(), 'poet_public_key': signup_info.poet_public_key.upper() } target_report_data_digest = \ pybitcointools.sha256(dict2json(target_report_data)) if report_data != target_report_data_digest: raise SignupInfoError('Enclave quote report data is invalid')
def serialize(self): """ Serializes to JSON that can later be reconstituted to an EnclaveSignupInfo object Returns: A JSON string representing the serialized version of the object. Note that the sealed signup data is not encluded in the serialized data. """ # Note - we are not serializing the sealed signup data. Sealed signup # data is meant to be used locally on the system and not serialized # and sent to anyone else. signup_info_dict = { 'poet_public_key': self.poet_public_key, 'proof_data': self.proof_data } return dict2json(signup_info_dict)
def serialize(self): """ Serializes to JSON that can later be reconstituted to an EnclaveWaitTimer object Returns: A JSON string representing the serialized version of the object """ if self._serialized is None: timer_dict = { 'request_time': self.request_time, 'validator_address': self.validator_address, 'duration': self.duration, 'previous_certificate_id': self.previous_certificate_id, 'local_mean': self.local_mean } self._serialized = dict2json(timer_dict) return self._serialized
def serialize(self): """ Serializes to JSON that can later be reconstituted to an EnclaveWaitCertificate object Returns: A JSON string representing the serialized version of the object """ if self._serialized is None: certificate_dict = { 'duration': self.duration, 'previous_certificate_id': self.previous_certificate_id, 'local_mean': self.local_mean, 'request_time': self.request_time, 'nonce': self.nonce, 'block_digest': self.block_digest } self._serialized = dict2json(certificate_dict) return self._serialized
def do_post(self, request): """ Handle two types of HTTP POST requests: - gossip messages. relayed to the gossip network as is - validator command and control (/command) """ # break the path into its component parts components = request.path.split('/') while components and components[0] == '': components.pop(0) try: response = self.render_post(request, components, request.args) encoding = request.getHeader('Content-Type') request.responseHeaders.addRawHeader("content-type", encoding) if encoding == 'application/json': result = dict2json(response.dump()) else: result = dict2cbor(response.dump()) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing request {0}; {1}', request.path, str(e)) except: LOGGER.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response(request, http.BAD_REQUEST, 'error processing http request {0}', request.path)
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
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)
def _posturl(self, url, info, encoding='application/cbor'): """ Post a transaction message to the validator, parse the returning CBOR and return the corresponding dictionary. """ if encoding == 'application/json': data = dict2json(info) elif encoding == 'application/cbor': data = dict2cbor(info) else: LOGGER.error('unknown request encoding %s', encoding) return None datalen = len(data) LOGGER.debug('post transaction to %s with DATALEN=%d, DATA=<%s>', url, datalen, data) try: request = urllib2.Request(url, data, {'Content-Type': 'application/cbor', 'Content-Length': datalen}) opener = urllib2.build_opener(self.proxy_handler) response = opener.open(request, timeout=10) except urllib2.HTTPError as err: LOGGER.error('peer operation on url %s failed with response: %d', url, err.code) raise MessageException('operation failed with resonse: {0}'.format( err.code)) except urllib2.URLError as err: LOGGER.error('peer operation on url %s failed: %s', url, err.reason) raise MessageException('operation failed: {0}'.format(err.reason)) except NameError as err: LOGGER.error('name error %s', err) raise MessageException('operation failed: {0}'.format(url)) except: LOGGER.error('no response from peer server for url %s; %s', url, sys.exc_info()[0]) raise MessageException('no response from server') content = response.read() headers = response.info() response.close() encoding = headers.get('Content-Type') if encoding == 'application/json': value = json2dict(content) elif encoding == 'application/cbor': value = cbor2dict(content) else: LOGGER.info('server responds with message %s of unknown type %s', content, encoding) value = OrderedDict() return value
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')
def render_POST(self, request): """ Handle a POST request on the HTTP interface. All message on the POST interface are gossip messages that should be relayed into the gossip network as is. """ # pylint: disable=invalid-name # break the path into its component parts components = request.path.split('/') while components and components[0] == '': components.pop(0) prefix = components.pop(0) if components else 'error' if prefix not in self.PostPageMap: prefix = 'default' # process the message encoding encoding = request.getHeader('Content-Type') data = request.content.getvalue() try: if encoding == 'application/json': minfo = json2dict(data) elif encoding == 'application/cbor': minfo = cbor2dict(data) else: return self.error_response(request, http.BAD_REQUEST, 'unknown message encoding, {0}', encoding) typename = minfo.get('__TYPE__', '**UNSPECIFIED**') if typename not in self.Ledger.MessageHandlerMap: return self.error_response( request, http.BAD_REQUEST, 'received request for unknown message type, {0}', typename) msg = self.Ledger.MessageHandlerMap[typename][0](minfo) except: logger.info('exception while decoding http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, 'unabled to decode incoming request {0}', data) # and finally execute the associated method and send back the results try: response = self.PostPageMap[prefix](request, components, msg) request.responseHeaders.addRawHeader("content-type", encoding) if encoding == 'application/json': result = dict2json(response.dump()) else: result = dict2cbor(response.dump()) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing request {0}; {1}', request.path, str(e)) except: logger.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response(request, http.BAD_REQUEST, 'error processing http request {0}', request.path)
def _posturl(self, url, info, encoding='application/cbor'): """ Post a transaction message to the validator, parse the returning CBOR and return the corresponding dictionary. """ if encoding == 'application/json': data = dict2json(info) elif encoding == 'application/cbor': data = dict2cbor(info) else: LOGGER.error('unknown request encoding %s', encoding) return None datalen = len(data) LOGGER.debug('post transaction to %s with DATALEN=%d, DATA=<%s>', url, datalen, data) try: request = urllib2.Request(url, data, { 'Content-Type': 'application/cbor', 'Content-Length': datalen }) opener = urllib2.build_opener(self.proxy_handler) response = opener.open(request, timeout=10) except urllib2.HTTPError as err: LOGGER.error('peer operation on url %s failed with response: %d', url, err.code) raise MessageException( 'operation failed with response: {0}'.format(err.code)) except urllib2.URLError as err: LOGGER.error('peer operation on url %s failed: %s', url, err.reason) raise MessageException('operation failed: {0}'.format(err.reason)) except NameError as err: LOGGER.error('name error %s', err) raise MessageException('operation failed: {0}'.format(url)) except: LOGGER.error('no response from peer server for url %s; %s', url, sys.exc_info()[0]) raise MessageException('no response from server') content = response.read() headers = response.info() response.close() encoding = headers.get('Content-Type') if encoding == 'application/json': value = json2dict(content) elif encoding == 'application/cbor': value = cbor2dict(content) else: LOGGER.info('server responds with message %s of unknown type %s', content, encoding) value = OrderedDict() return value
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(bytes(dict2json(signup_data).encode())) # 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).encode()).hexdigest() } # Fake our "proof" data. verification_report = { 'id': base64.b64encode( bytes(hashlib.sha256( datetime.datetime.now().isoformat().encode()) .hexdigest().encode())).decode(), 'isvEnclaveQuoteStatus': 'OK', 'isvEnclaveQuoteBody': base64.b64encode( dict2json(quote).encode()).decode(), 'pseManifestStatus': 'OK', 'pseManifestHash': base64.b64encode( hashlib.sha256( bytes(b'Do you believe in ' b'manifest destiny?')).hexdigest() .encode()).decode(), '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)
def verify_signup_info(cls, signup_info, originator_public_key, validator_network_basename, most_recent_wait_certificate_id): # Verify the attestation verification report signature attestation_verification_report = \ signup_info.proof_data.get('attestation_verification_report') if attestation_verification_report is None: raise \ SignupInfoError( 'Attestation verification report is missing from proof ' 'data') if not pybitcointools.ecdsa_verify( dict2json(attestation_verification_report), signup_info.proof_data.get('signature'), cls._report_public_key): raise \ SignupInfoError( 'Attestation verification report signature is invalid') # Verify the presence of the anti-Sybil ID anti_sybil_id = attestation_verification_report.get('anti_sybil_id') if anti_sybil_id is None: raise \ SignupInfoError( 'Attestation verification report does not contain an ' 'anti-Sybil ID') # Verify that the report data field in the report contains the SHA256 # digest of the originator's public key SHA 256 digest and the PoET # public key. attestation_evidence_payload = \ attestation_verification_report.get( 'attestation_evidence_payload') if attestation_evidence_payload is None: raise \ SignupInfoError( 'Attestation verification report does not contain ' 'attestation evidence payload') enclave_quote = attestation_evidence_payload.get('enclave_quote') if enclave_quote is None: raise \ SignupInfoError( 'Attestation evidence payload does not contain an ' 'enclave quote') report = json2dict(pybitcointools.base64.b64decode(enclave_quote)) report_data = report.get('report_data') if report_data is None: raise \ SignupInfoError('Enclave quote does not contain report data') target_report_data = { 'originator_public_key_hash': pybitcointools.sha256( pybitcointools.encode_pubkey( originator_public_key, 'hex')), 'poet_public_key': signup_info.poet_public_key } target_report_data_digest = \ pybitcointools.sha256(dict2json(target_report_data)) if report_data != target_report_data_digest: raise SignupInfoError('Enclave quote report data is invalid') # Verify that the validator base name in the enclave quote report # matches the provided validator network basename validator_net_basename = report.get('validator_network_basename') if validator_net_basename is None: raise \ SignupInfoError( 'Enclave quote report does not have a validator network ' 'basename') if validator_net_basename != validator_network_basename: raise \ SignupInfoError( 'Enclave quote report validator network basename [{0}] ' 'does not match [{1}]'.format( validator_net_basename, validator_network_basename))
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') if not signing.verify( verification_report, signature, cls._report_public_key): raise ValueError('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 \ ValueError( 'Verification report does not contain a PSE manifest ' 'status') if pse_manifest_status != '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 # and it is the value we expect 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') expected_pse_manifest_hash = \ base64.b64encode( hashlib.sha256( bytes(b'Do you believe in ' b'manifest destiny?')).hexdigest() .encode()).decode() if pse_manifest_hash != expected_pse_manifest_hash: 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 and # that its 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 != 'OK': raise \ ValueError( '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 \ ValueError( '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).encode()).hexdigest() enclave_quote_dict = \ json2dict(base64.b64decode(enclave_quote).decode()) report_body = enclave_quote_dict.get('report_body') if report_body is None: raise ValueError('Enclave quote does not contain a report body') if report_body != expected_report_body: raise \ ValueError( '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 \ ValueError( 'Verification report does not have a nonce')
def do_post(self, request): """ Handle two types of HTTP POST requests: - gossip messages. relayed to the gossip network as is - validator command and control (/command) """ # break the path into its component parts components = request.path.split('/') while components and components[0] == '': components.pop(0) prefix = components.pop(0) if components else 'error' if prefix not in self.PostPageMap: prefix = 'default' encoding = request.getHeader('Content-Type') data = request.content.getvalue() # process non-gossip API requests if prefix == 'command': try: if encoding == 'application/json': minfo = json2dict(data) else: return self.error_response(request, http.BAD_REQUEST, 'bad message encoding, {0}', encoding) except: logger.info('exception while decoding http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, 'unable to decode incoming request {0}', data) # process /command try: response = self.PostPageMap[prefix](request, components, minfo) request.responseHeaders.addRawHeader("content-type", encoding) result = dict2json(response) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing request {0}; {1}', request.path, str(e)) except: logger.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, 'error processing http request {0}', request.path) else: try: if encoding == 'application/json': minfo = json2dict(data) elif encoding == 'application/cbor': minfo = cbor2dict(data) else: return self.error_response( request, http.BAD_REQUEST, 'unknown message encoding, {0}', encoding) typename = minfo.get('__TYPE__', '**UNSPECIFIED**') if typename not in self.Ledger.MessageHandlerMap: return self.error_response( request, http.BAD_REQUEST, 'received request for unknown message type, {0}', typename) msg = self.Ledger.MessageHandlerMap[typename][0](minfo) except: logger.info('exception while decoding http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, 'unabled to decode incoming request {0}', data) # determine if the message contains a valid transaction before # we send the message to the network # we need to start with a copy of the message due to cases # where side effects of the validity check may impact objects # related to msg mymsg = copy.deepcopy(msg) if hasattr(mymsg, 'Transaction') and mymsg.Transaction is not None: mytxn = mymsg.Transaction logger.info( 'starting local validation ' 'for txn id: %s type: %s', mytxn.Identifier, mytxn.TransactionTypeName) block_id = self.Ledger.MostRecentCommittedBlockID real_store_map = self.Ledger.GlobalStoreMap.get_block_store( block_id) temp_store_map = \ global_store_manager.BlockStore(real_store_map) if not temp_store_map: logger.info('no store map for block %s', block_id) return self.error_response( request, http.BAD_REQUEST, 'unable to validate enclosed transaction {0}', data) transaction_type = mytxn.TransactionTypeName if transaction_type not in temp_store_map.TransactionStores: logger.info('transaction type %s not in global store map', transaction_type) return self.error_response( request, http.BAD_REQUEST, 'unable to validate enclosed transaction {0}', data) # clone a copy of the ledger's message queue so we can # temporarily play forward all locally submitted yet # uncommitted transactions my_queue = copy.deepcopy(self.Ledger.MessageQueue) # apply any enqueued messages while len(my_queue) > 0: qmsg = my_queue.pop() if qmsg and \ qmsg.MessageType in self.Ledger.MessageHandlerMap: if (hasattr(qmsg, 'Transaction') and qmsg.Transaction is not None): my_store = temp_store_map.get_transaction_store( qmsg.Transaction.TransactionTypeName) if qmsg.Transaction.is_valid(my_store): myqtxn = copy.copy(qmsg.Transaction) myqtxn.apply(my_store) # apply any local pending transactions for txn_id in self.Ledger.PendingTransactions.iterkeys(): pend_txn = self.Ledger.TransactionStore[txn_id] my_store = temp_store_map.get_transaction_store( pend_txn.TransactionTypeName) if pend_txn and pend_txn.is_valid(my_store): my_pend_txn = copy.copy(pend_txn) logger.debug( 'applying pending transaction ' '%s to temp store', txn_id) my_pend_txn.apply(my_store) # determine validity of the POSTed transaction against our # new temporary state my_store = temp_store_map.get_transaction_store( mytxn.TransactionTypeName) try: mytxn.check_valid(my_store) except InvalidTransactionError as e: logger.info( 'submitted transaction fails transaction ' 'family validation check: %s; %s', request.path, mymsg.dump()) return self.error_response( request, http.BAD_REQUEST, "enclosed transaction failed transaction " "family validation check: {}".format(str(e)), data) except: logger.info( 'submitted transaction is ' 'not valid %s; %s; %s', request.path, mymsg.dump(), traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, "enclosed transaction is not valid", data) logger.info('transaction %s is valid', msg.Transaction.Identifier) # and finally execute the associated method # and send back the results try: response = self.PostPageMap[prefix](request, components, msg) request.responseHeaders.addRawHeader("content-type", encoding) if encoding == 'application/json': result = dict2json(response.dump()) else: result = dict2cbor(response.dump()) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing request {0}; {1}', request.path, str(e)) except: logger.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, 'error processing http request {0}', request.path)
def serialize(self): return dict2json({ 'anti_sybil_id': self.anti_sybil_id, 'poet_public_key': self.poet_public_key, 'proof_data': self.proof_data })
def render_POST(self, request): """ Handle two types of HTTP POST requests: - gossip messages. relayed to the gossip network as is - validator command and control (/command) """ # pylint: disable=invalid-name # break the path into its component parts components = request.path.split('/') while components and components[0] == '': components.pop(0) prefix = components.pop(0) if components else 'error' if prefix not in self.PostPageMap: prefix = 'default' encoding = request.getHeader('Content-Type') data = request.content.getvalue() # process non-gossip API requests if prefix == 'command': try: if encoding == 'application/json': minfo = json2dict(data) else: return self.error_response(request, http.BAD_REQUEST, 'bad message encoding, {0}', encoding) except: logger.info('exception while decoding http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, 'unable to decode incoming request {0}', data) # process /command try: response = self.PostPageMap[prefix](request, components, minfo) request.responseHeaders.addRawHeader("content-type", encoding) result = dict2json(response) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing request {0}; {1}', request.path, str(e)) except: logger.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response(request, http.BAD_REQUEST, 'error processing http request {0}', request.path) else: try: if encoding == 'application/json': minfo = json2dict(data) elif encoding == 'application/cbor': minfo = cbor2dict(data) else: return self.error_response(request, http.BAD_REQUEST, 'unknown message encoding, {0}', encoding) typename = minfo.get('__TYPE__', '**UNSPECIFIED**') if typename not in self.Ledger.MessageHandlerMap: return self.error_response( request, http.BAD_REQUEST, 'received request for unknown message type, {0}', typename) msg = self.Ledger.MessageHandlerMap[typename][0](minfo) except: logger.info('exception while decoding http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, 'unabled to decode incoming request {0}', data) # determine if the message contains a valid transaction before # we send the message to the network # we need to start with a copy of the message due to cases # where side effects of the validity check may impact objects # related to msg mymsg = copy.deepcopy(msg) if hasattr(mymsg, 'Transaction') and mymsg.Transaction is not None: mytxn = mymsg.Transaction logger.info('starting local validation ' 'for txn id: %s type: %s', mytxn.Identifier, mytxn.TransactionTypeName) blockid = self.Ledger.MostRecentCommittedBlockID realstoremap = self.Ledger.GlobalStoreMap.get_block_store( blockid) tempstoremap = global_store_manager.BlockStore(realstoremap) if not tempstoremap: logger.info('no store map for block %s', blockid) return self.error_response( request, http.BAD_REQUEST, 'unable to validate enclosed transaction {0}', data) transtype = mytxn.TransactionTypeName if transtype not in tempstoremap.TransactionStores: logger.info('transaction type %s not in global store map', transtype) return self.error_response( request, http.BAD_REQUEST, 'unable to validate enclosed transaction {0}', data) # clone a copy of the ledger's message queue so we can # temporarily play forward all locally submitted yet # uncommitted transactions myqueue = copy.deepcopy(self.Ledger.MessageQueue) # apply any enqueued messages while len(myqueue) > 0: qmsg = myqueue.pop() if qmsg and \ qmsg.MessageType in self.Ledger.MessageHandlerMap: if (hasattr(qmsg, 'Transaction') and qmsg.Transaction is not None): mystore = tempstoremap.get_transaction_store( qmsg.Transaction.TransactionTypeName) if qmsg.Transaction.is_valid(mystore): myqtxn = copy.copy(qmsg.Transaction) myqtxn.apply(mystore) # apply any local pending transactions for txnid in self.Ledger.PendingTransactions.iterkeys(): pendtxn = self.Ledger.TransactionStore[txnid] mystore = tempstoremap.get_transaction_store( pendtxn.TransactionTypeName) if pendtxn and pendtxn.is_valid(mystore): mypendtxn = copy.copy(pendtxn) logger.debug('applying pending transaction ' '%s to temp store', txnid) mypendtxn.apply(mystore) # determine validity of the POSTed transaction against our # new temporary state mystore = tempstoremap.get_transaction_store( mytxn.TransactionTypeName) try: mytxn.check_valid(mystore) except InvalidTransactionError as e: logger.info('submitted transaction fails transaction ' 'family validation check: %s; %s', request.path, mymsg.dump()) return self.error_response( request, http.BAD_REQUEST, "enclosed transaction failed transaction " "family validation check: {}".format(str(e)), data) except: logger.info('submitted transaction is ' 'not valid %s; %s; %s', request.path, mymsg.dump(), traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, "enclosed transaction is not valid", data) logger.info('transaction %s is valid', msg.Transaction.Identifier) # and finally execute the associated method # and send back the results try: response = self.PostPageMap[prefix](request, components, msg) request.responseHeaders.addRawHeader("content-type", encoding) if encoding == 'application/json': result = dict2json(response.dump()) else: result = dict2cbor(response.dump()) return result except Error as e: return self.error_response( request, int(e.status), 'exception while processing request {0}; {1}', request.path, str(e)) except: logger.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response(request, http.BAD_REQUEST, 'error processing http request {0}', request.path)
def render_POST(self, request): """ Handle a POST request on the HTTP interface. All message on the POST interface are gossip messages that should be relayed into the gossip network as is. """ # pylint: disable=invalid-name # break the path into its component parts components = request.path.split('/') while components and components[0] == '': components.pop(0) prefix = components.pop(0) if components else 'error' if prefix not in self.PostPageMap: prefix = 'default' # process the message encoding encoding = request.getHeader('Content-Type') data = request.content.getvalue() try: if encoding == 'application/json': minfo = json2dict(data) elif encoding == 'application/cbor': minfo = cbor2dict(data) else: return self.error_response(request, http.BAD_REQUEST, 'unknown message encoding, {0}', encoding) typename = minfo.get('__TYPE__', '**UNSPECIFIED**') if typename not in self.Ledger.MessageHandlerMap: return self.error_response( request, http.BAD_REQUEST, 'received request for unknown message type, {0}', typename) msg = self.Ledger.MessageHandlerMap[typename][0](minfo) except: logger.info('exception while decoding http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response( request, http.BAD_REQUEST, 'unabled to decode incoming request {0}', data) # and finally execute the associated method and send back the results try: response = self.PostPageMap[prefix](request, components, msg) request.responseHeaders.addRawHeader("content-type", encoding) if encoding == 'application/json': return dict2json(response.dump()) else: return dict2cbor(response.dump()) except Error as e: return self.error_response( request, int(e.status), 'exception while processing request {0}; {1}', request.path, str(e)) except: logger.info('exception while processing http request %s; %s', request.path, traceback.format_exc(20)) return self.error_response(request, http.BAD_REQUEST, 'error processing http request {0}', request.path)
def create_wait_certificate(cls, wait_timer, 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 \ ValueError( '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 caller's wait timer is the active wait timer. We are not # going to rely the objects, being the same, but will compute # a signature over the object and verify that the signatures # are the same. # 3. The active timer has expired # 4. 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 \ ValueError( 'There is not a current enclave active wait timer') if wait_timer is None or \ cls._active_wait_timer.signature != \ signing.sign( wait_timer.serialize(), cls._poet_private_key): raise \ ValueError( 'Validator is not using the current wait timer') 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 \ ValueError( '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 \ 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': cls._active_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=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
def serialize(self): serialized = dict2json({'anti_sybil_id': self.anti_sybil_id, 'poet_pubkey': self.poet_pubkey, 'proof_data': self.proof_data}) return serialized
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 we 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 if cls._active_wait_timer is None: raise \ WaitCertificateError( 'Enclave active wait timer has not been initialized') # HACK ALERT!! HACK ALERT!! HACK ALERT!! HACK ALERT!! # # Today, without the genesis utility we cannot make these checks. # Once we have the genesis utility, this code needs to change to # Depend upon the timer not being expired or timed out. The # Original specification requires this check. # # HACK ALERT!! HACK ALERT!! HACK ALERT!! HACK ALERT!! # # if not cls._active_wait_timer.has_expired(): # raise \ # WaitCertificateError( # 'Cannot create wait certificate because timer has ' # 'not expired') # if wait_timer.has_timed_out(): # 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 = pybitcointools.sha256(random_string) # 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 = \ pybitcointools.ecdsa_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
def create_signup_info(cls, originator_public_key, validator_network_basename, 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 = pybitcointools.random_key() cls._poet_public_key = \ pybitcointools.privtopub(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': pybitcointools.encode_pubkey(cls._poet_public_key, 'hex'), 'poet_private_key': pybitcointools.encode_privkey( cls._poet_private_key, 'hex') } sealed_signup_data = \ pybitcointools.base64.b32encode(dict2json(signup_data)) # Create a fake report report_data = { 'originator_public_key_hash': pybitcointools.sha256( pybitcointools.encode_pubkey( originator_public_key, 'hex')), 'poet_public_key': pybitcointools.encode_pubkey(cls._poet_public_key, 'hex') } report = { 'report_data': pybitcointools.sha256(dict2json(report_data)), 'validator_network_basename': validator_network_basename } # Fake our "proof" data. attestation_evidence_payload = { 'enclave_quote': pybitcointools.base64.b64encode(dict2json(report)), 'pse_manifest': pybitcointools.base64.b64encode( pybitcointools.sha256( 'manifest destiny')), 'nonce': most_recent_wait_certificate_id } attestation_verification_report = { 'attestation_evidence_payload': attestation_evidence_payload, 'anti_sybil_id': cls._anti_sybil_id } proof_data = { 'attestation_verification_report': attestation_verification_report, 'signature': pybitcointools.ecdsa_sign( dict2json(attestation_verification_report), cls._report_private_key) } return \ EnclaveSignupInfo( poet_public_key=signup_data['poet_public_key'], proof_data=proof_data, sealed_signup_data=sealed_signup_data)