Cryptography: Support non-standard X509 country names

Created on 10 Aug 2017  路  13Comments  路  Source: pyca/cryptography

I used the cryptography library to parse android APK certificates in androguard.
But there are some people, who create their certificates in a non standard way. Usually they do not put the two char country code, but instead use some string.
Here is an example of such an certificate: ASN1. JavaScript Decoder with loaded Certificate.
As you can see, someone used siavash as the country name.
From my point of view, such certificates are not common but also not very rare. I found out, that creating a new signing key in Android Studio does not reject the certificate if you enter something stupid in the country code field.

When you load the certificate (after removing the pkcs7 message around it), you will get an error message:

import binascii
from pyasn1.codec.der.decoder import decode
from pyasn1.codec.der.encoder import encode
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes

certhex  = b"3082039506092a864886f70d010702a082038630820382020101310b300906052b0e03021a0500300b06092a864886f70d010701a08202d0308202cc30820289a003020102020466eda548300b06072a8648ce380403050030363110300e06035504061307736961766173683110300e060355040a1307736961766173683110300e06035504031307736961766173683020170d3136303132353037323533305a180f32303534303532353037323533305a30363110300e06035504061307736961766173683110300e060355040a1307736961766173683110300e0603550403130773696176617368308201b83082012c06072a8648ce3804013082011f02818100fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b76b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c70215009760508f15230bccb292b982a2eb840bf0581cf502818100f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d0782675159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e13c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243bcca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a0381850002818100e9b46a500aabcbb7b42e93477db9f8ac7c819eadd392f4fa2dd7c10973c399d00d45796052bfb376567a518cfe1432a0f845e4be75aa76462335bf7f84b6c634bb6c884b6ae2e7b6f1cc78c3be6f98b65980832c40d9da3d2b047cba98b24c877b2ace7908b0af56fd62367df355e011a0482c9b8751c373a8d131076cd01421a321301f301d0603551d0e04160414eeac32e5f08781bb6f2f392814951b328868ae4c300b06072a8648ce3804030500033000302d0215009209eb12e966aafae281620e7da9743774d7846302144bf36a9634967ca47639bbc10712571b036e13d531818e30818b020101303e30363110300e06035504061307736961766173683110300e060355040a1307736961766173683110300e0603550403130773696176617368020466eda548300906052b0e03021a0500300b06072a8648ce3804010500042e302c02145de23a86f0c2e45dcad93ef8cab675ec56a6ff4b021404c1a9f7f791631ae00d61fe8ae23f502c33fe84"

pkcs7message = binascii.unhexlify(certhex)

message, _ = decode(pkcs7message)
cert = encode(message[1][3])
l = cert[1]
if not isinstance(l, int):
    l = ord(l)
cert = cert[2 + (l & 0x7F) if l & 0x80 > 1 else 2:]

certificate = x509.load_der_x509_certificate(cert, default_backend())

print(certificate.issuer)
print(certificate.fingerprint(hashes.SHA1()))
Traceback (most recent call last):
  File "Desktop\test.py", line 30, in <module>
    print(certificate.issuer)
  File "C:\Program Files\Anaconda3\lib\site-packages\cryptography\hazmat\backends\openssl\x509.py", line 101, in issuer
    return _decode_x509_name(self._backend, issuer)
  File "C:\Program Files\Anaconda3\lib\site-packages\cryptography\hazmat\backends\openssl\decode_asn1.py", line 65, in _decode_x509_name
    attribute = _decode_x509_name_entry(backend, entry)
  File "C:\Program Files\Anaconda3\lib\site-packages\cryptography\hazmat\backends\openssl\decode_asn1.py", line 56, in _decode_x509_name_entry
    return x509.NameAttribute(x509.ObjectIdentifier(oid), value)
  File "C:\Program Files\Anaconda3\lib\site-packages\cryptography\x509\name.py", line 27, in __init__
    "Country name must be a 2 character country code"
ValueError: Country name must be a 2 character country code

I know this is not really a bug, because the RFC says 2 char country code... But it would be nice to work with those "broken" certificates as well.

Versions (cryptography was installed via pip):

c:\>pip show cryptography
Name: cryptography
Version: 2.0.3
Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers.
Home-page: https://github.com/pyca/cryptography
Author: The cryptography developers
Author-email: [email protected]
License: BSD or Apache License, Version 2.0
Location: c:\program files\anaconda3\lib\site-packages
Requires: six, cffi, asn1crypto, idna

