replace pyasn1 with asn1crypto (#3361)

* replace pyasn1 with asn1crypto

* allow trailing bytes

* fix x509 test

* update CHANGELOG.rst

* fix assert

* make asn1crypto code more idiomatic

* find tag

* final clean-up

* leave trailing byte logic unchanged

* document dependency change

* spelling

* fix spelling
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 744ec20..ee0a345 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -7,6 +7,9 @@
 .. note:: This version is not yet released and is under active development.
 
 * Added support for Python 3.6.
+* Changed ASN.1 dependency from ``pyasn1`` to ``asn1crypto`` resulting in a
+  general performance increase when encoding/decoding ASN.1 structures. Also,
+  the ``pyasn1_modules`` test dependency is no longer required.
 
 * Added
   :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization.private_bytes`
diff --git a/setup.py b/setup.py
index 8419e6a..1b1ff60 100644
--- 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
index 44bf59d..4c2337b 100644
--- 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_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 @@
     ):
         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
index f7f6fcd..1a3ced7 100644
--- 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 @@
             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()
 
diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py
index bd1fa35..4835f09 100644
--- a/tests/hazmat/primitives/test_asym_utils.py
+++ b/tests/hazmat/primitives/test_asym_utils.py
@@ -73,8 +73,7 @@
         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
index 1ecf6b6..db26f56 100644
--- 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 @@
 
         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):