rguilbault
rguilbault

Reputation: 583

CertCreateCertificateContext returns CRYPT_E_ASN1_BADTAG / 8009310b

I realize this is a very similar post to others (e.g. this one), but there are details missing from the posts which might be significant for my case.

To start with, here's my simplified program:

#include "stdafx.h"
#include <windows.h>
#include <wincrypt.h>

int _tmain(int argc, _TCHAR* argv[])
{
    // usage: CertExtract certpath

    char keyFile[] = "C:\\Certificates\\public.crt";
    BYTE lp[65536];

    SECURITY_ATTRIBUTES sa;
    HANDLE hKeyFile;
    DWORD bytes;

    PCCERT_CONTEXT  certContext;

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = FALSE;

    hKeyFile = CreateFile(keyFile, GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hKeyFile) {

        if (ReadFile(hKeyFile, lp, GetFileSize(hKeyFile, NULL), &bytes, NULL) && bytes > 0) {

            certContext = CertCreateCertificateContext(X509_ASN_ENCODING, lp, bytes);

            if (certContext) {

                printf("yay!");

                CertFreeCertificateContext(certContext);
            }
            else {
                printf("Could not convert certificate to internal form\n");
            }
        }
        else {
            printf("Failed to read key file: %s\n", keyFile);
        }
    }
    else {
        printf("Failed to open key file: %s\n", keyFile);
    }

    CloseHandle(hKeyFile);


    return 0;
}

In order to create the certificate, I used the following steps with OpenSSL:

C:\Certificates>openssl genrsa -out private.key 1024
Loading 'screen' into random state - done
Generating RSA private key, 1024 bit long modulus
......................................++++++
................++++++
e is 65537 (0x10001)

C:\Certificates>openssl req -new -key private.key -out public.csr
Loading 'screen' into random state - done

C:\Certificates>copy private.key private.key.org
        1 file(s) copied.

C:\Certificates>openssl rsa -in private.key.org -out private.key
writing RSA key

C:\Certificates>openssl x509 -req -days 365 -in public.csr -signkey private.key -ou
t public.crt
Loading 'screen' into random state - done
Signature ok
subject=/CN=My Signing Cert
Getting Private key

with the following conf file:

RANDFILE        = .rnd

[ req ]
distinguished_name     = req_distinguished_name
prompt                 = no

[ req_distinguished_name ]
commonName             = My Signing Cert

The certificate file looks like:

-----BEGIN CERTIFICATE-----
MIIBqzCCARQCCQDUJyWk0OxlRTANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDDA9N
eSBTaWduaW5nIENlcnQwHhcNMTYwMTA1MjIzODU5WhcNMTcwMTA0MjIzODU5WjAa
MRgwFgYDVQQDDA9NeSBTaWduaW5nIENlcnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
MIGJAoGBAJobIhfSSMLEPeG9SOBelWHo4hjKXe8dT6cllPr6QXdXe2VNLh9fxVlx
spVGFQwjlF3OHYnmSQnY3m2b5wlFNYVuHvy8rUsZWOF4drSbiqWKh0TuJ+4MBeGq
EormTJ+kiGqNm5IVRrTu9OV8f0XQTGV1pxHircQxsGhxY5w0QTjjAgMBAAEwDQYJ
KoZIhvcNAQEFBQADgYEAedqjKfMyIFC8nUbJ6t/Y8D+fJFwCcdwojUFizr78FEwA
IZSas1b1bXSkA+QEooW7pYdBAfzNuD3WfZAIZpqFlr4rPNIqHzYa0OIdDPwzQQLa
3zPKqjj6QeTWEi5/ArzO+sTVv4m3Og3GQjMChb8H/GxsWdbComPVP82DTUet+ZU=
-----END CERTIFICATE-----

Converting the PEM-encoding to hex allows me to identify the parts of the certificate:

