【问题标题】:How to do mutual tls authentication in C code using openssl?如何使用openssl在C代码中进行相互tls认证?
【发布时间】:2019-05-25 22:25:52
【问题描述】:

我将尝试使用 openssl 进行双向身份验证的 TLS。

但是,如下图的输出结果,客户端可以接收到服务器证书并输出,但是服务器并没有收到客户端证书。

我的工作细节如下。

  1. 服务器和客户端证书生成(无需通过 CA 进行证书签名,只需自签名)

(1) 生成服务器密钥和证书。

$ openssl genrsa -des3 -out server.key 2048

$ openssl req -new -key server.key -out server.csr

$ cp server.key server.key.origin

$ openssl rsa -in server.key.origin -out server.key

$ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

(2) 生成客户端密钥和证书。 $ openssl genrsa -des3 -out client.key 2048

$ openssl req -new -key client.key -out client.csr

$ cp client.key client.key.origin

$ openssl rsa -in client.key.origin -out client.key

$ openssl x509 -req -days 365 -in client.csr -signkey client.key -out client.crt

// 服务器.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/rsa.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define HOME "./"
#define CERTF HOME "server.crt"
#define KEYF HOME "server.key"

#define CHK_NULL(x) if((x) == NULL) exit(1);
#define CHK_ERR(err, s) if((err) == -1) { perror(s); exit(1); }
#define CHK_SSL(err) if((err) == -1) { ERR_print_errors_fp(stderr); exit(2); }

int main(void) {
int err;
    int listen_sd;
    int sd;
    struct sockaddr_in sa_serv;
    struct sockaddr_in sa_cli;
    size_t client_len;

    SSL_CTX  *ctx;
    SSL    *ssl;
    X509                *client_cert;
    char                *str;
    char                buf[4096];
    SSL_METHOD  *meth;

    SSL_load_error_strings();
    SSLeay_add_ssl_algorithms();
    meth = TLSv1_2_server_method();
    ctx = SSL_CTX_new(meth);

    if(!ctx) {
        ERR_print_errors_fp(stderr);
        exit(2);
    }

    if(SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(3);
    }

    if(SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(4);
    }

    if(!SSL_CTX_check_private_key(ctx)) {
        fprintf(stderr, "Private key does not match the certificate public keyn");
        exit(5);
    }

    listen_sd = socket(AF_INET, SOCK_STREAM, 0);
    CHK_ERR(listen_sd, "socket");

    memset(&sa_serv, 0x00, sizeof(sa_serv));
    sa_serv.sin_family = AF_INET;
    sa_serv.sin_addr.s_addr = INADDR_ANY;
    sa_serv.sin_port = htons(1111);

    err = bind(listen_sd, (struct sockaddr*)&sa_serv, sizeof(sa_serv));
    CHK_ERR(err, "bind");

    err = listen(listen_sd, 5);
    CHK_ERR(err, "listen");

    client_len = sizeof(sa_cli);
    sd = accept(listen_sd, (struct sockaddr*)&sa_cli, &client_len);
    CHK_ERR(sd, "accept");
    close(listen_sd);

    ssl = SSL_new(ctx);
    CHK_NULL(ssl);
    SSL_set_fd(ssl, sd);

// to request client's certificate
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

    err = SSL_accept(ssl);
    CHK_SSL(err);

    printf("SSL connection using %s \n", SSL_get_cipher(ssl));

    client_cert = SSL_get_peer_certificate(ssl);

    if(client_cert != NULL) {
        printf("Client certificate: \n");

        str = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);
        CHK_NULL(str);
        printf("\t subject: %s\n", str);
        OPENSSL_free(str);

        str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);
        CHK_NULL(str);
        printf("\t issuer: %s\n", str);
        OPENSSL_free(str);

        X509_free(client_cert);
    } else {
        printf("Client does not have certificate. \n");
    }

    err = SSL_read(ssl, buf, sizeof(buf)-1);
    CHK_SSL(err);
    buf[err] = 0x00;
    printf("Got %d chars: %s \n", err, buf);

    err = SSL_write(ssl, "I hear you/", strlen("I hear you."));
    CHK_SSL(err);

    close(sd);
    SSL_free(ssl);
    SSL_CTX_free(ctx);

    return(0);
}

// 客户端.c

#include <stdio.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define CHK_NULL(x) if((x) == NULL) exit(1);
#define CHK_ERR(err, s) if((err) == -1) { perror(s); exit(1); }
#define CHK_SSL(err) if((err) == -1) { ERR_print_errors_fp(stderr); exit(2); }