c:\>pip show cffi
Name: cffi
Version: 1.10.0
Summary: Foreign Function Interface for Python calling C code.
Home-page: http://cffi.readthedocs.org
Author: Armin Rigo, Maciej Fijalkowski
Author-email: [email protected]
License: MIT
Location: c:\program files\anaconda3\lib\site-packages
Requires: pycparser

c:\>pip show setuptools
Name: setuptools
Version: 27.2.0
Summary: Easily download, build, install, upgrade, and uninstall Python packages
Home-page: https://github.com/pypa/setuptools
Author: Python Packaging Authority
Author-email: [email protected]
License: UNKNOWN
Location: c:\program files\anaconda3\lib\site-packages\setuptools-27.2.0-py3.6.egg
Requires:
c:\>python --version
Python 3.6.0 :: Anaconda 4.3.0 (64-bit)

c:\>pip --version
pip 9.0.1 from C:\Program Files\Anaconda3\lib\site-packages (python 3.6)
x509

Most helpful comment

I'm another user who is encountering malformed certificates in the wild, and it would be great to be able to finally work with them using cryptography out-of-the-box.

All 13 comments

3857 also has discussion of what to do with another RFC violation that we might still want to allow in parsing. There's a proposal there for allowing violating certificates during parsing but rejecting them during generation.

I just ran into this. Certificate issued by Salesforce in 2017 for development purposes. Self-signed, but our customers use and we need to be able to work with it.

File "/usr/local/lib/python3.6/dist-packages/cryptography/x509/name.py", line 50, in __init__
  "Country name must be a 2 character country code"

Country: USA

Today I found a similar case:

Traceback (most recent call last):
[...]
  File "/usr/lib/python3/dist-packages/cryptography/hazmat/backends/openssl/x509.py", line 102, in issuer
    return _decode_x509_name(self._backend, issuer)
  File "/usr/lib/python3/dist-packages/cryptography/hazmat/backends/openssl/decode_asn1.py", line 66, in _decode_x509_name
    attribute = _decode_x509_name_entry(backend, entry)
  File "/usr/lib/python3/dist-packages/cryptography/hazmat/backends/openssl/decode_asn1.py", line 57, in _decode_x509_name_entry
    return x509.NameAttribute(x509.ObjectIdentifier(oid), value, type)
  File "/usr/lib/python3/dist-packages/cryptography/x509/name.py", line 57, in __init__
    raise ValueError("Value cannot be an empty string")
ValueError: Value cannot be an empty string

It looks like there are some certificates which uses an empty string... Here is an example:

import binascii
from cryptography import x509
from cryptography.hazmat.backends import default_backend
cert_der = b'3082032a30820212a00302010202045120ef99300d06092a864886f70d01010505003057310b30090603550406130249543109300706035504081300310f300d060355040713064d696c616e6f31093007060355040a130031093007060355040b1300311630140603550403130d47617261676520476c6f726961301e170d3133303231373134353632355a170d3430303730353134353632355a3057310b30090603550406130249543109300706035504081300310f300d060355040713064d696c616e6f31093007060355040a130031093007060355040b1300311630140603550403130d47617261676520476c6f72696130820122300d06092a864886f70d01010105000382010f003082010a0282010100838680f282fc9f8d5ff9d24c7786e5faf9f336f3f28fccf3a884252e15142385af1d13a0e46b8458a15c6aba62c06523739c3140b54529fef86f4d23248cba885837f5d1e5e944ad5893ee1de42a5bfc229a7cc8c2895dc4bc941c83897d4a694200ceb536c0b159ed674e44e7a42306ee06ac7b64c3177430907e79554501b4c19c0cf8489a70d122c2eede770f64719db35e7cbb8b545ada395af680f597c1100994474330f2f42d2b3ca7bb682dff23a47e6aa45a530c79cad339e0ffba6af6aadda6c288c7f02921a8e8d2732e73a72eaafacd10ee2e369090ba3730a9c46bf212cf1b44abba1262f50d0c472aeaf3fd7d8f4a3cd448867f11075b52708b0203010001300d06092a864886f70d01010505000382010100542112f5734ea59e2aad2b7b499913d3dc310161c0e2d0ab9fc86c8cf0b8a654a530d104d06dbd2f210a6f717ae0d89b5c738295f139a24443f29f77d224daef67366a9dc1beebfedae568a18b3e2715112b659002b113de26c5661891e9195ea67a594bbea5ae97d103b4f746c672e3424026e66f707a6666a1c8c32dbc83369aa57aedcf4f1070d314c43603463016fc441458f0d9cf85afa6191294c4b1d119077bcddd88d44ddf49de8bb821c12561a5d3e60c856502c4bc31ddd94ad40accce294fa712c58b1c17f16b11587e839addc84071ea100cf426fdee1b05851117b70b1e42f26097d7ae25ece48436f27a0daf879bdcd111ccf79375cd3f60ea'

