编程验证X509证书和私钥匹配 [英] Programmatically verify a X509 certificate and private key match

查看:280
本文介绍了编程验证X509证书和私钥匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建使用 EVP_aes_256_cbc()加密RSA密钥对。私钥是PEM连接codeD,并具有密码。这需要一个口令到由用户输入

下面是创建私钥电话:

  //保存私钥
    bio_priv = BIO_new_file(full_asymKeyFilePath.c_str(),A +);
    如果(PEM_write_bio_RSAPrivateKey(
        bio_priv,// BIO手柄
        RSA,//钥匙手柄
        EVP_aes_256_cbc(),//密码编码格式
        PWD,//密码
        pwd_len,//密码长度
        NULL,//回调
        NULL //不知道
        )!= 1){
            //报告犯错
    }

然后我生成的证书和私钥签了名。

  // SIGN与生成的密钥证书
    如果(!X509_sign(CERT,evpKey,EVP_sha1())){
        //报告犯错
    }

后来,我想验证此证书此RSA密钥对的比赛。当我 SSL_CTX_check_private_key(),我按提示输入从控制台密码。

是否有办法自动输入密码,这样我就不会被从控制台提示?

  //加载服务器证书,必须不断呼吁使用私钥之前调用
    如果(SSL_CTX_use_certificate_file(背景下,full_certFilePath.c_str(),SSL_FILETYPE_PEM)== 0){//将PEM文件导入到SSL_CTX所有证书
        //呃
    }    //加载相应的证书私钥
    如果(SSL_CTX_use_PrivateKey_file(背景下,full_asymKeyFilePath.c_str(),SSL_FILETYPE_PEM)== 0){//将PEM文件导入到SSL_CTX所有证书
        //文件类型不是PEM或私钥调用此函数之前加载。私钥不匹配证书中的公钥
        //呃
    }    //验证的证书和私钥匹配
    如果{//&LT(SSL_CTX_check_private_key(上下文)!); ======提示我输入传递:(
        //呃
    }


解决方案

  

编程验证X509证书和私钥匹配。私钥PEM的密码


这里有两个答案。一个用于该证书,并且第二个是私钥。私钥首先显示,因为它是用来验证证书(所以是有意义的首次访问它)。

此外,其重要致电 * _ check_key 程序,因为OpenSSL的只检查一个关键是良好的连接codeD;它不检查其实际有效。见,例如,<一href=\"http://stackoverflow.com/questions/22119835/private-key-generated-by-openssl-does-not-satisfy-n-p-q\">Private通过OpenSSL的生成的密钥不满足N = P * Q 。


在OpenSSL的,你可以使用下面的验证私钥以及连接codeD:

FILE *文件=的fopen(...);
EVP_PKEY * p键= PEM_read_PrivateKey(文件,NULL,PasswordCallback,NULL);
无符号长ERR = ERR_get_error();如果(p键)
    EVP_PKEY_free(p键);

如果 p键 NULL ,然后出现了一个问题, ERR 持有理由code。否则,你有一个正确的连接codeD私钥(但不一定有效)。

如果关键是正确地连接codeD,您可以检查私钥的类型,与下面的验证。

int型的= EVP_PKEY_get_type(p键);
开关(类型)
{
案例EVP_PKEY_RSA:
案例EVP_PKEY_RSA2:
    RSA * RSA = EVP_PKEY_get1_RSA(p键);
    RC = RSA_check_key(RSA);
    ASSERT(RC);
    RSA_free(RSA);    打破;案例EVP_PKEY_DSA:
案例EVP_PKEY_DSA1:
案例EVP_PKEY_DSA2:
案例EVP_PKEY_DSA3:
案例EVP_PKEY_DSA4:
    DSA * DSA = EVP_PKEY_get1_DSA(p键);
    RC = DSA_check_key(DSA);
    ASSERT(RC);
    DSA_free(DSA);    打破;案例EVP_PKEY_DH:
    DH * DH = EVP_PKEY_get1_DH(p键);
    RC = DH_check_key(DH);
    ASSERT(RC);
    DH_free(DH);    打破;案例EVP_PKEY_EC:
    EC_KEY * EC = EVP_PKEY_get1_EC_KEY(p键);
    RC = EC_KEY_check_key(EC);
    ASSERT(RC);
    EC_KEY_free(EC);    打破;默认:
    ASSERT(0);
}

EVP_PKEY_get_type 不是OpenSSL的一部分。下面是我如何实现它:

