You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过序列号从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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 04:23:17