gpt4 book ai didi

c - 相互身份验证并将用户证书限制为服务器上的特定集合

转载 作者:太空宇宙 更新时间:2023-11-03 23:44:38 24 4
gpt4 key购买 nike

我正在寻找一种方法,使用 OpenSSL API 在服务器端将客户端证书限制为特定的一组自签名证书。

有一组可信的自签名证书,比如 ./dir/*.pem。如果他们不提供这些证书之一,我想拒绝连接。

我可以通过在 SSL 上下文验证回调中比较服务器和客户端证书指纹来实现几乎所需的行为:

SSL_CTX *ctx;
...

SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback);


static inline int get_fingerprint(X509* cert, unsigned char *md, unsigned int *n)
{
return X509_digest(cert, EVP_sha1(), md, n);
}


static inline int compare_certificates(X509 *c1, X509 *c2)
{
unsigned char md1[EVP_MAX_MD_SIZE], md2[EVP_MAX_MD_SIZE];
unsigned int n1, n2;

if (!(get_fingerprint(c1, md1, &n1) && get_fingerprint(c2, md2, &n2))) {
return -1;
}

return memcmp(md1, md2, n1);
}


static int verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
{
SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
int err = X509_STORE_CTX_get_error(ctx);

/* Allow self-signed certificates */
if (!preverify_ok && err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) {
preverify_ok = 1;
}

if (0 != compare_certificates(ctx->current_cert, SSL_CTX_get0_certificate(ssl->ctx))) {
/* Peer certificate doesn't match the server certificate */
preverify_ok = 0;
}

/* More checks ... */

return preverify_ok;
}

因此,如果服务器和客户端证书指纹匹配,则验证通过。否则,连接将被服务器关闭。

我可能会在初始化阶段的某处计算可信证书的指纹,然后在 verify_callback 的循环中检查它们。但是,我不喜欢这个主意。应该有更简单的方法来做到这一点。

我以为 SSL_CTX_load_verify_locations() 正是我要找的(但看起来不是;我会解释原因):

SSL_CTX_load_verify_locations() specifies the locations for ctx, at which CA certificates for verification purposes are located. ... If CAfile is not NULL, it points to a file of CA certificates in PEM format. The file can contain several CA certificates... The certificates in CApath are only looked up when required, e.g. when building the certificate chain or when actually performing the verification of a peer certificate.

( man 3 SSL_CTX_load_verify_locations )

嗯,我想 SSL_VERIFY_FAIL_IF_NO_PEER_CERT 意味着验证对等证书。然后看起来我需要做的就是制作一堆受信任的证书并将其传递给 SSL_CTX_load_verify_locations():

bundle_file=CAbundle.pem

cd ./dir
rm -f $bundle_file

for i in *.pem; do
openssl x509 -in $i -text >> $bundle_file
done

c_rehash .
SSL_CTX *ctx;
const char *cafile = "dir/CAbundle.pem";
const char *capath = NULL;
...

if (!SSL_CTX_load_verify_locations(ctx, cafile, capath)) {
/* Unable to set verify locations ... */
}

cert_names = SSL_load_client_CA_file(cafile);
if (cert_names != NULL) {
SSL_CTX_set_client_CA_list(ctx, cert_names);
} else {
/* Handle error ... */
}

一切看起来都很好。但是服务器仍然接受具有不同对等证书的连接。

我在此处使用标准 OpenSSL 实用程序重现了此行为:https://gist.github.com/rosmanov/d960a5d58a96bdb730303c5b8e86f951

所以我的问题是:如何配置服务器以仅接受仅提供特定证书的对等点?

更新

我发现证书(CA bundle )的“白名单”确实有效,当我从 verify_callback删除以下内容时:

if (!preverify_ok && err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) {
preverify_ok = 1;
}

所以没有这个 block 一切正常。服务器响应与 CAbundle.pem 中列出的证书之一连接的客户端。如果客户端使用不同的证书连接,服务器将关闭连接。

但是,有一个奇怪的事情。在这两种情况下,openssl s_client 输出:

Verify return code: 18 (self signed certificate)

也许吧

if (!preverify_ok
&& err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT
&& allow_self_signed
&& !cafile
&& !capath) {
preverify_ok = 1;
}

?

更新 2

