diff options
author | David Benjamin <davidben@google.com> | 2019-07-28 13:06:40 -0400 |
---|---|---|
committer | Paul Kehrer <paul.l.kehrer@gmail.com> | 2019-07-28 12:06:40 -0500 |
commit | 2d3b420383fc6aa16675e04caec56ca6b16069a1 (patch) | |
tree | 6122d7ad96d04a828ab413a4b8c788172192590a /tests/x509/test_x509.py | |
parent | 85d6043f21bbc8bc3f97f8a8be25581f8bc7f376 (diff) | |
download | cryptography-2d3b420383fc6aa16675e04caec56ca6b16069a1.tar.gz cryptography-2d3b420383fc6aa16675e04caec56ca6b16069a1.tar.bz2 cryptography-2d3b420383fc6aa16675e04caec56ca6b16069a1.zip |
Remove asn1crypto dependency (#4941)
* Remove non-test dependencies on asn1crypto.
cryptography.io actually contains two OpenSSL bindings right now, the
expected cffi one, and an optional one hidden in asn1crypto. asn1crypto
contains a lot of things that cryptography.io doesn't use, including a
BER parser and a hand-rolled and not constant-time EC implementation.
Instead, check in a much small DER-only parser in cryptography/hazmat. A
quick benchmark suggests this parser is also faster than asn1crypto:
from __future__ import absolute_import, division, print_function
import timeit
print(timeit.timeit(
"decode_dss_signature(sig)",
setup=r"""
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
sig=b"\x30\x2d\x02\x15\x00\xb5\xaf\x30\x78\x67\xfb\x8b\x54\x39\x00\x13\xcc\x67\x02\x0d\xdf\x1f\x2c\x0b\x81\x02\x14\x62\x0d\x3b\x22\xab\x50\x31\x44\x0c\x3e\x35\xea\xb6\xf4\x81\x29\x8f\x9e\x9f\x08"
""",
number=10000))
Python 2.7:
asn1crypto: 0.25
_der.py: 0.098
Python 3.5:
asn1crypto: 0.17
_der.py: 0.10
* Remove test dependencies on asn1crypto.
The remaining use of asn1crypto was some sanity-checking of
Certificates. Add a minimal X.509 parser to extract the relevant fields.
* Add a read_single_element helper function.
The outermost read is a little tedious.
* Address flake8 warnings
* Fix test for long-form vs short-form lengths.
Testing a zero length trips both this check and the non-minimal long
form check. Use a one-byte length to cover the missing branch.
* Remove support for negative integers.
These never come up in valid signatures. Note, however, this does
change public API.
* Update src/cryptography/hazmat/primitives/asymmetric/utils.py
Co-Authored-By: Alex Gaynor <alex.gaynor@gmail.com>
* Review comments
* Avoid hardcoding the serialization of NULL in decode_asn1.py too.
Diffstat (limited to 'tests/x509/test_x509.py')
-rw-r--r-- | tests/x509/test_x509.py | 98 |
1 files changed, 76 insertions, 22 deletions
diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 20e23d5f..bb0ad022 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -6,12 +6,11 @@ from __future__ import absolute_import, division, print_function import binascii +import collections import datetime import ipaddress import os -from asn1crypto.x509 import Certificate - import pytest import pytz @@ -20,6 +19,10 @@ import six from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat._der import ( + BIT_STRING, CONSTRUCTED, CONTEXT_SPECIFIC, DERReader, GENERALIZED_TIME, + INTEGER, OBJECT_IDENTIFIER, PRINTABLE_STRING, SEQUENCE, SET, UTC_TIME +) from cryptography.hazmat.backends.interfaces import ( DSABackend, EllipticCurveBackend, RSABackend, X509Backend ) @@ -65,6 +68,53 @@ def _load_cert(filename, loader, backend): return cert +ParsedCertificate = collections.namedtuple( + "ParsedCertificate", + ["not_before_tag", "not_after_tag", "issuer", "subject"] +) + + +def _parse_cert(der): + # See the Certificate structured, defined in RFC 5280. + cert = DERReader(der).read_single_element(SEQUENCE) + tbs_cert = cert.read_element(SEQUENCE) + # Skip outer signature algorithm + _ = cert.read_element(SEQUENCE) + # Skip signature + _ = cert.read_element(BIT_STRING) + cert.check_empty() + + # Skip version + _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 0) + # Skip serialNumber + _ = tbs_cert.read_element(INTEGER) + # Skip inner signature algorithm + _ = tbs_cert.read_element(SEQUENCE) + issuer = tbs_cert.read_element(SEQUENCE) + validity = tbs_cert.read_element(SEQUENCE) + subject = tbs_cert.read_element(SEQUENCE) + # Skip subjectPublicKeyInfo + _ = tbs_cert.read_element(SEQUENCE) + # Skip issuerUniqueID + _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 1) + # Skip subjectUniqueID + _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 2) + # Skip extensions + _ = tbs_cert.read_optional_element(CONTEXT_SPECIFIC | CONSTRUCTED | 3) + tbs_cert.check_empty() + + not_before_tag, _ = validity.read_any_element() + not_after_tag, _ = validity.read_any_element() + validity.check_empty() + + return ParsedCertificate( + not_before_tag=not_before_tag, + not_after_tag=not_after_tag, + issuer=issuer, + subject=subject, + ) + + @pytest.mark.requires_backend_interface(interface=X509Backend) class TestCertificateRevocationList(object): def test_load_pem_crl(self, backend): @@ -1587,12 +1637,24 @@ class TestRSACertificateRequest(object): cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) - parsed = Certificate.load( - cert.public_bytes(serialization.Encoding.DER)) + parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) + subject = parsed.subject + issuer = parsed.issuer + + def read_next_rdn_value_tag(reader): + rdn = reader.read_element(SET) + attribute = rdn.read_element(SEQUENCE) + # Assume each RDN has a single attribute. + rdn.check_empty() + + _ = attribute.read_element(OBJECT_IDENTIFIER) + tag, value = attribute.read_any_element() + attribute.check_empty() + return tag # 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 + assert read_next_rdn_value_tag(subject) == PRINTABLE_STRING + assert read_next_rdn_value_tag(issuer) == PRINTABLE_STRING if ( # This only works correctly in OpenSSL 1.1.0f+ and 1.0.2l+ backend._lib.CRYPTOGRAPHY_OPENSSL_110F_OR_GREATER or ( @@ -1600,8 +1662,8 @@ class TestRSACertificateRequest(object): not backend._lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER ) ): - assert parsed.subject.chosen[1][0]['value'].chosen.tag == 19 - assert parsed.issuer.chosen[1][0]['value'].chosen.tag == 19 + assert read_next_rdn_value_tag(subject) == PRINTABLE_STRING + assert read_next_rdn_value_tag(issuer) == PRINTABLE_STRING class TestCertificateBuilder(object): @@ -1738,13 +1800,9 @@ class TestCertificateBuilder(object): cert = builder.sign(private_key, hashes.SHA256(), backend) assert cert.not_valid_before == not_valid_before assert cert.not_valid_after == not_valid_after - parsed = Certificate.load( - cert.public_bytes(serialization.Encoding.DER) - ) - not_before = parsed['tbs_certificate']['validity']['not_before'] - not_after = parsed['tbs_certificate']['validity']['not_after'] - assert not_before.chosen.tag == 23 # UTCTime - assert not_after.chosen.tag == 24 # GeneralizedTime + parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) + assert parsed.not_before_tag == UTC_TIME + assert parsed.not_after_tag == GENERALIZED_TIME @pytest.mark.requires_backend_interface(interface=RSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) @@ -2038,13 +2096,9 @@ class TestCertificateBuilder(object): cert = cert_builder.sign(private_key, hashes.SHA256(), backend) assert cert.not_valid_before == time assert cert.not_valid_after == time - parsed = Certificate.load( - cert.public_bytes(serialization.Encoding.DER) - ) - not_before = parsed['tbs_certificate']['validity']['not_before'] - not_after = parsed['tbs_certificate']['validity']['not_after'] - assert not_before.chosen.tag == 23 # UTCTime - assert not_after.chosen.tag == 23 # UTCTime + parsed = _parse_cert(cert.public_bytes(serialization.Encoding.DER)) + assert parsed.not_before_tag == UTC_TIME + assert parsed.not_after_tag == UTC_TIME def test_invalid_not_valid_after(self): with pytest.raises(TypeError): |