OpenSSL3自定义CNG Provider客户端mTLS无法指定证书私钥
自定义OpenSSL3 Provider实现mTLS客户端认证的问题
开发背景
基于C语言开发mTLS客户端认证功能,使用自定义OpenSSL3 Provider(基于Microsoft CNG API实现):
- Provider调度表已实现函数:
OSSL_FUNC_PROVIDER_GETTABLE_PARAMSOSSL_FUNC_PROVIDER_GET_PARAMSOSSL_FUNC_PROVIDER_QUERY_OPERATIONOSSL_FUNC_PROVIDER_GET_CAPABILITIESOSSL_FUNC_PROVIDER_TEARDOWN
- 查询操作函数返回以下操作的调度表:
OSSL_OP_STORE:从Windows证书存储枚举并加载证书和私钥OSSL_OP_KEYMGMT:基础密钥管理操作OSSL_OP_KEYEXCH:EC密钥交换实现(当前为存根,待完善)OSSL_OP_SIGNATURE:ECDSA签名操作实现
- 当capability为
TLS-GROUP时,get capabilities函数返回secp256r1组,确保Client Hello包含该组。
目标是使用Windows证书存储中由Provider加载的特定证书进行密钥交换认证:证书私钥存储在CNG容器中,密钥对采用EC secp256r1曲线,证书信息如下:
> openssl x509 -in user1.cert.pem -text -noout Certificate: Data: ... Signature Algorithm: ecdsa-with-SHA256 ... Subject Public Key Info: Public Key Algorithm: id-ecPublicKey ... ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Cert Type: SSL Client, S/MIME Netscape Comment: OpenSSL Generated Client Certificate ... X509v3 Key Usage: critical Digital Signature X509v3 Extended Key Usage: TLS Web Client Authentication Signature Algorithm: ecdsa-with-SHA256 ...
操作步骤与异常现象
通过OSSL_OP_STORE加载证书和私钥,得到X509和EVP_PKEY结构,调用SSL_CTX_use_certificate和SSL_CTX_use_PrivateKey设置SSL上下文,SSL_CTX_check_private_key返回1,但抓包发现异常:
- 首次Client Hello的
supported_group扩展包含secp256r1 (0x0017),但key_shareextension使用的是x25519组的临时密钥,而非指定证书的公钥; - 服务器不支持x25519,回复Hello Retry Request并选择secp256r1,符合预期;
- 客户端随后中止连接,错误提示该密钥类型无对应操作,排查发现客户端尝试生成新的secp256r1密钥对,但未实现keymgmt密钥生成函数;
- 实现keymgmt密钥生成函数后,客户端每次运行都会生成新密钥,而非使用指定的密钥对;
- 尝试调用
SSL_use_certificate、SSL_use_PrivateKey和SSL_check_privateKey均返回1,但指定的证书和私钥仍未被使用。
相关代码实现
以下是keymgmt导出函数(OpenSSL用于匹配证书与私钥):
int cng_keymgmt_export_ec(void* keydata, int selection, OSSL_CALLBACK* param_cb, void* cbarg) { printf(">> cng_keymgmt_export_ec \n"); T_CNG_KEYMGMT_KEYDATA* cng_keydata = (T_CNG_KEYMGMT_KEYDATA*) keydata; OSSL_PARAM* p = NULL; OSSL_PARAM params[3]; unsigned char* raw_keydata = NULL; if (selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) { /* Tough luck, no private key exporting planned */ return 0; } if (selection & OSSL_KEYMGMT_SELECT_PUBLIC_KEY) { DWORD public_blob_size; SECURITY_STATUS ss = NCryptExportKey( cng_keydata->windows_key_handle, 0, BCRYPT_PUBLIC_KEY_BLOB, NULL, NULL, 0, &public_blob_size, 0 ); if (ss != ERROR_SUCCESS) { return 0; } DWORD publicBlobExpectedSize = public_blob_size; PBYTE public_blob = malloc(public_blob_size); if (public_blob == NULL) { return 0; } ss = NCryptExportKey( cng_keydata->windows_key_handle, 0, BCRYPT_PUBLIC_KEY_BLOB, NULL, public_blob, publicBlobExpectedSize, &public_blob_size, 0 ); if (ss != ERROR_SUCCESS) { free(public_blob); return 0; } ULONG magic = ((BCRYPT_KEY_BLOB*) public_blob)->Magic; if (magic != BCRYPT_ECDH_PUBLIC_P256_MAGIC && magic != BCRYPT_ECDH_PUBLIC_P384_MAGIC && magic != BCRYPT_ECDH_PUBLIC_P521_MAGIC && magic != BCRYPT_ECDSA_PUBLIC_P256_MAGIC && magic != BCRYPT_ECDSA_PUBLIC_P384_MAGIC && magic != BCRYPT_ECDSA_PUBLIC_P521_MAGIC) { free(public_blob); return 0; } BCRYPT_ECCKEY_BLOB* pb = (BCRYPT_ECCKEY_BLOB*) public_blob; ULONG xy_size = pb->cbKey; const char* curve_group_name = NULL; if (magic == BCRYPT_ECDH_PUBLIC_P256_MAGIC || magic == BCRYPT_ECDSA_PUBLIC_P256_MAGIC) { curve_group_name = "P-256"; } else if (magic == BCRYPT_ECDH_PUBLIC_P384_MAGIC || magic == BCRYPT_ECDSA_PUBLIC_P384_MAGIC) { curve_group_name = "P-384"; } else if (magic == BCRYPT_ECDH_PUBLIC_P521_MAGIC || magic == BCRYPT_ECDSA_PUBLIC_P521_MAGIC) { curve_group_name = "P-521"; } size_t raw_keydata_size = sizeof(unsigned char) * (1 + 2 * xy_size); raw_keydata = (unsigned char*) malloc(raw_keydata_size); if (NULL == raw_keydata) { free(public_blob); return 0; } raw_keydata[0] = 0x04; memcpy(raw_keydata + 1, public_blob + sizeof(BCRYPT_ECCKEY_BLOB), xy_size); memcpy(raw_keydata + 1 + xy_size, public_blob + sizeof(BCRYPT_ECCKEY_BLOB) + xy_size, xy_size); free(public_blob); params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_group_name, 0); params[1] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, raw_keydata, raw_keydata_size); params[2] = OSSL_PARAM_construct_end(); p = params; } if (selection & OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS) { if (p == NULL) { return 0; } } if (selection & OSSL_KEYMGMT_SELECT_OTHER_PARAMETERS) { if (p == NULL) { return 0; } } int ret = param_cb(p, cbarg); if (raw_keydata) { free(raw_keydata); } return ret; }
疑问
我遗漏了什么?这个问题已经困扰我两周,甚至考虑辞职,昨天我哭了。
内容的提问来源于stack exchange,提问作者David Harabagiu
相关产品推荐
相关产品推荐

