【问题标题】:Correct way of handling Node.js TLS server and C TLS client (openSSL) connections处理 Node.js TLS 服务器和 C TLS 客户端 (openSSL) 连接的正确方法
【发布时间】:2016-11-27 15:09:11
【问题描述】:

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

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

我目前所拥有的

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

Node.js 服务器代码 sn-p

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 客户端代码 sn-p

     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)。文档说

指定 ctx 的位置,用于验证的 CA 证书所在的位置。通过 CAfile 和 CApath 获得的证书是可信的。

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

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

任何帮助将不胜感激。谢谢!

【问题讨论】:

  • 查看 OpenSSL wiki 上的 TLS Client。它包含您可能会觉得有用的信息。
  • 谢谢!这很有帮助。

标签: c node.js ssl openssl ssl-certificate


【解决方案1】:

我在“观察”部分回答问题 1)。好像

 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

没有必要。您可以查看

的结果
SSL_get_verify_result(const SSL *ssl);

并基于此拒绝连接。它返回一些代码,可以在here 找到。 0 是验证成功。

代码 sn-p 看起来像这样

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;

               :
               :
}

【讨论】:

    【解决方案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 颁发/签名)和单独的客户端证书(也由 CA 颁发/签名)。由于您同时控制客户端服务器,因此您没有特别需要从公共 CA购买这些证书; 浏览器需要这些(因为公共 CA 的证书在浏览器的信任存储中),但这听起来不像您的用例所需要的(因此您可以通过生成/使用您自己的 CA)。 This site 提供了执行此操作的示例命令。

    这样,您的服务器将拥有 证书(和私钥)和 CA 证书;您的客户将拥有 证书(和私钥)和 CA 证书。那么,CA 证书将是在SSL_CTX_load_verify_locations() 路径/文件中配置的证书,ca 选项中tls.createServer

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

    希望这会有所帮助!

    【讨论】:

    • 几个指针 1) 如果我错了,请纠正我,我认为我不需要明确调用SSL_CTX_set_verify()。看起来 OpenSSL 进行了验证,但我需要根据结果拒绝连接。 SSL_get_verify_result 返回验证结果。 2)在您的 Node.js sn-p 中,我认为 ca 应该是来自客户端的证书,而不是来自服务器重新发送的证书。这意味着证书必须生成自己的自签名证书,并且服务器应该有它的副本。
    • 服务器端的ca 应该是验证客户端证书所需的任何证书。在您的情况下,您的客户端证书是自签名的,这意味着 good 服务器 不会 验证该客户端 - 这无异于信任客户端所说的任何内容(这不是好主意)。所以假设您将使用相同的自签名证书为您的客户颁发证书,那么该自签名证书ca服务器端。
    • 我认为如果客户端使用自签名证书,该证书的副本应该在服务器上。然后服务器根据它已经拥有的证书验证客户端发送的证书。 Node.js TLS 文档中的代码 sn-p const options = { key: fs.readFileSync('server-key.pem'), cert: fs.readFileSync('server-cert.pem'), // This is necessary only if using the client certificate authentication. requestCert: true, // This is necessary only if the client uses the self-signed certificate. ca: [ fs.readFileSync('client-cert.pem') ] };
    • 强烈建议不要对客户端或服务器证书使用自签名证书。这是一种非常不常见的(在信任的意义上也不安全)配置,并且不能代表这类事情的最佳实践。因此,尝试使自签名证书以这种方式工作不符合您声明的正确和正确执行此操作的目标。
    • 谢谢。我意识到安全问题。我在 TCP 级别工作,负责机器之间的通信(M2M/IoT 通信)。为每个客户端拥有 CA 签名证书会变得很昂贵。
    猜你喜欢
    • 2019-07-20
    • 2023-04-11
    • 2015-07-11
    • 2012-09-12
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    • 2021-08-13
    • 2017-03-06
    相关资源
    最近更新 更多