简体   繁体   中英

OpenSSL error: unable to verify the first certificate

I've been trying to make a simple client/server echo application for the sake of getting to know OpenSSL. I've been following Network Security with OpenSSL by John Viega, Matt Messier, Pravir Chandra (O'Reilly). Their example shows how to set up a root certificate authority, a server CA signed with the root CA, then server certificate signed with the server CA and, finally, a client certificate signed with the root CA and then import them in the C program. I have made small changes to the program (such as not using threads at all) but most of it is their. My idea was to learn as I try this out but I've come to a problem. When I try to connect the two, I get an error message saying "unable to get local issuer certificate" (error 20). I would also like to state that the "previous version" worked (meaning that they progress in their examples). Not sure if the problem is in the code, the certificates or both. My OpenSSL version is OpenSSL 1.0.1f 6 Jan 2014. (as returned by the openssl version command)

My knowledge in this matter is very shallow at the moment and I've not had much luck finding an answer. I realize that I still have much reading to do and I will do so but this is a problem I need to overcome. int seed_prng(int bytes) function is mine, a fix to the problem of not finding the seed_prng(void) function in all these libraries (also one of the things I found that is not fitting).

Perhaps, as this tutorial is 14 years old (at least), it is outdated and I should follow another one? If so, please recommend a tutorial with a high detail level, since the documentation of OpenSSL is not very friendly, in my experience.

Below, I provide all the files included in the process (except for the Makefile.)

The flags I use for the compilation are: -L/usr/lib -lssl -lcrypto -Wall

common.h :

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/rsa.h>
#include <openssl/objects.h>


#define PORT "8081"
#define SERVER "localhost"
#define CLIENT "localhost"
#define int_error(msg) handle_error(__FILE__, __LINE__, msg)

//Do not confuse with SSL_METHOD ; TLSv1_method() is also a viable option here
#define SSL_METHOD_ SSLv23_method()
#define SEED_PRNG_() seed_prng(30) 

#define DEFAULT_DEPTH_ 4

#define DATA_SIZE_ 256


void handle_error(const char *file, int lineno, const char *msg) ;
void init_OpenSSL(void) ;
int seed_prng(int bytes) ;
int verify_callback(int ok, X509_STORE_CTX *store) ;
long post_connection_check(SSL *ssl, char *host) ;

common.c :

#include "common.h"

void handle_error(const char *file, int lineno, const char *msg)
{
    fprintf(stderr, "** %s:%i %s\n", file, lineno, msg);
    ERR_print_errors_fp(stderr);
    exit(-1);
}
void init_OpenSSL(void)
{
    if (!SSL_library_init())
    {
        fprintf(stderr, "** OpenSSL initialization failed!\n");
        exit(-1);
    }
    SSL_load_error_strings();
}

//Not sure if this is good idea! Have to do some research
int seed_prng(int bytes)
{
    if (!RAND_load_file("/dev/urandom", bytes))
    {
        return 0;
    }

    return 1;
}

int verify_callback(int ok, X509_STORE_CTX *store)
{
    char data[DATA_SIZE_];
    if (!ok)
    {
        X509 *cert = X509_STORE_CTX_get_current_cert(store);
        int depth = X509_STORE_CTX_get_error_depth(store);
        int err = X509_STORE_CTX_get_error(store);
        fprintf(stderr, "-Error with certificate at depth: %i\n", depth);
        X509_NAME_oneline(X509_get_issuer_name(cert), data, 256);
        fprintf(stderr, " issuer = %s\n", data);
        X509_NAME_oneline(X509_get_subject_name(cert), data, 256);
        fprintf(stderr, " subject = %s\n", data);
        fprintf(stderr, " err %i:%s\n", err,
        X509_verify_cert_error_string(err));
    }
    return ok;
}

long post_connection_check(SSL *ssl, char *host)
{
    X509 *cert;
    X509_NAME *subj;
    char data[DATA_SIZE_];
    int extcount;
    int ok = 0;

    if (!(cert = SSL_get_peer_certificate(ssl)) || !host)
    {
        if (cert)
        {
            X509_free(cert);
        }
        return X509_V_ERR_APPLICATION_VERIFICATION;
    }
    if ((extcount = X509_get_ext_count(cert)) > 0)
    {
        int i;
        for (i = 0; i < extcount; i++)
        {
            const char *extstr;
            X509_EXTENSION *ext;
            ext = X509_get_ext(cert, i);
            extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
            if (!strcmp(extstr, "subjectAltName"))
            {
                int j;
                unsigned char *data;
                STACK_OF(CONF_VALUE) *val;
                CONF_VALUE *nval;
                const X509V3_EXT_METHOD *meth;
                if (!(meth = X509V3_EXT_get(ext)))
                {
                    break;
                }
                data = ext->value->data;
                val = meth->i2v(meth,(meth->d2i(NULL, (const unsigned char **)(&data), ext->value->length)), NULL);
                for (j = 0; j < sk_CONF_VALUE_num(val); j++)
                {
                    nval = sk_CONF_VALUE_value(val, j);
                    if (!strcmp(nval->name, "DNS") && !strcmp(nval->value, host))
                    {
                        ok = 1;
                        break;
                    }
                }
            }
            if (ok)
            {
                break;
            }
        }
    }
    if (!ok && (subj = X509_get_subject_name(cert)) && X509_NAME_get_text_by_NID(subj, NID_commonName, data, DATA_SIZE_) > 0)
    {
        data[DEFAULT_DEPTH_ - 1] = 0;
        if (strcasecmp(data, host) != 0)
        {
            if (cert)
            {
                X509_free(cert);
            }
            return X509_V_ERR_APPLICATION_VERIFICATION;
        }
    }
    X509_free(cert);
    return SSL_get_verify_result(ssl);
}

server.c :

#include "common.h"


#define BUF_SIZE_ 80

#define CERTFILE "certificates/serverNOKEY.pem"
#define CAFILE "certificates/rootcert.pem"
#define CADIR NULL


SSL_CTX *setup_server_ctx(void)
{
    SSL_CTX *ctx;

    //This specifies that either SSL or TLS can be used
    //Later, we will "filter" out SSLv2
    ctx = SSL_CTX_new(SSLv23_method());

    //These two functions are used to load trusted CAs
    if (SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR) != 1)
    {
        int_error("Error loading CA file and/or directory");
    }
    if (SSL_CTX_set_default_verify_paths(ctx) != 1)
    {
        int_error("Error loading default CA file and/or directory");
    }

    //This loads a certificate from a file
    if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
    {
            int_error("Error loading certificate from file");
    }
    //This loads a private key (in our code, from the same file but I think that it is not necessary)
    if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
    {
            int_error("Error loading private key from file");
    }
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback);
    SSL_CTX_set_verify_depth(ctx, DEFAULT_DEPTH_);

    return ctx;
}

