如何通过序列号从Windows证书存储检索证书?代码问题排查
解决Windows证书存储检索DigiCert根证书的问题
你的代码存在三个核心问题,导致无法成功找到目标证书:
1. 颁发者名称格式错误
CERT_NAME_BLOB不能直接传入普通ASCII字符串,它要求的是ASN.1 DER编码的X.500标准名称。必须通过CertStrToName函数,将可读的颁发者名称转换成符合要求的BLOB格式。
另外,传入完整的颁发者DN(如CN=DigiCert Global Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US)能保证唯一性,测试场景下仅传CN字段也能工作,但正式环境建议用完整DN。
2. 序列号字节序错误
证书序列号采用**大端(Big-Endian)**存储,但你的代码从左到右解析十六进制字符串,得到的是小端字节序,必须反转字节数组才能匹配存储中的格式。
3. 其他细节问题
- 动态分配的序列号字节数组未释放,存在内存泄漏
CertFindCertificateInStore的注释描述错误(CERT_FIND_CERT_ID是按颁发者+序列号查找,而非subject)- 首次查找时,最后一个参数传未初始化的
pTargetCert是错误的,应传NULL
修正后的完整代码
#pragma comment(lib, "crypt32.lib") #include <iostream> #include <stdio.h> #include <windows.h> #include <Wincrypt.h> #include <algorithm> // 用于std::reverse void panic(const char* s); int main(void) { #define MY_ENCODING_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING) HCERTSTORE hSystemStore = NULL; PCCERT_CONTEXT pTargetCert = NULL; BYTE* pIssuerBlob = NULL; DWORD cbIssuerBlob = 0; BYTE* pSerialBlob = NULL; DWORD cbSerialBlob = 0; // 打开本地机器根证书存储 if (!(hSystemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"Root"))) panic("Error opening the Root store."); // 1. 将可读颁发者名称转为ASN.1编码的BLOB const wchar_t* issuer_str = L"CN=DigiCert Global Root CA, OU=www.digicert.com, O=DigiCert Inc, C=US"; // 先调用一次获取所需缓冲区大小 if (!CertStrToName(MY_ENCODING_TYPE, issuer_str, CERT_X500_NAME_STR, NULL, NULL, &cbIssuerBlob, NULL)) panic("Failed to get issuer blob size"); pIssuerBlob = new BYTE[cbIssuerBlob]; if (!CertStrToName(MY_ENCODING_TYPE, issuer_str, CERT_X500_NAME_STR, NULL, pIssuerBlob, &cbIssuerBlob, NULL)) panic("Failed to convert issuer name to blob"); CERT_NAME_BLOB issuer = { cbIssuerBlob, pIssuerBlob }; // 2. 解析序列号并反转字节序(转为大端格式) const char* sn_string = "083be056904246b1a1756ac95991c74a"; cbSerialBlob = (DWORD)strlen(sn_string) / 2; pSerialBlob = new BYTE[cbSerialBlob]; const char* pos = sn_string; for (DWORD count = 0; count < cbSerialBlob; count++) { sscanf_s(pos, "%2hhx", &pSerialBlob[count]); pos += 2; } // 反转字节序,匹配证书存储的大端格式 std::reverse(pSerialBlob, pSerialBlob + cbSerialBlob); CRYPT_INTEGER_BLOB serial = { cbSerialBlob, pSerialBlob }; // 构造查找参数 CERT_ISSUER_SERIAL_NUMBER findparams; findparams.Issuer = issuer; findparams.SerialNumber = serial; CERT_ID id; id.dwIdChoice = CERT_ID_ISSUER_SERIAL_NUMBER; id.IssuerSerialNumber = findparams; // 查找目标证书 if (!(pTargetCert = CertFindCertificateInStore( hSystemStore, MY_ENCODING_TYPE, 0, CERT_FIND_CERT_ID, &id, NULL))) // 首次查找传NULL panic("Could not find the required certificate"); std::cout << "Successfully found the DigiCert Global Root CA certificate!" << std::endl; // 释放所有资源 if (pTargetCert) CertFreeCertificateContext(pTargetCert); if (hSystemStore) { if (!CertCloseStore(hSystemStore, CERT_CLOSE_STORE_CHECK_FLAG)) panic("Could not close the certificate store"); } delete[] pIssuerBlob; delete[] pSerialBlob; return 0; } void panic(const char* s) { fprintf(stderr, "%s: Error number %x.\n", s, GetLastError()); exit(1); }
关键修改说明
- 颁发者名称处理:通过
CertStrToName将Unicode格式的颁发者DN转为CryptoAPI可识别的ASN.1 BLOB - 序列号字节序修正:添加
std::reverse将解析后的小端字节数组转为大端,匹配证书存储的格式 - 资源管理:补充动态分配内存的释放逻辑,避免内存泄漏
- 查找参数修正:首次调用
CertFindCertificateInStore时,最后一个参数传NULL,符合API调用规范
内容的提问来源于stack exchange,提问作者Will I Am
相关产品推荐
相关产品推荐

