From 8ddf35d0112b257b9b6c50fd564f538cddc50823 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Mon, 5 Oct 2015 17:27:12 +0100 Subject: [PATCH] MetadataResolver: Verify sigs with cryptography Use cryptography + pyasn1 to implement MetadataResolver._verify() This replaces the last use of M2Crypto. However it's rough and probably misses varisou corner cases. It's probably better to wait for pyca/cryptography#2381 --- NEWS | 2 +- setup.py | 1 - u2flib_server/attestation/resolvers.py | 57 +++++++++++++++++++++----- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index 1c70699..08842c5 100644 --- a/NEWS +++ b/NEWS @@ -1,7 +1,7 @@ * Version 4.0.0 (not yet released) ** Major release: Changed backend from M2Crypto to cryptography ** Added: dependency on cryptography 1.0 or higher - ** For now M2Crypto is still needed for verifying issuer certificates + ** Removed: dependency on M2Crypto ** utils.rand_bytes() now sources bytes from os.urandom() * Version 3.2.0 (released 2015-06-16) diff --git a/setup.py b/setup.py index 01652f1..4a780a5 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,6 @@ url='https://github.com/Yubico/python-u2flib-server', install_requires=[ 'cryptography>=1.0', - 'M2Crypto', 'pyasn1>=0.1.7', 'pyasn1-modules', ], diff --git a/u2flib_server/attestation/resolvers.py b/u2flib_server/attestation/resolvers.py index 6b73c6a..7c4c7b5 100644 --- a/u2flib_server/attestation/resolvers.py +++ b/u2flib_server/attestation/resolvers.py @@ -28,10 +28,12 @@ __all__ = ['MetadataResolver', 'create_resolver'] from cryptography import x509 +from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives import asymmetric, serialization -import M2Crypto.X509 +from pyasn1.codec.der import decoder, encoder +from pyasn1_modules import rfc2459 from u2flib_server.jsapi import MetadataObject from u2flib_server.attestation.data import YUBICO @@ -79,9 +81,34 @@ def _index(self, metadata): self._certs[subject].append(cert) self._metadata[cert] = metadata - # FIXME This is the only remaining use of M2Crypto @staticmethod - def _verify(cert, issuer_cert): + def _bitstring_bytes(bitstring): + """Returns the raw bytes in a pyasn1 BitString + """ + bits = ''.join(str(bit) for bit in bitstring) + byte_size_bits = [bits[n:n+8] for n in range(0, len(bits), 8)] + return bytes(bytearray(int(chunk, 2) for chunk in byte_size_bits)) + + @staticmethod + def _verifier(pubkey, sig, sig_hash_algorithm): + """Returns a suitable cryptography AsymmetricVerificationContext + instance + """ + if isinstance(pubkey, asymmetric.ec.EllipticCurvePublicKey): + ec_sig_algorithm = asymmetric.ec.EllipticCurveSignatureAlgorithm( + sig_hash_algorithm, + ) + return pubkey.verifier(sig, ec_sig_algorithm) + + elif isinstance(pubkey, asymmetric.dsa.DSAPublicKey): + backend = default_backend() + return pubkey.verifier(sig, sig_hash_algorithm, backend) + + elif isinstance(pubkey, asymmetric.rsa.RSAPublicKey): + padding = asymmetric.padding.PKCS1v15() + return pubkey.verifier(sig, padding, sig_hash_algorithm) + + def _verify(self, cert, issuer_cert): """Returns True if cert contains a correct signature made using the private key for issuer_cert @@ -91,13 +118,23 @@ def _verify(cert, issuer_cert): """ # Serialize from cryptography.x509 objects cert_der = cert.public_bytes(serialization.Encoding.DER) - issuer_cert_der = issuer_cert.public_bytes(serialization.Encoding.DER) - - # Deserialize as M2Crypto.X509 objects - cert_m2c = M2Crypto.X509.load_cert_der_string(cert_der) - issuer_cert_m2c = M2Crypto.X509.load_cert_der_string(issuer_cert_der) - return bool(cert_m2c.verify(issuer_cert_m2c.get_pubkey()) == 1) + # Deserialize as pyasn1_modules.rfc2459.Certificate + cert_asn, _ = decoder.decode(cert_der, asn1Spec=rfc2459.Certificate()) + + issuer_pubkey = issuer_cert.public_key() + cert_sig_bytes = self._bitstring_bytes(cert_asn['signatureValue']) + verifier = self._verifier( + issuer_pubkey, + cert_sig_bytes, + cert.signature_hash_algorithm, + ) + verifier.update(encoder.encode(cert_asn['tbsCertificate'])) + try: + verifier.verify() + except InvalidSignature: + return False + return True def resolve(self, cert): for issuer in self._certs.get(self._name_key(cert.issuer), []):