使用OpenSSL API编程验证证书链

31

这与其他问题非常相似,但我查看的那些要么没有答案,要么没有完全问同样的问题。我有一个自签名的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 = X509_verify_cert(ctx);

     /* check for errors and clean up */
}
我的问题是上面的代码总是返回“找不到颁发者证书”。我做错了什么?我相信我正在创建一个新的存储库,添加cacert,创建一个新的上下文,并使用指向包含CA的存储库的指针将要验证的子证书添加到上下文中。很明显我做错了什么,但我不确定是什么。 有什么想法吗? 更新:我知道我可以将这些证书保存到磁盘并使用类似X509_LOOKUP_file之类的东西。我正在寻找一种不必要地触摸磁盘的解决方案。

可能是 C 中的 X509 证书验证 的重复问题。 - jww
@noloader 这不是重复的问题,链接的问题没有使用内存证书和内存证书存储。而这个问题使用了。 - clemej
1
@clemej(不好意思,如果现在帮不上你的话!)我正在我们的商业应用程序中使用与您基本相同的代码,该应用程序使用封闭的CA系统。它对我们有用!让我们遇到困扰的事情是OpenSSL检查证书扩展;也许 X509_STORE_CTX_set_purpose(X509_PURPOSE_ANY) 可以帮助您,如果它被卡住了,因为您的CA证书没有使用公共CA使用的用途扩展之一?除此之外,我使用了与您完全相同的代码,并且它有效,因此差异必须是我生成证书的方式与您的方式有所不同。 - Nicholas Wilson
1
请查看此链接:http://openssl.6102.n7.nabble.com/trust-chain-building-for-X509-verify-cert-td26335.html。似乎需要调用“OpenSSL_add_all_algorithms()”。 - Étienne
另一个有用的代码示例 - Francois
显示剩余2条评论
6个回答

20
您可以使用常规验证例程(请参见如何验证公钥是否由您的私有CA发行?),就像OpenSSL中的-verify函数一样。您需要创建一个查找方法(X509_LOOKUP_METHOD),例如X509_LOOKUP_file(),但它使用字符字符串而不是文件名。 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);
}

4
定义一个全新的回调机制需要进行大量工作,但这似乎是唯一的方法。提供详细的示例代码可以获得额外加分。 - clemej
7
这段代码是“正确”的,但全部都是毫无用处的!这段代码中的核心调用是X509_STORE_add_cert,这正是OP最初使用的完全相同的API调用。然而,它被包裹在大量混淆的代码中,通过将其隐藏在X509_load_cert_buf函数内部,然后以非常间接的方式使用X509_LOOKUP_load_buf来调用它。这段代码与OP最初直接调用X509_STORE_add_cert相比没有任何优势。 - Nicholas Wilson
抱歉我提供了“无用”的代码!我的目的是复制 Open SSL 验证函数,这个函数是有效的,而且我的代码也是有效的。我没有试图比 Open SSL 的代码更好地优化它。 - Mr Ed
这并不是毫无用处的。是的,就OP的基本要求而言,它似乎很复杂,但实际上它会补充openssl的存储/查找功能。请参见新的功能请求 - klaus triendl

5
我遇到了这个问题,开始时使用的代码与OP非常接近。我的证书链包括3个证书: 证书1(根CA)颁发者:根CA 主题:根CA 证书2(签名CA)颁发者:根CA 主题:签名CA 证书3(设备)颁发者:签名CA 主题:设备
我想验证设备证书。我的ca.pem等效物(相对于OP)包含根CA和签名CA。
X509_verify_cert函数需要在X509_store中完整的证书链一直到根(根CA和签名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;
}

我不需要创建任何查找方法。对我来说,关键是从内存中的字符串循环遍历我的证书,这样我就拥有了完成链所需的所有证书。该字符串等同于我将为openssl verify选项-CAfile输入的内容。

此外,请确保在使用时您的X509指针不为空。


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

如果我们不知道错误代码,很难猜测实际问题。否则代码看起来没问题。


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 先生的解决方案的需要。


2
请看一下 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文件。

希望能对您有所帮助!


1
我有点困惑,为什么我在使用add_cert明确加载证书时还需要指定位置(文件或目录)? - clemej
我在OpenSSL中没有找到X590_STORE_add_cert()函数,它是从哪里来的?通常,您需要使用SSL_CTX_load_verify_locations()函数并提供PEM文件路径来验证证书链。 - Paul
http://www.umich.edu/~x509/ssleay/x509_store.html。但问题在于,这些文件不在磁盘上,它们已经以x509结构体的形式存储在内存中。我真的需要将它们写入磁盘才能进行验证吗? - clemej
嘿,SSleay是什么?OpenSSL说它是“OpenSSL的祖先包”,你为什么要使用它?这真的取决于你如何获取这些证书,如果它们位于磁盘上,那么使用我上面发布的函数,让它完成工作。如果你想验证服务器证书,那么这是一个不同的问题。 - Paul
2
我没有使用ssleay,但是这些函数是相同的,而且这是谷歌上排名最高的结果。感谢您抽出时间回答,但我真的希望有一个不需要触碰磁盘的解决方案。 - clemej

0
请查看官方源代码: apps/verify.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中找到。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接