From 5bd0c10d500d57144db9cd0c46b80d3f6bd64dc3 Mon Sep 17 00:00:00 2001 From: Paul Kehrer Date: Sun, 21 Nov 2021 11:16:43 +0800 Subject: [PATCH] allow parsing of nonstandard country name and jurisdiction country name (#6641) The spec requires both of these to be exactly two characters to correspond with ISO country codes. Reality is sometimes messier, so this allows parsing (but not encoding) of this invalid data. Parsing will raise a UserWarning if incorrect lengths are detected. --- docs/development/test-vectors.rst | 3 +++ src/cryptography/x509/name.py | 17 ++++++++++++-- src/rust/src/x509/common.rs | 4 +++- tests/x509/test_x509.py | 22 +++++++++++++++++++ .../x509/custom/bad_country.pem | 18 +++++++++++++++ 5 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 vectors/cryptography_vectors/x509/custom/bad_country.pem diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst index 6a81da2c359d..0cd10439c083 100644 --- a/docs/development/test-vectors.rst +++ b/docs/development/test-vectors.rst @@ -444,6 +444,9 @@ Custom X.509 Vectors version. * ``invalid-sct-length.der`` - A certificate with an SCT with an internal length greater than the amount of data. +* ``bad_country.pem`` - A certificate with country name and jurisdiction + country name values in its subject and issuer distinguished names which + are longer than 2 characters. Custom X.509 Request Vectors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py index 925a88f07e01..b02f08babc4a 100644 --- a/src/cryptography/x509/name.py +++ b/src/cryptography/x509/name.py @@ -3,6 +3,7 @@ # for complete details. import typing +import warnings from cryptography import utils from cryptography.hazmat.bindings._rust import ( @@ -79,7 +80,12 @@ def _escape_dn_value(val: str) -> str: class NameAttribute(object): def __init__( - self, oid: ObjectIdentifier, value: str, _type=_SENTINEL + self, + oid: ObjectIdentifier, + value: str, + _type=_SENTINEL, + *, + _validate=True, ) -> None: if not isinstance(oid, ObjectIdentifier): raise TypeError( @@ -93,10 +99,17 @@ def __init__( oid == NameOID.COUNTRY_NAME or oid == NameOID.JURISDICTION_COUNTRY_NAME ): - if len(value.encode("utf8")) != 2: + c_len = len(value.encode("utf8")) + if c_len != 2 and _validate is True: raise ValueError( "Country name must be a 2 character country code" ) + elif c_len != 2: + warnings.warn( + "Country names should be two characters, but the " + "attribute is {} characters in length.".format(c_len), + stacklevel=2, + ) # The appropriate ASN1 string type varies by OID and is defined across # multiple RFCs including 2459, 3280, and 5280. In general UTF8String diff --git a/src/rust/src/x509/common.rs b/src/rust/src/x509/common.rs index 4b3ae5b01963..e3b36c8a26dc 100644 --- a/src/rust/src/x509/common.rs +++ b/src/rust/src/x509/common.rs @@ -5,6 +5,7 @@ use crate::asn1::PyAsn1Error; use crate::x509; use chrono::{Datelike, TimeZone, Timelike}; +use pyo3::types::IntoPyDict; use pyo3::ToPyObject; use std::collections::HashSet; use std::convert::TryInto; @@ -382,8 +383,9 @@ fn parse_name_attribute( _ => std::str::from_utf8(attribute.value.data()) .map_err(|_| asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue))?, }; + let kwargs = [("_validate", false)].into_py_dict(py); Ok(x509_module - .call_method1("NameAttribute", (oid, py_data, py_tag))? + .call_method("NameAttribute", (oid, py_data, py_tag), Some(kwargs))? .to_object(py)) } diff --git a/tests/x509/test_x509.py b/tests/x509/test_x509.py index 17076d992c34..23e97a7684f7 100644 --- a/tests/x509/test_x509.py +++ b/tests/x509/test_x509.py @@ -747,6 +747,28 @@ def test_negative_serial_number(self, backend): with pytest.warns(utils.DeprecatedIn36): assert cert.serial_number == -18008675309 + def test_country_jurisdiction_country_too_long(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "bad_country.pem"), + x509.load_pem_x509_certificate, + backend, + ) + with pytest.warns(UserWarning): + assert ( + cert.subject.get_attributes_for_oid(x509.NameOID.COUNTRY_NAME)[ + 0 + ].value + == "too long" + ) + + with pytest.warns(UserWarning): + assert ( + cert.subject.get_attributes_for_oid( + x509.NameOID.JURISDICTION_COUNTRY_NAME + )[0].value + == "also too long" + ) + def test_alternate_rsa_with_sha1_oid(self, backend): cert = _load_cert( os.path.join("x509", "custom", "alternate-rsa-sha1-oid.der"), diff --git a/vectors/cryptography_vectors/x509/custom/bad_country.pem b/vectors/cryptography_vectors/x509/custom/bad_country.pem new file mode 100644 index 000000000000..fd4d60170cb2 --- /dev/null +++ b/vectors/cryptography_vectors/x509/custom/bad_country.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC4DCCAcigAwIBAgICAwkwDQYJKoZIhvcNAQENBQAwMzERMA8GA1UEBhMIdG9v +IGxvbmcxHjAcBgsrBgEEAYI3PAIBAxMNYWxzbyB0b28gbG9uZzAeFw0wMjAxMDEx +MjAxMDBaFw0zMDEyMzEwODMwMDBaMDMxETAPBgNVBAYTCHRvbyBsb25nMR4wHAYL +KwYBBAGCNzwCAQMTDWFsc28gdG9vIGxvbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDBevx+d0dMqlqoMDYVij/797UhaFG6IjDl1qv8wcbP71npI+oT +MLxZO3OAKrYIpuSjMGUjoxFrpao5ZhRRdOE7bEnpt4Bi5EnXLvsQ/UnpH6CLltBR +54Lp9avFtab3mEgnrbjnPaAPIrLv3Nt26rRu2tmO1lZidD/cbA4zal0M26p9wp5T +Y14kyHpbLEIVloBjzetoqXK6u8Hjz/APuagONypNDCySDR6M7jM85HDcLoFFrbBb +8pruHSTxQejMeEmJxYf8b7rNl58/IWPB1ymbNlvHL/4oSOlnrtHkjcxRWzpQ7U3g +T9BThGyhCiI7EMyEHMgP3r7kTzEUwT6IavWDAgMBAAEwDQYJKoZIhvcNAQENBQAD +ggEBALEK2PhqEfqH6/q3M7Guq9E/GuB0qAlqBkZNqIzX8WdRuMKRCnE2I0TDFtbp +jGrhqYcugOB12HeOWT3iSg491KDphsWGFR+La7zZkFKdSf3Cc/ktw6lOgu66CQxI +Bfgp0O4yGexKYkeW1C/gQVoAzczelykfSFthG+BJsX4OGsb6g98y6fsOnHfx7s2t +UkPMYUgom3fhs/J4RhRTKHAOiPBTKg91qGRcGr4TjqCRmiWVw1hFJL0p4vZopnS8 +VX/OrLRnNsj+VxoSIksoEUuxNdUuN4lw14IDZFUEw9CErnyisX2DEozjrg6jca8n +gdJuDRk4TlNl/CpgNraJcu47pME= +-----END CERTIFICATE-----