Repository: pyca/cryptography

Issue:
Replace pyasn1 with asn1crypto
Replacing pyasn1 with @wbond's excellent  [asn1crypto](https://github.com/wbond/asn1crypto) will result in a non-trivial performance increase (4x based on decoding tests) ameliorating our [public key digest computation](https://github.com/pyca/cryptography/pull/3361#issuecomment-274328410) situation, better extensibility in future, access to a pythonic API, and the removal of our pyasn1_modules test dependency.

Conversion is complete in #3361.


Discussion:

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -35,7 +35,7 @@
 
 requirements = [
     "idna>=2.0",
-    "pyasn1>=0.1.8",
+    "asn1crypto>=0.21.0",
     "six>=1.4.1",
     "setuptools>=11.3",
 ]
@@ -61,7 +61,6 @@
     "pytest>=2.9.0",
     "pretend",
     "iso8601",
-    "pyasn1_modules",
     "pytz",
 ]
 if sys.version_info[:2] > (2, 6):
diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py
--- a/src/cryptography/hazmat/primitives/asymmetric/utils.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py
@@ -6,9 +6,7 @@
 
 import warnings
 
-from pyasn1.codec.der import decoder, encoder
-from pyasn1.error import PyAsn1Error
-from pyasn1.type import namedtype, univ
+from asn1crypto.algos import DSASignature
 
 import six
 
@@ -16,13 +14,6 @@
 from cryptography.hazmat.primitives import hashes
 
 
-class _DSSSigValue(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('r', univ.Integer()),
-        namedtype.NamedType('s', univ.Integer())
-    )
-
-
 def decode_rfc6979_signature(signature):
     warnings.warn(
         "decode_rfc6979_signature is deprecated and will "
@@ -34,19 +25,8 @@ def decode_rfc6979_signature(signature):
 
 
 def decode_dss_signature(signature):
-    try:
-        data, remaining = decoder.decode(signature, asn1Spec=_DSSSigValue())
-    except PyAsn1Error:
-        raise ValueError("Invalid signature data. Unable to decode ASN.1")
-
-    if remaining:
-        raise ValueError(
-            "The signature contains bytes after the end of the ASN.1 sequence."
-        )
-
-    r = int(data.getComponentByName('r'))
-    s = int(data.getComponentByName('s'))
-    return (r, s)
+    data = DSASignature.load(signature, strict=True).native
+    return data['r'], data['s']
 
 
 def encode_rfc6979_signature(r, s):
@@ -66,10 +46,7 @@ def encode_dss_signature(r, s):
     ):
         raise ValueError("Both r and s must be integers")
 
-    sig = _DSSSigValue()
-    sig.setComponentByName('r', r)
-    sig.setComponentByName('s', s)
-    return encoder.encode(sig)
+    return DSASignature({'r': r, 's': s}).dump()
 
 
 class Prehashed(object):
diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py
--- a/src/cryptography/x509/extensions.py
+++ b/src/cryptography/x509/extensions.py
@@ -11,8 +11,7 @@
 import warnings
 from enum import Enum
 
-from pyasn1.codec.der import decoder
-from pyasn1.type import namedtype, univ
+from asn1crypto.keys import PublicKeyInfo
 
 import six
 
@@ -27,13 +26,6 @@
 )
 
 
-class _SubjectPublicKeyInfo(univ.Sequence):
-    componentType = namedtype.NamedTypes(
-        namedtype.NamedType('algorithm', univ.Sequence()),
-        namedtype.NamedType('subjectPublicKey', univ.BitString())
-    )
-
-
 def _key_identifier_from_public_key(public_key):
     if isinstance(public_key, RSAPublicKey):
         data = public_key.public_bytes(
@@ -48,18 +40,8 @@ def _key_identifier_from_public_key(public_key):
             serialization.Encoding.DER,
             serialization.PublicFormat.SubjectPublicKeyInfo
         )
-        spki, remaining = decoder.decode(
-            serialized, asn1Spec=_SubjectPublicKeyInfo()
-        )
-        assert not remaining
-        # the univ.BitString object is a tuple of bits. We need bytes and
-        # pyasn1 really doesn't want to give them to us. To get it we'll
-        # build an integer and convert that to bytes.
-        bits = 0
-        for bit in spki.getComponentByName("subjectPublicKey"):
-            bits = bits << 1 | bit
-
-        data = utils.int_to_bytes(bits)
+
+        data = six.binary_type(PublicKeyInfo.load(serialized)['public_key'])
 
     return hashlib.sha1(data).digest()
 


Test patch:
diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py
--- a/tests/hazmat/primitives/test_asym_utils.py
+++ b/tests/hazmat/primitives/test_asym_utils.py
@@ -73,8 +73,7 @@ def test_decode_dss_invalid_asn1():
         decode_dss_signature(b"0\x07\x02\x01\x01\x02\x02\x01")
 
     with pytest.raises(ValueError):
-        # This is the BER "end-of-contents octets," which older versions of
-        # pyasn1 are wrongly willing to return from top-level DER decoding.
+        # This is the BER "end-of-contents octets".
         decode_dss_signature(b"\x00\x00")
 
 
diff --git a/tests/test_x509.py b/tests/test_x509.py
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -11,9 +11,7 @@
 import sys
 import warnings
 
-from pyasn1.codec.der import decoder
-
-from pyasn1_modules import rfc2459
+from asn1crypto.x509 import Certificate
 
 import pytest
 
@@ -1458,17 +1456,12 @@ def test_build_cert_printable_string_country_name(self, backend):
 
         cert = builder.sign(issuer_private_key, hashes.SHA256(), backend)
 
-        parsed, _ = decoder.decode(
-            cert.public_bytes(serialization.Encoding.DER),
-            asn1Spec=rfc2459.Certificate()
-        )
-        tbs_cert = parsed.getComponentByName('tbsCertificate')
-        subject = tbs_cert.getComponentByName('subject')
-        issuer = tbs_cert.getComponentByName('issuer')
-        # \x13 is printable string. The first byte of the value of the
-        # node corresponds to the ASN.1 string type.
-        assert subject[0][0][0][1][0] == b"\x13"[0]
-        assert issuer[0][0][0][1][0] == b"\x13"[0]
+        parsed = Certificate.load(
+            cert.public_bytes(serialization.Encoding.DER))
+
+        # Check that each value was encoded as an ASN.1 PRINTABLESTRING.
+        assert parsed.subject.chosen[0][0]['value'].chosen.tag == 19
+        assert parsed.issuer.chosen[0][0]['value'].chosen.tag == 19
 
 
 class TestCertificateBuilder(object):