现在我明白了为什么 openssl s_client 输出 Verify return code: 18 (self signed...)。它不信任服务器的证书,除非 -CAfile-CApath 包含服务器证书。并且服务器证书是自签名的。

最佳答案

解释(针对命令行)和半个答案(针对库):

我(这次完全)重做了你的要点,并被提醒这里有一个不一致的地方。命令行 openssl xxx 实用程序主要设计为测试/调试工具,特别是:

  • s_client 通常(匿名、SRP 等除外)从服务器接收证书链,但使用回调仅记录它获得的内容并忽略/覆盖任何错误;这是街区

    depth=0 C = AU, ST = StateA, L = CityA, O = CompanyA, CN = localhost, emailAddress = a@gmail.com
    verify error:num=18:self signed certificate
    verify return:1
    depth=0 C = AU, ST = StateA, L = CityA, O = CompanyA, CN = localhost, emailAddress = a@gmail.com
    verify return:1

    就在 s_client 输出中的 CONNECTED(fd) 之后,但如您所见,尽管出现错误,它仍继续握手,从而产生可用的连接。

  • s_server 比较复杂。默认情况下,它不会从客户端请求证书,只有当您指定 -verify-Verify (设置 SSL_VERIFY_PEER 时,这不是默认设置对于服务器),如果它确实请求证书客户端有选项是否发送一个(在 CertVerify 中有相关证明)。 如果客户端确实发送链,s_server 使用与 s_client 相同的回调,它会覆盖任何错误并继续连接;这在您的 s_server 输出中具有相同的 verify error:num-18... 这实际上意味着接收链中的“根(包括自签名,它是它自己的根)但不是在本地信任库中'。 如果客户端不发送链,-verify 继续,但-Verify(设置SSL_VERIFY_FAIL_IF_NO_PEER_CERT ) 使用警报 40 中止握手并返回错误,因此 s_server 输出非常不同:

    verify depth is 0, must return a certificate    Using default temp DH parameters    Using default temp ECDH parameters    ACCEPT    ERROR    140679792887624:error:140890C7:SSL routines:SSL3_GET_CLIENT_CERTIFICATE:peer did not return a certificate:s3_srvr.c:3271:    shutting down SSL    CONNECTION CLOSED    ACCEPT

But a program using the library should work. I hacked up this simple test from parts of some other programs (hence the odd indentation):