INT EVP_PKEY_get_type(EVP_PKEY * p键)
{
    ASSERT(p键);
    如果(!p键)
        返回NID_undef;    返回EVP_PKEY_type(pkey-&GT;类型);
}


在OpenSSL的,你可以使用下面的验证证书以及连接codeD:

FILE *文件=的fopen(...);
X509 * X509 = PEM_read_X509(文件,NULL,NULL,NULL);
无符号长ERR = ERR_get_error();

如果 X509 NULL ,然后出现了一个问题, ERR 持有理由code。否则,你有一个正确的连接codeD证书(但不一定有效)。

您可以然后验证与证书:

/ *请参阅上述验证私钥* /
EVP_PKEY * p键= ReadPrivateKey(...);INT RC = X509_verify(X509,p键);
ERR = ERR_get_error();

如果 RC!= 1 ,然后出现了一个问题, ERR 持有理由code。否则,你有一个有效的证书和私钥对。如果证书是有效的,那么你就不能使用 ERR ,因为 ERR 才有效,如果有一个问题。

如果您的证书是由发行人(例如,CA或中间)签署的,那么你就需要使用 X509_STORE 来验证您的证书颁发者的签名(很多错误检查省略):

为const char * serverCertFilename = ...;
为const char * issuerCertFilename = ...;X509_STORE *商店= X509_STORE_new();
ASSERT(存储);静态常量长标志= X509_V_FLAG_X509_STRICT | X509_V_FLAG_CHECK_SS_SIGNATURE
        | X509_V_FLAG_POLICY_CHECK;
RC = X509_STORE_set_flags(存储,旗);
ERR = ERR_get_error();
ASSERT(RC);/ *一些其他的对象/功能拥有'查找',但我不知道它(也许是店铺)* /
X509_LOOKUP *查找= X509_STORE_add_lookup(店,X509_LOOKUP_file());
/ * ERR = ERR_get_error(); //不设置错误codeS。 * /
ASSERT(查找);/ *无法从内存中加载此。没有API! * /
RC = X509_LOOKUP_load_file(查找,issuerCertFilename,X509_FILETYPE_PEM);
/ * ERR = ERR_get_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,存储,serverCert,NULL);
RET = ERR = ERR_get_error();
ASSERT(RC);/ *错误codeS在https://www.openssl.org/docs/crypto/X509_STORE_CTX_get_error.html * /
RC = X509_verify_cert(CTX);
ERR = X509_STORE_CTX_get_error(CTX);/ *做清理工作,返回成功/失败* /



  

时有方法来自动输入密码,这样我就不会被从控制台提示?


是的。使用 PEM_read_PrivateKey 密码回调。在 PasswordCallback 可以简单地在缓冲器提供密码,或者可以提示用户并在缓冲器返回的密码。

我的密码回调较为复杂。据其传递到库之前执行的原始密码的哈希值单。这确保了纯文本密码不使用(但不减慢习惯攻击)。你可以提示用户输入一个字符串,或者它可以返回一个硬codeD字符串。

我的密码回调使用的标签。标签可以让我根据具体使用情况(即使同'基地'的秘密时)得出不同的密钥。通过指定不同的用法或标签,我得到的键位不同的推导。通过 ARG 下面提供的标签,你可以用 SSL_CTX_set_default_passwd_cb_userdata 设置。

 使用EVP_MD_CTX_ptr =的std ::的unique_ptr&LT; EVP_MD_CTX,decltype(安培; :: EVP_MD_CTX_destroy)取代;INT PasswordCallback(字符*缓冲区,诠释大小,INT rwflag,无效* ARG)
{
    未使用(rwflag);    INT RC;
    无符号长犯错;
    ostringstream OSS;    为const char *标签=(字符*)ARG;
    为size_t LSIZE =(标签的strlen(标签):0);    SecureVector SV = config.GetMasterKey();
    ASSERT(sv.empty()!);
    如果(sv.empty())
    {
        ...
        抛出runtime_error(oss.str()c_str());
    }    EVP_MD_CTX_ptr CTX(EVP_MD_CTX_create(),:: EVP_MD_CTX_destroy);
    ASSERT(ctx.get()!= NULL);    常量EVP_MD *散列= EVP_sha512();
    ASSERT(哈希!= NULL);    RC = EVP_DigestInit_ex(ctx.get(),散列,NULL);
    ERR = ERR_get_error();    ASSERT(RC == 1);
    如果(RC!= 1)
    {
        ...
        抛出runtime_error(oss.str()c_str());
    }    RC = EVP_DigestUpdate(ctx.get(),sv.data(),sv.size());
    ERR = ERR_get_error();    ASSERT(RC == 1);
    如果(RC!= 1)
    {
        ...
        抛出runtime_error(oss.str()c_str());
    }    如果(标签和放大器;&安培; LSIZE)
    {
        RC = EVP_DigestUpdate(ctx.get(),标签,LSIZE);
        ERR = ERR_get_error();        ASSERT(RC == 1);
        如果(RC!= 1)
        {
            ...
            抛出runtime_error(oss.str()c_str());
        }
    }    INT N =的std ::分钟(大小,EVP_MD_size(散));
    如果(N下; = 0)
        返回0;    RC = EVP_DigestFinal_ex(ctx.get(),(无符号字符*)缓冲,(无符号整数*)及N);
    ERR = ERR_get_error();    ASSERT(RC == 1);
    如果(RC!= 1)
    {
        ...
        抛出runtime_error(oss.str()c_str());
    }    返回N;
}

