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

时间:2016-07-24 01:52:21

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

目标:客户端应将数据发送到受信任的服务器(通过自签名证书),服务器也是如此。

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

到目前为止我有什么

  1. 在服务器上,我生成了一个2048私钥(private-key.pem)和一个自签名证书(public-cert.pem)
  2. 将自签名证书(public-cert.pem)复制到客户端
  3. 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)。文档说
    4.   

      指定ctx的位置,用于验证目的的CA证书位于该位置。通过CAfile和CApath提供的证书是可信的。

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

      1. 与往常一样,在提出这个问题之前,我已经在网上浏览过很多资源(SO帖子和openSSL手册页)。 Node.js_TLSopenSSLSO_1SO_2仅举几例。我还省略了头文件和其他样板代码。
      2. 任何帮助将不胜感激。谢谢!

2 个答案:

答案 0 :(得分: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数组 requestCert和{{ 1}}布尔值:

rejectUnauthorized

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) { 布尔值指示服务器请求客户端的证书。在TLS中,服务器必须明确地从客户端请求证书;除非服务器询问,否则客户端不提供一个。如果没有将requestCert设置为rejectUnauthorized,您的TLS服务器将请求证书,但忽略任何验证错误并允许连接继续,这是不可取的。 true数组配置验证证书列表(类似于Nodejs将用于验证客户端证书的OpenSSL的ca)。

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

这样,您的服务器将拥有证书(和私钥)以及CA证书;您的客户端将具有证书(和私钥)以及CA证书。然后,CA证书将是SSL_CTX_load_verify_locations()的{​​{1}}选项中的SSL_CTX_load_verify_locations()路径/文件,中配置的证书。

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

希望这有帮助!

答案 1 :(得分:1)

我在观察部分回答了问题1)。它看起来像

 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);

没有必要。您可以查看

的结果
SSL_get_verify_result(const SSL *ssl);

并根据此拒绝连接。它返回了许多可以找到here的代码。 0是一次成功的验证。

代码段看起来像这样

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;

               :
               :
}