使用 OpenSSL API 以编程方式验证证书链
Posted
技术标签:
【中文标题】使用 OpenSSL API 以编程方式验证证书链【英文标题】:Programmatically verify certificate chain using OpenSSL API 【发布时间】:2013-04-23 21:30:34 【问题描述】:这与其他问题非常相似,但我看过的问题要么没有答案,要么没有完全问同一个问题。我有一个自签名 CA 证书,以及使用该 CA 证书签名的另外两个证书。我相当确定证书是正确的,因为“openssl verify”有效:
$ openssl verify -CAfile ca.pem server.pem
server.pem: OK
(以上是凭记忆,我面前没有,所以可能有点偏)。
现在我想以编程方式验证证书。我有一个带有伪代码的实用函数:
int verify_cert(X509 *cert, X509 *cacert)
int ret;
X509_STORE *store;
X509_STORE_CTX *ctx;
store = X509_STORE_new();
X590_STORE_add_cert(store, cacert);
ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(ctx, store, cert, NULL);
ret = X590_verify_cert(ctx);
/* check for errors and clean up */
我的问题是上面的代码总是返回“找不到颁发者证书”。我做错了什么?我相信我正在创建一个新存储,添加 cacert,创建一个新上下文,并将要验证的子证书添加到上下文中,并带有指向包含 CA 的存储的指针。我很明显做错了什么,但我不确定是什么。
有什么想法吗?
更新:我知道我可以将这些证书保存到磁盘并使用 X509_LOOKUP_file 之类的东西或类似的东西。我正在寻找一种不会不必要地接触磁盘的解决方案。
【问题讨论】:
我也面临同样的问题 - 你找到解决方案了吗? @koch.trier 不,很遗憾不是。我现在把它放在次要位置,专注于其他事情。我仍然在这里寻找答案。 x509 certificate verification in C 的可能重复项 @clemej (抱歉,如果来晚了一年无法提供帮助!)我在使用封闭式 CA 系统的商业应用程序中使用与您基本相同的代码。它对我们有用!让我们感到困惑的是 OpenSSL 对证书扩展的检查。如果因为您的 CA 证书没有公共 CA 使用的使用扩展之一而被卡住,也许X509_STORE_CTX_set_purpose(X509_PURPOSE_ANY)
会帮助您?除此之外,我使用的字面意思是 相同 代码与您的代码相同,并且它可以工作,因此与您相比,我生成证书的方式必须有所不同。
查看此链接:openssl.6102.n7.nabble.com/… 似乎需要调用“OpenSSL_add_all_algorithms()”。
【参考方案1】:
请看SSL_CTX_load_verify_locations ()
函数:http://www.openssl.org/docs/ssl/SSL_CTX_load_verify_locations.html
SSL_CTX_load_verify_locations() 指定 ctx 的位置,在 用于验证目的的 CA 证书位于哪些位置。这 通过 CAfile 和 CApath 获得的证书是受信任的。
您可以生成一个包含 ca.pem server.pem 的 CA 证书文件:
#!/bin/sh
rm CAfile.pem
for i in ca.pem server.pem ; do
openssl x509 -in $i -text >> CAfile.pem
done
然后设置CAfile
变量指向CAfile.pem
文件。
希望对你有帮助!
【讨论】:
我很困惑,为什么在使用 add_cert 显式加载证书时需要指定位置(文件或目录)? 我在OpenSSL中没有找到任何X590_STORE_add_cert()函数,它是从哪里来的?通常你想使用带有 PEM 文件路径的 SSL_CTX_load_verify_locations() 函数来验证证书链。 umich.edu/~x509/ssleay/x509_store.html 。但这就是问题所在。这些文件不在磁盘上。它们已经在 x509 结构的内存中。我真的不需要将它们写入磁盘只是为了验证它们.. 是吗? 嘿,SSleay 是什么? OpenSSL 说它是“OpenSSL 的祖先包”,你为什么要使用它?这真的取决于你如何获得这些证书,如果它们位于光盘上,那么使用我上面发布的功能,让它完成工作。如果您想验证服务器证书,那么这是一个不同的问题。 我没有使用 ssleay,但功能相同,这是 Google 上的热门话题。感谢您花时间回答,但我真的更喜欢不涉及触摸磁盘的解决方案。【参考方案2】:您可以使用正常的验证例程(请参阅How do you verify a public key was issued by your private CA?),就像 OpenSSL 中的 -verify 函数一样。您需要创建一个类似于 X509_LOOKUP_file() 的查找方法 (X509_LOOKUP_METHOD),但它使用字符串而不是文件名。 X509_LOOKUP_buffer()的代码如下。
头文件by_buffer.h:
/* File: by_buffer.h */
#ifndef BY_BUFFER_H
#define BY_BUFFER_H
#include <openssl/x509.h>
#ifdef __cplusplus
extern "C"
#endif
#define X509_L_BUF_LOAD 1
#define X509_LOOKUP_load_buf(x,name,type) \
X509_LOOKUP_ctrl((x),X509_L_BUF_LOAD,(name),(long)(type),NULL)
X509_LOOKUP_METHOD *X509_LOOKUP_buffer(void);
#ifdef __cplusplus
#endif
#endif /* BY_BUFFER_H */
c程序by_buffer.c:
/* by_buffer.c - copied and modified from crypto/x509/by_file.c */
/* Copyright (C) - should be the same as for OpenSSL
*/
#include "by_buffer.h"
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include "../crypto/cryptlib.h"
#include <openssl/lhash.h>
#include <openssl/buffer.h>
#include <openssl/pem.h>
#include <openssl/err.h>
static int by_buffer_ctrl(X509_LOOKUP *ctx, int cmd, const char *argc,
long argl, char **ret);
X509_LOOKUP_METHOD x509_buffer_lookup=
"Load buffer into cache",
NULL, /* new */
NULL, /* free */
NULL, /* init */
NULL, /* shutdown */
by_buffer_ctrl, /* ctrl */
NULL, /* get_by_subject */
NULL, /* get_by_issuer_serial */
NULL, /* get_by_fingerprint */
NULL, /* get_by_alias */
;
X509_LOOKUP_METHOD *X509_LOOKUP_buffer(void)
return(&x509_buffer_lookup);
static int by_buffer_ctrl(X509_LOOKUP *ctx, int cmd, const char *argp, long argl,
char **ret)
int ok=0;
char *certBuf;
switch (cmd)
case X509_L_BUF_LOAD:
if (argl == X509_FILETYPE_DEFAULT)
X509err(X509_F_BY_FILE_CTRL,X509_R_LOADING_DEFAULTS);
else
if(argl == X509_FILETYPE_PEM)
ok = (X509_load_cert_crl_buf(ctx,argp,
X509_FILETYPE_PEM) != 0);
else
ok = (X509_load_cert_buf(ctx,argp,(int)argl) != 0);
break;
return(ok);
int X509_load_cert_buf(X509_LOOKUP *ctx, const char *certBuf, int type)
int ret=0;
BIO *in=NULL;
int i,count=0;
X509 *x=NULL;
if (certBuf == NULL) return(1);
in=BIO_new(BIO_s_mem());
if(in==NULL) goto err;
if (type == X509_FILETYPE_PEM)
for (;;)
x=PEM_read_bio_X509_AUX(in,NULL,NULL,NULL);
if (x == NULL)
if ((ERR_GET_REASON(ERR_peek_last_error()) ==
PEM_R_NO_START_LINE) && (count > 0))
ERR_clear_error();
break;
else
X509err(X509_F_X509_LOAD_CERT_FILE,
ERR_R_PEM_LIB);
goto err;
i=X509_STORE_add_cert(ctx->store_ctx,x);
if (!i) goto err;
count++;
X509_free(x);
x=NULL;
ret=count;
else if (type == X509_FILETYPE_ASN1)
x=d2i_X509_bio(in,NULL);
if (x == NULL)
X509err(X509_F_X509_LOAD_CERT_FILE,ERR_R_ASN1_LIB);
goto err;
i=X509_STORE_add_cert(ctx->store_ctx,x);
if (!i) goto err;
ret=i;
else
X509err(X509_F_X509_LOAD_CERT_FILE,X509_R_BAD_X509_FILETYPE);
goto err;
err:
if (x != NULL) X509_free(x);
if (in != NULL) BIO_free(in);
return(ret);
int X509_load_crl_buf(X509_LOOKUP *ctx, const char *certBuf, int type)
int ret=0;
BIO *in=NULL;
int i,count=0;
X509_CRL *x=NULL;
if (certBuf == NULL) return(1);
//in=BIO_new(BIO_s_file_internal());
in=BIO_new(BIO_s_mem());
if(in==NULL) goto err;
if (type == X509_FILETYPE_PEM)
for (;;)
x=PEM_read_bio_X509_CRL(in,NULL,NULL,NULL);
if (x == NULL)
if ((ERR_GET_REASON(ERR_peek_last_error()) ==
PEM_R_NO_START_LINE) && (count > 0))
ERR_clear_error();
break;
else
X509err(X509_F_X509_LOAD_CRL_FILE,
ERR_R_PEM_LIB);
goto err;
i=X509_STORE_add_crl(ctx->store_ctx,x);
if (!i) goto err;
count++;
X509_CRL_free(x);
x=NULL;
ret=count;
else if (type == X509_FILETYPE_ASN1)
x=d2i_X509_CRL_bio(in,NULL);
if (x == NULL)
X509err(X509_F_X509_LOAD_CRL_FILE,ERR_R_ASN1_LIB);
goto err;
i=X509_STORE_add_crl(ctx->store_ctx,x);
if (!i) goto err;
ret=i;
else
X509err(X509_F_X509_LOAD_CRL_FILE,X509_R_BAD_X509_FILETYPE);
goto err;
err:
if (x != NULL) X509_CRL_free(x);
if (in != NULL) BIO_free(in);
return(ret);
int X509_load_cert_crl_buf(X509_LOOKUP *ctx, const char *certBuf, int type)
STACK_OF(X509_INFO) *inf;
X509_INFO *itmp;
BIO *in;
int i, count = 0;
if(type != X509_FILETYPE_PEM)
return X509_load_cert_buf(ctx, certBuf, type);
in = BIO_new(BIO_s_mem());
if(!in)
X509err(X509_F_X509_LOAD_CERT_CRL_FILE,ERR_R_SYS_LIB);
return 0;
BIO_write(in, certBuf, strlen(certBuf));
inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL);
BIO_free(in);
if(!inf)
X509err(X509_F_X509_LOAD_CERT_CRL_FILE,ERR_R_PEM_LIB);
return 0;
for(i = 0; i < sk_X509_INFO_num(inf); i++)
itmp = sk_X509_INFO_value(inf, i);
if(itmp->x509)
X509_STORE_add_cert(ctx->store_ctx, itmp->x509);
count++;
if(itmp->crl)
X509_STORE_add_crl(ctx->store_ctx, itmp->crl);
count++;
sk_X509_INFO_pop_free(inf, X509_INFO_free);
return count;
调用上述例程的C++例程:
#include "by_buffer.h"
static int check(X509_STORE *ctx, const char *certBuf);
static X509 *load_cert(const char *certBuf);
int validateKey(const char *rsaKeyCA, const char *rsaCertificate)
int ret=0;
X509_STORE *cert_ctx=NULL;
X509_LOOKUP *lookup=NULL;
cert_ctx=X509_STORE_new();
if (cert_ctx == NULL) goto end;
OpenSSL_add_all_algorithms();
lookup=X509_STORE_add_lookup(cert_ctx,X509_LOOKUP_buffer());
if (lookup == NULL)
goto end;
if(!X509_LOOKUP_load_buf(lookup,rsaKeyCA,X509_FILETYPE_PEM))
goto end;
lookup=X509_STORE_add_lookup(cert_ctx,X509_LOOKUP_hash_dir());
if (lookup == NULL)
goto end;
X509_LOOKUP_add_dir(lookup,NULL,X509_FILETYPE_DEFAULT);
ret = check(cert_ctx, rsaCertificate);
end:
if (cert_ctx != NULL) X509_STORE_free(cert_ctx);
return ret;
static X509 *load_cert(const char *certBuf)
X509 *x=NULL;
BIO *cert;
if ((cert=BIO_new(BIO_s_mem())) == NULL)
goto end;
BIO_write(cert, certBuf, strlen(certBuf));
x=PEM_read_bio_X509_AUX(cert,NULL, NULL, NULL);
end:
if (cert != NULL) BIO_free(cert);
return(x);
static int check(X509_STORE *ctx, const char *certBuf)
X509 *x=NULL;
int i=0,ret=0;
X509_STORE_CTX *csc;
x = load_cert(certBuf);
if (x == NULL)
goto end;
csc = X509_STORE_CTX_new();
if (csc == NULL)
goto end;
X509_STORE_set_flags(ctx, 0);
if(!X509_STORE_CTX_init(csc,ctx,x,0))
goto end;
////// See crypto/asn1/t_x509.c for ideas on how to access and print the values
//printf("X.509 name: %s\n", x->name);
i=X509_verify_cert(csc);
X509_STORE_CTX_free(csc);
ret=0;
end:
ret = (i > 0);
if (x != NULL)
X509_free(x);
return(ret);
【讨论】:
定义一个全新的回调机制需要做大量的工作。但这似乎也是唯一的方法。大量示例代码的奖励积分。 这段代码是“正确的”,但完全没用!此代码中的中心调用是X509_STORE_add_cert
,这与 OP 最初使用的 API 调用完全相同。它被包裹在一堆模糊不清的 gloop 中,将其隐藏在 X509_load_cert_buf
函数中,然后使用 X509_LOOKUP_load_buf
以非常间接的方式调用它。与直接调用 X509_STORE_add_cert
的 OP 原始代码相比,此代码没有任何优势。
对不起,我提供了“无用”的代码!我想要做的是复制 openssl 验证功能,该功能与我的代码一样有效。我没有尝试比 OpenSSL 的代码更好地优化它。
一点用处都没有。是的,就 OP 的基本要求而言,它似乎令人费解,但实际上它会补充 openssl 的存储/查找功能。查看新的feature request。【参考方案3】:
可能答案(没有代表点添加评论,抱歉):SSL_CTX_load_verify_locations(3)
的手册页说,
When building its own certificate chain, an OpenSSL client/server will try to fill in
missing certificates from CAfile/CApath, if the certificate chain was not explicitly
specified (see SSL_CTX_add_extra_chain_cert(3), SSL_CTX_use_certificate(3).
(未能匹配他们的括号,不是我的。)
这似乎意味着,作为SSL_CTX_load_verify_locations(3)
的替代方案,应该可以使用SSL_CTX_add_extra_chain_cert(3)
或SSL_CTX_use_certificate(3)
——两者都采用X509 *
参数。从而消除了对 Ed 先生解决方案的需要,如上所示。
【讨论】:
【参考方案4】:我认为,您可以使用“X509_STORE_set_verify_cb”添加回调来识别实际错误:
static int verify_cb(int ok, X509_STORE_CTX *ctx)
if (!ok)
/* check the error code and current cert*/
X509 *currentCert = X509_STORE_CTX_get_current_cert(ctx);
int certError = X509_STORE_CTX_get_error(ctx);
int depth = X509_STORE_CTX_get_error_depth(ctx);
printCert(currentCert);
printf("Error depth %d, certError %d", depth, certError)
return(ok);
int verify_cert(X509 *cert, X509 *cacert)
int ret;
X509_STORE *store;
X509_STORE_CTX *ctx;
store = X509_STORE_new();
X509_STORE_set_verify_cb(store, verify_cb);
X590_STORE_add_cert(store, cacert);
ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(ctx, store, cert, NULL);
ret = X590_verify_cert(ctx);
/* check for errors and clean up */
除非我们知道错误代码,否则很难猜出实际问题。否则代码看起来没问题。
【讨论】:
【参考方案5】:我自己也遇到了这个问题,并从非常接近 OP 的代码开始。我的证书链包括 3 个证书: 证书 1 (root-ca) 颁发者:root-ca 主题:root-ca 证书 2 (signing-ca) 颁发者:root-ca 主题:signing-ca 证书 3(设备)颁发者:signing-ca 主题:设备
我想验证设备证书。我的 ca.pem 等效项(wrt OP)包含 root-ca 和 signing-ca。
X509_verify_cert 函数需要整个证书链一直到 X509_store 中的根 (root-ca & signing-ca)。
以下是适用于我的代码。省略了对返回值的检查以精简代码。
int getIssuerCert(X509_STORE *x509_store)
STACK_OF(X509_INFO) *inf;
X509_INFO *itmp;
BIO *in;
int i, count = 0;
in = BIO_new(BIO_s_mem());
BIO_write(in, issuerCertStr, strlen(issuerCertStr)); //string containing root-ca & signing-ca
inf = PEM_X509_INFO_read_bio(in, NULL, NULL, NULL);
if(in != NULL) BIO_free(in);
for(i = 0; i < sk_X509_INFO_num(inf); i++)
itmp = sk_X509_INFO_value(inf, i);
if(itmp->x509)
X509_STORE_add_cert(x509_store, itmp->x509);
count++;
if(itmp->crl)
X509_STORE_add_crl(x509_store, itmp->crl);
count++;
sk_X509_INFO_pop_free(inf, X509_INFO_free);
return 0;
int verify_cert()
int ret = 0;
X509 *devCert = NULL;
X509_STORE *x509_store = NULL;
X509_STORE_CTX *x509_store_ctx = NULL;
OpenSSL_add_all_algorithms();
devCert = getDeviceCert(); // Returns X509 pointer
x509_store = X509_STORE_new();
X509_STORE_set_verify_cb(x509_store, verify_cb);
X509_STORE_set_flags(x509_store, 0);
x509_store_ctx = X509_STORE_CTX_new();
X509_STORE_CTX_init(x509_store_ctx, x509_store, devCert, NULL)
X509_STORE_CTX_set_purpose(x509_store_ctx, X509_PURPOSE_ANY);
ret = X509_verify_cert(x509_store_ctx);
if(x509_store_ctx != NULL) X509_STORE_CTX_free(x509_store_ctx);
if(x509_store != NULL) X509_STORE_free(x509_store);
if(devCert != NULL) X509_free(devCert);
EVP_cleanup();
return ret;
我不需要创建任何查找方法。对我来说,关键是从内存中的字符串中循环遍历我的证书,因此我拥有完成链所需的所有证书。该字符串相当于我为选项 -CAfile 输入 openssl verify 的字符串。
另外,请确保您的 X509 指针在使用时不为空。
【讨论】:
【参考方案6】:查看官方源码: 应用程序/验证.c
static int check(X509_STORE *ctx, const char *file,
STACK_OF(X509) *uchain, STACK_OF(X509) *tchain,
STACK_OF(X509_CRL) *crls, int show_chain);
您可以在此处查看如何输出“OK”:
if (i > 0 && X509_STORE_CTX_get_error(csc) == X509_V_OK)
printf("%s: OK\n", (file == NULL) ? "stdin" : file);
函数依赖可以在apps/apps.c中找到
【讨论】:
以上是关于使用 OpenSSL API 以编程方式验证证书链的主要内容,如果未能解决你的问题,请参考以下文章