Программная проверка цепочки сертификатов с помощью OpenSSL API

Это очень похоже на другие вопросы, но те, на которые я смотрел, либо не имеют ответа, либо задают не совсем тот же вопрос. У меня есть самозаверяющий сертификат ЦС и два других сертификата, подписанных этим сертификатом ЦС. Я почти уверен, что сертификаты верны, потому что 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 или что-то подобное. Ищу решение, которое не трогает диск без надобности.


person clemej    schedule 30.04.2013    source источник
comment
У меня та же проблема - вы еще не нашли решение?   -  person koch.trier    schedule 13.05.2013
comment
@ koch.trier нет, к сожалению, нет. Я отложил это на задний план и сосредоточился на других вещах. Я все еще ищу здесь ответ.   -  person clemej    schedule 14.05.2013
comment
возможный дубликат проверки сертификата x509 на C   -  person jww    schedule 03.03.2014
comment
@noloader Это не дубликат, связанный вопрос не использует сертификаты в памяти и хранилище сертификатов в памяти. Это делает.   -  person clemej    schedule 03.03.2014
comment
@clemej (извините, если на год слишком поздно, чтобы помочь!) Я использую, по сути, идентичный вам код в нашем коммерческом приложении, которое использует закрытую систему CA. У нас работает! Что нас поразило, так это проверка OpenSSL расширений сертификатов; возможно X509_STORE_CTX_set_purpose(X509_PURPOSE_ANY) поможет вам, если он застрял из-за того, что в вашем сертификате ЦС нет одного из расширений использования, используемых общедоступными ЦС? Кроме того, я использую буквально идентичный код с вашим, и он работает, поэтому разница должна заключаться в том, как я генерирую сертификаты по сравнению с вами.   -  person Nicholas Wilson    schedule 04.11.2014
comment
Проверьте эту ссылку: openssl. 6102.n7.nabble.com/ Кажется, нужно вызвать OpenSSL_add_all_algorithms ().   -  person Étienne    schedule 01.12.2014


Ответы (6)


Вы можете использовать обычные процедуры проверки (см. Как вы проверяете, что открытый ключ был выдан вашим частным центром сертификации?), как это делает функция -verify в OpenSSL. Вам необходимо создать метод поиска (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);
}
person Mr Ed    schedule 09.06.2013
comment
Это ужасно много работы, чтобы определить совершенно новый механизм обратного вызова. Но это также кажется единственным выходом. Бонусные баллы за обширный пример кода. - person clemej; 11.06.2013
comment
Этот код правильный, но все это совершенно бесполезно! Центральным вызовом в этом коде является X509_STORE_add_cert, который представляет собой точно такой же вызов API, который первоначально использовал OP. Хотя он был завернут в груды запутывающего глобуса, спрятав его внутри функции X509_load_cert_buf, а затем вызвав это очень косвенным образом с помощью X509_LOOKUP_load_buf. Этот код не дает никаких преимуществ по сравнению с исходным кодом OP, который просто вызывает X509_STORE_add_cert напрямую. - person Nicholas Wilson; 04.11.2014
comment
Извините, что предоставил бесполезный код! Я хотел продублировать функцию проверки openssl, которая работает, как и МОЙ код. Я не пытался оптимизировать его лучше, чем код OpenSSL. - person Mr Ed; 29.12.2014
comment
Это совсем не бесполезно. Да, это кажется запутанным в отношении основных требований OP, но на самом деле это дополнит функциональность openssl store / lookup. См. Новый запрос функции. - person klaus triendl; 14.10.2020

Я думаю, вы можете использовать «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 */
}

Если мы не знаем код ошибки, трудно угадать реальную проблему. В остальном код выглядит нормально.

person Riyadh    schedule 20.01.2016

Я сам столкнулся с этой проблемой и начал с кода, очень близкого к OP. Моя цепочка сертификатов включала 3 сертификата: Сертификат 1 (root-ca) Эмитент: root-ca Тема: root-ca Сертификат 2 (подписывание-ca) Эмитент: root-ca Тема: подписывание-ca Сертификат 3 (устройство) Эмитент: подписывание- ca Тема: устройство

Я хотел проверить сертификат устройства. Мой эквивалент ca.pem (wrt OP) содержал root-ca и signed-ca.

Для функции X509_verify_cert требуется вся цепочка сертификатов вплоть до корня (root-ca и подписывание-ca) в X509_store.

Ниже мой код, который мне подходит. Проверки возвращаемых значений были опущены, чтобы наклонить код вниз.

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 не равны нулю при использовании.

person Steve Miskovetz    schedule 04.10.2016

возможный ответ (не набирайте репутацию, чтобы добавить комментарий, извините): на странице руководства для 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 * arg. Таким образом устраняется необходимость решения г-на Эда, как показано выше.

person querentns    schedule 20.03.2015

Обратите внимание на функцию 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, содержащий как ca.pem server.pem:

 #!/bin/sh
 rm CAfile.pem
 for i in ca.pem server.pem ; do
   openssl x509 -in $i -text >> CAfile.pem
 done

Затем установите переменную CAfile, чтобы она указывала на CAfile.pem файл.

Надеюсь, поможет !

person Paul    schedule 30.04.2013
comment
Я запутался, зачем мне указывать местоположение (файл или каталог), когда я загружаю сертификаты явно с помощью add_cert? - person clemej; 30.04.2013
comment
Я не нашел ни одной функции X590_STORE_add_cert () в OpenSSL, откуда она взялась? Обычно вы хотите использовать функцию SSL_CTX_load_verify_locations () с путем к файлу PEM для проверки цепочки сертификатов. - person Paul; 30.04.2013
comment
umich.edu/~x509/ssleay/x509_store.html. Но вот в чем загвоздка. Этих файлов нет на диске. Они уже находятся в памяти в структурах x509. Мне не нужно записывать их на диск только для того, чтобы проверить их ... А? - person clemej; 30.04.2013
comment
эй, что такое SSleay? OpenSSL утверждает, что это пакет-предок OpenSSL, почему вы его используете? Это действительно зависит от того, как вы получаете эти сертификаты, если они находятся на диске, тогда используйте функцию, которую я опубликовал выше, пусть она сделает свою работу. если вы хотите проверить сертификат сервера, то это уже другая проблема. - person Paul; 30.04.2013
comment
Я не использую ssleay, но функции те же, и это лучший хит в Google. Спасибо, что нашли время ответить, но я бы предпочел решение, не касающееся диска. - person clemej; 30.04.2013

См. Официальный исходный код: 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);

Вы можете увидеть, как вывести «ОК» здесь:

    if (i > 0 && X509_STORE_CTX_get_error(csc) == X509_V_OK) {
        printf("%s: OK\n", (file == NULL) ? "stdin" : file);

Зависимость функции можно найти в apps / apps.c

person vrqq    schedule 15.06.2020