处理 Node.js TLS 服务器和 C TLS 客户端 (openSSL) 连接的正确方法

Correct way of handling Node.js TLS server and C TLS client (openSSL) connections

目标:客户端将数据发送到受信任的服务器(通过自签名证书)和服务器相同。

我正在 运行 设置一个 Node.js TLS 服务器并有许多嵌入式客户端 运行 C 中的 openSSL TLS 客户端。我有整个设置工作,我可以看到数据已加密(通过 Wireshark),但我不相信我这样做是正确的(尤其是我处理证书的方式)。

到目前为止我有什么

  1. 在服务器上,我生成了一个2048的私钥(private-key.pem)和一个自签名证书(public-cert.pem)
  2. 已将自签名证书 (public-cert.pem) 复制到客户端

Node.js 服务器代码片段

var tls = require('tls');
var fs = require('fs');

var options = {
    key: fs.readFileSync('private-key.pem'),
    cert: fs.readFileSync('public-cert.pem')
};
tls.createServer(options, function (socket) {

socket.on('data', function(data) {
    // do something
});

socket.on('close', function() {
    socket.destroy();
});

}).listen(PORT);

C 客户端代码片段

     SSL_library_init(); 
     SSL_load_error_strings();
     ERR_load_BIO_strings();
     OpenSSL_add_all_algorithms();

     // create new context object
     ctx = SSL_CTX_new(TLSv1_2_client_method());

    //load trust cert

    if(! SSL_CTX_load_verify_locations(ctx, "public-cert.pem", NULL)){

          printf("sslInitialize() Error: Cannot load certificate\n");
          ERR_print_errors_fp(stderr);

          if(ctx != NULL) {

            SSL_CTX_free(ctx);

          }


          return;
    } 

    bio = BIO_new_ssl_connect(ctx);

    BIO_get_ssl(bio, & ssl);


    if (ssl == NULL) {

         printf("sslInitialize() Error: Can't locate SSL pointer\n");
         ERR_print_errors_fp(stderr);

         if(ctx != NULL){

            SSL_CTX_free(ctx);

        } 


        if(bio != NULL){

         BIO_free_all(bio);

        }
         return;
    }

    BIO_set_conn_hostname(bio, HOST);

   int connectStatus;                            

   if((connectStatus = BIO_do_connect(bio)) <= 0) {

    printf("Connect Status: %d",connectStatus);
     printf("sslInitialize() Error: Cannot connect to server\n");
     ERR_print_errors_fp(stderr);

    sslCloseConnection();

     return;
   }

观察

  1. 我将客户端上的证书更改为相同的随机证书,它仍然有效(服务器在 ServerHello TLS 握手期间发送的证书和使用 SSL_CTX_load_verify_locations 加载的客户端() 不同)。这让我想知道客户如何信任证书。 我该如何解决这个问题?
  2. 截至目前,我已将其设置为客户端验证服务器(尽管它不工作)并发送数据并且服务器盲目地接受它。 如何让客户端发送证书(由客户端自签名)并且服务器只有在文件中有该特定证书时才会接受它。
  3. 在客户端我使用SSL_CTX_load_verify_locations(ctx, cert, NULL)。文档说

specifies the locations for ctx, at which CA certificates for verification purposes are located. The certificates available via CAfile and CApath are trusted.

这是否意味着客户端会根据在 TLS 握手的 ServerHello 消息中收到的证书检查此信息?如果是这样,我应该调用另一个函数来执行此检查吗?

  1. 一如既往,在问这个问题之前,我已经浏览了很多在线资源(SO 帖子和 openSSL 手册页)。 Node.js_TLS,openSSL,SO_1, SO_2 仅举几例。我还省略了头文件和其他样板代码。

如有任何帮助,我们将不胜感激。谢谢!

对于 C 客户端,您可能希望确保客户端通过显式调用 OpenSSL 的 SSL_CTX_set_verify() 函数来验证服务器证书:

SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

在调用 BIO_new_ssl_connect() 之前。 注意这可能不是必需的。

是的,您通过 SSL_CTX_load_verify_locations() 设置的证书是用于验证服务器证书(它是 ServerHello 的一部分)的证书。根据这些验证证书检查服务器证书是默认 OpenSSL 验证回调的一部分。

根据 Nodejs tls createServer docs,您可能需要为 TLS 服务器提供更多选项,特别是 ca 数组 requestCertrejectUnauthorized 布尔值:

var options = {
    key: fs.readFileSync('private-key.pem'),
    cert: fs.readFileSync('public-cert.pem'),
    ca: [ fs.readFileSync('ca-cert.pem') ],
    requestCert: true,
    rejectUnauthorized: true
};
tls.createServer(options, function (socket) {

requestCert 布尔值指示服务器请求客户端的证书。在 TLS 中,服务器必须明确地向客户端请求证书;除非服务器要求,否则客户端不提供。如果不将 rejectUnauthorized 设置为 true,您的 TLS 服务器将请求证书,但 忽略任何验证错误 并允许连接继续,这是不可取的。 ca 数组配置验证证书列表(类似于 OpenSSL 的 SSL_CTX_load_verify_locations(),Nodejs 将使用它来验证客户端证书)。

理想情况下,不是为服务器使用自签名证书,为客户端使用单独的自签名证书,而是拥有 三个 个证书:一个 CA 证书 (根据定义是自签名的),一个服务器证书(由该 CA issued/signed)和一个单独的客户端证书(也由 CA issued/signed)。由于您同时控制客户端 服务器,因此您没有特别需要从 public CA 购买 这些证书; browsers 需要这些(因为 public CA 的证书在浏览器的信任库中),但这听起来不像是您的用例所需要的(因此generating/using 你自己的 CA 可以节省一些钱)。 This site 提供了执行此操作的示例命令。

这样,您的服务器将拥有它的 证书(和私钥),以及 CA 证书;您的客户将拥有 its 证书(和私钥)和 CA 证书。那么,CA 证书将是在 tls.createServerca 选项中的 SSL_CTX_load_verify_locations() paths/files、 中配置的证书。

作为奖励,您可以考虑添加对 TLS 会话缓存的支持,如 here 所述。

希望对您有所帮助!

我正在“观察”部分回答问题 1)。看起来像

 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

没有必要。您可以查看

的结果
SSL_get_verify_result(const SSL *ssl);

并据此拒绝连接。它 returns 可以找到一些代码 here0为验证成功。

代码片段看起来像这样

BIO_set_conn_hostname(bio, HOST);
if(BIO_do_connect(bio) <= 0) {
    ERR_print_errors_fp(stderr);
    BIO_free_all(bio);
    SSL_CTX_free(ctx);
    return;
}
switch(SSL_get_verify_result(ssl)){


            case 0:
                    printf("X509_V_OK\n");
                    break;


            case 2: 
                    printf("X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT\n");
                    //write code to close connection
                    break;

               :
               :
}