Skip to content

Commit

Permalink
MetadataResolver: Verify sigs with cryptography
Browse files Browse the repository at this point in the history
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
  • Loading branch information
moreati committed Oct 5, 2015
1 parent f5bdada commit 8ddf35d
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 12 deletions.
2 changes: 1 addition & 1 deletion NEWS
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
url='https://github.com/Yubico/python-u2flib-server',
install_requires=[
'cryptography>=1.0',
'M2Crypto',
'pyasn1>=0.1.7',
'pyasn1-modules',
],
Expand Down
57 changes: 47 additions & 10 deletions u2flib_server/attestation/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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), []):
Expand Down

0 comments on commit 8ddf35d

Please sign in to comment.