int main(void) {
    int err;
    int sd;
    struct sockaddr_in sa;

    SSL_CTX   *ctx;
    SSL     *ssl;
    X509                    *server_cert;
    char                    *str;
    char                    buf[4096];
    SSL_METHOD    *meth;

    SSL_load_error_strings();
    SSLeay_add_ssl_algorithms();
    meth = TLSv1_2_client_method();
    ctx = SSL_CTX_new(meth);
    CHK_NULL(ctx);

    if(SSL_CTX_use_certificate_file(ctx, "./client.crt", SSL_FILETYPE_PEM) <= 0) {
        ERR_print_errors_fp(stderr);
        exit(3);
    }

    if(SSL_CTX_use_PrivateKey_file(ctx, "./client.key", SSL_FILETYPE_PEM) <= 0) {
    ERR_print_errors_fp(stderr);
    exit(4);
    }

    if(!SSL_CTX_check_private_key(ctx)) {
        fprintf(stderr, "Private key does not match the certificate public keyn");
        exit(5);
    }

    CHK_SSL(err);

    sd = socket(AF_INET, SOCK_STREAM, 0);
    CHK_ERR(sd, "socket");

    memset(&sa, 0x00, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr("127.0.0.1");
    sa.sin_port = htons(1111);

    err = connect(sd, (struct sockaddr*)&sa, sizeof(sa));
    CHK_ERR(err, "connect");

    ssl = SSL_new(ctx);
    CHK_NULL(ssl);

    SSL_set_fd(ssl, sd);
    err = SSL_connect(ssl);
    CHK_NULL(err);

    printf("SSL connection using %s \n", SSL_get_cipher(ssl));

    server_cert = SSL_get_peer_certificate(ssl);
    CHK_NULL(server_cert);
    printf("Server certificate: \n");

    str = X509_NAME_oneline(X509_get_subject_name(server_cert), 0, 0);
    CHK_NULL(str);
    printf("\t subject: %s \n", str);
    OPENSSL_free(str);

    str = X509_NAME_oneline(X509_get_issuer_name(server_cert), 0, 0);
    CHK_NULL(str);
    printf("\t issuer: %s \n", str);
    OPENSSL_free(str);

    X509_free(server_cert);

    err = SSL_write(ssl, "Hello World!", strlen("Hello World!"));
    CHK_SSL(err);

    err = SSL_read(ssl, buf, sizeof(buf)-1);
    CHK_SSL(err);
    buf[err] = 0x0;
    printf("Got %d chars: %s \n", err, buf);
    SSL_shutdown(ssl);

    close(sd);
    SSL_free(ssl);
    SSL_CTX_free(ctx);

    return 0;
}

下面是输出结果。

(1) 服务器

$ ./服务器

使用 ECDHE-RSA-AES256-GCM-SHA384 的 SSL 连接

客户端没有证书。

有 12 个字符:Hello World!

(2) 客户端

$ ./客户端

使用 ECDHE-RSA-AES256-GCM-SHA384 的 SSL 连接

服务器证书:

主题:/C=IN/ST=WB/L=Kolkata/O=TEST-INFO-CLIENTA/OU=IT/CN=clienta.com/emailAddress=aaa@aaa.com

发行人:/C=IN/ST=WB/L=Kolkata/O=TEST-INFO-CLIENTA/OU=IT/CN=clienta.com/emailAddress=aaa@aaa.com

有 11 个字符:我听到了/

================================================ ========

我不知道为什么服务器的输出显示“客户端没有证书”。

虽然我在server.c中添加了“SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL)”,但是服务器并没有收到客户端的证书。

如果有人知道,请回复。

【问题讨论】:

  • 离题了,但`-des3` 似乎是个糟糕的选择。三重 DES 坏了。 en.wikipedia.org/wiki/Triple_DES#Security 。想不出将其用于任何事情的充分理由,除非您专门针对它。实际上,您创建自签名证书的过程看起来有点奇怪。 openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem 是我比较喜欢的模式
  • 您在 context 使用 SSL_new() 创建 SSL 对象后设置了 SSL_VERIFY_PEER,因此会忽略上下文中的此更改。 要么将其移到 SSL_new 或改为 SSL_set_verify(...)(不是SSL_CTX_)。 @DanFarrell:3key-3DES 仍然具有 112 位的强度,这已经足够 并且 比用于 OpenSSL“旧版”文件的可怕的蹩脚 KDF 好得多,而且一个私钥文件永远不会接近千兆字节其中 64 位块是一个问题。尽管 OP 实际上在设置后立即删除了加密,所以它根本不重要。
  • @dave_thompson_085:我还有其他问题,你能回答我吗?
  • sangsub:正如杰伊所说,你应该把你的问题,包括对你问题的补充,放在问题中;这就是问答格式的设计方式。而且周杰伦的回答是正确的,虽然我补充了一些小问题。

标签: c authentication openssl tls1.2


【解决方案1】:

如何避免证书验证失败错误?

如果您要创建“rootCA”然后颁发服务器证书 和客户端证书使用相同的“rootCA”,您需要确保 将“rootCA”证书文件作为 CA 添加到 OpenSSL 通过 API SSL_CTX_load_verify_locations。即使只使用自签名证书,我们仍然需要使用 API SSL_CTX_load_verify_locations 添加它们。此操作必须在 SSL_new 之前完成。

有没有办法将证书的验证与客户端分开?现在代码是 SSL_accept (ssl);似乎证书验证是在函数中完成的。也就是说,我想要的是服务端收到客户端证书后,先打印出证书信息,然后进行验证。

对收到的证书的验证进行一些控制 从客户端,您可以设置证书验证回调。你 将在此回调中获取从客户端收到的证书,其中 您出于调试目的打印,然后继续 验证。请参考 API SSL_CTX_set_verify。尽管您似乎正在设置它,但您正在传递 NULL 以进行回调。如果您将有效的回调函数作为参数传递,您将在回调中收到证书。

【讨论】:

  • 对于自签名证书,就像这个 Q 中的情况一样,自签名证书实际上是它自己的根,所以在服务器中只需使用 SSL_CTX_load_verify_locations (clientcertfilename, NULL)。和其他上下文操作一样,这必须在 SSL_new 之前。
  • 谢谢@dave_thompson_085。我已经编辑了我的答案以包括你的观点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-02
  • 1970-01-01
  • 2014-07-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多