From ef597bf0c2e2fc3b63b4d205e1a69e1873561ccd Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Thu, 1 Oct 2015 14:50:34 +0100 Subject: [PATCH] Use pyasn1 to get extensions in x509 certificates This change - Merges branch 'test-ext-by-oid' into cryptography - Reintroduces the dependence on pyasn1 - Adds tests for get_ext_by_oid() See https://github.com/Yubico/python-u2flib-server/commit/72f7e00b9cecb6a4e4d5576cfbcf7974d49e72d4#diff-1595778f95f527cab74dfcd7ec0e70c0R58 --- NEWS | 1 - setup.py | 2 ++ test/test_matchers.py | 47 +++++++++++++++++++++++++++ u2flib_server/attestation/matchers.py | 19 +++++++---- 4 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 test/test_matchers.py diff --git a/NEWS b/NEWS index 89265a4..1c70699 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,6 @@ * Version 4.0.0 (not yet released) ** Major release: Changed backend from M2Crypto to cryptography ** Added: dependency on cryptography 1.0 or higher - ** Removed: dependency on PyASN and PyASN-modules ** For now M2Crypto is still needed for verifying issuer certificates ** utils.rand_bytes() now sources bytes from os.urandom() diff --git a/setup.py b/setup.py index 2f987d0..01652f1 100755 --- a/setup.py +++ b/setup.py @@ -38,6 +38,8 @@ install_requires=[ 'cryptography>=1.0', 'M2Crypto', + 'pyasn1>=0.1.7', + 'pyasn1-modules', ], test_suite='test', tests_require=[], diff --git a/test/test_matchers.py b/test/test_matchers.py new file mode 100644 index 0000000..4338de4 --- /dev/null +++ b/test/test_matchers.py @@ -0,0 +1,47 @@ +import unittest + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend + +from u2flib_server.attestation.matchers import get_ext_by_oid + +YUBICO_ATTESTATION_CERT_SERIAL_544338083 = '''-----BEGIN CERTIFICATE----- +MIICIjCCAQygAwIBAgIEIHHwozALBgkqhkiG9w0BAQswDzENMAsGA1UEAxMEdGVz +dDAeFw0xNTA4MTEwOTAwMzNaFw0xNjA4MTAwOTAwMzNaMCkxJzAlBgNVBAMTHll1 +YmljbyBVMkYgRUUgU2VyaWFsIDU0NDMzODA4MzBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABPdFG1pBjBBQVhLrD39Qg1vKjuR2kRdBZnwLI/zgzztQpf4ffpkrkB/3 +E0TXj5zg8gN9sgMkX48geBe+tBEpvMmjOzA5MCIGCSsGAQQBgsQKAgQVMS4zLjYu +MS40LjEuNDE0ODIuMS4yMBMGCysGAQQBguUcAgEBBAQDAgQwMAsGCSqGSIb3DQEB +CwOCAQEAb3YpnmHHduNuWEXlLqlnww9034ZeZaojhPAYSLR8d5NPk9gc0hkjQKmI +aaBM7DsaHbcHMKpXoMGTQSC++NCZTcKvZ0Lt12mp5HRnM1NNBPol8Hte5fLmvW4t +Q9EzLl4gkz7LSlORxTuwTbae1eQqNdxdeB+0ilMFCEUc+3NGCNM0RWd+sP5+gzMX +BDQAI1Sc9XaPIg8t3du5JChAl1ifpu/uERZ2WQgtxeBDO6z1Xoa5qz4svf5oURjP +ZjxS0WUKht48Z2rIjk5lZzERSaY3RrX3UtrnZEIzCmInXOrcRPeAD4ZutpiwuHe6 +2ABsjuMRnKbATbOUiLdknNyPYYQz2g== +-----END CERTIFICATE-----''' + +# From https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers +# Regsitered number Enterprise +# 1.3.6.1.4.1.41482 Yubico +# 1.3.6.1.4.1.45724 FIDO Alliance, Inc. + + +class X509ExtensionsTest(unittest.TestCase): + + attestation_cert = x509.load_pem_x509_certificate( + YUBICO_ATTESTATION_CERT_SERIAL_544338083, + default_backend(), + ) + + def test_get_ext_by_oid_yubico(self): + self.assertEqual( + b'1.3.6.1.4.1.41482.1.2', + get_ext_by_oid(self.attestation_cert, '1.3.6.1.4.1.41482.2'), + ) + + def test_get_ext_by_oid_fido_alliance(self): + self.assertEqual( + b'\x03\x02\x040', + get_ext_by_oid(self.attestation_cert, '1.3.6.1.4.1.45724.2.1.1'), + ) + diff --git a/u2flib_server/attestation/matchers.py b/u2flib_server/attestation/matchers.py index f5176e7..aa8e66d 100644 --- a/u2flib_server/attestation/matchers.py +++ b/u2flib_server/attestation/matchers.py @@ -26,7 +26,10 @@ # POSSIBILITY OF SUCH DAMAGE. from cryptography import x509 -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization + +from pyasn1.codec.der import decoder +from pyasn1_modules import rfc2459 __all__ = [ 'DeviceMatcher', @@ -53,12 +56,14 @@ def matches(self, certificate, parameters=[]): def get_ext_by_oid(cert, oid): - oid = x509.ObjectIdentifier(oid) - try: - ext = cert.extensions.get_extension_for_oid(oid) - except x509.ExtensionNotFound: - return None - return ext.value + # This is needed until cryptography supports reading custom extensions, + # see https://github.com/pyca/cryptography/issues/2288 + cert_der = cert.public_bytes(serialization.Encoding.DER) + cert, _ = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate()) + for ext in cert['tbsCertificate']['extensions']: + if ext['extnID'].prettyPrint() == oid: + return decoder.decode(ext['extnValue'])[0].asOctets() + return None class ExtensionMatcher(DeviceMatcher):