wbond / asn1crypto

Python ASN.1 library with a focus on performance and a pythonic API
MIT License
335 stars 140 forks source link

Error parsing Microsoft Root Agency Certificate #254

Closed AndyCWB closed 1 year ago

AndyCWB commented 1 year ago

Windows Machines have an Intermediate Certificate called Root Agency. Attempting to parse this using asn1cyrpto results in the following error:

    self._bit_size = int(math.ceil(math.log(prime, 2)))
ValueError: math domain error

This can be reproduced with the following code snippet:

from asn1crypto.pem import unarmor

with open("root-agency.pem") as f:
    cert_data = f.read().encode()
    for type_name, _, der_bytes in unarmor(cert_data, multiple=True):
        cert =  Certificate.load(der_bytes)
        print(cert.public_key.bit_size)

Debugging shows that the prime value stored in the certificate is being parsed as a negative number by asn1crypto, but OpenSSL correctly reports it as a 512 bit certificate.

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            06:37:6c:00:aa:00:64:8a:11:cf:b8:d4:aa:5c:35:f4
    Signature Algorithm: md5WithRSAEncryption
        Issuer: CN=Root Agency
        Validity
            Not Before: May 28 22:02:59 1996 GMT
            Not After : Dec 31 23:59:59 2039 GMT
        Subject: CN=Root Agency
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (512 bit)
                Modulus:
                    00:81:55:22:b9:8a:a4:6f:ed:d6:e7:d9:66:0f:55:
                    bc:d7:cd:d5:bc:4e:40:02:21:a2:b1:f7:87:30:85:
                    5e:d2:f2:44:b9:dc:9b:75:b6:fb:46:5f:42:b6:9d:
                    23:36:0b:de:54:0f:cd:bd:1f:99:2a:10:58:11:cb:
                    40:cb:b5:a7:41
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            commonName:
                .GFor Testing Purposes Only Sample Software Publishing Credentials Agency
            2.5.29.1:
                0>.....-...O..a!..dc..0.1.0...U....Root Agency...7l...d......\5.
    Signature Algorithm: md5WithRSAEncryption
         2d:2e:3e:7b:89:42:89:3f:a8:21:17:fa:f0:f5:c3:95:db:62:
         69:5b:c9:dc:c1:b3:fa:f0:c4:6f:6f:64:9a:bd:e7:1b:25:68:
         72:83:67:bd:56:b0:8d:01:bd:2a:f7:cc:4b:bd:87:a5:ba:87:
         20:4c:42:11:41:ad:10:17:3b:8c

Certificate Contents are:

MIIByjCCAXSgAwIBAgIQBjdsAKoAZIoRz7jUqlw19DANBgkqhkiG9w0BAQQFADAW
MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw05NjA1MjgyMjAyNTlaFw0zOTEyMzEy
MzU5NTlaMBYxFDASBgNVBAMTC1Jvb3QgQWdlbmN5MFswDQYJKoZIhvcNAQEBBQAD
SgAwRwJAgVUiuYqkb+3W59lmD1W8183VvE5AAiGisfeHMIVe0vJEudybdbb7Rl9C
tp0jNgveVA/NvR+ZKhBYEctAy7WnQQIDAQABo4GeMIGbMFAGA1UEAwRJE0dGb3Ig
VGVzdGluZyBQdXJwb3NlcyBPbmx5IFNhbXBsZSBTb2Z0d2FyZSBQdWJsaXNoaW5n
IENyZWRlbnRpYWxzIEFnZW5jeTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRj
oRgwFjEUMBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJ
KoZIhvcNAQEEBQADQQAtLj57iUKJP6ghF/rw9cOV22JpW8ncwbP68MRvb2Savecb
JWhyg2e9VrCNAb0q98xLvYeluocgTEIRQa0QFzuM
-----END CERTIFICATE-----
geitda commented 1 year ago

OpenSSL is apparently more forgiving and treats the negative integer as a positive one, essentially adding one null '00' octet on the front. The output from the command even shows it as "00:81:55:22..." even though that is NOT what's encoded in the ASN.1. OpenSSL does not try re-encoding the certificate, either: it will spit out exactly what it took in, so that representation is very misleading. However, openssl x509 -inform der -pubkey -noout < der_cert_bytes will produce a corrected key. In short, however, this certificate is malformed, period. asn1crypto doesn't make any such assumptions, and keeps it as a negative integer. I would have to imagine plenty of other crypto software would choke: see https://github.com/mirleft/ocaml-x509/issues/56#issuecomment-112906844 for this exact same problem surfacing elsewhere, and I agree with the sentiment, "I am very reluctant to add warts to support other people's old bugs."

OpenSSL's own asn1parse command will show you that it's a negative value:

> openssl asn1parse -offset 146 -length 73 < der_cert_bytes
    0:d=0  hl=2 l=  71 cons: SEQUENCE
    2:d=1  hl=2 l=  64 prim: INTEGER           :-7EAADD46755B901229182699F0AA4328322A43B1BFFDDE5D4E0878CF7AA12D0DBB4623648A4904B9A0BD4962DCC9F421ABF03242E066D5EFA7EE34BF344A58BF
   68:d=1  hl=2 l=   3 prim: INTEGER           :010001

The offset is necessary since asn1parse won't try to read inside the bit-string where the public key is stored.

AndyCWB commented 1 year ago

@geitda I meant to respond to this earlier - thank you for the explanation. I also agree with not adding warts to support other people's old bugs, even though the project I'm working on is surfacing an awful lot of other people's old bugs!