30 SEQUENCE             //Certificate
(82 01 AB) 

   30 SEQUENCE          //tbsCertificate
   (82   01   14) 
   
      02 INTEGER        //serialNumber  
      (09)  
         00   D4   27   25   A4   D0   EC   65   45 
   
      30 SEQUENCE       //signature
      (0D) 
      
         06 OBJECT IDENTIFIER
         (09)  
         2A   86   48   86   F7   0D   01   01   05
      
         05 NULL 
         (00)
      
      30 SEQUENCE       //issuer
      (1A) 
      
         31 SET
         (18) 

            30 SEQUENCE
            (16) 

               06 OBJECT IDENTIFIER
               (03)  
               55   04   03

               0C UTF8String
               (0F)  
                  4D  79   20   53   69   67   6E   69   6E   67   20   43   65   72   74

      30 SEQUENCE       //validity
      (1E) 
         17 UTCTime
         (0D)  
            31   36   30   31   30   35   32   32   33   38   35   39   5A

         17 UTCTime
         (0D)  
            31   37   30   31   30   34   32   32   33   38   35   39   5A
            
      30 SEQUENCE       //subjectName
      (1A) 

         31 SET
         (18) 
         
            30 SEQUENCE
            (16) 
               06 OBJECT IDENTIFIER
               (03)  
                  55   04   03
               
               0C UTF8String
               (0F)  
                  4D   79   20   53   69  67   6E   69   6E   67   20   43   65   72   74
      
      30 SEQUENCE       //subjectPublicKeyInfo
      (81   9F) 
         30 SEQUENCE    //algorithmId
         (0D) 
            06 OBJECT IDENTIFIER    //algorithm
            (09)  
               2A   86   48   86   F7   0D   01   01   01
            
            05 NULL
            (00)
            
         03 BIT STRING   //subjectPublicKey
         (81   8D)  
            [00] //padding bits
            30 SEQUENCE  //RSAPublicKey
            (81   89)   
               
               02 INTEGER  //modulus
               (81   81)   
                  00   9A   1B   22   17   D2   48   C2   C4   3D  E1   BD   48   E0   5E   95   61   E8   E2   18   CA   5D   EF   1D   4F   A7  25   94   FA   FA   41   77   57   7B   65   4D   2E   1F   5F   C5   59   71  B2   95   46   15   0C   23   94   5D   CE   1D   89   E6   49   09   D8   DE  6D   9B   E7   09   45   35   85   6E   1E   FC   BC   AD   4B   19   58   E1  78   76   B4   9B   8A   A5   8A   87   44   EE   27   EE   0C   05   E1   AA  12   8A   E6   4C   9F   A4   88   6A   8D   9B   92   15   46   B4   EE   F4  E5   7C   7F   45   D0   4C   65   75   A7   11   E2   AD   C4   31   B0   68  71   63   9C   34   41   38   E3   02   03   01   00   01
 
   30 SEQUENCE             //signatureAlgorithm
   (0D) 
      06 OBJECT IDENTIFIER
      (09) 
         2A   86   48   86   F7   0D   01   01   05

      05 NULL
      (00)
   
   03 BIT STRING           //signatureValue
   (81   81)  
      [00] //padding bits
      79  DA   A3   29   F3   32   20   50   BC   9D   46   C9   EA   DF   D8   F0   3F   9F   24   5C   02   71   DC   28   8D   41   62   CE   BE   FC   14   4C   00   21   94   9A   B3   56   F5   6D   74   A4   03   E4   04   A2   85   BB   A5   87   41   01   FC   CD   B8   3D   D6   7D   90   08   66   9A   85   96   BE   2B   3C   D2   2A   1F   36   1A   D0   E2   1D   0C   FC   33   41   02   DA   DF   33   CA   AA   38   FA   41   E4   D6   12   2E   7F   02   BC   CE   FA   C4   D5   BF   89   B7   3A   0D   C6   42   33   02   85   BF   07   FC   6C   6C   59   D6   C2   A2   63   D5   3F   CD   83   4D   47   AD   F9   95

which appears to conform to the X.509 specs (as I would expect it to):

Certificate ::= {
   tbsCertificate TBSCertificate,
   signatureAlgorithm AlgorithmIdentifier,
   signatureValue BIT STRING
}
   
