def build(self, responder_private_key=None, responder_certificate=None): """ Validates the request information, constructs the ASN.1 structure and signs it. The responder_private_key and responder_certificate parameters are onlystr required if the response_status is "successful". :param responder_private_key: An asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey object for the private key to sign the response with :param responder_certificate: An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object of the certificate associated with the private key :return: An asn1crypto.ocsp.OCSPResponse object of the response """ if self._response_status != 'successful': return ocsp.OCSPResponse({ 'response_status': self._response_status }) is_oscrypto = isinstance(responder_private_key, asymmetric.PrivateKey) if not isinstance(responder_private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError(_pretty_message( ''' responder_private_key must be an instance ofthe c asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey, not %s ''', _type_name(responder_private_key) )) cert_is_oscrypto = isinstance(responder_certificate, asymmetric.Certificate) if not isinstance(responder_certificate, x509.Certificate) and not cert_is_oscrypto: raise TypeError(_pretty_message( ''' responder_certificate must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(responder_certificate) )) if cert_is_oscrypto: responder_certificate = responder_certificate.asn1 if self._certificate_status_list is None: raise ValueError(_pretty_message( ''' certificate_status_list must be set if the response_status is "successful" ''' )) def _make_extension(name, value): return { 'extn_id': name, 'critical': False, 'extn_value': value } responses = [] for serial, status in self._certificate_status_list: response_data_extensions = [] single_response_extensions = [] for name, value in self._response_data_extensions.items(): response_data_extensions.append(_make_extension(name, value)) if self._nonce: response_data_extensions.append( _make_extension('nonce', self._nonce) ) if not response_data_extensions: response_data_extensions = None for name, value in self._single_response_extensions.items(): single_response_extensions.append(_make_extension(name, value)) if self._certificate_issuer: single_response_extensions.append( _make_extension( 'certificate_issuer', [ x509.GeneralName( name='directory_name', value=self._certificate_issuer.subject ) ] ) ) if not single_response_extensions: single_response_extensions = None responder_key_hash = getattr(responder_certificate.public_key, self._key_hash_algo) if status == 'good': cert_status = ocsp.CertStatus( name='good', value=core.Null() ) elif status == 'unknown': cert_status = ocsp.CertStatus( name='unknown', value=core.Null() ) else: reason = status if status != 'revoked' else 'unspecified' cert_status = ocsp.CertStatus( name='revoked', value={ 'revocation_time': self._revocation_date, 'revocation_reason': reason, } ) issuer = self._certificate_issuer if self._certificate_issuer else responder_certificate produced_at = datetime.now(timezone.utc).replace(microsecond=0) if self._this_update is None: self._this_update = produced_at if self._next_update is None: self._next_update = (self._this_update + timedelta(days=7)).replace(microsecond=0) response = { 'cert_id': { 'hash_algorithm': { 'algorithm': self._key_hash_algo }, 'issuer_name_hash': getattr(issuer.subject, self._key_hash_algo), 'issuer_key_hash': getattr(issuer.public_key, self._key_hash_algo), 'serial_number': serial, }, 'cert_status': cert_status, 'this_update': self._this_update, 'next_update': self._next_update, 'single_extensions': single_response_extensions } responses.append(response) response_data = ocsp.ResponseData({ 'responder_id': ocsp.ResponderId(name='by_key', value=responder_key_hash), 'produced_at': produced_at, 'responses': responses, 'response_extensions': response_data_extensions }) signature_algo = responder_private_key.algorithm if signature_algo == 'ec': signature_algo = 'ecdsa' signature_algorithm_id = '%s_%s' % (self._hash_algo, signature_algo) if responder_private_key.algorithm == 'rsa': sign_func = asymmetric.rsa_pkcs1v15_sign elif responder_private_key.algorithm == 'dsa': sign_func = asymmetric.dsa_sign elif responder_private_key.algorithm == 'ec': sign_func = asymmetric.ecdsa_sign if not is_oscrypto: responder_private_key = asymmetric.load_private_key(responder_private_key) signature_bytes = sign_func(responder_private_key, response_data.dump(), self._hash_algo) certs = None if self._certificate_issuer and getattr(self._certificate_issuer.public_key, self._key_hash_algo) != responder_key_hash: certs = [responder_certificate] return ocsp.OCSPResponse({ 'response_status': self._response_status, 'response_bytes': { 'response_type': 'basic_ocsp_response', 'response': { 'tbs_response_data': response_data, 'signature_algorithm': {'algorithm': signature_algorithm_id}, 'signature': signature_bytes, 'certs': certs, } } })
def __call__(self, req, resp): try: if req.method == "GET": _, _, _, tail = req.path.split("/", 3) body = b64decode(tail) elif req.method == "POST": body = req.stream.read(req.content_length or 0) else: raise falcon.HTTPMethodNotAllowed() ocsp_req = ocsp.OCSPRequest.load(body) except ValueError: raise falcon.HTTPBadRequest() fh = open(config.AUTHORITY_CERTIFICATE_PATH, "rb") # TODO: import from authority server_certificate = asymmetric.load_certificate(fh.read()) fh.close() now = datetime.now(timezone.utc) response_extensions = [] try: for ext in ocsp_req["tbs_request"]["request_extensions"]: if ext["extn_id"].native == "nonce": response_extensions.append( ocsp.ResponseDataExtension({ 'extn_id': "nonce", 'critical': False, 'extn_value': ext["extn_value"] })) except ValueError: # https://github.com/wbond/asn1crypto/issues/56 pass responses = [] for item in ocsp_req["tbs_request"]["request_list"]: serial = item["req_cert"]["serial_number"].native assert serial > 0, "Serial number correctness check failed" try: link_target = os.readlink( os.path.join(config.SIGNED_BY_SERIAL_DIR, "%x.pem" % serial)) assert link_target.startswith("../") assert link_target.endswith(".pem") path, buf, cert, signed, expires = self.authority.get_signed( link_target[3:-4]) if serial != cert.serial_number: logger.error( "Certificate store integrity check failed, %s refers to certificate with serial %x" % (link_target, cert.serial_number)) raise EnvironmentError("Integrity check failed") status = ocsp.CertStatus(name='good', value=None) except EnvironmentError: try: path, buf, cert, signed, expires, revoked = self.authority.get_revoked( serial) status = ocsp.CertStatus(name='revoked', value={ 'revocation_time': revoked, 'revocation_reason': "key_compromise", }) except EnvironmentError: status = ocsp.CertStatus(name="unknown", value=None) responses.append({ 'cert_id': { 'hash_algorithm': { 'algorithm': "sha1" }, 'issuer_name_hash': server_certificate.asn1.subject.sha1, 'issuer_key_hash': server_certificate.public_key.asn1.sha1, 'serial_number': serial, }, 'cert_status': status, 'this_update': now, 'single_extensions': [] }) response_data = ocsp.ResponseData({ 'responder_id': ocsp.ResponderId(name='by_key', value=server_certificate.public_key.asn1.sha1), 'produced_at': now, 'responses': responses, 'response_extensions': response_extensions }) resp.body = ocsp.OCSPResponse({ 'response_status': "successful", 'response_bytes': { 'response_type': "basic_ocsp_response", 'response': { 'tbs_response_data': response_data, 'certs': [server_certificate.asn1], 'signature_algorithm': { 'algorithm': "sha1_ecdsa" if self.authority.public_key.algorithm == "ec" else "sha1_rsa" }, 'signature': (asymmetric.ecdsa_sign if self.authority.public_key.algorithm == "ec" else asymmetric.rsa_pkcs1v15_sign)(self.authority.private_key, response_data.dump(), "sha1") } } }).dump()
def build(self, responder_private_key=None, responder_certificate=None): """ Validates the request information, constructs the ASN.1 structure and signs it. The responder_private_key and responder_certificate parameters are only required if the response_status is "successful". :param responder_private_key: An asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey object for the private key to sign the response with :param responder_certificate: An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate object of the certificate associated with the private key :return: An asn1crypto.ocsp.OCSPResponse object of the response """ if self._response_status != 'successful': return ocsp.OCSPResponse( {'response_status': self._response_status}) is_oscrypto = isinstance(responder_private_key, asymmetric.PrivateKey) if not isinstance(responder_private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError( _pretty_message( ''' responder_private_key must be an instance of asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey, not %s ''', _type_name(responder_private_key))) cert_is_oscrypto = isinstance(responder_certificate, asymmetric.Certificate) if not isinstance(responder_certificate, x509.Certificate) and not cert_is_oscrypto: raise TypeError( _pretty_message( ''' responder_certificate must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(responder_certificate))) if cert_is_oscrypto: responder_certificate = responder_certificate.asn1 for cert in self._certificates: if not cert: raise ValueError( _pretty_message(''' certificates must be set if the response_status is "successful" ''')) for cert_status in self._certificates_status: if not cert_status: raise ValueError( _pretty_message(''' certificates_status for all certificates must be set if the response_status is "successful" ''')) def _make_extension(name, value): return {'extn_id': name, 'critical': False, 'extn_value': value} issuer = self._certificate_issuer if self._certificate_issuer else responder_certificate for x in range(len(self._certificates)): if issuer.subject != self._certificates[x].issuer: raise ValueError( _pretty_message(''' responder_certificate does not appear to be the issuer for the certificate. Perhaps set the .certificate_issuer attribute? ''')) total_issuers = len(self._certificates) response_data_extensions = [] single_response_extensions = [[]] * total_issuers for x in range(len(self._certificates)): for name, value in self._response_data_extensions[x].items(): response_data_extensions.append(_make_extension(name, value)) for name, value in self._single_response_extensions[x].items(): single_response_extensions[x].append( _make_extension(name, value)) # This means single_response_extensions can never be empty single_response_extensions[x].append( _make_extension('certificate_issuer', [ x509.GeneralName(name='directory_name', value=issuer.subject) ])) if self._nonce: response_data_extensions.append( _make_extension('nonce', self._nonce)) if len(response_data_extensions) == 0: response_data_extensions = None responder_key_hash = getattr(responder_certificate.public_key, self._key_hash_algo) certs_status = [] for x in range(len(self._certificates_status)): c_stat = self._certificates_status[x] if c_stat == 'good': cert_status = ocsp.CertStatus(name='good', value=core.Null()) elif c_stat == 'unknown': cert_status = ocsp.CertStatus(name='unknown', value=core.Null()) else: status = c_stat reason = status if status != 'revoked' else 'unspecified' cert_status = ocsp.CertStatus(name='revoked', value={ 'revocation_time': self._revocation_dates[x], 'revocation_reason': reason, }) certs_status.append(cert_status) produced_at = datetime.now(timezone.utc) # Construct the multiple certs response responses = [] for x in range(len(self._certificates)): if self._this_updates[x] is None: self._this_updates[x] = produced_at if self._next_updates[x] is None: self._next_updates[x] = self._this_updates[x] + timedelta( days=7) item = { 'cert_id': { 'hash_algorithm': { 'algorithm': self._key_hash_algo }, 'issuer_name_hash': getattr(self._certificates[x].issuer, self._key_hash_algo), 'issuer_key_hash': getattr(issuer.public_key, self._key_hash_algo), 'serial_number': self._certificates[x].serial_number }, 'cert_status': certs_status[x], 'this_update': self._this_updates[x], 'next_update': self._next_updates[x], 'single_extensions': single_response_extensions[x] } responses.append(item) signature_algo = responder_private_key.algorithm if signature_algo == 'ec': signature_algo = 'ecdsa' signature_algorithm_id = '%s_%s' % (self._hash_algo, signature_algo) if responder_private_key.algorithm == 'rsa': sign_func = asymmetric.rsa_pkcs1v15_sign elif responder_private_key.algorithm == 'dsa': sign_func = asymmetric.dsa_sign elif responder_private_key.algorithm == 'ec': sign_func = asymmetric.ecdsa_sign if not is_oscrypto: responder_private_key = asymmetric.load_private_key( responder_private_key) # Set response_data for use in ocsp-service response_data = ocsp.ResponseData({ 'responder_id': ocsp.ResponderId(name='by_key', value=responder_key_hash), 'produced_at': produced_at, 'responses': responses, 'response_extensions': response_data_extensions }) signature_bytes = sign_func(responder_private_key, response_data.dump(), self._hash_algo) certs = None if self._certificate_issuer: certs = [responder_certificate] return ocsp.OCSPResponse({ 'response_status': self._response_status, 'response_bytes': { 'response_type': 'basic_ocsp_response', 'response': { 'tbs_response_data': response_data, 'signature_algorithm': { 'algorithm': signature_algorithm_id }, 'signature': signature_bytes, 'certs': certs } } })
def build_ocsp_response(self, req: ocsp.OCSPRequest) -> ocsp.OCSPResponse: nonce = req.nonce_value.native requests = req['tbs_request']['request_list'] issuer_cert = self.revinfo_interface.get_issuer_cert() err = SimpleOCSPResponder.build_error_response if len(requests) == 0: return err('malformed_request') responses = [] for req_item in requests: cid: ocsp.CertId = req_item['req_cert'] if not issuer_match(cid, issuer_cert): return err('unauthorized') revinfo_interface = self.revinfo_interface try: cert_status, exts = revinfo_interface.check_revocation_status( cid, self.at_time ) except NotImplementedError: return err('internal_error') except CertomancerServiceError: return err('unauthorized') responses.append( ocsp.SingleResponse({ 'cert_id': cid, 'cert_status': cert_status, 'this_update': self.at_time, 'next_update': self.at_time + self.validity, 'single_extensions': exts or None }) ) rdata = ocsp.ResponseData({ 'responder_id': ocsp.ResponderId( name='by_key', value=self.responder_cert.public_key.sha1 ), 'produced_at': self.at_time, 'responses': responses, }) response_extensions = list(self.response_extensions) if nonce is not None: nonce_extension = ocsp.ResponseDataExtension({ 'extn_id': 'nonce', 'extn_value': nonce }) response_extensions.append(nonce_extension) rdata['response_extensions'] = response_extensions signature = generic_sign( self.responder_key, rdata.dump(), signature_algo=self.signature_algo ) basic_resp = ocsp.BasicOCSPResponse({ 'tbs_response_data': rdata, 'signature_algorithm': self.signature_algo, 'signature': signature, 'certs': [self.responder_cert] }) response_bytes = ocsp.ResponseBytes({ 'response_type': 'basic_ocsp_response', 'response': core.ParsableOctetString(basic_resp.dump()) }) return ocsp.OCSPResponse({ 'response_status': 'successful', 'response_bytes': response_bytes })
def build(self, responder_private_key=None, responder_certificate=None): if self._response_status != 'successful': return ocsp.OCSPResponse( {'response_status': self._response_status}) is_oscrypto = isinstance(responder_private_key, asymmetric.PrivateKey) if not isinstance(responder_private_key, keys.PrivateKeyInfo) and not is_oscrypto: raise TypeError( _pretty_message( ''' responder_private_key must be an instance of asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey, not %s ''', _type_name(responder_private_key))) cert_is_oscrypto = isinstance(responder_certificate, asymmetric.Certificate) if not isinstance(responder_certificate, x509.Certificate) and not cert_is_oscrypto: raise TypeError( _pretty_message( ''' responder_certificate must be an instance of asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate, not %s ''', _type_name(responder_certificate))) if cert_is_oscrypto: responder_certificate = responder_certificate.asn1 if self._certificate is None: raise ValueError( _pretty_message(''' certificate must be set if the response_status is "successful" ''')) if self._certificate_status is None: raise ValueError( _pretty_message(''' certificate_status must be set if the response_status is "successful" ''')) def _make_extension(name, value): return {'extn_id': name, 'critical': False, 'extn_value': value} response_data_extensions = [] single_response_extensions = [] for name, value in self._response_data_extensions.items(): response_data_extensions.append(_make_extension(name, value)) if self._nonce: response_data_extensions.append( _make_extension('nonce', self._nonce)) if not response_data_extensions: response_data_extensions = None for name, value in self._single_response_extensions.items(): single_response_extensions.append(_make_extension(name, value)) if self._certificate_issuer: single_response_extensions.append( _make_extension('certificate_issuer', [ x509.GeneralName(name='directory_name', value=self._certificate_issuer.subject) ])) if not single_response_extensions: single_response_extensions = None responder_key_hash = getattr(responder_certificate.public_key, self._key_hash_algo) if self._certificate_status == 'good': cert_status = ocsp.CertStatus(name='good') elif self._certificate_status == 'unknown': cert_status = ocsp.CertStatus(name='unknown') else: status = self._certificate_status reason = status if status != 'revoked' else 'unspecified' revoked_info = ocsp.RevokedInfo({ 'revocation_time': datetime.now(timezone.utc), 'revocation_reason': crl.CRLReason(0) }) cert_status = ocsp.CertStatus(name='revoked', value=revoked_info) issuer = self._certificate_issuer produced_at = datetime.now(timezone.utc) if self._this_update is None: self._this_update = produced_at if self._next_update is None: self._next_update = self._this_update + timedelta(days=7) response_data = ocsp.ResponseData({ 'responder_id': ocsp.ResponderId(name='by_name', value=self._certificate.subject), 'produced_at': produced_at, 'responses': [{ 'cert_id': { 'hash_algorithm': { 'algorithm': self._key_hash_algo }, 'issuer_name_hash': getattr(self.issuer.subject, self._key_hash_algo), 'issuer_key_hash': getattr(self.issuer.public_key, self._key_hash_algo), 'serial_number': self._certificate.serial_number, }, 'cert_status': cert_status, 'this_update': self._this_update, 'next_update': self._next_update, 'single_extensions': single_response_extensions }], 'response_extensions': response_data_extensions }) signature_algo = responder_private_key.algorithm if signature_algo == 'ec': signature_algo = 'ecdsa' signature_algorithm_id = '%s_%s' % (self._hash_algo, signature_algo) if responder_private_key.algorithm == 'rsa': sign_func = asymmetric.rsa_pkcs1v15_sign elif responder_private_key.algorithm == 'dsa': sign_func = asymmetric.dsa_sign elif responder_private_key.algorithm == 'ec': sign_func = asymmetric.ecdsa_sign if not is_oscrypto: responder_private_key = asymmetric.load_private_key( responder_private_key) signature_bytes = sign_func(responder_private_key, response_data.dump(), self._hash_algo) certs = [responder_certificate] if self._certificate_issuer: certs = [responder_certificate] return ocsp.OCSPResponse({ 'response_status': self._response_status, 'response_bytes': { 'response_type': 'basic_ocsp_response', 'response': { 'tbs_response_data': response_data, 'signature_algorithm': { 'algorithm': signature_algorithm_id }, 'signature': signature_bytes, 'certs': certs } } })