I created an RSA key pair using the EVP_aes_256_cbc() cipher. The private key is PEM encoded and has a passphrase. This requires a passphrase to be entered by the user.

Here's the create private key call:

//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
    } 

Then I generated a certificate and signed it with the private key.

//Sign the certificate with the generated key
    if (!X509_sign(cert, evpKey, EVP_sha1())){
        //report err
    }

Later on, I want to verify that this certificate matches this RSA key pair. When I SSL_CTX_check_private_key(), I'm prompted to input a passphrase from the console.

Is there are way to automatically input the password so that I don't get prompted from the console?

//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
    }

解决方案

Programmatically verify a X509 certificate and private key match. Private key has a PEM passphrase

There are two answers here. One is for the certificate, and the second is for the private key. The private key is shown first because it is used to validate the certificate (so it makes sense to visit it first).

Also, its important to call the *_check_key routines because OpenSSL only checks that a key is well encoded; and it does not check that its actually valid. See, for example, Private key generated by openssl does not satisfy n = p * q.


In OpenSSL, you would use the following to verify the the private key is well encoded:

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);

If pkey is NULL, then there was a problem and err holds a reason code. Otherwise, you have a properly encoded private key (but not necessarily valid).

If the key is properly encoded, you can check the type of private key and validate it with the following.

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 is not part of OpenSSL. Here's how I implemented it:

int EVP_PKEY_get_type(EVP_PKEY *pkey)
{
    ASSERT(pkey);
    if (!pkey)
        return NID_undef;

    return EVP_PKEY_type(pkey->type);
}


In OpenSSL, you would use the following to verify the the certificate is well encoded:

FILE* file = fopen(...);
X509* x509 = PEM_read_X509(file, NULL, NULL, NULL);
unsigned long err = ERR_get_error();

If x509 is NULL, then there was a problem and err holds a reason code. Otherwise, you have a properly encoded certificate (but not necessarily valid).

You can then verify the certificate with:

/* See above on validating the private key */
EVP_PKEY* pkey = ReadPrivateKey(...);

int rc = X509_verify(x509, pkey);
err = ERR_get_error();

If rc != 1, then there was a problem and err holds a reason code. Otherwise, you have a valid certificate and private key pair. If the certificate is valid, then you can't use err because err is only valid if there's a problem.

If your certificate is signed by an issuer (for example, a CA or intermediate), then you need to use a X509_STORE to verify the issuer's signature on your certificate (a lot of error checking omitted):

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 */


Is there are way to automatically input the password so that I don't get prompted from the console?

Yes. use the password callback in PEM_read_PrivateKey. The PasswordCallback can simply provide a password in the buffer, or it can prompt the user and return the password in the buffer.

My password callback is somewhat involved. It performs a single hash of the raw password before passing it on to the library. That ensures a "plain text" password is not used (but does not slow down the customary attacks). Yours can prompt the user for a string, or it can return a hard coded string.

My password callback uses a label. The label allows me to derive different keys depending on usage (even though the same 'base' secret is used). By specifying a different usage or label, I get a different derivation of key bits. The label is provided through arg below, and you can set it with SSL_CTX_set_default_passwd_cb_userdata.

using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_destroy)>;

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;
}

这篇关于编程验证X509证书和私钥匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