OpenSSL之SSL_CTX_use_PrivateKey_file分析
本系列OpenSSL使用的代码版本为:1.0.2o
前言
本篇文章纯属个人学习的一点经验分享,若有不对之处烦请各位大神现身指点,希望能和大家一起共同进步。
一、
SSL_CTX_use_PrivateKey_file
是什么?
SSL_CTX_use_PrivateKey_file是openssl中的加载密钥的函数,函数申明为:
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type)
参数:
ctx:SSL的上下文句柄
file:密钥文件的路径
type:密钥文件的类型,
SSL_FILETYPE_ASN1:ASN.1格式又称DER格式。
SSL_FILETYPE_PEM:PEM格式,简单点讲就是DER格式经过base64计算后的格式。
返回值:
0:失败
1:成功
二、代码分析
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type)
{
int j, ret = 0;
BIO *in;
EVP_PKEY *pkey = NULL;
in = BIO_new(BIO_s_file_internal());
if (in == NULL) {
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_BUF_LIB);
goto end;
}
if (BIO_read_filename(in, file) <= 0) {
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_SYS_LIB);
goto end;
}
if (type == SSL_FILETYPE_PEM) {
j = ERR_R_PEM_LIB;
pkey = PEM_read_bio_PrivateKey(in, NULL,
ctx->default_passwd_callback,
ctx->default_passwd_callback_userdata);
} else if (type == SSL_FILETYPE_ASN1) {
j = ERR_R_ASN1_LIB;
pkey = d2i_PrivateKey_bio(in, NULL);
} else {
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, SSL_R_BAD_SSL_FILETYPE);
goto end;
}
if (pkey == NULL) {
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, j);
goto end;
}
ret = SSL_CTX_use_PrivateKey(ctx, pkey);
EVP_PKEY_free(pkey);
end:
if (in != NULL)
BIO_free(in);
return (ret);
}
首先调用BIO_new(BIO_s_file_internal())创建一个文件类型的BIO,为加载密钥文件做准备,然后调用BIO_read_filename(in, file)将密钥文件设置到BIO中,然后根据密钥类型不同调用不用的证书加载方法,type == SSL_FILETYPE_ASN1时调用d2i_PrivateKey_bio(in, NULL)将密钥文件加载到EVP_PKEY句柄中,当type == SSL_FILETYPE_PEM时调用PEM_read_bio_PrivateKey(in, NULL,ctx->default_passwd_callback, ctx->default_passwd_callback_userdata)将密钥文件加载到EVP_PKEY句柄中,文件加载失败则goto end;释放句柄,退出操作。
加载密钥文件成功后调用ret = SSL_CTX_use_PrivateKey(ctx, pkey)将密钥设置到ctx上下文中,下面我们来看看SSL_CTX_use_PrivateKey函数的实现。
int SSL_CTX_use_PrivateKey(SSL_CTX *ctx, EVP_PKEY *pkey)
{
if (pkey == NULL) {
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_PASSED_NULL_PARAMETER);
return (0);
}
if (!ssl_cert_inst(&ctx->cert)) {
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY, ERR_R_MALLOC_FAILURE);
return (0);
}
return (ssl_set_pkey(ctx->cert, pkey));
}
开头是常见的判NULL操作,不管,接着是ssl_cert_inst(&ctx->cert)操作,用来查看ctx句柄中cert字段是否存在内容,存在则什么都不干,不存在则调用ssl_cert_new()函数申请一个CERT赋值给ctx->cert,接着调用ssl_set_pkey(ctx->cert, pkey)将pkey设置到ctx->cert中,下面我们来看看ssl_set_pkey函数的实现。
static int ssl_set_pkey(CERT *c, EVP_PKEY *pkey)
{
int i;
/*
* Special case for DH: check two DH certificate types for a match. This
* means for DH certificates we must set the certificate first.
*/
if (pkey->type == EVP_PKEY_DH) {
X509 *x;
i = -1;
x = c->pkeys[SSL_PKEY_DH_RSA].x509;
if (x && X509_check_private_key(x, pkey))
i = SSL_PKEY_DH_RSA;
x = c->pkeys[SSL_PKEY_DH_DSA].x509;
if (i == -1 && x && X509_check_private_key(x, pkey))
i = SSL_PKEY_DH_DSA;
ERR_clear_error();
} else
i = ssl_cert_type(NULL, pkey);
if (i < 0) {
SSLerr(SSL_F_SSL_SET_PKEY, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
return (0);
}
if (c->pkeys[i].x509 != NULL) {
EVP_PKEY *pktmp;
pktmp = X509_get_pubkey(c->pkeys[i].x509);
if (pktmp == NULL) {
SSLerr(SSL_F_SSL_SET_PKEY, ERR_R_MALLOC_FAILURE);
EVP_PKEY_free(pktmp);
return 0;
}
/*
* The return code from EVP_PKEY_copy_parameters is deliberately
* ignored. Some EVP_PKEY types cannot do this.
*/
EVP_PKEY_copy_parameters(pktmp, pkey);
EVP_PKEY_free(pktmp);
ERR_clear_error();
#ifndef OPENSSL_NO_RSA
/*
* Don't check the public/private key, this is mostly for smart
* cards.
*/
if ((pkey->type == EVP_PKEY_RSA) &&
(RSA_flags(pkey->pkey.rsa) & RSA_METHOD_FLAG_NO_CHECK)) ;
else
#endif
if (!X509_check_private_key(c->pkeys[i].x509, pkey)) {
X509_free(c->pkeys[i].x509);
c->pkeys[i].x509 = NULL;
return 0;
}
}
if (c->pkeys[i].privatekey != NULL)
EVP_PKEY_free(c->pkeys[i].privatekey);
CRYPTO_add(&pkey->references, 1, CRYPTO_LOCK_EVP_PKEY);
c->pkeys[i].privatekey = pkey;
c->key = &(c->pkeys[i]);
c->valid = 0;
return (1);
}
首先检查密钥类型是否是EVP_PKEY_DH,如果是则调用X509_check_private_key来判断属于哪一类DH密钥,SSL_PKEY_DH_RSA还是SSL_PKEY_DH_DSA,然后记录对应的下标。如果不是EVP_PKEY_DH类型的密钥则调用ssl_cert_type(NULL, pkey)来获取密钥类型对应的下标,如:
# define SSL_PKEY_RSA_ENC 0
# define SSL_PKEY_RSA_SIGN 1
# define SSL_PKEY_DSA_SIGN 2
# define SSL_PKEY_DH_RSA 3
# define SSL_PKEY_DH_DSA 4
# define SSL_PKEY_ECC 5
# define SSL_PKEY_GOST94 6
# define SSL_PKEY_GOST01 7
# define SSL_PKEY_NUM 8
然后根据下标查看对应的下标是否存在证书内容,如果存在证书内容则检测密钥与证书是否匹配,然后将密钥指针设置到ctx->cert对应的下标上,最后释放句柄返回成功。
总结
好了,讲到这里差不多将SSL_CTX_use_PrivateKey_file流程过了一遍,希望对其他有兴趣学习OpenSSL的同学起到帮助。