diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3434b61fcb5b3a27faea8827d555ae43099866e0_Q0hBTkdFTE9HLnJzdA==..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_Q0hBTkdFTE9HLnJzdA== 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -35,6 +35,8 @@ * Added :meth:`~cryptography.fernet.Fernet.encrypt_at_time` and :meth:`~cryptography.fernet.Fernet.decrypt_at_time` to :class:`~cryptography.fernet.Fernet`. +* Added support for the :class:`~cryptography.x509.SubjectInformationAccess` + X.509 extension. .. _v2-9-2: diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 3434b61fcb5b3a27faea8827d555ae43099866e0_ZG9jcy9kZXZlbG9wbWVudC90ZXN0LXZlY3RvcnMucnN0..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_ZG9jcy9kZXZlbG9wbWVudC90ZXN0LXZlY3RvcnMucnN0 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -401,6 +401,9 @@ a ``policyConstraints`` extension with a ``requireExplicitPolicy`` value. * ``freshestcrl.pem`` - A self-signed certificate containing a ``freshestCRL`` extension. +* ``sia.pem`` - An RSA 2048 bit self-signed certificate containing a subject + information access extension with both a CA repository entry and a custom + OID entry. * ``ca/ca.pem`` - A self-signed certificate with ``basicConstraints`` set to true. Its private key is ``ca/ca_key.pem``. This certificate is encoded in several of the PKCS12 custom vectors. diff --git a/docs/x509/reference.rst b/docs/x509/reference.rst index 3434b61fcb5b3a27faea8827d555ae43099866e0_ZG9jcy94NTA5L3JlZmVyZW5jZS5yc3Q=..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_ZG9jcy94NTA5L3JlZmVyZW5jZS5yc3Q= 100644 --- a/docs/x509/reference.rst +++ b/docs/x509/reference.rst @@ -2146,6 +2146,29 @@ :attr:`~cryptography.x509.oid.ExtensionOID.AUTHORITY_INFORMATION_ACCESS`. +.. class:: SubjectInformationAccess(descriptions) + + .. versionadded:: 3.0 + + The subject information access extension indicates how to access + information and services for the subject of the certificate in which + the extension appears. When the subject is a CA, information and + services may include certificate validation services and CA policy + data. When the subject is an end entity, the information describes + the type of services offered and how to access them. It is an iterable, + containing one or more :class:`~cryptography.x509.AccessDescription` + instances. + + :param list descriptions: A list of :class:`AccessDescription` objects. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.SUBJECT_INFORMATION_ACCESS`. + + .. class:: AccessDescription(access_method, access_location) .. versionadded:: 0.9 @@ -2155,5 +2178,5 @@ :type: :class:`ObjectIdentifier` The access method defines what the ``access_location`` means. It must - be either + be :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` or @@ -2159,8 +2182,13 @@ :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` or - :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS`. + :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS` + when used with :class:`~cryptography.x509.AuthorityInformationAccess` + or + :attr:`~cryptography.x509.oid.SubjectInformationAccessOID.CA_REPOSITORY` + when used with :class:`~cryptography.x509.SubjectInformationAccess`. + If it is :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` the access location will be where to obtain OCSP information for the certificate. If it is :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS` the access location will provide additional information about the @@ -2161,10 +2189,12 @@ If it is :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` the access location will be where to obtain OCSP information for the certificate. If it is :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS` the access location will provide additional information about the - issuing certificate. + issuing certificate. Finally, if it is + :attr:`~cryptography.x509.oid.SubjectInformationAccessOID.CA_REPOSITORY` + the access location will be the location of the CA's repository. .. attribute:: access_location @@ -2973,6 +3003,17 @@ :class:`~cryptography.x509.AccessDescription` objects. +.. class:: SubjectInformationAccessOID + + .. versionadded:: 3.0 + + .. attribute:: CA_REPOSITORY + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.5"``. Used as the + identifier for CA repository data in + :class:`~cryptography.x509.AccessDescription` objects. + + .. class:: CertificatePoliciesOID .. versionadded:: 1.0 @@ -3050,6 +3091,14 @@ for the :class:`~cryptography.x509.AuthorityInformationAccess` extension type. + .. attribute:: SUBJECT_INFORMATION_ACCESS + + .. versionadded:: 3.0 + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.1.11"``. The + identifier for the :class:`~cryptography.x509.SubjectInformationAccess` + extension type. + .. attribute:: INHIBIT_ANY_POLICY Corresponds to the dotted string ``"2.5.29.54"``. The identifier diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py index 3434b61fcb5b3a27faea8827d555ae43099866e0_c3JjL2NyeXB0b2dyYXBoeS9oYXptYXQvYmFja2VuZHMvb3BlbnNzbC9kZWNvZGVfYXNuMS5weQ==..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_c3JjL2NyeXB0b2dyYXBoeS9oYXptYXQvYmFja2VuZHMvb3BlbnNzbC9kZWNvZGVfYXNuMS5weQ== 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -377,13 +377,13 @@ ) -def _decode_authority_information_access(backend, aia): - aia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", aia) - aia = backend._ffi.gc( - aia, +def _decode_information_access(backend, ia): + ia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", ia) + ia = backend._ffi.gc( + ia, lambda x: backend._lib.sk_ACCESS_DESCRIPTION_pop_free( x, backend._ffi.addressof( backend._lib._original_lib, "ACCESS_DESCRIPTION_free" ) ) ) @@ -384,9 +384,9 @@ lambda x: backend._lib.sk_ACCESS_DESCRIPTION_pop_free( x, backend._ffi.addressof( backend._lib._original_lib, "ACCESS_DESCRIPTION_free" ) ) ) - num = backend._lib.sk_ACCESS_DESCRIPTION_num(aia) + num = backend._lib.sk_ACCESS_DESCRIPTION_num(ia) access_descriptions = [] for i in range(num): @@ -391,9 +391,9 @@ access_descriptions = [] for i in range(num): - ad = backend._lib.sk_ACCESS_DESCRIPTION_value(aia, i) + ad = backend._lib.sk_ACCESS_DESCRIPTION_value(ia, i) backend.openssl_assert(ad.method != backend._ffi.NULL) oid = x509.ObjectIdentifier(_obj2txt(backend, ad.method)) backend.openssl_assert(ad.location != backend._ffi.NULL) gn = _decode_general_name(backend, ad.location) access_descriptions.append(x509.AccessDescription(oid, gn)) @@ -394,9 +394,14 @@ backend.openssl_assert(ad.method != backend._ffi.NULL) oid = x509.ObjectIdentifier(_obj2txt(backend, ad.method)) backend.openssl_assert(ad.location != backend._ffi.NULL) gn = _decode_general_name(backend, ad.location) access_descriptions.append(x509.AccessDescription(oid, gn)) + return access_descriptions + + +def _decode_authority_information_access(backend, aia): + access_descriptions = _decode_information_access(backend, aia) return x509.AuthorityInformationAccess(access_descriptions) @@ -400,6 +405,11 @@ return x509.AuthorityInformationAccess(access_descriptions) +def _decode_subject_information_access(backend, aia): + access_descriptions = _decode_information_access(backend, aia) + return x509.SubjectInformationAccess(access_descriptions) + + def _decode_key_usage(backend, bit_string): bit_string = backend._ffi.cast("ASN1_BIT_STRING *", bit_string) bit_string = backend._ffi.gc(bit_string, backend._lib.ASN1_BIT_STRING_free) @@ -816,6 +826,9 @@ ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( _decode_authority_information_access ), + ExtensionOID.SUBJECT_INFORMATION_ACCESS: ( + _decode_subject_information_access + ), ExtensionOID.CERTIFICATE_POLICIES: _decode_certificate_policies, ExtensionOID.CRL_DISTRIBUTION_POINTS: _decode_crl_distribution_points, ExtensionOID.FRESHEST_CRL: _decode_freshest_crl, diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py index 3434b61fcb5b3a27faea8827d555ae43099866e0_c3JjL2NyeXB0b2dyYXBoeS9oYXptYXQvYmFja2VuZHMvb3BlbnNzbC9lbmNvZGVfYXNuMS5weQ==..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_c3JjL2NyeXB0b2dyYXBoeS9oYXptYXQvYmFja2VuZHMvb3BlbnNzbC9lbmNvZGVfYXNuMS5weQ== 100644 --- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py @@ -343,7 +343,7 @@ return constraints -def _encode_authority_information_access(backend, authority_info_access): +def _encode_information_access(backend, info_access): aia = backend._lib.sk_ACCESS_DESCRIPTION_new_null() backend.openssl_assert(aia != backend._ffi.NULL) aia = backend._ffi.gc( @@ -354,7 +354,7 @@ ) ) ) - for access_description in authority_info_access: + for access_description in info_access: ad = backend._lib.ACCESS_DESCRIPTION_new() method = _txt2obj( backend, access_description.access_method.dotted_string @@ -622,9 +622,8 @@ ExtensionOID.EXTENDED_KEY_USAGE: _encode_extended_key_usage, ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, ExtensionOID.CERTIFICATE_POLICIES: _encode_certificate_policies, - ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( - _encode_authority_information_access - ), + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _encode_information_access, + ExtensionOID.SUBJECT_INFORMATION_ACCESS: _encode_information_access, ExtensionOID.CRL_DISTRIBUTION_POINTS: _encode_cdps_freshest_crl, ExtensionOID.FRESHEST_CRL: _encode_cdps_freshest_crl, ExtensionOID.INHIBIT_ANY_POLICY: _encode_inhibit_any_policy, @@ -636,9 +635,7 @@ _CRL_EXTENSION_ENCODE_HANDLERS = { ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, - ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( - _encode_authority_information_access - ), + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: _encode_information_access, ExtensionOID.CRL_NUMBER: _encode_crl_number_delta_crl_indicator, ExtensionOID.DELTA_CRL_INDICATOR: _encode_crl_number_delta_crl_indicator, ExtensionOID.ISSUING_DISTRIBUTION_POINT: _encode_issuing_dist_point, diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py index 3434b61fcb5b3a27faea8827d555ae43099866e0_c3JjL2NyeXB0b2dyYXBoeS94NTA5L19faW5pdF9fLnB5..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_c3JjL2NyeXB0b2dyYXBoeS94NTA5L19faW5pdF9fLnB5 100644 --- a/src/cryptography/x509/__init__.py +++ b/src/cryptography/x509/__init__.py @@ -24,8 +24,8 @@ IssuingDistributionPoint, KeyUsage, NameConstraints, NoticeReference, OCSPNoCheck, OCSPNonce, PolicyConstraints, PolicyInformation, PrecertPoison, PrecertificateSignedCertificateTimestamps, ReasonFlags, - SubjectAlternativeName, SubjectKeyIdentifier, TLSFeature, TLSFeatureType, - UnrecognizedExtension, UserNotice + SubjectAlternativeName, SubjectInformationAccess, SubjectKeyIdentifier, + TLSFeature, TLSFeatureType, UnrecognizedExtension, UserNotice ) from cryptography.x509.general_name import ( DNSName, DirectoryName, GeneralName, IPAddress, OtherName, RFC822Name, @@ -142,6 +142,7 @@ "CRLNumber", "KeyUsage", "AuthorityInformationAccess", + "SubjectInformationAccess", "AccessDescription", "CertificatePolicies", "PolicyInformation", diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py index 3434b61fcb5b3a27faea8827d555ae43099866e0_c3JjL2NyeXB0b2dyYXBoeS94NTA5L2V4dGVuc2lvbnMucHk=..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_c3JjL2NyeXB0b2dyYXBoeS94NTA5L2V4dGVuc2lvbnMucHk= 100644 --- a/src/cryptography/x509/extensions.py +++ b/src/cryptography/x509/extensions.py @@ -317,6 +317,38 @@ return hash(tuple(self._descriptions)) +@utils.register_interface(ExtensionType) +class SubjectInformationAccess(object): + oid = ExtensionOID.SUBJECT_INFORMATION_ACCESS + + def __init__(self, descriptions): + descriptions = list(descriptions) + if not all(isinstance(x, AccessDescription) for x in descriptions): + raise TypeError( + "Every item in the descriptions list must be an " + "AccessDescription" + ) + + self._descriptions = descriptions + + __len__, __iter__, __getitem__ = _make_sequence_methods("_descriptions") + + def __repr__(self): + return "<SubjectInformationAccess({})>".format(self._descriptions) + + def __eq__(self, other): + if not isinstance(other, SubjectInformationAccess): + return NotImplemented + + return self._descriptions == other._descriptions + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(tuple(self._descriptions)) + + class AccessDescription(object): def __init__(self, access_method, access_location): if not isinstance(access_method, ObjectIdentifier): diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py index 3434b61fcb5b3a27faea8827d555ae43099866e0_c3JjL2NyeXB0b2dyYXBoeS94NTA5L29pZC5weQ==..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_c3JjL2NyeXB0b2dyYXBoeS94NTA5L29pZC5weQ== 100644 --- a/src/cryptography/x509/oid.py +++ b/src/cryptography/x509/oid.py @@ -149,6 +149,10 @@ OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") +class SubjectInformationAccessOID(object): + CA_REPOSITORY = ObjectIdentifier("1.3.6.1.5.5.7.48.5") + + class CertificatePoliciesOID(object): CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1") CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2") @@ -251,6 +255,7 @@ ExtensionOID.TLS_FEATURE: "TLSFeature", AuthorityInformationAccessOID.OCSP: "OCSP", AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers", + SubjectInformationAccessOID.CA_REPOSITORY: "caRepository", CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps", CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice", OCSPExtensionOID.NONCE: "OCSPNonce", diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 3434b61fcb5b3a27faea8827d555ae43099866e0_dGVzdHMveDUwOS90ZXN0X3g1MDkucHk=..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_dGVzdHMveDUwOS90ZXN0X3g1MDkucHk= 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -36,7 +36,7 @@ from cryptography.x509.name import _ASN1Type from cryptography.x509.oid import ( AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, - NameOID, SignatureAlgorithmOID + NameOID, SignatureAlgorithmOID, SubjectInformationAccessOID, ) from ..hazmat.primitives.fixtures_dsa import DSA_KEY_2048 @@ -1720,6 +1720,40 @@ @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_encode_nonstandard_sia(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + sia = x509.SubjectInformationAccess([ + x509.AccessDescription( + x509.ObjectIdentifier("2.999.7"), + x509.UniformResourceIdentifier(u"http://example.com") + ), + ]) + + builder = x509.CertificateBuilder().subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + datetime.datetime(2015, 1, 1) + ).not_valid_after( + datetime.datetime(2040, 1, 1) + ).add_extension( + sia, False + ) + + cert = builder.sign(private_key, hashes.SHA256(), backend) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_INFORMATION_ACCESS + ) + assert ext.value == sia + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) def test_subject_dn_asn1_types(self, backend): private_key = RSA_KEY_2048.private_key(backend) @@ -3710,6 +3744,45 @@ @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_sia(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + sia = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + ]) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + sia, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_INFORMATION_ACCESS + ) + assert ext.value == sia + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) def test_build_cert_with_ski(self, backend): issuer_private_key = RSA_KEY_2048.private_key(backend) subject_private_key = RSA_KEY_2048.private_key(backend) diff --git a/tests/x509/test_x509_ext.py b/tests/x509/test_x509_ext.py index 3434b61fcb5b3a27faea8827d555ae43099866e0_dGVzdHMveDUwOS90ZXN0X3g1MDlfZXh0LnB5..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_dGVzdHMveDUwOS90ZXN0X3g1MDlfZXh0LnB5 100644 --- a/tests/x509/test_x509_ext.py +++ b/tests/x509/test_x509_ext.py @@ -26,7 +26,7 @@ from cryptography.x509.general_name import _lazy_import_idna from cryptography.x509.oid import ( AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, - NameOID, ObjectIdentifier, _OID_NAMES + NameOID, ObjectIdentifier, SubjectInformationAccessOID, _OID_NAMES ) from .test_x509 import _load_cert @@ -3052,6 +3052,198 @@ assert hash(aia) != hash(aia3) +class TestSubjectInformationAccess(object): + def test_invalid_descriptions(self): + with pytest.raises(TypeError): + x509.SubjectInformationAccess(["notanAccessDescription"]) + + def test_iter_len(self): + sia = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca2.domain.com") + ) + ]) + assert len(sia) == 2 + assert list(sia) == [ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca2.domain.com") + ) + ] + + def test_iter_input(self): + desc = [ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ) + ] + sia = x509.SubjectInformationAccess(iter(desc)) + assert list(sia) == desc + + def test_repr(self): + sia = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ) + ]) + if not six.PY2: + assert repr(sia) == ( + "<SubjectInformationAccess([<AccessDescription(access_method" + "=<ObjectIdentifier(oid=1.3.6.1.5.5.7.48.5, name=caRepositor" + "y)>, access_location=<UniformResourceIdentifier(value='http" + "://ca.domain.com')>)>])>" + ) + else: + assert repr(sia) == ( + "<SubjectInformationAccess([<AccessDescription(access_method" + "=<ObjectIdentifier(oid=1.3.6.1.5.5.7.48.5, name=caRepositor" + "y)>, access_location=<UniformResourceIdentifier(value=u'htt" + "p://ca.domain.com')>)>])>" + ) + + def test_eq(self): + sia = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca2.domain.com") + ) + ]) + sia2 = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca2.domain.com") + ) + ]) + assert sia == sia2 + + def test_ne(self): + sia = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca2.domain.com") + ) + ]) + sia2 = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + ]) + + assert sia != sia2 + assert sia != object() + + def test_indexing(self): + sia = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca2.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca3.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca4.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca5.domain.com") + ), + ]) + assert sia[-1] == sia[4] + assert sia[2:6:2] == [sia[2], sia[4]] + + def test_hash(self): + sia = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca2.domain.com") + ), + ]) + sia2 = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca2.domain.com") + ), + ]) + sia3 = x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca.domain.com") + ), + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"http://ca3.domain.com") + ), + ]) + assert hash(sia) == hash(sia2) + assert hash(sia) != hash(sia3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestSubjectInformationAccessExtension(object): + def test_sia(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "sia.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.SubjectInformationAccess([ + x509.AccessDescription( + SubjectInformationAccessOID.CA_REPOSITORY, + x509.UniformResourceIdentifier(u"https://my.ca.issuer/") + ), + x509.AccessDescription( + x509.ObjectIdentifier("2.999.7"), + x509.UniformResourceIdentifier(u"gopher://info-mac-archive") + ), + ]) + + @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) class TestAuthorityInformationAccessExtension(object): diff --git a/vectors/cryptography_vectors/x509/custom/sia.pem b/vectors/cryptography_vectors/x509/custom/sia.pem new file mode 100644 index 0000000000000000000000000000000000000000..452e0ac3c60c17b6e9de0682e36e7a09fccf1aec_dmVjdG9ycy9jcnlwdG9ncmFwaHlfdmVjdG9ycy94NTA5L2N1c3RvbS9zaWEucGVt --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/sia.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7TCCAdWgAwIBAgICAwkwDQYJKoZIhvcNAQELBQAwDTELMAkGA1UEBhMCVVMw +HhcNMTUwMTAxMDAwMDAwWhcNNDAwMTAxMDAwMDAwWjANMQswCQYDVQQGEwJVUzCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMF6/H53R0yqWqgwNhWKP/v3 +tSFoUboiMOXWq/zBxs/vWekj6hMwvFk7c4Aqtgim5KMwZSOjEWulqjlmFFF04Tts +Sem3gGLkSdcu+xD9SekfoIuW0FHngun1q8W1pveYSCetuOc9oA8isu/c23bqtG7a +2Y7WVmJ0P9xsDjNqXQzbqn3CnlNjXiTIelssQhWWgGPN62ipcrq7wePP8A+5qA43 +Kk0MLJINHozuMzzkcNwugUWtsFvymu4dJPFB6Mx4SYnFh/xvus2Xnz8hY8HXKZs2 +W8cv/ihI6Weu0eSNzFFbOlDtTeBP0FOEbKEKIjsQzIQcyA/evuRPMRTBPohq9YMC +AwEAAaNXMFUwUwYIKwYBBQUHAQsERzBFMCEGCCsGAQUFBzAFhhVodHRwczovL215 +LmNhLmlzc3Vlci8wIAYDiDcHhhlnb3BoZXI6Ly9pbmZvLW1hYy1hcmNoaXZlMA0G +CSqGSIb3DQEBCwUAA4IBAQB4AdYx02aXDJURPbZNi3j7FnK3LRVvJcq8vRHaG9b4 +soD/7qA8RJX11WTFNDY7g5OQhYT+WBc8OUinJaqJOPvEzgp5Prgq5AlAtcImvNX7 +dI3lr9esZ5gBWbsMK9saNEERhEZDUCSYW/GRMN4yxdUgTDPsfNr8N6bwfnGRR0xM +EBr+p+fT1xth4uren7J/edYrY9a171y6bMdZQ1iVnFH2dFO25D+3k9sM6FRWWsWu +mmrcg79QAl6jqC/6SkqVzpBPzi7dgGYluaKJjREC8e/cMcpphW1TP+8rZ161BmDk +hk5/PrWguFuguWUyEkPH5oqFqoZuqeM0fULxHh2JiqOx +-----END CERTIFICATE-----