概述
在使用openssl建立ssl/tls连接时,需要加载对端的证书链,自己的证书以及私钥。本文主要描述如何从文件以及内存中加载通信对端的证书链、自己的证书以及私钥(RSA证书
和ECC证书
通用)。
PS:为什么要从内存中加载私钥?私钥是要求安全存储的,通常是存储到软的密钥存储或者专用的安全硬件中,不会明文落盘,所以应用是无法从磁盘上读取到明文的私钥文件的。
1.加载证书链
加载证书链有三种方式:(1)指定证书链所在的文件夹路径(2)指定证书链文件(把证书链cat 到一个文件内)(3)从内存中加载证书链。
前两种方式都有现成的接口SSL_CTX_load_verify_locations
,而从内存中加载证书链,稍微麻烦一点儿,不过网络上已经有很多的文章,通过gdb跟踪openssl的代码,也能找到方法。摘录如下:
bool load_cert_chain_from_shared_mem(SSL_CTX *context, const char *cert_buffer) {
BIO *cbio = BIO_new_mem_buf((void*)cert_buffer, -1);
if (!cbio)
return false;
X509_INFO *itmp;
int i;
STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
if (!inf) {
BIO_free(cbio);
return false;
}
/* Iterate over contents of the PEM buffer, and add certs. */
bool first = true;
for (i = 0; i < sk_X509_INFO_num(inf); i++) {
itmp = sk_X509_INFO_value(inf, i);
if (itmp->x509) {
/* First cert is server cert. Remaining, if any, are intermediate certs. */
if (first) {
first = false;
/*
* Set server certificate. Note that this operation increments the
* reference count, which means that it is okay for cleanup to free it.
*/
if (!SSL_CTX_use_certificate(context, itmp->x509))
goto Error;
if (ERR_peek_error() != 0)
goto Error;
/* Get ready to store intermediate certs, if any. */
SSL_CTX_clear_chain_certs(context);
}
else {
/* Add intermediate cert to chain. */
if (!SSL_CTX_add0_chain_cert(context, itmp->x509))
goto Error;
/*
* Above function doesn't increment cert reference count. NULL the info
* reference to it in order to prevent it from being freed during cleanup.
*/
itmp->x509 = NULL;
}
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
BIO_free(cbio);
return true;
Error:
sk_X509_INFO_pop_free(inf, X509_INFO_free);
BIO_free(cbio);
return false;
}
2.加载证书
从文件中加载证书: SSL_CTX_use_certificate_file
从内存中加载证书:
static X509* buffer2x509(const uint8_t* cert, size_t len) {
/*read the cert and decode it*/
BIO *b = BIO_new_mem_buf((void *)cert, len);
if (NULL == b) {
return NULL;
}
X509* x509 = PEM_read_bio_X509(b, NULL, NULL, NULL);
if (NULL == x509) {
BIO_free(b);
return NULL;
}
BIO_free(b);
return x509;
}
3.加载私钥
从文件中加载私钥:SSL_CTX_use_PrivateKey_file
从内存中加载私钥:
static EVP_PKEY* buffer2evpkey(const uint8_t* key, size_t key_len) {
BIO *b = BIO_new_mem_buf((void *)key, key_len);
if (NULL == b) {
return NULL;
}
//如果私钥文件使用了密码进行了加密,则下面函数的第二个和第三个入参要赋值,具体请参见openssl的PEM_read_bio_PrivateKey接口描述
EVP_PKEY* evpkey = PEM_read_bio_PrivateKey(b, NULL, NULL, NULL);
if (NULL == evpkey) {
BIO_free(b);
return NULL;
}
BIO_free(b);
return evpkey;
}
从内存中加载证书和私钥的方法并不仅有上文描述的方式,还有其他的方式,比如RSA私钥可以通过构建RSA结构来加载,ECC私钥可以通过构建EC_KEY结构的方式来加载,不过个人觉得上面的方法更为通用。