def test_build_no_certificate(self): issuer_key = asymmetric.load_private_key( os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test-inter.crt')) with self.assertRaisesRegexp( ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert, 'good') builder.certificate = None builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp( ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert, 'good') builder.certificate_status = None builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp( ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert) builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp( ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', None, 'good') builder.build(issuer_key, issuer_cert)
def test_build_good_response(self): issuer_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) builder = OCSPResponseBuilder('successful', subject_cert, 'good') ocsp_response = builder.build(issuer_key, issuer_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] self.assertEqual('sha256', basic_response['signature_algorithm'].hash_algo) self.assertEqual('rsassa_pkcs1v15', basic_response['signature_algorithm'].signature_algo) self.assertEqual('v1', response_data['version'].native) self.assertEqual('by_key', response_data['responder_id'].name) self.assertEqual( issuer_cert.asn1.public_key.sha1, response_data['responder_id'].chosen.native ) self.assertGreaterEqual(datetime.now(timezone.utc), response_data['produced_at'].native) self.assertEqual(1, len(response_data['responses'])) self.assertEqual(0, len(response_data['response_extensions'])) cert_response = response_data['responses'][0] self.assertEqual('sha1', cert_response['cert_id']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, cert_response['cert_id']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, cert_response['cert_id']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, cert_response['cert_id']['serial_number'].native) self.assertEqual('good', cert_response['cert_status'].name) self.assertGreaterEqual(datetime.now(timezone.utc), cert_response['this_update'].native) self.assertGreaterEqual(set(), cert_response.critical_extensions)
def test_build_revoked_no_reason(self): issuer_key = asymmetric.load_private_key( os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test-inter.crt')) revoked_time = datetime(2015, 9, 1, 12, 0, 0, tzinfo=timezone.utc) builder = OCSPResponseBuilder('successful', [{ 'certificate': subject_cert, 'certificate_status': 'revoked', 'revocation_date': revoked_time }]) ocsp_response = builder.build(issuer_key, issuer_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] cert_response = response_data['responses'][0] self.assertEqual('revoked', cert_response['cert_status'].name) self.assertEqual( revoked_time, cert_response['cert_status'].chosen['revocation_time'].native) self.assertEqual( 'unspecified', cert_response['cert_status'].chosen['revocation_reason'].native)
def test_build_revoked_response(self): issuer_key = asymmetric.load_private_key( os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test-inter.crt')) revoked_time = datetime(2015, 9, 1, 12, 0, 0, tzinfo=timezone.utc) builder = OCSPResponseBuilder( 'successful', [{ 'certificate': subject_cert, 'certificate_status': 'key_compromise', 'revocation_date': revoked_time }]) ocsp_response = builder.build(issuer_key, issuer_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] self.assertEqual('sha256', basic_response['signature_algorithm'].hash_algo) self.assertEqual('rsassa_pkcs1v15', basic_response['signature_algorithm'].signature_algo) self.assertEqual('v1', response_data['version'].native) self.assertEqual('by_key', response_data['responder_id'].name) self.assertEqual(issuer_cert.asn1.public_key.sha1, response_data['responder_id'].chosen.native) self.assertGreaterEqual(datetime.now(timezone.utc), response_data['produced_at'].native) self.assertEqual(1, len(response_data['responses'])) self.assertEqual(0, len(response_data['response_extensions'])) cert_response = response_data['responses'][0] self.assertEqual( 'sha1', cert_response['cert_id']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, cert_response['cert_id']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, cert_response['cert_id']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, cert_response['cert_id']['serial_number'].native) self.assertEqual('revoked', cert_response['cert_status'].name) self.assertEqual( revoked_time, cert_response['cert_status'].chosen['revocation_time'].native) self.assertEqual( 'key_compromise', cert_response['cert_status'].chosen['revocation_reason'].native) self.assertGreaterEqual(datetime.now(timezone.utc), cert_response['this_update'].native) self.assertGreaterEqual(set(), cert_response.critical_extensions)
def test_build_no_certificate(self): issuer_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) with self.assertRaisesRegexp(ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert, 'good') builder.certificate = None ocsp_response = builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp(ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert, 'good') builder.certificate_status = None ocsp_response = builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp(ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', subject_cert) ocsp_response = builder.build(issuer_key, issuer_cert) with self.assertRaisesRegexp(ValueError, 'must be set if the response_status is "successful"'): builder = OCSPResponseBuilder('successful', None, 'good') ocsp_response = builder.build(issuer_key, issuer_cert)
def test_build_delegated_good_response_cert_serial(self): responder_key = asymmetric.load_private_key( os.path.join(fixtures_dir, 'test-ocsp.key'), 'password') responder_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test-ocsp.crt')) issuer_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate( os.path.join(fixtures_dir, 'test-inter.crt')) builder = OCSPResponseBuilder('successful', None, 'good') builder.certificate_issuer = issuer_cert builder.certificate_serial_number = subject_cert.asn1.serial_number ocsp_response = builder.build(responder_key, responder_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] self.assertEqual('sha256', basic_response['signature_algorithm'].hash_algo) self.assertEqual('rsassa_pkcs1v15', basic_response['signature_algorithm'].signature_algo) self.assertEqual('v1', response_data['version'].native) self.assertEqual('by_key', response_data['responder_id'].name) self.assertEqual(responder_cert.asn1.public_key.sha1, response_data['responder_id'].chosen.native) self.assertGreaterEqual(datetime.now(timezone.utc), response_data['produced_at'].native) self.assertEqual(1, len(response_data['responses'])) self.assertEqual(0, len(response_data['response_extensions'])) cert_response = response_data['responses'][0] self.assertEqual( 'sha1', cert_response['cert_id']['hash_algorithm']['algorithm'].native) self.assertEqual(issuer_cert.asn1.subject.sha1, cert_response['cert_id']['issuer_name_hash'].native) self.assertEqual(issuer_cert.asn1.public_key.sha1, cert_response['cert_id']['issuer_key_hash'].native) self.assertEqual(subject_cert.asn1.serial_number, cert_response['cert_id']['serial_number'].native) self.assertEqual('good', cert_response['cert_status'].name) self.assertGreaterEqual(datetime.now(timezone.utc), cert_response['this_update'].native) self.assertGreaterEqual(set(), cert_response.critical_extensions)
def test_build_error_response(self): """ Build a response with error status. """ error_statuses = ['malformed_request', 'internal_error', 'try_later', 'sign_required', 'unauthorized'] for status in error_statuses: builder = OCSPResponseBuilder(status) ocsp_response = builder.build() der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) assert dict(new_response.native) == { 'response_status': status, 'response_bytes': None, }
def test_build_revoked_no_reason(self): issuer_key = asymmetric.load_private_key(os.path.join(fixtures_dir, 'test.key')) issuer_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test.crt')) subject_cert = asymmetric.load_certificate(os.path.join(fixtures_dir, 'test-inter.crt')) revoked_time = datetime(2015, 9, 1, 12, 0, 0, tzinfo=timezone.utc) builder = OCSPResponseBuilder('successful', subject_cert, 'revoked', revoked_time) ocsp_response = builder.build(issuer_key, issuer_cert) der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) basic_response = new_response['response_bytes']['response'].parsed response_data = basic_response['tbs_response_data'] cert_response = response_data['responses'][0] self.assertEqual('revoked', cert_response['cert_status'].name) self.assertEqual(revoked_time, cert_response['cert_status'].chosen['revocation_time'].native) self.assertEqual('unspecified', cert_response['cert_status'].chosen['revocation_reason'].native)
def test_build_error_response(self): """ Build a response with error status. """ error_statuses = [ 'malformed_request', 'internal_error', 'try_later', 'sign_required', 'unauthorized' ] for status in error_statuses: builder = OCSPResponseBuilder(status) ocsp_response = builder.build() der_bytes = ocsp_response.dump() new_response = asn1crypto.ocsp.OCSPResponse.load(der_bytes) assert dict(new_response.native) == { 'response_status': status, 'response_bytes': None, }
def handle_ocsp_requests(caid): # Import section (specifically for OCSP) from asn1crypto.util import timezone from asn1crypto.ocsp import OCSPRequest from oscrypto import asymmetric from ocspbuilder import OCSPResponseBuilder # Getting CA information key = Key.query.filter_by(ca=caid).first() if not key: abort(config.http_notfound, {"message": config.error_pkey_notfound}) private, public = key.dump(config.path_keys) with open(private, "rb") as f: issuer_key = asymmetric.load_private_key(f.read(), "testtest") with open(public, "rb") as f: issuer_cert = asymmetric.load_certificate(f.read()) # Parsing the OCSP request ocsp = OCSPRequest.load(request.get_data()) tbs_request = ocsp['tbs_request'] request_list = tbs_request['request_list'] if len(request_list) != 1: abort(config.http_notimplemented, {"message": config.error_multiple_requests}) single_request = request_list[0] # TODO: Support more than one request req_cert = single_request['req_cert'] serial = hex(req_cert['serial_number'].native)[2:] # Getting certificate cert = Certificate.query.filter_by(serial=serial).first() if not cert: abort(config.http_notfound, {"message": config.error_cert_notfound}) cert_path = cert.dump(config.path_keys) with open(cert_path, "rb") as f: subject_cert = asymmetric.load_certificate(f.read()) # A response for a certificate in good standing builder = OCSPResponseBuilder(u'successful', subject_cert, u'good') ocsp_response = builder.build(issuer_key, issuer_cert) return ocsp_response.dump()
def fail(self, reason): builder = OCSPResponseBuilder(response_status=reason) return builder.build()
def _fail(status: ResponseStatus) -> OCSPResponse: builder = OCSPResponseBuilder(response_status=status.value) return builder.build()
def _fail(self, status: ResponseStatus) -> OCSPResponse: builder = OCSPResponseBuilder(response_status=status.value) return builder.build()
def _build_ocsp_response(self, ocsp_request: OCSPRequest) -> OCSPResponse: """ Create and return an OCSP response from an OCSP request. """ # Get the certificate serial tbs_request = ocsp_request['tbs_request'] request_list = tbs_request['request_list'] if len(request_list) != 1: logger.warning('Received OCSP request with multiple sub requests') raise NotImplemented('Combined requests not yet supported') single_request = request_list[0] # TODO: Support more than one request req_cert = single_request['req_cert'] serial = req_cert['serial_number'].native # Check certificate status try: certificate_status, revocation_date = self._validate(serial) except Exception as e: logger.exception('Could not determine certificate status: %s', e) return self._fail(ResponseStatus.internal_error) # Retrieve certificate try: subject_cert_contents = self._cert_retrieve(serial) except Exception as e: logger.exception('Could not retrieve certificate with serial %s: %s', serial, e) return self._fail(ResponseStatus.internal_error) # Parse certificate try: subject_cert = asymmetric.load_certificate(subject_cert_contents.encode('utf8')) except Exception as e: logger.exception('Returned certificate with serial %s is invalid: %s', serial, e) return self._fail(ResponseStatus.internal_error) # Build the response builder = OCSPResponseBuilder(**{ 'response_status': ResponseStatus.successful.value, 'certificate': subject_cert, 'certificate_status': certificate_status.value, 'revocation_date': revocation_date, }) # Parse extensions for extension in tbs_request['request_extensions']: extn_id = extension['extn_id'].native critical = extension['critical'].native value = extension['extn_value'].parsed # This variable tracks whether any unknown extensions were encountered unknown = False # Handle nonce extension if extn_id == 'nonce': builder.nonce = value.native # That's all we know else: unknown = True # If an unknown critical extension is encountered (which should not # usually happen, according to RFC 6960 4.1.2), we should throw our # hands up in despair and run. if unknown is True and critical is True: logger.warning('Could not parse unknown critical extension: %r', dict(extension.native)) return self._fail(ResponseStatus.internal_error) # If it's an unknown non-critical extension, we can safely ignore it. elif unknown is True: logger.info('Ignored unknown non-critical extension: %r', dict(extension.native)) # Set certificate issuer builder.certificate_issuer = self._issuer_cert # Set next update date builder.next_update = datetime.now(timezone.utc) + timedelta(days=self._next_update_days) return builder.build(self._responder_key, self._responder_cert)
def get_ocsp_response(self, data): try: ocsp_request = asn1crypto.ocsp.OCSPRequest.load(data) tbs_request = ocsp_request['tbs_request'] request_list = tbs_request['request_list'] if len(request_list) != 1: log.error('Received OCSP request with multiple sub requests') raise NotImplemented('Combined requests not yet supported') single_request = request_list[ 0] # TODO: Support more than one request req_cert = single_request['req_cert'] serial = serial_from_int(req_cert['serial_number'].native) except Exception as e: log.exception('Error parsing OCSP request: %s', e) return self.fail(u'malformed_request') # Get CA and certificate ca = CertificateAuthority.objects.get(serial=self.ca) try: cert = Certificate.objects.filter(ca=ca).get(serial=serial) except Certificate.DoesNotExist: log.warn('OCSP request for unknown cert received.') return self.fail(u'internal_error') # load ca cert and responder key/cert ca_cert = load_certificate(force_bytes(ca.pub)) responder_key = load_private_key(self.responder_key) responder_cert = load_certificate(self.responder_cert) builder = OCSPResponseBuilder( response_status=u'successful', # ResponseStatus.successful.value, certificate=load_certificate(force_bytes(cert.pub)), certificate_status=force_text(cert.ocsp_status), revocation_date=cert.revoked_date, ) # Parse extensions for extension in tbs_request['request_extensions']: extn_id = extension['extn_id'].native critical = extension['critical'].native value = extension['extn_value'].parsed # This variable tracks whether any unknown extensions were encountered unknown = False # Handle nonce extension if extn_id == 'nonce': builder.nonce = value.native # That's all we know else: # pragma: no cover unknown = True # If an unknown critical extension is encountered (which should not # usually happen, according to RFC 6960 4.1.2), we should throw our # hands up in despair and run. if unknown is True and critical is True: # pragma: no cover log.warning('Could not parse unknown critical extension: %r', dict(extension.native)) return self._fail('internal_error') # If it's an unknown non-critical extension, we can safely ignore it. elif unknown is True: # pragma: no cover log.info('Ignored unknown non-critical extension: %r', dict(extension.native)) builder.certificate_issuer = ca_cert builder.next_update = datetime.utcnow() + timedelta( seconds=self.expires) return builder.build(responder_key, responder_cert)
def build_ocsp_response(self, ocsp_request: OCSPRequest) -> OCSPResponse: """ Create and return an OCSP response from an OCSP request. """ # Get the certificate serial req_cert_id = _get_req_cert_id(ocsp_request) #serial = req_cert_id['serial_number'].native #issuer_key_hash = req_cert_id['issuer_key_hash'].native #issuer_name_hash = req_cert_id['issuer_name_hash'].native #hash_algorithm = req_cert_id['hash_algorithm'] #hash_algo_name = hash_algorithm['algorithm'].native #hash_algo_params = hash_algorithm['parameters'].native # Check certificate status try: certificate_status, revocation_date = self._validate(req_cert_id, self.issuer_cert) except Exception as e: logger.exception('Could not determine certificate status: %s', e) return _fail(ResponseStatus.internal_error) # Retrieve certificate try: subject_cert_contents = self._cert_retrieve(req_cert_id, self.issuer_cert) except Exception as e: logger.exception('Could not retrieve certificate with serial %s: %s', serial, e) return _fail(ResponseStatus.internal_error) # Parse certificate if needed if isinstance(subject_cert_contents, x509.Certificate): subject_cert = subject_cert_contents elif isinstance(subject_cert_contents, asymmetric.Certificate): subject_cert = subject_cert_contents.asn1 else: try: subject_cert = asymmetric.load_certificate(subject_cert_contents.encode('utf8')) except Exception as e: logger.exception('Returned certificate with serial %s is invalid: %s', serial, e) return _fail(ResponseStatus.internal_error) # Build the response builder = OCSPResponseBuilder(**{ 'response_status': ResponseStatus.successful.value, 'certificate': subject_cert, 'certificate_status': certificate_status.value, 'revocation_date': revocation_date, }) # Parse extensions tbs_request = ocsp_request['tbs_request'] for extension in tbs_request['request_extensions']: extn_id = extension['extn_id'].native critical = extension['critical'].native value = extension['extn_value'].parsed # This variable tracks whether any unknown extensions were encountered unknown = False # Handle nonce extension if extn_id == 'nonce': builder.nonce = value.native # That's all we know else: unknown = True # If an unknown critical extension is encountered (which should not # usually happen, according to RFC 6960 4.1.2), we should throw our # hands up in despair and run. if unknown is True and critical is True: logger.warning('Could not parse unknown critical extension: %r', dict(extension.native)) return _fail(ResponseStatus.internal_error) # If it's an unknown non-critical extension, we can safely ignore it. elif unknown is True: logger.info('Ignored unknown non-critical extension: %r', dict(extension.native)) # Set certificate issuer builder.certificate_issuer = self._issuer_cert # Set next update date builder.next_update = datetime.now(timezone.utc) + timedelta(days=self._next_update_days) return builder.build(self._responder_key, self._responder_cert)
def get_ocsp_response(self, data): try: ocsp_request = asn1crypto.ocsp.OCSPRequest.load(data) tbs_request = ocsp_request['tbs_request'] request_list = tbs_request['request_list'] if len(request_list) != 1: log.error('Received OCSP request with multiple sub requests') raise NotImplemented('Combined requests not yet supported') single_request = request_list[0] # TODO: Support more than one request req_cert = single_request['req_cert'] serial = serial_from_int(req_cert['serial_number'].native) except Exception as e: log.exception('Error parsing OCSP request: %s', e) return self.fail('malformed_request') ca = CertificateAuthority.objects.get(serial=self.ca_serial) try: cert = Certificate.objects.filter(ca=ca).get(serial=serial) except Certificate.DoesNotExist: log.warn('OCSP request for unknown cert received.') return self.fail('internal_error') # TODO: return a 'unkown' response instead builder = OCSPResponseBuilder( response_status='successful', # ResponseStatus.successful.value, certificate=load_certificate(force_bytes(cert.pub)), certificate_status=cert.ocsp_status, revocation_date=cert.revoked_date, ) # Parse extensions for extension in tbs_request['request_extensions']: extn_id = extension['extn_id'].native critical = extension['critical'].native value = extension['extn_value'].parsed # This variable tracks whether any unknown extensions were encountered unknown = False # Handle nonce extension if extn_id == 'nonce': builder.nonce = value.native # That's all we know else: unknown = True # If an unknown critical extension is encountered (which should not # usually happen, according to RFC 6960 4.1.2), we should throw our # hands up in despair and run. if unknown is True and critical is True: log.warning('Could not parse unknown critical extension: %r', dict(extension.native)) return self._fail('internal_error') # If it's an unknown non-critical extension, we can safely ignore it. elif unknown is True: log.info('Ignored unknown non-critical extension: %r', dict(extension.native)) builder.certificate_issuer = load_certificate(force_bytes(ca.pub)) builder.next_update = timezone.now() + timedelta(days=1) responder_cert = self.get_responder_cert() return builder.build(self.responder_key, responder_cert)
def fail(self, reason=u'internal_error'): builder = OCSPResponseBuilder(response_status=reason) return self.http_response(builder.build().dump())