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

如何用Schannel库加载私钥?客户端认证遇格式错误

C++客户端Schannel认证私钥加载失败问题

问题背景

为C++客户端添加客户端认证功能时,无法正确向应用提供私钥,也不清楚如何将私钥转换为Schannel API可加载的格式。调用CertCreateCertificateContext()时失败,错误码为0x8009310B(CRYPT_E_ASN1_BADTAG)。

现有实现代码

// Contents of private.key
const char* cPrivateKey = "\
-----BEGIN ENCRYPTED PRIVATE KEY-----\n\n<omitted>\n\n-----END ENCRYPTED PRIVATE KEY-----";

DWORD iBinaryCertBytes = 0;

// 获取二进制数据大小
if (!CryptStringToBinary(cPrivateKey, 0, CRYPT_STRING_BASE64_ANY, NULL, (DWORD*)&iBinaryCertBytes, NULL, NULL))
{
    Error("Unable to get private key length.");
    return;
}

// 分配二进制数据内存
UINT8* aiBinaryCert = new UINT8[iBinaryCertBytes];

// 转换为二进制数据
if (!CryptStringToBinary(cPrivateKey, 0, CRYPT_STRING_BASE64_ANY, aiBinaryCert, (DWORD*)&iBinaryCertBytes, NULL, NULL))
{
    Error("Unable to convert private key to binary.");
    return;
}

// 创建证书上下文
sCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &aiBinaryCert[0], aiBinaryCert.GetCount());
if (sCertContext == NULL)
{
// ERROR: CRYPT_E_ASN1_BADTAG
    Error("Failed to create certificate key.  0x%08x", GetLastError());
    return;
}

SCHANNEL_CRED sCred = {};
sCred.dwVersion = SCHANNEL_CRED_VERSION;
sCred.dwFlags = 0;
sCred.dwFlags |= SCH_USE_STRONG_CRYPTO;
sCred.dwFlags |= SCH_CRED_AUTO_CRED_VALIDATION;
sCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
sCred.grbitEnabledProtocols = SP_PROT_TLS1_2;
sCred.cCreds = 1;
sCred.paCred = &sCertContext;

// 获取凭证
CredHandle sCredential = {};
int iStatus = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &sCred, NULL, NULL, &sCredential, NULL);
if (iStatus != SEC_E_OK)
{
    Error("TLS Credential Failed.  <%d>", iStatus);
    return;
}

ZeroMemory(aiBinaryCert, iBinaryCertBytes);
delete [] aiBinaryCert;
CertFreeCertificateContext(sCertContext);
return;

密钥生成流程

最初使用OpenSSL生成密钥的命令:

openssl req -sha256 -new -newkey rsa:2048 -nodes -keyout private.key -subj "/C=US/ST=state/L=city/O=org/OU=IT/CN=identifier/emailAddress=me@example.com" -out request.csr

将生成的request.csr发送给HiveMQ服务器运维方后,收到certificate.p7b证书文件。

问题原因及解决方法

核心问题

CertCreateCertificateContext()是用于加载证书的API,无法直接加载私钥。私钥与证书的ASN.1结构完全不同,直接传入私钥必然会触发格式错误。此外,Schannel需要的是包含私钥的完整证书存储(或PFX/PKCS#12格式文件),而非单独的私钥和证书文件。

解决步骤

  1. 合并私钥与证书为PFX格式
    使用OpenSSL从certificate.p7b提取证书,并与private.key合并为PKCS#12(.pfx)格式文件:

    # 从p7b文件中提取证书
    openssl pkcs7 -in certificate.p7b -print_certs -out client.crt
    # 合并私钥与证书生成PFX文件
    openssl pkcs12 -export -in client.crt -inkey private.key -out client.pfx
    

    执行命令时会要求设置PFX文件的保护密码,需记住该密码用于后续代码加载。

  2. 修改代码加载PFX文件
    无需手动解析Base64私钥,改用PFXImportCertStore()加载PFX文件,示例代码如下:

    // 假设pfxDataBlob是PFX文件的二进制数据对象
    HCERTSTORE hCertStore = PFXImportCertStore(
        (const CRYPT_DATA_BLOB*)&pfxDataBlob,
        L"你的PFX文件密码",
        CRYPT_USER_KEYSET | PKCS12_PERSIST_KEYSET
    );
    if (hCertStore == NULL) {
        Error("Failed to import PFX store: 0x%08x", GetLastError());
        return;
    }
    
    // 从存储中查找证书上下文
    PCCERT_CONTEXT pCertContext = CertFindCertificateInStore(
        hCertStore,
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        0,
        CERT_FIND_ANY,
        NULL,
        NULL
    );
    if (pCertContext == NULL) {
        Error("Failed to find certificate in store: 0x%08x", GetLastError());
        CertCloseStore(hCertStore, 0);
        return;
    }
    
    // 配置SCHANNEL_CRED
    SCHANNEL_CRED sCred = {};
    sCred.dwVersion = SCHANNEL_CRED_VERSION;
    sCred.dwFlags = SCH_USE_STRONG_CRYPTO | SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS;
    sCred.grbitEnabledProtocols = SP_PROT_TLS1_2;
    sCred.cCreds = 1;
    sCred.paCred = &pCertContext;
    
    // 获取凭证
    CredHandle sCredential = {};
    TimeStamp tsExpiry = {};
    int iStatus = AcquireCredentialsHandle(
        NULL,
        UNISP_NAME,
        SECPKG_CRED_OUTBOUND,
        NULL,
        &sCred,
        NULL,
        NULL,
        &sCredential,
        &tsExpiry
    );
    if (iStatus != SEC_E_OK) {
        Error("TLS Credential Failed: %d", iStatus);
        CertFreeCertificateContext(pCertContext);
        CertCloseStore(hCertStore, 0);
        return;
    }
    
    // 后续业务逻辑...
    
    // 清理资源
    CertFreeCertificateContext(pCertContext);
    CertCloseStore(hCertStore, 0);
    FreeCredentialsHandle(&sCredential);
    

    若需将PFX内容嵌入代码,可先将PFX文件转换为Base64编码,再通过CryptStringToBinary转为二进制数据后传入PFXImportCertStore。

  3. 注意事项

    • 生成private.key时使用了-nodes参数,因此私钥为明文状态,PFX文件的密码仅用于保护该打包文件。
    • Schannel依赖Windows证书存储机制,使用PFX格式是最可靠的加载方式,避免手动解析私钥和证书的ASN.1结构。

内容的提问来源于stack exchange,提问作者mccoyn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 00:57:13