TBSCertificate ::= SEQUENCE {   
   version [0] Version DEFAULT v1,  <-- what does this mean?
   serialNumber INTEGER,
   signature AlgorithmIdentifier,
   issuer Name,
   validity Validity,
   subjectName Name,
   subjectPublicKeyInfo SubjectPublicKeyInfo
   ...
}

with the lone exception of the version part, which isn't clear to me whether it is optional or not (though it never seems to be added with certificates I create with OpenSSL).

I can open the certificate to import into a certificate store (and can successfully import to a store), so I don't think anything is specifically wrong with the file/encoding.

When I reach the call to CertCreateCertificateContext, my lp buffer looks like:

-----BEGIN CERTIFICATE-----\nMIIBqzCCARQCCQDUJyWk0OxlRTANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDDA9N\neSBTaWduaW5nIENlcnQwHhcNMTYwMTA1MjIzODU5WhcNMTcwMTA0MjIzODU5WjAa\nMRgwFgYDVQQDDA9NeSBTaWduaW5nIENlcnQwgZ8wDQ...

and bytes = 639 -- which is the file size.

I've tried adding logic to strip out the certificate comments, but examples of importing a certificate in this manner don't indicate that should be necessary.

I've tried setting the dwCertEncodingType to X509_ASN_ENCODING | PKCS_7_ASN_ENCODING and PKCS_7_ASN_ENCODING out of desperation (though I don't believe I am using PKCS#7 encoding here...a little fuzzy on that).

Does anyone have any suggestions on what I might be doing incorrectly here? I appreciate it.

Upvotes: 4

Views: 6639

Answers (1)

rguilbault
rguilbault

Reputation: 583

I figured out my issue. CertCreateCertificateContext is expecting the binary ASN.1 data, not the PEM-encoded certificate I created with openssl. I figured this out by using a Microsoft certificate generation tool and testing that certificate out:

C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin>makecert.exe -n "CN=Test Signing Cert" -b 0
1/06/2016 -e 01/06/2017 -len 1024 -r C:\Certificates\public_v2.crt
Succeeded

looking at the file in a hex editor, it looked precisely like the ASN.1 binary data. next, I used the Copy to File feature from the certificate viewer that launches when you double-click a certificate to copy my original public.crt file to a DER encoded binary X.509 (.CER) file and verified that my program began to work (that is, the CertCreateCertificateContext was now happy).

so, in case someone else is bumping up against the same issue I was having, here is a complete solution to importing a PEM-encoded certificate from a file into memory for use with the Crypto API:

#include "stdafx.h"
#include <windows.h>
#include <wincrypt.h>

#define LF 0x0A

int _tmain(int argc, _TCHAR* argv[])
{
    char keyFile[] = "C:\\Certificates\\public.crt";
    BYTE lp[65536];

    SECURITY_ATTRIBUTES sa;
    HANDLE hKeyFile;
    DWORD bytes;

    PCCERT_CONTEXT  certContext;
    BYTE *p;
    DWORD flags;

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = FALSE;

    hKeyFile = CreateFile(keyFile, GENERIC_READ, FILE_SHARE_READ, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hKeyFile) {

        if (ReadFile(hKeyFile, lp, GetFileSize(hKeyFile, NULL), &bytes, NULL) && bytes > 0) {

            p = lp + bytes;
            if (CryptStringToBinary((char *)lp, p - lp, CRYPT_STRING_BASE64_ANY, p, &bytes, NULL, &flags) && bytes > 0) {

                certContext = CertCreateCertificateContext(X509_ASN_ENCODING, p, bytes);

                if (certContext) {

                    printf("yay!");

                    CertFreeCertificateContext(certContext);
                }
                else {
                    printf("Could not convert certificate to internal form\n");
                }
            }
            else {
                printf("Failed to convert from PEM");
            }
        }
        else {
            printf("Failed to read key file: %s\n", keyFile);
        }
    }
    else {
        printf("Failed to open key file: %s\n", keyFile);
    }

    CloseHandle(hKeyFile);


    return 0;
}

Note:

because I'm lazy, I decode the PEM encoding to binary in the same BYTE array I used to load the file into -- for this simple test, it was expedient, but if you're looking to implement this sort of thing for real, I wouldn't recommend it

Upvotes: 5

Related Questions