Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support non-standard X509 country names #3857

Closed
reox opened this issue Aug 10, 2017 · 13 comments · Fixed by #6641
Closed

Support non-standard X509 country names #3857

reox opened this issue Aug 10, 2017 · 13 comments · Fixed by #6641
Labels

Comments

@reox
Copy link

reox commented Aug 10, 2017

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: cryptography-dev@python.org
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: python-cffi@googlegroups.com
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: distutils-sig@python.org
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)
@reaperhulk
Copy link
Member

#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.

@ashemedai
Copy link
Contributor

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

@reox
Copy link
Author

reox commented Mar 30, 2018

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.

@404d
Copy link

404d commented May 8, 2018

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

@ianarsenault
Copy link

ianarsenault commented Mar 6, 2019

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: cryptography-dev@python.org
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

@reaperhulk
Copy link
Member

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

@ianarsenault
Copy link

ianarsenault commented Mar 8, 2019

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")

@recvfrom
Copy link

recvfrom commented Oct 1, 2019

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)

@stevenwinfield
Copy link

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.

@evandrix
Copy link

evandrix commented Jul 16, 2020

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

https://github.com/konstantinstadler/country_converter

cc = country_converter.CountryConverter()
value = cc.convert(names=[value],to="ISO2")```

@sysopfb
Copy link

sysopfb commented Feb 15, 2021

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)

@pprindeville
Copy link

pprindeville commented May 19, 2021

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?

@weddige
Copy link

weddige commented Jun 16, 2021

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.