int do_server_loop(SSL *ssl)
{
    int err, nread;
    char buf[BUF_SIZE_];
    //I think that this reading should be redone 'cause there's something wrong
    do
    {
        for (nread = 0; nread < sizeof(buf) - 1; nread += err)
        {
            err = SSL_read(ssl, buf + nread, sizeof(buf) - nread);
            printf("read %d bytes\n", err);
            if (err <= 0)
            {
                break;
            }
        }
        fwrite(buf, sizeof(char), nread, stdout);

    } while (err > 0);
    return (SSL_get_shutdown(ssl) & SSL_RECEIVED_SHUTDOWN) ? 1 : 0;
}


void server_part(SSL *ssl)
{
    long err;
    //accepting connection from ssl object (structure)
    if (SSL_accept(ssl) <= 0)
    {
        int_error("Error accepting SSL connection");
    }
    if ((err = post_connection_check(ssl, CLIENT)) != X509_V_OK)
    {
        fprintf(stderr, "-Error: peer certificate: %s\n",
        X509_verify_cert_error_string(err));
        int_error("Error checking SSL object after connection");
    }
    fprintf(stderr, "SSL Connection opened\n");
    //As I figured, SSL_shutdown is a clean way to go but SSL_clear 
    //will force the closing of the communication if it wasn't closed cleanly
    if (do_server_loop(ssl))
    {
        //See this https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html
        SSL_shutdown(ssl);
    }
    else
    {
        // https://www.openssl.org/docs/manmaster/ssl/SSL_clear.html
        SSL_clear(ssl);
    }
    fprintf(stderr, "SSL Connection closed\n");
    SSL_free(ssl);  
}


