CertCreateCertificateContext returns CRYPT_E_ASN1_BADTAG / 8009310b
CertCreateCertificateContext returns CRYPT_E_ASN1_BADTAG / 8009310b
我意识到这与其他 post 非常相似(例如 this one),但是 post 中缺少可能对我的情况很重要的细节。
首先,这是我的简化程序:
#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;
}
为了创建证书,我对 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
使用以下配置文件:
RANDFILE = .rnd
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
commonName = My Signing Cert
证书文件如下所示:
-----BEGIN CERTIFICATE-----
MIIBqzCCARQCCQDUJyWk0OxlRTANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDDA9N
eSBTaWduaW5nIENlcnQwHhcNMTYwMTA1MjIzODU5WhcNMTcwMTA0MjIzODU5WjAa
MRgwFgYDVQQDDA9NeSBTaWduaW5nIENlcnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
MIGJAoGBAJobIhfSSMLEPeG9SOBelWHo4hjKXe8dT6cllPr6QXdXe2VNLh9fxVlx
spVGFQwjlF3OHYnmSQnY3m2b5wlFNYVuHvy8rUsZWOF4drSbiqWKh0TuJ+4MBeGq
EormTJ+kiGqNm5IVRrTu9OV8f0XQTGV1pxHircQxsGhxY5w0QTjjAgMBAAEwDQYJ
KoZIhvcNAQEFBQADgYEAedqjKfMyIFC8nUbJ6t/Y8D+fJFwCcdwojUFizr78FEwA
IZSas1b1bXSkA+QEooW7pYdBAfzNuD3WfZAIZpqFlr4rPNIqHzYa0OIdDPwzQQLa
3zPKqjj6QeTWEi5/ArzO+sTVv4m3Og3GQjMChb8H/GxsWdbComPVP82DTUet+ZU=
-----END CERTIFICATE-----
将 PEM 编码转换为十六进制允许我识别证书的各个部分:
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
这似乎符合 X.509 specs(如我所料):
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
...
}
除了版本部分的唯一例外,我不清楚它是否是可选的(尽管它似乎从未与我使用 OpenSSL 创建的证书一起添加)。
我可以打开证书导入到证书存储区(并且可以成功导入到存储区),所以我不认为 file/encoding.
有什么特别的问题
当我到达对 CertCreateCertificateContext 的调用时,我的 lp 缓冲区看起来像:
-----BEGIN CERTIFICATE-----\nMIIBqzCCARQCCQDUJyWk0OxlRTANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDDA9N\neSBTaWduaW5nIENlcnQwHhcNMTYwMTA1MjIzODU5WhcNMTcwMTA0MjIzODU5WjAa\nMRgwFgYDVQQDDA9NeSBTaWduaW5nIENlcnQwgZ8wDQ...
and bytes = 639 -- 这是文件大小。
我已经尝试添加逻辑来删除证书注释,但是 examples 以这种方式导入证书并不表示这是必要的。
我试过将 dwCertEncodingType 设置为 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING 和 PKCS_7_ASN_ENCODING 出于绝望(虽然我不相信我在这里使用 PKCS#7 编码......对此有点模糊)。
有没有人对我在这里可能做错的事情有任何建议?我很感激。
我解决了我的问题。 CertCreateCertificateContext 需要二进制 ASN.1 数据,而不是我用 openssl 创建的 PEM-encoded 证书。我通过使用 Microsoft 证书生成工具并测试该证书来解决这个问题:
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
在十六进制编辑器中查看该文件,它看起来非常像 ASN.1 二进制数据。接下来,我使用证书查看器中的复制到文件功能,该查看器在您 double-click 证书时启动,将我的原始 public.crt 文件复制到 DER 编码的二进制 X.509 (.CER) 文件并验证我的程序开始工作(也就是说,CertCreateCertificateContext 现在很开心)。
所以,如果其他人遇到我遇到的同样问题,这里有一个完整的解决方案,可以将 PEM-encoded 证书从文件导入内存以用于加密 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;
}
注:
因为我很懒,我将 PEM 编码解码为二进制文件,放在我用来加载文件的同一个 BYTE 数组中——对于这个简单的测试,这是权宜之计,但如果你想实现这个有点真实,我不会推荐它
我意识到这与其他 post 非常相似(例如 this one),但是 post 中缺少可能对我的情况很重要的细节。
首先,这是我的简化程序:
#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;
}
为了创建证书,我对 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
使用以下配置文件:
RANDFILE = .rnd
[ req ]
distinguished_name = req_distinguished_name
prompt = no
[ req_distinguished_name ]
commonName = My Signing Cert
证书文件如下所示:
-----BEGIN CERTIFICATE-----
MIIBqzCCARQCCQDUJyWk0OxlRTANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDDA9N
eSBTaWduaW5nIENlcnQwHhcNMTYwMTA1MjIzODU5WhcNMTcwMTA0MjIzODU5WjAa
MRgwFgYDVQQDDA9NeSBTaWduaW5nIENlcnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0A
MIGJAoGBAJobIhfSSMLEPeG9SOBelWHo4hjKXe8dT6cllPr6QXdXe2VNLh9fxVlx
spVGFQwjlF3OHYnmSQnY3m2b5wlFNYVuHvy8rUsZWOF4drSbiqWKh0TuJ+4MBeGq
EormTJ+kiGqNm5IVRrTu9OV8f0XQTGV1pxHircQxsGhxY5w0QTjjAgMBAAEwDQYJ
KoZIhvcNAQEFBQADgYEAedqjKfMyIFC8nUbJ6t/Y8D+fJFwCcdwojUFizr78FEwA
IZSas1b1bXSkA+QEooW7pYdBAfzNuD3WfZAIZpqFlr4rPNIqHzYa0OIdDPwzQQLa
3zPKqjj6QeTWEi5/ArzO+sTVv4m3Og3GQjMChb8H/GxsWdbComPVP82DTUet+ZU=
-----END CERTIFICATE-----
将 PEM 编码转换为十六进制允许我识别证书的各个部分:
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
这似乎符合 X.509 specs(如我所料):
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
...
}
除了版本部分的唯一例外,我不清楚它是否是可选的(尽管它似乎从未与我使用 OpenSSL 创建的证书一起添加)。
我可以打开证书导入到证书存储区(并且可以成功导入到存储区),所以我不认为 file/encoding.
有什么特别的问题当我到达对 CertCreateCertificateContext 的调用时,我的 lp 缓冲区看起来像:
-----BEGIN CERTIFICATE-----\nMIIBqzCCARQCCQDUJyWk0OxlRTANBgkqhkiG9w0BAQUFADAaMRgwFgYDVQQDDA9N\neSBTaWduaW5nIENlcnQwHhcNMTYwMTA1MjIzODU5WhcNMTcwMTA0MjIzODU5WjAa\nMRgwFgYDVQQDDA9NeSBTaWduaW5nIENlcnQwgZ8wDQ...
and bytes = 639 -- 这是文件大小。
我已经尝试添加逻辑来删除证书注释,但是 examples 以这种方式导入证书并不表示这是必要的。
我试过将 dwCertEncodingType 设置为 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING 和 PKCS_7_ASN_ENCODING 出于绝望(虽然我不相信我在这里使用 PKCS#7 编码......对此有点模糊)。
有没有人对我在这里可能做错的事情有任何建议?我很感激。
我解决了我的问题。 CertCreateCertificateContext 需要二进制 ASN.1 数据,而不是我用 openssl 创建的 PEM-encoded 证书。我通过使用 Microsoft 证书生成工具并测试该证书来解决这个问题:
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
在十六进制编辑器中查看该文件,它看起来非常像 ASN.1 二进制数据。接下来,我使用证书查看器中的复制到文件功能,该查看器在您 double-click 证书时启动,将我的原始 public.crt 文件复制到 DER 编码的二进制 X.509 (.CER) 文件并验证我的程序开始工作(也就是说,CertCreateCertificateContext 现在很开心)。
所以,如果其他人遇到我遇到的同样问题,这里有一个完整的解决方案,可以将 PEM-encoded 证书从文件导入内存以用于加密 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;
}
注:
因为我很懒,我将 PEM 编码解码为二进制文件,放在我用来加载文件的同一个 BYTE 数组中——对于这个简单的测试,这是权宜之计,但如果你想实现这个有点真实,我不会推荐它