/* SO36821430 2016-04-25 */#include <stdio.h>#if defined(_WIN32)&&!defined(WIN32)#define WIN32 /*anything*/#endif#ifdef WIN32  #include <winsock2.h>  typedef int socklen_t;  #define SOCKERR WSAGetLastError()  #include "openssl/applink.c"#else  #include <unistd.h>  #include <sys/socket.h>  #include <netinet/in.h>  #include <arpa/inet.h>  #ifndef INADDR_NONE  #define INADDR_NONE (ipaddr_t)-1  #endif  typedef int SOCKET;  enum { INVALID_SOCKET = -1, SOCKET_ERROR = -1 };  #define SOCKERR errno  #define closesocket close#endif#include "openssl/ssl.h"#include "openssl/err.h"#include "openssl/rand.h"void sockerr (const char *what){  fprintf (stderr, "%s %d %s\n", what, SOCKERR, strerror(SOCKERR));}void sslerrn (const char *what){  fprintf (stderr, "* %s failed:\n", what);  ERR_print_errors_fp (stderr);}void sslerr (const char *what, int rv){  fprintf (stderr, "* %s return %d:\n", what, rv);  ERR_print_errors_fp (stderr);}void sslerrx (SSL * ssl, const char *what, int rv){  int rc = SSL_get_error (ssl, rv);  if( rv == -1 && rc == SSL_ERROR_SYSCALL ) sockerr (what);  else fprintf (stderr, "* %s return %d,%d\n", what, rv, rc);  ERR_print_errors_fp (stderr); }void subj_oneline (X509 * cert, FILE *fp){  X509_NAME * subj = X509_get_subject_name (cert);  BIO *bmem = BIO_new (BIO_s_mem()); char *ptr; int n;  X509_NAME_print_ex (bmem, subj, 0, XN_FLAG_ONELINE);   n = (int) BIO_get_mem_data (bmem, &ptr);  if( n <= 0 ) ptr = "?", n = 1;  fwrite (ptr,1,n,fp);}const char * inaddr;int inport;char buf [9999];int main (int argc, char* argv[] ){  int rv;   struct sockaddr_in sin; socklen_t sinlen;  SOCKET s1, s2; SSL_CTX *ctx = NULL;  time_t now; struct tm * tm;#ifdef WIN32  struct WSAData wsa;  rv = WSAStartup (MAKEWORD(1,1), &wsa);  if(rv){ printf ("WSAStartup %d\n", rv); exit(1); }#endif  if( argc < 2 || argc > 6 )    printf ("usage: %s port key cert CAcerts\n", argv[0]), exit(1);  sin.sin_addr.s_addr = INADDR_ANY;  sin.sin_port = htons (atoi(argv[1]));  sin.sin_family = AF_INET;  /**/    SSL_library_init();    SSL_load_error_strings();    ctx = SSL_CTX_new (SSLv23_server_method());    if( !ctx ){ sslerrn("CTX_new"); exit(1); }    SSL_CTX_set_options (ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);    rv = SSL_CTX_use_PrivateKey_file (ctx, argv[2], SSL_FILETYPE_PEM);    if( rv != 1 ){ sslerr ("use_PrivateKey_file",rv); exit(1); }    rv = SSL_CTX_use_certificate_file (ctx, argv[3], SSL_FILETYPE_PEM);    if( rv != 1 ){ sslerr ("use_certificate_file",rv); exit(1); }    SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);     if( !SSL_CTX_load_verify_locations (ctx, argv[4], NULL) ){      sslerrn ("load_verify_locations"); exit(1); }    SSL_CTX_set_client_CA_list (ctx, SSL_load_client_CA_file (argv[4]));  /**/  if( (s1 = socket (AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET ){    sockerr ("socket()"); exit(1); }  if( bind (s1, (struct sockaddr*)&sin, sizeof sin) < 0 ){    sockerr ("bind()"); exit(1); }  if( listen (s1, 5) < 0 ){    sockerr ("listen()"); exit(1); }  do{    sinlen = sizeof sin;    if( (s2 = accept (s1, (struct sockaddr*)&sin, &sinlen)) == INVALID_SOCKET ){      sockerr ("accept()"); exit(1); }    now = time(NULL); tm = localtime(&now);    printf ("+ %s %u @%02d.%02d.%02d\n", inet_ntoa (sin.sin_addr),      ntohs (sin.sin_port), tm->tm_hour, tm->tm_min, tm->tm_sec);    /**/      SSL * ssl = SSL_new (ctx);      if( !ssl ){ sslerrn("SSL_new"); goto next; }      SSL_set_fd (ssl, s2);      if( (rv = SSL_accept(ssl)) < 0 ){        sslerrx (ssl, "SSL_accept", rv); goto next; }      { X509 * cert = SSL_get_peer_certificate (ssl);        /*EVP_PKEY * key = cert? X509_get_pubkey (cert): NULL;*/        fprintf (stdout, "=%ld", SSL_get_verify_result (ssl));        if( cert ) putchar (':'), subj_oneline (cert, stdout);        putchar ('\n');      }      while( (rv = SSL_read (ssl, buf, sizeof buf)) > 0 )        printf ("%d: %.*s\n", rv, rv, buf);      sslerrx (ssl, "SSL_read", rv);next:      if( ssl ) SSL_free (ssl);    /**/    now = time(NULL); tm = localtime(&now);    printf ("- %s %u @%02d.%02d.%02d\n", inet_ntoa (sin.sin_addr),      ntohs (sin.sin_port), tm->tm_hour, tm->tm_min, tm->tm_sec);    closesocket (s2);  } while (1);  return 0;}

When run with $port cert1.key cert1.pem CAbundle.pem and connected from client using cert2.key & cert2.pem this aborts the handshake with alert 48 unknown_ca and returns an error as desired:

+ 127.0.0.1 46765 @22.07.36
* SSL_accept return -1,1
140240689366696:error:14089086:SSL routines:ssl3_get_client_certificate:certificate verify failed:s3_srvr.c:3270:
- 127.0.0.1 46765 @22.07.36

HTH.

关于c - 相互身份验证并将用户证书限制为服务器上的特定集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36821430/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com