int main(int argc, char *argv[])
{
    BIO *acc, *client;
    SSL *ssl;
    SSL_CTX *ctx;

    init_OpenSSL();

    //This is my function, gotta investigate it and see what should be there (maybe I got it right?)
    SEED_PRNG_();


    ctx = setup_server_ctx();
    acc = BIO_new_accept(PORT);
    if (!acc)
    {
        int_error("Error creating server socket");
    }
    if (BIO_do_accept(acc) <= 0)
    {
        int_error("Error binding server socket");
    }
    for (;;)
    {
        if (BIO_do_accept(acc) <= 0)
        {
            int_error("Error accepting connection");
        }
        client = BIO_pop(acc);
        if (!(ssl = SSL_new(ctx)))
        {
            int_error("Error creating SSL context");
        }
        SSL_set_bio(ssl, client, client);
        server_part(ssl);
    }
    SSL_CTX_free(ctx);
    BIO_free(acc);
    return 0;
}

client.c :

#include "common.h"

#define BUF_SIZE_ 80

#define CERTFILE "certificates/clientNOKEY.pem"
#define CAFILE "certificates/rootcert.pem"
#define CADIR NULL

SSL_CTX *setup_client_ctx(void)
{
    SSL_CTX *ctx;
    ctx = SSL_CTX_new(SSL_METHOD_);

    if (SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR) != 1)
    {   
        int_error("Error loading CA file and/or directory");
    }
    if (SSL_CTX_set_default_verify_paths(ctx) != 1)
    {
        int_error("Error loading default CA file and/or directory");
    }
    if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
    {
        int_error("Error loading certificate from file");
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
    {
        int_error("Error loading private key from file");
    }
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
    SSL_CTX_set_verify_depth(ctx, DEFAULT_DEPTH_);

    return ctx;
}

int do_client_loop(SSL *ssl)
{
    int err, nwritten;
    char buf[BUF_SIZE_];
    for (;;)
    {
        if (!fgets(buf, sizeof(buf), stdin))
        {
            break;
        }
        //printf("ucitao: %s\n", buf);
        for (nwritten = 0; nwritten < sizeof(buf); nwritten += err)
        {

            err = SSL_write(ssl, buf + nwritten, strlen(buf) - nwritten);
            if (err <= 0)
            {
                return 0;
            }
            else
            {
                printf("Sent: %s\n", buf);
            }
        }

    }
    return 1;
}

int main(int argc, char *argv[])
{
    BIO *conn;
    SSL *ssl;
    SSL_CTX *ctx; 
    long err;

    init_OpenSSL();
    SEED_PRNG_();

    ctx = setup_client_ctx();

    conn = BIO_new_connect(SERVER ":" PORT);
    if (!conn)
    {
        int_error("Error creating connection BIO");
    }
    if (BIO_do_connect(conn) <= 0)
    {
        int_error("Error connecting to remote machine");
    }
    if (!(ssl = SSL_new(ctx)))
    int_error("Error creating an SSL context");
    SSL_set_bio(ssl, conn, conn);
    if (SSL_connect(ssl) <= 0)
    {
        int_error("Error connecting SSL object");
    }
    if ((err = post_connection_check(ssl, SERVER)) != X509_V_OK)
    {
        fprintf(stderr, "-Error: peer certificate: %s\n",
        X509_verify_cert_error_string(err));
        int_error("Error checking SSL object after connection");
    }
    fprintf(stderr, "SSL Connection opened\n");
    if (do_client_loop(ssl))
    {
        SSL_shutdown(ssl);
    }
    else
    {
        SSL_clear(ssl);
    }
    fprintf(stderr, "SSL Connection closed\n");
    SSL_free(ssl);
    SSL_CTX_free(ctx);
    return 0;
}

