diff options
author | Paul Kehrer <paul.l.kehrer@gmail.com> | 2017-10-11 09:48:40 +0800 |
---|---|---|
committer | Alex Gaynor <alex.gaynor@gmail.com> | 2017-10-10 21:48:40 -0400 |
commit | d3f73e0de5bf407f375c18b94f3f9535439ece3d (patch) | |
tree | eef49930db8851e77eeb6aec75a08ec1a5d99036 /src/cryptography | |
parent | ed32105be8daa27d39e5ef1f26e3f7bc672a7939 (diff) | |
download | cryptography-d3f73e0de5bf407f375c18b94f3f9535439ece3d.tar.gz cryptography-d3f73e0de5bf407f375c18b94f3f9535439ece3d.tar.bz2 cryptography-d3f73e0de5bf407f375c18b94f3f9535439ece3d.zip |
backwards incompatible change to RFC822Name (#3953)
* backwards incompatible change to RFC822Name
During this release cycle we decided to officially deprecate passing
U-labels to our GeneralName constructors. At first we tried changing
this in a purely backwards compatible way but get_values_for_type made
that untenable. This PR modifies RFC822Name to accept two types:
U-label strings (which raises a deprecation warning) and A-label strings
(the new preferred type). There is also a constructor for RFC822Name
that bypasses validation so we can parse garbage out of certificates
(and round trip it if necessary)
* whoops
Diffstat (limited to 'src/cryptography')
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/decode_asn1.py | 10 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/encode_asn1.py | 7 | ||||
-rw-r--r-- | src/cryptography/x509/general_name.py | 62 |
3 files changed, 32 insertions, 47 deletions
diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py index aefb2422..86f8f8d4 100644 --- a/src/cryptography/hazmat/backends/openssl/decode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -134,8 +134,14 @@ def _decode_general_name(backend, gn): _decode_x509_name(backend, gn.d.directoryName) ) elif gn.type == backend._lib.GEN_EMAIL: - data = _asn1_string_to_bytes(backend, gn.d.rfc822Name) - return x509.RFC822Name(data) + # Convert to bytes and then decode to utf8. We don't use + # asn1_string_to_utf8 here because it doesn't properly convert + # utf8 from ia5strings. + data = _asn1_string_to_bytes(backend, gn.d.rfc822Name).decode("utf8") + # We don't use the constructor for RFC822Name so we can bypass + # validation. This allows us to create RFC822Name objects that have + # unicode chars when a certificate (against the RFC) contains them. + return x509.RFC822Name._init_without_validation(data) elif gn.type == backend._lib.GEN_OTHERNAME: type_id = _obj2txt(backend, gn.d.otherName.type_id) value = _asn1_to_der(backend, gn.d.otherName.value) diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py index 3177cf96..3f0a4c8c 100644 --- a/src/cryptography/hazmat/backends/openssl/encode_asn1.py +++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py @@ -434,9 +434,10 @@ def _encode_general_name(backend, name): elif isinstance(name, x509.RFC822Name): gn = backend._lib.GENERAL_NAME_new() backend.openssl_assert(gn != backend._ffi.NULL) - asn1_str = _encode_asn1_str( - backend, name.bytes_value, len(name.bytes_value) - ) + # ia5strings are supposed to be ITU T.50 but to allow round-tripping + # of broken certs that encode utf8 we'll encode utf8 here too. + data = name.value.encode("utf8") + asn1_str = _encode_asn1_str(backend, data, len(data)) gn.type = backend._lib.GEN_EMAIL gn.d.rfc822Name = asn1_str elif isinstance(name, x509.UniformResourceIdentifier): diff --git a/src/cryptography/x509/general_name.py b/src/cryptography/x509/general_name.py index d4d92c88..6f7fa7a7 100644 --- a/src/cryptography/x509/general_name.py +++ b/src/cryptography/x509/general_name.py @@ -53,77 +53,55 @@ class RFC822Name(object): def __init__(self, value): if isinstance(value, six.text_type): try: - value = value.encode("ascii") + value.encode("ascii") except UnicodeEncodeError: value = self._idna_encode(value) warnings.warn( - "RFC822Name values should be passed as bytes, not strings." - " Support for passing unicode strings will be removed in a" - " future version.", - utils.DeprecatedIn21, - stacklevel=2, - ) - else: - warnings.warn( - "RFC822Name values should be passed as bytes, not strings." - " Support for passing unicode strings will be removed in a" - " future version.", + "RFC822Name values should be passed as an A-label string. " + "This means unicode characters should be encoded via " + "idna. Support for passing unicode strings (aka U-label) " + " will be removed in a future version.", utils.DeprecatedIn21, stacklevel=2, ) - elif not isinstance(value, bytes): - raise TypeError("value must be bytes") + else: + raise TypeError("value must be string") - name, address = parseaddr(value.decode("ascii")) + name, address = parseaddr(value) if name or not address: # parseaddr has found a name (e.g. Name <email>) or the entire # value is an empty string. raise ValueError("Invalid rfc822name value") - self._bytes_value = value + self._value = value - bytes_value = utils.read_only_property("_bytes_value") + value = utils.read_only_property("_value") + + @classmethod + def _init_without_validation(cls, value): + instance = cls.__new__(cls) + instance._value = value + return instance def _idna_encode(self, value): _, address = parseaddr(value) parts = address.split(u"@") - return parts[0].encode("ascii") + b"@" + idna.encode(parts[1]) - - @property - def value(self): - warnings.warn( - "RFC822Name.bytes_value should be used instead of RFC822Name.value" - "; it contains the name as raw bytes, instead of as an idna-" - "decoded unicode string. RFC822Name.value will be removed in a " - "future version.", - utils.DeprecatedIn21, - stacklevel=2 - ) - _, address = parseaddr(self.bytes_value.decode("ascii")) - parts = address.split(u"@") - if len(parts) == 1: - # Single label email name. This is valid for local delivery. - # No IDNA decoding needed since there is no domain component. - return address - else: - # A normal email of the form user@domain.com. Let's attempt to - # encode the domain component and reconstruct the address. - return parts[0] + u"@" + idna.decode(parts[1]) + return parts[0] + "@" + idna.encode(parts[1]).decode("ascii") def __repr__(self): - return "<RFC822Name(bytes_value={0!r})>".format(self.bytes_value) + return "<RFC822Name(value={0!r})>".format(self.value) def __eq__(self, other): if not isinstance(other, RFC822Name): return NotImplemented - return self.bytes_value == other.bytes_value + return self.value == other.value def __ne__(self, other): return not self == other def __hash__(self): - return hash(self.bytes_value) + return hash(self.value) def _idna_encode(value): |