cert_der = binascii.unhexlify(cert_der)
x509.load_der_x509_certificate(cert_der, default_backend())

In this particular case, there are empty strings in stateOrProvinceName, organizationName and organizationalUnitName for both issuer and subject.

This certificate popped up in logs today. It uses "Taiwan" as jurisdiction country name, and thus triggers the same exception as this field is validated in the same way as country names:

Subject:
    postalCode                = 104
    streetAddress             = 36, Nanking E. Rd., Sec. 3,Taipei City,Taipei 10489 ,Taiwan
    serialNumber              = 86517384
    jurisdictionCountryName   = Taiwan
    jurisdictionStateOrProvinceName = Taiwan
    jurisdictionLocalityName  = Taipei

I am using the library as a way to send alerts for expiring certificates. I have a certificate on file that is also throwing ValueError: Country name must be a 2 character country code. The issue is this one particular certificate doesn't have a value for Country, along with a few other attributes.

Traceback (most recent call last):
  File "expring-cert.py", line 41, in <module>
    for attribute in cert.subject:
  File "/Users/ian/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/x509.py", line 111, in subject
    return _decode_x509_name(self._backend, subject)
  File "/Users/ian/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/decode_asn1.py", line 69, in _decode_x509_name
    attribute = _decode_x509_name_entry(backend, entry)
  File "/Users/ian/anaconda3/lib/python3.6/site-packages/cryptography/hazmat/backends/openssl/decode_asn1.py", line 60, in _decode_x509_name_entry
    return x509.NameAttribute(x509.ObjectIdentifier(oid), value, type)
  File "/Users/ian/anaconda3/lib/python3.6/site-packages/cryptography/x509/name.py", line 58, in __init__
    "Country name must be a 2 character country code"
ValueError: Country name must be a 2 character country code

Taking a look at the certificate which is breaking shows empty attributes for

  • Organization
  • Organization Unit
  • Locality
  • State
  • Country

cryptography version:

Name: cryptography
Version: 2.4.2
Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers.
Home-page: https://github.com/pyca/cryptography
Author: The cryptography developers
Author-email: [email protected]
License: BSD or Apache License, Version 2.0
Location: /Users/ian/anaconda3/lib/python3.6/site-packages
Requires: idna, six, asn1crypto, cffi
Required-by: pyOpenSSL

Figuring out how we want to handle this is on our roadmap for the next release.

As a temporary fix to not being able to decode x509 certs with empty Country value, I removed/commented out this section in

  • .../lib/python3.6/site-packages/cryptography/x509/name.py
if (
    oid == NameOID.COUNTRY_NAME or
    oid == NameOID.JURISDICTION_COUNTRY_NAME
):
       if len(value.encode("utf8")) != 2:
            raise ValueError(
                "Country name must be a 2 character country code"
            )

if len(value) == 0:
    raise ValueError("Value cannot be an empty string")

I'm using the cryptography library as part of a Microsoft Authenticode signature parsing project, and encountered the Value cannot be an empty string ValueError when parsing a certificate used to sign an executable (and Windows has no problem with the certificate or the Authenticode signature).

More info on the executable can be found at https://www.virustotal.com/gui/file/c32ade7ee7dac2bfb68c726739211f683fedb98624ba2c9aa7e9122ded1ccf56/details, and here's a program to reproduce:

import binascii
from cryptography.hazmat.backends import default_backend
from cryptography.x509 import load_der_x509_certificate

cert_hex = '308203bb30820324a00302010202102c0e339024b942e5181f483f2d772131300d06092a864886f70d01010505003055310b3009060355040613025a4131253023060355040a131c54686177746520436f6e73756c74696e67202850747929204c74642e311f301d0603550403131654686177746520436f6465205369676e696e67204341301e170d3038303230353038313030385a170d3039303230343038313030385a3067310b3009060355040613024a50310d300b060355040813044f697461310e300c06035504071305426570707531163014060355040a130d4379626572466f7274204c4c4331093007060355040b1300311630140603550403130d4379626572466f7274204c4c4330820122300d06092a864886f70d01010105000382010f003082010a0282010100c617df265375d32d895aa61456b53d31bd5dbb65aa4b3561bf959466a9a14d4f2545e70640bdccca429fbc3ee8a8d7c35cae29a135747d1f8fbf6df94f96278d64ad5d6aae0794ffc40679d5171def855bf4ebc20e767b531bb8467ba9eeff5a15bab436d19bd28903d410665de313a54d395aebd63e941d594e290d689a80f938dfddb0c0e733d5fd79148ec50e62494a5d389adffe5ef720623dea14581296f209876ca40a5b493148fe65d2a12284961c92a6d68873005eda7f46d199f8ddf98193b42c9f00c500fb5d72cfc6eb5dc192e7f6b9a57380ba1fe3b59639b0164a8921e539a1a39df1ff42285beea65c2ae60da8223a9750ecf9a5b69e9f8e2b0203010001a381f53081f2301f0603551d250418301606082b06010505070303060a2b060104018237020116301106096086480186f8420101040403020410301d0603551d0404163014300e300c060a2b06010401823702011603020780301b0603551d110414301282107777772e6379626572666f72742e6a70303e0603551d1f043730353033a031a02f862d687474703a2f2f63726c2e7468617774652e636f6d2f546861777465436f64655369676e696e6743412e63726c303206082b0601050507010104263024302206082b060105050730018616687474703a2f2f6f6373702e7468617774652e636f6d300c0603551d130101ff04023000300d06092a864886f70d0101050500038181007201bdd2be014f0daf518338092577dc8ef4b8abd66d64e5b29ee7a80f7734cf3502ad872f1844cad6dbe521f4f8fe1504fda8d51cf085ddaf78d3fed0b466d9306acd2241d5a4931742310426b52c7cd5462c2068f5ab7e6c3bed07a2e04190b326382ac9bddb710a181b30b5df7dd32b684a92acf12fa43d0a8680e3e74b8b'

cert_bytes = binascii.unhexlify(cert_hex)
cert = load_der_x509_certificate(cert_bytes, default_backend())
subject = cert.subject.public_bytes(default_backend)

I'm another user who is encountering malformed certificates in the wild, and it would be great to be able to finally work with them using cryptography out-of-the-box.

bump http://pedump.me/16ac01292a94b86ecb1453330a2c893d/#signature

https://github.com/konstantinstadler/country_converter
import country_converter cc = country_converter.CountryConverter() value = cc.convert(names=[value],to="ISO2")

Can work around it for now by using OpenSSL.crypto which will parse the ASN.1 data as straight ASN.1 and not attempt to conform to specifications, example:

        try:
            x509obj = x509.load_pem_x509_certificate(cert, default_backend())
            cert_str = str(x509obj.subject)
            print('IP: ' + ip + ' - ' + cert_str)
        except ValueError:
            x509obj = x509.load_pem_x509_certificate(cert, default_backend())
                        temp = crypto.X509.from_cryptography(x509obj)
                        cert_str = str(temp.get_subject())
            print('IP: ' + ip + ' - ' + cert_str)

Given that certificates are a central component of security in many architectures, and invalid data is often the starting point for security exploits... __why__? This just seems like an invitation to disaster. Why have standards (like ISO-3166) if we're not going to follow them? More broadly, why encourage bad behavior?

More broadly, why encourage bad behavior?

Because reality is messy and in too many cases developers would prefer to skip certificate validation over issuing a new certificate (especially if the certificate was issued by another department).

I for example have to deal regularly with malformed certificates when auditing TLS configurations with sslyze. My solution so far is to monkeypatch cryptography. I want to assume, that I know what I am doing, but tbh that is something that want to disencourage.

Especially in security sensitive contexts, I believe reasonable interoperability is often more beneficial than strict adherence to standards. This way we disencourage shadey workarounds.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

chitoge picture chitoge  路  26Comments

doowon picture doowon  路  23Comments

abhishek-ram picture abhishek-ram  路  37Comments

reaperhulk picture reaperhulk  路  42Comments

mhils picture mhils  路  28Comments