The script I used to create the CAs, certificates and sign them:

#! /bin/bash

echo "Begin"

#the line below ensures that the script finishes after an unsuccessful command, not trying to execute any next command,
#since it's the assumption that every previous command was successful 
set -e

echo "Creating the root CA"
openssl req -newkey rsa:1024 -sha1 -keyout rootkey.pem -out rootreq.pem
openssl x509 -req -in rootreq.pem -sha1 -extfile myopenssl.cnf -extensions v3_ca -signkey rootkey.pem -out rootcert.pem
cat rootcert.pem rootkey.pem > root.pem
openssl x509 -subject -issuer -noout -in root.pem

echo "Creating the server CA and signing it with the root CA"
openssl req -newkey rsa:1024 -sha1 -keyout serverCAkey.pem -out serverCAreq.pem
openssl x509 -req -in serverCAreq.pem -sha1 -extfile myopenssl.cnf -extensions v3_ca -CA root.pem -CAkey root.pem -CAcreateserial -out serverCAcert.pem
cat serverCAcert.pem serverCAkey.pem rootcert.pem > serverCA.pem
openssl x509 -subject -issuer -noout -in serverCA.pem

echo "Creating the server\'s certificate and signing it with the server CA"
openssl req -newkey rsa:1024 -sha1 -keyout serverkey.pem -out serverreq.pem
openssl x509 -req -in serverreq.pem -sha1 -extfile myopenssl.cnf -extensions usr_cert -CA serverCA.pem -CAkey serverCA.pem -CAcreateserial -out servercert.pem
cat servercert.pem serverkey.pem serverCAcert.pem rootcert.pem > server.pem
openssl x509 -subject -issuer -noout -in server.pem

echo "Creating the client certificate and signing it with the root CA"
openssl req -newkey rsa:1024 -sha1 -keyout clientkey.pem -out clientreq.pem
openssl x509 -req -in clientreq.pem -sha1 -extfile myopenssl.cnf -extensions usr_cert -CA root.pem -CAkey root.pem -CAcreateserial -out clientcert.pem
cat clientcert.pem clientkey.pem rootcert.pem > client.pem
openssl x509 -subject -issuer -noout -in client.pem

echo "creating dh512.pem and dh1024.pem"
openssl dhparam -check -text -5 512 -out dh512.pem
openssl dhparam -check -text -5 1024 -out dh1024.pem

echo "Making new keys which do not require passkey"

echo "Creating no passkey server key"
openssl rsa -in server.pem -out serverNOKEY.pem
openssl x509 -in server.pem >>serverNOKEY.pem
echo "serverNOKEY.pem created"

echo "Creating no passkey client key"
openssl rsa -in client.pem -out clientNOKEY.pem
openssl x509 -in client.pem >>clientNOKEY.pem
echo "clientNOKEY.pem created"

echo "Done! Success!"

I faced similar issue when I was setting up SSL Communications between java applications. To determine whether its a code issue or certificate issue itself you can run below openssl command. If certificate isn't getting verified it means there is problems in verifying certificates using root CA.

Verify a certificate

openssl verify certificate_name

Reference .

Also, as it seems you are working on self-signed certificate you can switch off verification of certificates in your code and proceed. In production environment when certificates are signed by Certificate Authority (CA) such errors are less likely to appear.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM