diff options
Diffstat (limited to 'tests/x509/test_ocsp.py')
| -rw-r--r-- | tests/x509/test_ocsp.py | 858 |
1 files changed, 858 insertions, 0 deletions
diff --git a/tests/x509/test_ocsp.py b/tests/x509/test_ocsp.py new file mode 100644 index 00000000..2b6ec569 --- /dev/null +++ b/tests/x509/test_ocsp.py @@ -0,0 +1,858 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import base64 +import datetime +import os + +import pytest + +from cryptography import x509 +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec, ed25519, ed448 +from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 +from cryptography.x509 import ocsp + +from .test_x509 import _load_cert +from ..hazmat.primitives.fixtures_ec import EC_KEY_SECP256R1 +from ..utils import load_vectors_from_file + + +def _load_data(filename, loader): + return load_vectors_from_file( + filename=filename, + loader=lambda data: loader(data.read()), + mode="rb" + ) + + +def _cert_and_issuer(): + from cryptography.hazmat.backends.openssl.backend import backend + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + issuer = _load_cert( + os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), + x509.load_pem_x509_certificate, + backend + ) + return cert, issuer + + +def _generate_root(private_key=None, algorithm=hashes.SHA256()): + from cryptography.hazmat.backends.openssl.backend import backend + + if private_key is None: + private_key = EC_KEY_SECP256R1.private_key(backend) + + subject = x509.Name([ + x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(x509.NameOID.COMMON_NAME, u'Cryptography CA'), + ]) + + builder = x509.CertificateBuilder().serial_number( + 123456789 + ).issuer_name( + subject + ).subject_name( + subject + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime.now() + ).not_valid_after( + datetime.datetime.now() + datetime.timedelta(days=3650) + ) + + cert = builder.sign(private_key, algorithm, backend) + return cert, private_key + + +class TestOCSPRequest(object): + def test_bad_request(self): + with pytest.raises(ValueError): + ocsp.load_der_ocsp_request(b"invalid") + + def test_load_request(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-sha1.der"), + ocsp.load_der_ocsp_request, + ) + assert req.issuer_name_hash == (b"8\xcaF\x8c\x07D\x8d\xf4\x81\x96" + b"\xc7mmLpQ\x9e`\xa7\xbd") + assert req.issuer_key_hash == (b"yu\xbb\x84:\xcb,\xdez\t\xbe1" + b"\x1bC\xbc\x1c*MSX") + assert isinstance(req.hash_algorithm, hashes.SHA1) + assert req.serial_number == int( + "98D9E5C0B4C373552DF77C5D0F1EB5128E4945F9", 16 + ) + assert len(req.extensions) == 0 + + def test_load_request_with_extensions(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-ext-nonce.der"), + ocsp.load_der_ocsp_request, + ) + assert len(req.extensions) == 1 + ext = req.extensions[0] + assert ext.critical is False + assert ext.value == x509.OCSPNonce( + b"\x04\x10{\x80Z\x1d7&\xb8\xb8OH\xd2\xf8\xbf\xd7-\xfd" + ) + + def test_load_request_two_requests(self): + with pytest.raises(NotImplementedError): + _load_data( + os.path.join("x509", "ocsp", "req-multi-sha1.der"), + ocsp.load_der_ocsp_request, + ) + + def test_invalid_hash_algorithm(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-invalid-hash-alg.der"), + ocsp.load_der_ocsp_request, + ) + with pytest.raises(UnsupportedAlgorithm): + req.hash_algorithm + + def test_serialize_request(self): + req_bytes = load_vectors_from_file( + filename=os.path.join("x509", "ocsp", "req-sha1.der"), + loader=lambda data: data.read(), + mode="rb" + ) + req = ocsp.load_der_ocsp_request(req_bytes) + assert req.public_bytes(serialization.Encoding.DER) == req_bytes + + def test_invalid_serialize_encoding(self): + req = _load_data( + os.path.join("x509", "ocsp", "req-sha1.der"), + ocsp.load_der_ocsp_request, + ) + with pytest.raises(ValueError): + req.public_bytes("invalid") + with pytest.raises(ValueError): + req.public_bytes(serialization.Encoding.PEM) + + +class TestOCSPRequestBuilder(object): + def test_add_two_certs(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + with pytest.raises(ValueError): + builder.add_certificate(cert, issuer, hashes.SHA1()) + + def test_create_ocsp_request_no_req(self): + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(ValueError): + builder.build() + + def test_create_ocsp_request_invalid_alg(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(ValueError): + builder.add_certificate(cert, issuer, hashes.MD5()) + + def test_add_extension_twice(self): + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_extension(x509.OCSPNonce(b"123"), False) + with pytest.raises(ValueError): + builder.add_extension(x509.OCSPNonce(b"123"), False) + + def test_add_invalid_extension(self): + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(TypeError): + builder.add_extension("notanext", False) + + def test_create_ocsp_request_invalid_cert(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + with pytest.raises(TypeError): + builder.add_certificate(b"notacert", issuer, hashes.SHA1()) + + with pytest.raises(TypeError): + builder.add_certificate(cert, b"notacert", hashes.SHA1()) + + def test_create_ocsp_request(self): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate(cert, issuer, hashes.SHA1()) + req = builder.build() + serialized = req.public_bytes(serialization.Encoding.DER) + assert serialized == base64.b64decode( + b"MEMwQTA/MD0wOzAJBgUrDgMCGgUABBRAC0Z68eay0wmDug1gfn5ZN0gkxAQUw5zz" + b"/NNGCDS7zkZ/oHxb8+IIy1kCAj8g" + ) + + @pytest.mark.parametrize( + ("ext", "critical"), + [ + [x509.OCSPNonce(b"0000"), False], + [x509.OCSPNonce(b"\x00\x01\x02"), True], + ] + ) + def test_create_ocsp_request_with_extension(self, ext, critical): + cert, issuer = _cert_and_issuer() + builder = ocsp.OCSPRequestBuilder() + builder = builder.add_certificate( + cert, issuer, hashes.SHA1() + ).add_extension( + ext, critical + ) + req = builder.build() + assert len(req.extensions) == 1 + assert req.extensions[0].value == ext + assert req.extensions[0].oid == ext.oid + assert req.extensions[0].critical is critical + + +class TestOCSPResponseBuilder(object): + def test_add_response_twice(self): + cert, issuer = _cert_and_issuer() + time = datetime.datetime.now() + builder = ocsp.OCSPResponseBuilder() + builder = builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, + time, None, None + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, time, + time, None, None + ) + + def test_invalid_add_response(self): + cert, issuer = _cert_and_issuer() + time = datetime.datetime.utcnow() + reason = x509.ReasonFlags.cessation_of_operation + builder = ocsp.OCSPResponseBuilder() + with pytest.raises(TypeError): + builder.add_response( + 'bad', issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, time, None, None + ) + with pytest.raises(TypeError): + builder.add_response( + cert, 'bad', hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, time, None, None + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, 'notahash', ocsp.OCSPCertStatus.GOOD, + time, time, None, None + ) + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + 'bad', time, None, None + ) + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, 'bad', None, None + ) + + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), 0, time, time, None, None + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, time, time, None + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.GOOD, + time, time, None, reason + ) + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, + time, time, None, reason + ) + with pytest.raises(TypeError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, + time, time, time, 0 + ) + with pytest.raises(ValueError): + builder.add_response( + cert, issuer, hashes.SHA256(), ocsp.OCSPCertStatus.REVOKED, + time, time, time - datetime.timedelta(days=36500), None + ) + + def test_invalid_certificates(self): + builder = ocsp.OCSPResponseBuilder() + with pytest.raises(ValueError): + builder.certificates([]) + with pytest.raises(TypeError): + builder.certificates(['notacert']) + with pytest.raises(TypeError): + builder.certificates('invalid') + + _, issuer = _cert_and_issuer() + builder = builder.certificates([issuer]) + with pytest.raises(ValueError): + builder.certificates([issuer]) + + def test_invalid_responder_id(self): + builder = ocsp.OCSPResponseBuilder() + cert, _ = _cert_and_issuer() + with pytest.raises(TypeError): + builder.responder_id(ocsp.OCSPResponderEncoding.HASH, 'invalid') + with pytest.raises(TypeError): + builder.responder_id('notanenum', cert) + + builder = builder.responder_id(ocsp.OCSPResponderEncoding.NAME, cert) + with pytest.raises(ValueError): + builder.responder_id(ocsp.OCSPResponderEncoding.NAME, cert) + + def test_invalid_extension(self): + builder = ocsp.OCSPResponseBuilder() + with pytest.raises(TypeError): + builder.add_extension("notanextension", True) + + def test_sign_no_response(self): + builder = ocsp.OCSPResponseBuilder() + root_cert, private_key = _generate_root() + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256()) + + def test_sign_no_responder_id(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + _, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256()) + + def test_sign_invalid_hash_algorithm(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + with pytest.raises(TypeError): + builder.sign(private_key, 'notahash') + + def test_sign_good_cert(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.responder_name == root_cert.subject + assert resp.responder_key_hash is None + assert (current_time - resp.produced_at).total_seconds() < 10 + assert (resp.signature_algorithm_oid == + x509.SignatureAlgorithmOID.ECDSA_WITH_SHA256) + assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD + assert resp.revocation_time is None + assert resp.revocation_reason is None + assert resp.this_update == this_update + assert resp.next_update == next_update + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_sign_revoked_cert(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, None + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is None + assert resp.this_update == this_update + assert resp.next_update == next_update + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_sign_with_appended_certs(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ).certificates( + [root_cert] + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.certificates == [root_cert] + + def test_sign_revoked_no_next_update(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, None, revoked_date, None + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is None + assert resp.this_update == this_update + assert resp.next_update is None + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_sign_revoked_with_reason(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, + x509.ReasonFlags.key_compromise + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is x509.ReasonFlags.key_compromise + assert resp.this_update == this_update + assert resp.next_update == next_update + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_sign_responder_id_key_hash(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.HASH, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert resp.responder_name is None + assert resp.responder_key_hash == ( + b'\x8ca\x94\xe0\x948\xed\x89\xd8\xd4N\x89p\t\xd6\xf9^_\xec}' + ) + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + def test_invalid_sign_responder_cert_does_not_match_private_key(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.HASH, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ) + from cryptography.hazmat.backends.openssl.backend import backend + diff_key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(ValueError): + builder.sign(diff_key, hashes.SHA256()) + + def test_sign_with_extension(self): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + root_cert, private_key = _generate_root() + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.HASH, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.GOOD, this_update, + next_update, None, None + ).add_extension( + x509.OCSPNonce(b"012345"), False + ) + resp = builder.sign(private_key, hashes.SHA256()) + assert len(resp.extensions) == 1 + assert resp.extensions[0].value == x509.OCSPNonce(b"012345") + assert resp.extensions[0].critical is False + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes, ec.ECDSA(hashes.SHA256()) + ) + + @pytest.mark.parametrize( + ("status", "der"), + [ + (ocsp.OCSPResponseStatus.MALFORMED_REQUEST, b"0\x03\n\x01\x01"), + (ocsp.OCSPResponseStatus.INTERNAL_ERROR, b"0\x03\n\x01\x02"), + (ocsp.OCSPResponseStatus.TRY_LATER, b"0\x03\n\x01\x03"), + (ocsp.OCSPResponseStatus.SIG_REQUIRED, b"0\x03\n\x01\x05"), + (ocsp.OCSPResponseStatus.UNAUTHORIZED, b"0\x03\n\x01\x06"), + ] + ) + def test_build_non_successful_statuses(self, status, der): + resp = ocsp.OCSPResponseBuilder.build_unsuccessful(status) + assert resp.response_status is status + assert resp.public_bytes(serialization.Encoding.DER) == der + + def test_invalid_build_not_a_status(self): + with pytest.raises(TypeError): + ocsp.OCSPResponseBuilder.build_unsuccessful("notastatus") + + def test_invalid_build_successful_status(self): + with pytest.raises(ValueError): + ocsp.OCSPResponseBuilder.build_unsuccessful( + ocsp.OCSPResponseStatus.SUCCESSFUL + ) + + +class TestOCSPResponse(object): + def test_bad_response(self): + with pytest.raises(ValueError): + ocsp.load_der_ocsp_response(b"invalid") + + def test_load_response(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-sha256.der"), + ocsp.load_der_ocsp_response, + ) + from cryptography.hazmat.backends.openssl.backend import backend + issuer = _load_cert( + os.path.join("x509", "letsencryptx3.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert resp.response_status == ocsp.OCSPResponseStatus.SUCCESSFUL + assert (resp.signature_algorithm_oid == + x509.SignatureAlgorithmOID.RSA_WITH_SHA256) + assert isinstance(resp.signature_hash_algorithm, hashes.SHA256) + assert resp.signature == base64.b64decode( + b"I9KUlyLV/2LbNCVu1BQphxdNlU/jBzXsPYVscPjW5E93pCrSO84GkIWoOJtqsnt" + b"78DLcQPnF3W24NXGzSGKlSWfXIsyoXCxnBm0mIbD5ZMnKyXEnqSR33Z9He/A+ML" + b"A8gbrDUipGNPosesenkKUnOtFIzEGv29hV5E6AMP2ORPVsVlTAZegPJFbbVIWc0" + b"rZGFCXKxijDxtUtgWzBhpBAI50JbPHi+IVuaOe4aDJLYgZ0BIBNa6bDI+rScyoy" + b"5U0DToV7SZn6CoJ3U19X7BHdYn6TLX0xi43eXuzBGzdHnSzmsc7r/DvkAKJm3vb" + b"dVECXqe/gFlXJUBcZ25jhs70MUA==" + ) + assert resp.tbs_response_bytes == base64.b64decode( + b"MIHWoUwwSjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzA" + b"hBgNVBAMTGkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzGA8yMDE4MDgzMDExMT" + b"UwMFowdTBzMEswCQYFKw4DAhoFAAQUfuZq53Kas/z4oiBkbBahLWBxCF0EFKhKa" + b"mMEfd265tE5t6ZFZe/zqOyhAhIDHHh6fckClQB7xfIiCztSevCAABgPMjAxODA4" + b"MzAxMTAwMDBaoBEYDzIwMTgwOTA2MTEwMDAwWg==" + ) + issuer.public_key().verify( + resp.signature, + resp.tbs_response_bytes, + PKCS1v15(), + resp.signature_hash_algorithm + ) + assert resp.certificates == [] + assert resp.responder_key_hash is None + assert resp.responder_name == issuer.subject + assert resp.produced_at == datetime.datetime(2018, 8, 30, 11, 15) + assert resp.certificate_status == ocsp.OCSPCertStatus.GOOD + assert resp.revocation_time is None + assert resp.revocation_reason is None + assert resp.this_update == datetime.datetime(2018, 8, 30, 11, 0) + assert resp.next_update == datetime.datetime(2018, 9, 6, 11, 0) + assert resp.issuer_key_hash == ( + b'\xa8Jjc\x04}\xdd\xba\xe6\xd19\xb7\xa6Ee\xef\xf3\xa8\xec\xa1' + ) + assert resp.issuer_name_hash == ( + b'~\xe6j\xe7r\x9a\xb3\xfc\xf8\xa2 dl\x16\xa1-`q\x08]' + ) + assert isinstance(resp.hash_algorithm, hashes.SHA1) + assert resp.serial_number == 271024907440004808294641238224534273948400 + assert len(resp.extensions) == 0 + + def test_load_unauthorized(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-unauthorized.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.response_status == ocsp.OCSPResponseStatus.UNAUTHORIZED + with pytest.raises(ValueError): + resp.signature_algorithm_oid + with pytest.raises(ValueError): + resp.signature_hash_algorithm + with pytest.raises(ValueError): + resp.signature + with pytest.raises(ValueError): + resp.tbs_response_bytes + with pytest.raises(ValueError): + resp.certificates + with pytest.raises(ValueError): + resp.responder_key_hash + with pytest.raises(ValueError): + resp.responder_name + with pytest.raises(ValueError): + resp.produced_at + with pytest.raises(ValueError): + resp.certificate_status + with pytest.raises(ValueError): + resp.revocation_time + with pytest.raises(ValueError): + resp.revocation_reason + with pytest.raises(ValueError): + resp.this_update + with pytest.raises(ValueError): + resp.next_update + with pytest.raises(ValueError): + resp.issuer_key_hash + with pytest.raises(ValueError): + resp.issuer_name_hash + with pytest.raises(ValueError): + resp.hash_algorithm + with pytest.raises(ValueError): + resp.serial_number + with pytest.raises(ValueError): + resp.extensions + + def test_load_revoked(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == datetime.datetime( + 2016, 9, 2, 21, 28, 48 + ) + assert resp.revocation_reason is None + + def test_load_delegate_unknown_cert(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-delegate-unknown-cert.der"), + ocsp.load_der_ocsp_response, + ) + assert len(resp.certificates) == 1 + assert isinstance(resp.certificates[0], x509.Certificate) + assert resp.certificate_status == ocsp.OCSPCertStatus.UNKNOWN + + def test_load_invalid_signature_oid(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-invalid-signature-oid.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.signature_algorithm_oid == x509.ObjectIdentifier( + "1.2.840.113549.1.1.2" + ) + with pytest.raises(UnsupportedAlgorithm): + resp.signature_hash_algorithm + + def test_load_responder_key_hash(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-responder-key-hash.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.responder_name is None + assert resp.responder_key_hash == ( + b'\x0f\x80a\x1c\x821a\xd5/(\xe7\x8dF8\xb4,\xe1\xc6\xd9\xe2' + ) + + def test_load_revoked_reason(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked-reason.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.revocation_reason is x509.ReasonFlags.superseded + + def test_load_revoked_no_next_update(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked-no-next-update.der"), + ocsp.load_der_ocsp_response, + ) + assert resp.serial_number == 16160 + assert resp.next_update is None + + def test_response_extensions(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked-reason.der"), + ocsp.load_der_ocsp_response, + ) + assert len(resp.extensions) == 1 + ext = resp.extensions[0] + assert ext.critical is False + assert ext.value == x509.OCSPNonce( + b'\x04\x105\x957\x9fa\x03\x83\x87\x89rW\x8f\xae\x99\xf7"' + ) + + def test_serialize_reponse(self): + resp_bytes = load_vectors_from_file( + filename=os.path.join("x509", "ocsp", "resp-revoked.der"), + loader=lambda data: data.read(), + mode="rb" + ) + resp = ocsp.load_der_ocsp_response(resp_bytes) + assert resp.public_bytes(serialization.Encoding.DER) == resp_bytes + + def test_invalid_serialize_encoding(self): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-revoked.der"), + ocsp.load_der_ocsp_response, + ) + with pytest.raises(ValueError): + resp.public_bytes("invalid") + with pytest.raises(ValueError): + resp.public_bytes(serialization.Encoding.PEM) + + def test_single_extensions(self, backend): + resp = _load_data( + os.path.join("x509", "ocsp", "resp-single-extension-reason.der"), + ocsp.load_der_ocsp_response, + ) + assert len(resp.single_extensions) == 1 + ext = resp.single_extensions[0] + assert ext.oid == x509.CRLReason.oid + assert ext.value == x509.CRLReason(x509.ReasonFlags.unspecified) + + +class TestOCSPEdDSA(object): + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support / OCSP" + ) + def test_invalid_algorithm(self, backend): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + private_key = ed25519.Ed25519PrivateKey.generate() + root_cert, _ = _generate_root(private_key, None) + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, + x509.ReasonFlags.key_compromise + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256()) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed25519_supported(), + skip_message="Requires OpenSSL with Ed25519 support / OCSP" + ) + def test_sign_ed25519(self, backend): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + private_key = ed25519.Ed25519PrivateKey.generate() + root_cert, _ = _generate_root(private_key, None) + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, + x509.ReasonFlags.key_compromise + ) + resp = builder.sign(private_key, None) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is x509.ReasonFlags.key_compromise + assert resp.this_update == this_update + assert resp.next_update == next_update + assert resp.signature_hash_algorithm is None + assert (resp.signature_algorithm_oid == + x509.SignatureAlgorithmOID.ED25519) + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.ed448_supported(), + skip_message="Requires OpenSSL with Ed448 support / OCSP" + ) + def test_sign_ed448(self, backend): + builder = ocsp.OCSPResponseBuilder() + cert, issuer = _cert_and_issuer() + private_key = ed448.Ed448PrivateKey.generate() + root_cert, _ = _generate_root(private_key, None) + current_time = datetime.datetime.utcnow().replace(microsecond=0) + this_update = current_time - datetime.timedelta(days=1) + next_update = this_update + datetime.timedelta(days=7) + revoked_date = this_update - datetime.timedelta(days=300) + builder = builder.responder_id( + ocsp.OCSPResponderEncoding.NAME, root_cert + ).add_response( + cert, issuer, hashes.SHA1(), ocsp.OCSPCertStatus.REVOKED, + this_update, next_update, revoked_date, + x509.ReasonFlags.key_compromise + ) + resp = builder.sign(private_key, None) + assert resp.certificate_status == ocsp.OCSPCertStatus.REVOKED + assert resp.revocation_time == revoked_date + assert resp.revocation_reason is x509.ReasonFlags.key_compromise + assert resp.this_update == this_update + assert resp.next_update == next_update + assert resp.signature_hash_algorithm is None + assert (resp.signature_algorithm_oid == + x509.SignatureAlgorithmOID.ED448) + private_key.public_key().verify( + resp.signature, resp.tbs_response_bytes + ) |
