以编程方式validationX509证书和私钥匹配
我使用EVP_aes_256_cbc()
密码创建了一个RSA密钥对。 私钥是PEM编码的并且具有密码短语。 这需要用户输入密码。
这是创建私钥调用:
//Save private key bio_priv = BIO_new_file(full_asymKeyFilePath.c_str(), "a+"); if (PEM_write_bio_RSAPrivateKey( bio_priv, //BIO handle rsa, //Key handle EVP_aes_256_cbc(), //Cipher encoding format pwd, //Password pwd_len, //Password length NULL, //Callback NULL //Not sure ) != 1) { //report err }
然后我生成了一个证书并用私钥签名。
//Sign the certificate with the generated key if (!X509_sign(cert, evpKey, EVP_sha1())){ //report err }
稍后,我想validation此证书是否与此RSA密钥对匹配。 当我SSL_CTX_check_private_key()
,系统会提示我从控制台输入密码。
有没有办法自动输入密码,以便我不会从控制台收到提示?
//Load server certificate, must be called before ever calling use private key if (SSL_CTX_use_certificate_file(context, full_certFilePath.c_str(), SSL_FILETYPE_PEM) == 0){ //load all certs from PEM file into SSL_CTX //err } //Load private key corresponding to the certificate if (SSL_CTX_use_PrivateKey_file(context, full_asymKeyFilePath.c_str(), SSL_FILETYPE_PEM) == 0){ //load all certs from PEM file into SSL_CTX //file type is not pem or private key was loaded before calling this function. Private key does not match the public key in the certificate //err } //Verify that certificate and private key match if (!SSL_CTX_check_private_key(context)){ //<====== Prompts me to enter pass :( //err }
以编程方式validationX509证书和私钥匹配。 私钥具有PEM密码
这里有两个答案。 一个用于证书,第二个用于私钥。 首先显示私钥,因为它用于validation证书(因此首先访问它是有意义的)。
此外,调用*_check_key
例程很重要,因为OpenSSL只检查密钥是否编码良好; 并且它不会检查它实际上是否有效。 例如,参见openssl生成的私钥不满足n = p * q 。
在OpenSSL中,您将使用以下内容来validation私钥是否编码良好:
FILE* file = fopen(...); EVP_PKEY* pkey = PEM_read_PrivateKey(file, NULL, PasswordCallback, NULL); unsigned long err = ERR_get_error(); if(pkey) EVP_PKEY_free(pkey);
如果pkey
为NULL
,则出现问题并且err
保存原因代码。 否则,您有一个正确编码的私钥(但不一定有效)。
如果密钥已正确编码,您可以检查私钥的类型并使用以下内容对其进行validation。
int type = EVP_PKEY_get_type(pkey); switch (type) { case EVP_PKEY_RSA: case EVP_PKEY_RSA2: RSA* rsa = EVP_PKEY_get1_RSA(pkey); rc = RSA_check_key(rsa); ASSERT(rc); RSA_free(rsa); break; case EVP_PKEY_DSA: case EVP_PKEY_DSA1: case EVP_PKEY_DSA2: case EVP_PKEY_DSA3: case EVP_PKEY_DSA4: DSA* dsa = EVP_PKEY_get1_DSA(pkey); rc = DSA_check_key(dsa); ASSERT(rc); DSA_free(dsa); break; case EVP_PKEY_DH: DH* dh = EVP_PKEY_get1_DH(pkey); rc = DH_check_key(dh); ASSERT(rc); DH_free(dh); break; case EVP_PKEY_EC: EC_KEY* ec = EVP_PKEY_get1_EC_KEY(pkey); rc = EC_KEY_check_key(ec); ASSERT(rc); EC_KEY_free(ec); break; default: ASSERT(0); }
EVP_PKEY_get_type
不是OpenSSL的一部分。 这是我实现它的方式:
int EVP_PKEY_get_type(EVP_PKEY *pkey) { ASSERT(pkey); if (!pkey) return NID_undef; return EVP_PKEY_type(pkey->type); }
在OpenSSL中,您将使用以下内容来validation证书是否编码良好:
FILE* file = fopen(...); X509* x509 = PEM_read_X509(file, NULL, NULL, NULL); unsigned long err = ERR_get_error();
如果x509
为NULL
,则出现问题并且err
保存原因代码。 否则,您具有正确编码的证书(但不一定有效)。
然后,您可以使用以下方式validation证书
/* See above on validating the private key */ EVP_PKEY* pkey = ReadPrivateKey(...); int rc = X509_verify(x509, pkey); err = ERR_get_error();
如果rc != 1
,则出现问题并且err
包含原因代码。 否则,您拥有有效的证书和私钥对。 如果证书有效,则不能使用err
因为err
仅在出现问题时才有效。
如果您的证书由颁发者(例如,CA或中间人)签名,那么您需要使用X509_STORE
来validation颁发者在您的证书上的签名(省略了大量错误检查):
const char* serverCertFilename = ...; const char* issuerCertFilename = ...; X509_STORE* store = X509_STORE_new(); ASSERT(store); static const long flags = X509_V_FLAG_X509_STRICT | X509_V_FLAG_CHECK_SS_SIGNATURE | X509_V_FLAG_POLICY_CHECK; rc = X509_STORE_set_flags(store, flags); err = ERR_get_error(); ASSERT(rc); /* Some other object/functions owns 'lookup', but I'm not sure which (perhaps the store) */ X509_LOOKUP* lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); /* err = ERR_get_error(); // Does not set error codes. */ ASSERT(lookup); /* Cannot load this from memory. No API!!! */ rc = X509_LOOKUP_load_file(lookup, issuerCertFilename, X509_FILETYPE_PEM); /* err = ERR_get_error(); // Does not set error codes. */ ASSERT(rc); X509_STORE_CTX* ctx = X509_STORE_CTX_new(); ASSERT(ctx); X509* serverCert = ReadCertifcate(serverCertFilename); ASSERT(serverCert); rc = X509_STORE_CTX_init(ctx, store, serverCert, NULL); ret = err = ERR_get_error(); ASSERT(rc); /* Error codes at https://www.openssl.org/docs/crypto/X509_STORE_CTX_get_error.html */ rc = X509_verify_cert(ctx); err = X509_STORE_CTX_get_error(ctx); /* Do cleanup, return success/failure */
有没有办法自动输入密码,以便我不会从控制台收到提示?
是。 在PEM_read_PrivateKey
使用密码回调。 PasswordCallback
可以简单地在缓冲区中提供密码,也可以提示用户并在缓冲区中返回密码。
我的密码回调有点牵扯。 它在将原始密码传递给库之前执行单个哈希密码。 这确保不使用“纯文本”密码(但不会减慢习惯性攻击)。 您可以提示用户输入字符串,也可以返回硬编码字符串。
我的密码回调使用标签。 标签允许我根据使用情况导出不同的密钥(即使使用相同的“基本”秘密)。 通过指定不同的用法或标签,我获得了不同的密钥位派生。 标签通过下面的arg
提供,您可以使用SSL_CTX_set_default_passwd_cb_userdata
进行设置。
using EVP_MD_CTX_ptr = std::unique_ptr; int PasswordCallback(char *buffer, int size, int rwflag, void *arg) { UNUSED(rwflag); int rc; unsigned long err; ostringstream oss; const char* label = (char*) arg; size_t lsize = (label ? strlen(label) : 0); SecureVector sv = config.GetMasterKey(); ASSERT(!sv.empty()); if (sv.empty()) { ... throw runtime_error(oss.str().c_str()); } EVP_MD_CTX_ptr ctx(EVP_MD_CTX_create(), ::EVP_MD_CTX_destroy); ASSERT(ctx.get() != NULL); const EVP_MD* hash = EVP_sha512(); ASSERT(hash != NULL); rc = EVP_DigestInit_ex(ctx.get(), hash, NULL); err = ERR_get_error(); ASSERT(rc == 1); if (rc != 1) { ... throw runtime_error(oss.str().c_str()); } rc = EVP_DigestUpdate(ctx.get(), sv.data(), sv.size()); err = ERR_get_error(); ASSERT(rc == 1); if (rc != 1) { ... throw runtime_error(oss.str().c_str()); } if (label && lsize) { rc = EVP_DigestUpdate(ctx.get(), label, lsize); err = ERR_get_error(); ASSERT(rc == 1); if (rc != 1) { ... throw runtime_error(oss.str().c_str()); } } int n = std::min(size, EVP_MD_size(hash)); if (n <= 0) return 0; rc = EVP_DigestFinal_ex(ctx.get(), (unsigned char*) buffer, (unsigned int*) &n); err = ERR_get_error(); ASSERT(rc == 1); if (rc != 1) { ... throw runtime_error(oss.str().c_str()); } return n; }