noloader / cryptopp-pem

PEM parsing of keys and parameters for Crypto++ project
38 stars 32 forks source link

BERDecodeSubjectPublicKeyInfo failed to read an EC public key that explicitly contains EC parameters from a certificate #18

Open roadicing opened 5 months ago

roadicing commented 5 months ago

When I use the following code to read an EC public key from a certificate that explicitly contains the EC parameters, the program fails to work as expected:

#include "cryptlib.h"
#include "x509cert.h"
#include "filters.h"
#include "sha.h"
#include "hex.h"
#include "pem.h"
#include "files.h"

#include <iostream>
#include <string>

extern const std::string pemCertificate;

int main(int argc, char* argv[])
{
    using namespace CryptoPP;

    StringSource ss(pemCertificate, true);
    X509Certificate cert;
    PEM_Load(ss, cert);

    const X509PublicKey& publicKey = cert.GetSubjectPublicKey(); // fail
}

const std::string pemCertificate =
    "-----BEGIN CERTIFICATE-----\r\n"
    "MIICcjCCAhegAwIBAgIUC8OM0gRX41iXU2vLJbGmRjDMzTMwCgYIKoZIzj0EAwIw\r\n"
    "FDESMBAGA1UEAwwJTUFMRk9STUVEMB4XDTI0MDIyNzA1MTU0NVoXDTI1MDIyMTA1\r\n"
    "MTU0NVowFDESMBAGA1UEAwwJTUFMRk9STUVEMIIBSzCCAQMGByqGSM49AgEwgfcC\r\n"
    "AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////\r\n"
    "MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr\r\n"
    "vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE\r\n"
    "axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W\r\n"
    "K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8\r\n"
    "YyVRAgEBA0IABL/gnM66tzV2I+W/+nnMwDP0XT/HHyQlE3CXNm+JTRHKNRMyJRX6\r\n"
    "Y5y5RIPdXfsfja9XJt4gjYuazSo8uAqkbRqjUzBRMB0GA1UdDgQWBBRZfvu1C6ya\r\n"
    "pgoCyrWwx+fyQ95PJzAfBgNVHSMEGDAWgBRZfvu1C6yapgoCyrWwx+fyQ95PJzAP\r\n"
    "BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQCN9TitNg9mjUHSIQ9k\r\n"
    "qieJjJtgkaOLA1f/h8Bs/sKNTQIhAMLhp4UgPphR1XhkLd9WN4oclaJ1M/ceLcKV\r\n"
    "6pcPVk1J\r\n"
    "-----END CERTIFICATE-----\r\n";

At first, I thought that the code for handling public keys that explicitly contains the EC parameters wasn't implemented in cryptopp-pem. However, later I realized that it seems to be implemented, but there are some issues with the implementation.

The issue lies in the line 1231 in function GetSubjectPublicKeyInfoOids, due to the inconsistency in the structure between EC public key that explicitly contain EC parameters and normal EC public key, the GetSubjectPublicKeyInfoOids function incorrectly parse the value of field as NULL after execution.

The reason for incorrectly parsing field to NULL lies in the GetSubjectPublicKeyInfoOids function, which expects to set algorithm and field to their corresponding OIDs through two separate decode operations in line 1279 and line 1282. This is because, in a normal public key as shown below, algorithm and field occur consecutively, allowing them to be properly set as their corresponding OIDs through this method:

123  86:     SEQUENCE {
125  16:       SEQUENCE {
127   7:         OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1) // algorithm
136   5:         OBJECT IDENTIFIER secp256k1 (1 3 132 0 10) // field
       :         }
143  66:       BIT STRING
       :         04 76 1F D6 09 08 4B 41 1D 99 83 1F 38 6E DB 9A
       :         4A FB 7F 94 1A 0F CE 79 16 93 F3 B9 BD BB 8F 3E
       :         FE 4A F2 FE 1C B0 9B 93 5E 37 FE 5E 14 47 84 2E
       :         28 E8 B7 49 F4 52 5E 53 B7 F0 C3 0F D9 CC DF 03
       :         57
       :       }

When an EC public key explicitly contains parameters, its public key structure becomes as follows:

