目标:客户端应将数据发送到受信任的服务器(通过自签名证书),服务器也是如此。
我正在运行Node.js TLS服务器并且有很多嵌入式客户端在C中运行openSSL TLS客户端。我有整个设置工作,我可以看到数据是加密的(通过Wireshark)但我不相信我正在以正确的方式(特别是我正在处理证书的方式)。
到目前为止我有什么
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;
}
SSL_CTX_load_verify_locations(ctx, cert, NULL)
。文档说指定ctx的位置,用于验证目的的CA证书位于该位置。通过CAfile和CApath提供的证书是可信的。
这是否意味着客户端会根据TLS握手的ServerHello
消息中收到的证书进行检查?如果是这样,我应该调用另一个函数进行检查吗?
任何帮助将不胜感激。谢谢!
答案 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;
:
:
}