Reputation: 583
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
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