127 259:       SEQUENCE {
131   7:         OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1) // algorithm
140 247:         SEQUENCE {
143   1:           INTEGER 1
146  44:           SEQUENCE {
148   7:             OBJECT IDENTIFIER prime-field (1 2 840 10045 1 1)  // field
157  33:             INTEGER
       :               00 FF FF FF FF 00 00 00 01 00 00 00 00 00 00 00
       :               00 00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF
       :               FF
       :             }
...

It can be seen that, in this case, what directly follows the algorithm is no longer an OID but a SEQUENCE, and field resides within this SEQUENCE. Therefore, when attempting to set algorithm and field as their corresponding OIDs through two consecutive decode operations, the program incorrectly decodes the SEQUENCE as an OID. Clearly, this results in decoding failure, leading to the setting of field as NULL.

Once field is set to NULL, this leads to subsequent functions in BERDecodeSubjectPublicKeyInfo such as IsECPrimeFieldAlgorithm or IsECBinaryFieldAlgorithm, erroneously considering that the public key does not belong to an EC public key, thus terminating and reporting an error.

fix

PR 19

noloader commented 5 months ago

Thanks @roadicing,

Out of curiosity, where did the certificate come from?

If the certificate(s) are publicly available, then I would like to get some test cases setup, like https://github.com/noloader/cryptopp-pem/blob/master/pem_test.cxx#L379 and https://github.com/noloader/cryptopp-pem/blob/master/pem_test.cxx#L444.


The certificate piqued my curiosity:

$ openssl x509 -in test-cert.pem -inform PEM -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            0b:c3:8c:d2:04:57:e3:58:97:53:6b:cb:25:b1:a6:46:30:cc:cd:33
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: CN = MALFORMED
        Validity
            Not Before: Feb 27 05:15:45 2024 GMT
            Not After : Feb 21 05:15:45 2025 GMT
        Subject: CN = MALFORMED
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:bf:e0:9c:ce:ba:b7:35:76:23:e5:bf:fa:79:cc:
                    c0:33:f4:5d:3f:c7:1f:24:25:13:70:97:36:6f:89:
                    4d:11:ca:35:13:32:25:15:fa:63:9c:b9:44:83:dd:
                    5d:fb:1f:8d:af:57:26:de:20:8d:8b:9a:cd:2a:3c:
                    b8:0a:a4:6d:1a
                Field Type: prime-field
                Prime:
                    00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
                    00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
                    ff:ff:ff
                A:   
                    00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
                    00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
                    ff:ff:fc
                B:   
                    5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86:
                    bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2:
                    60:4b
                Generator (uncompressed):
                    04:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4:
                    40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8:
                    98:c2:96:4f:e3:42:e2:fe:1a:7f:9b:8e:e7:eb:4a:
                    7c:0f:9e:16:2b:ce:33:57:6b:31:5e:ce:cb:b6:40:
                    68:37:bf:51:f5
                Order: 
                    00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff:
                    ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc:
                    63:25:51
                Cofactor:  1 (0x1)
                Seed:
                    c4:9d:36:08:86:e7:04:93:6a:66:78:e1:13:9d:26:
                    b7:81:9f:7e:90
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                59:7E:FB:B5:0B:AC:9A:A6:0A:02:CA:B5:B0:C7:E7:F2:43:DE:4F:27
            X509v3 Authority Key Identifier: 
                59:7E:FB:B5:0B:AC:9A:A6:0A:02:CA:B5:B0:C7:E7:F2:43:DE:4F:27
            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:46:02:21:00:8d:f5:38:ad:36:0f:66:8d:41:d2:21:0f:64:
        aa:27:89:8c:9b:60:91:a3:8b:03:57:ff:87:c0:6c:fe:c2:8d:
        4d:02:21:00:c2:e1:a7:85:20:3e:98:51:d5:78:64:2d:df:56:
        37:8a:1c:95:a2:75:33:f7:1e:2d:c2:95:ea:97:0f:56:4d:49

And:

$ openssl x509 -in test-cert.pem -inform PEM -outform DER | dumpasn1 -
Warning: Input is non-seekable, some functionality has been disabled.
  0 626: SEQUENCE {
  4 535:   SEQUENCE {
  8   3:     [0] {
 10   1:       INTEGER 2
       :       }
 13  20:     INTEGER 0B C3 8C D2 04 57 E3 58 97 53 6B CB 25 B1 A6 46 30 CC CD 33
 35  10:     SEQUENCE {
 37   8:       OBJECT IDENTIFIER ecdsaWithSHA256 (1 2 840 10045 4 3 2)
       :       }
 47  20:     SEQUENCE {
 49  18:       SET {
 51  16:         SEQUENCE {
 53   3:           OBJECT IDENTIFIER commonName (2 5 4 3)
 58   9:           UTF8String 'MALFORMED'
       :           }
       :         }
       :       }
 69  30:     SEQUENCE {
 71  13:       UTCTime 27/02/2024 05:15:45 GMT
 86  13:       UTCTime 21/02/2025 05:15:45 GMT
       :       }
101  20:     SEQUENCE {
103  18:       SET {
105  16:         SEQUENCE {
107   3:           OBJECT IDENTIFIER commonName (2 5 4 3)
112   9:           UTF8String 'MALFORMED'
       :           }
       :         }
       :       }
123 331:     SEQUENCE {
127 259:       SEQUENCE {
131   7:         OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
140 247:         SEQUENCE {
143   1:           INTEGER 1
146  44:           SEQUENCE {
148   7:             OBJECT IDENTIFIER prime-field (1 2 840 10045 1 1)
157  33:             INTEGER
       :               00 FF FF FF FF 00 00 00 01 00 00 00 00 00 00 00
       :               00 00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF
       :               FF
       :             }
192  91:           SEQUENCE {
194  32:             OCTET STRING
       :               FF FF FF FF 00 00 00 01 00 00 00 00 00 00 00 00
       :               00 00 00 00 FF FF FF FF FF FF FF FF FF FF FF FC
228  32:             OCTET STRING
       :               5A C6 35 D8 AA 3A 93 E7 B3 EB BD 55 76 98 86 BC
       :               65 1D 06 B0 CC 53 B0 F6 3B CE 3C 3E 27 D2 60 4B
262  21:             BIT STRING
       :               C4 9D 36 08 86 E7 04 93 6A 66 78 E1 13 9D 26 B7
       :               81 9F 7E 90
       :             }
285  65:           OCTET STRING
       :             04 6B 17 D1 F2 E1 2C 42 47 F8 BC E6 E5 63 A4 40
       :             F2 77 03 7D 81 2D EB 33 A0 F4 A1 39 45 D8 98 C2
       :             96 4F E3 42 E2 FE 1A 7F 9B 8E E7 EB 4A 7C 0F 9E
       :             16 2B CE 33 57 6B 31 5E CE CB B6 40 68 37 BF 51
       :             F5
352  33:           INTEGER
       :             00 FF FF FF FF 00 00 00 00 FF FF FF FF FF FF FF
       :             FF BC E6 FA AD A7 17 9E 84 F3 B9 CA C2 FC 63 25
       :             51
387   1:           INTEGER 1
       :           }
       :         }
390  66:       BIT STRING
       :         04 BF E0 9C CE BA B7 35 76 23 E5 BF FA 79 CC C0
       :         33 F4 5D 3F C7 1F 24 25 13 70 97 36 6F 89 4D 11
       :         CA 35 13 32 25 15 FA 63 9C B9 44 83 DD 5D FB 1F
       :         8D AF 57 26 DE 20 8D 8B 9A CD 2A 3C B8 0A A4 6D
       :         1A
       :       }
458  83:     [3] {
460  81:       SEQUENCE {
462  29:         SEQUENCE {
464   3:           OBJECT IDENTIFIER subjectKeyIdentifier (2 5 29 14)
469  22:           OCTET STRING
       :             04 14 59 7E FB B5 0B AC 9A A6 0A 02 CA B5 B0 C7
       :             E7 F2 43 DE 4F 27
       :           }
493  31:         SEQUENCE {
495   3:           OBJECT IDENTIFIER authorityKeyIdentifier (2 5 29 35)
500  24:           OCTET STRING
       :             30 16 80 14 59 7E FB B5 0B AC 9A A6 0A 02 CA B5
       :             B0 C7 E7 F2 43 DE 4F 27
       :           }
526  15:         SEQUENCE {
528   3:           OBJECT IDENTIFIER basicConstraints (2 5 29 19)
533   1:           BOOLEAN TRUE
536   5:           OCTET STRING 30 03 01 01 FF
       :           }
       :         }
       :       }
       :     }
543  10:   SEQUENCE {
545   8:     OBJECT IDENTIFIER ecdsaWithSHA256 (1 2 840 10045 4 3 2)
       :     }
555  73:   BIT STRING
       :     30 46 02 21 00 8D F5 38 AD 36 0F 66 8D 41 D2 21
       :     0F 64 AA 27 89 8C 9B 60 91 A3 8B 03 57 FF 87 C0
       :     6C FE C2 8D 4D 02 21 00 C2 E1 A7 85 20 3E 98 51
       :     D5 78 64 2D DF 56 37 8A 1C 95 A2 75 33 F7 1E 2D
       :     C2 95 EA 97 0F 56 4D 49
       :   }

0 warnings, 0 errors.
roadicing commented 5 months ago

Hi, the certificate I used was generated by using the command like:

openssl ecparam -out ec.key -name prime256v1 -genkey -noout -param_enc explicit
openssl req -new -x509 -key ec.key -out cert.pem -days 360 -subj "/CN=TEST/"
roadicing commented 5 months ago

Hello, is there any progress on the fix plan? @noloader