From df38700f360811c1ee25f79f2cef5d08ea50b5e0 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Mon, 27 Jul 2015 15:19:57 +0100 Subject: support encoding certificate policies in CertificateBuilder --- .../hazmat/backends/openssl/backend.py | 90 ++++++++++++++++++++++ tests/test_x509.py | 89 +++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py index 58587b94..e1ef63be 100644 --- a/src/cryptography/hazmat/backends/openssl/backend.py +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -94,6 +94,20 @@ def _encode_asn1_str(backend, data, length): return s +def _encode_asn1_utf8_str(backend, string): + """ + Create an ASN1_UTF8STRING from a Python unicode string. + This object will be a ASN1_STRING with UTF8 type in OpenSSL and + can be decoded with ASN1_STRING_to_UTF8. + """ + s = backend._lib.ASN1_UTF8STRING_new() + res = backend._lib.ASN1_STRING_set( + s, string.encode("utf8"), len(string.encode("utf8")) + ) + assert res == 1 + return s + + def _encode_asn1_str_gc(backend, data, length): s = _encode_asn1_str(backend, data, length) s = backend._ffi.gc(s, backend._lib.ASN1_OCTET_STRING_free) @@ -136,6 +150,81 @@ def _encode_name_gc(backend, attributes): return subject +def _encode_certificate_policies(backend, certificate_policies): + cp = backend._lib.sk_POLICYINFO_new_null() + assert cp != backend._ffi.NULL + cp = backend._ffi.gc(cp, backend._lib.sk_POLICYINFO_free) + for policy_info in certificate_policies: + pi = backend._lib.POLICYINFO_new() + assert pi != backend._ffi.NULL + res = backend._lib.sk_POLICYINFO_push(cp, pi) + assert res >= 1 + oid = _txt2obj(backend, policy_info.policy_identifier.dotted_string) + pi.policyid = oid + if policy_info.policy_qualifiers: + pqis = backend._lib.sk_POLICYQUALINFO_new_null() + assert pqis != backend._ffi.NULL + for qualifier in policy_info.policy_qualifiers: + pqi = backend._lib.POLICYQUALINFO_new() + assert pqi != backend._ffi.NULL + res = backend._lib.sk_POLICYQUALINFO_push(pqis, pqi) + assert res >= 1 + if isinstance(qualifier, six.text_type): + pqi.pqualid = _txt2obj( + backend, x509.OID_CPS_QUALIFIER.dotted_string + ) + pqi.d.cpsuri = _encode_asn1_str( + backend, + qualifier.encode("ascii"), + len(qualifier.encode("ascii")) + ) + else: + assert isinstance(qualifier, x509.UserNotice) + pqi.pqualid = _txt2obj( + backend, x509.OID_CPS_USER_NOTICE.dotted_string + ) + un = backend._lib.USERNOTICE_new() + assert un != backend._ffi.NULL + pqi.d.usernotice = un + if qualifier.explicit_text: + un.exptext = _encode_asn1_utf8_str( + backend, qualifier.explicit_text + ) + + un.noticeref = _encode_notice_reference( + backend, qualifier.notice_reference + ) + + pi.qualifiers = pqis + + pp = backend._ffi.new('unsigned char **') + r = backend._lib.i2d_CERTIFICATEPOLICIES(cp, pp) + assert r > 0 + pp = backend._ffi.gc( + pp, lambda pointer: backend._lib.OPENSSL_free(pointer[0]) + ) + return pp, r + + +def _encode_notice_reference(backend, notice): + if notice is None: + return backend._ffi.NULL + else: + nr = backend._lib.NOTICEREF_new() + assert nr != backend._ffi.NULL + # organization is a required field + nr.organization = _encode_asn1_utf8_str(backend, notice.organization) + + notice_stack = backend._lib.sk_ASN1_INTEGER_new_null() + nr.noticenos = notice_stack + for number in notice.notice_numbers: + num = _encode_asn1_int(backend, number) + res = backend._lib.sk_ASN1_INTEGER_push(notice_stack, num) + assert res >= 1 + + return nr + + def _txt2obj(backend, name): """ Converts a Python string with an ASN.1 object ID in dotted form to a @@ -487,6 +576,7 @@ _EXTENSION_ENCODE_HANDLERS = { ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, ExtensionOID.EXTENDED_KEY_USAGE: _encode_extended_key_usage, ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, + x509.OID_CERTIFICATE_POLICIES: _encode_certificate_policies, ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( _encode_authority_information_access ), diff --git a/tests/test_x509.py b/tests/test_x509.py index b9ea139b..a54cdc56 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -1694,6 +1694,95 @@ class TestCertificateBuilder(object): with pytest.raises(ValueError): builder.sign(issuer_private_key, hashes.SHA512(), backend) + @pytest.mark.parametrize( + "cp", + [ + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [u"http://other.com/cps"] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + None + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + u"http://example.com/cps", + u"http://other.com/cps", + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + u"thing" + ) + ] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + u"http://example.com/cps", + x509.UserNotice( + x509.NoticeReference(u"UTF8\u2122'", [1, 2, 3, 4]), + u"We heart UTF8!\u2122" + ) + ] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [x509.UserNotice(None, u"thing")] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + None + ) + ] + ) + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_certificate_policies(self, cp, 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) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + cp, critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_CERTIFICATE_POLICIES + ) + assert ext.value == cp + @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) def test_issuer_alt_name(self, backend): -- cgit v1.2.3