处理 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),但我不相信我这样做是正确的(尤其是我处理证书的方式)。
到目前为止我有什么
- 在服务器上,我生成了一个2048的私钥(private-key.pem)和一个自签名证书(public-cert.pem)
- 已将自签名证书 (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;
}
观察
- 我将客户端上的证书更改为相同的随机证书,它仍然有效(服务器在 ServerHello TLS 握手期间发送的证书和使用 SSL_CTX_load_verify_locations 加载的客户端() 不同)。这让我想知道客户如何信任证书。 我该如何解决这个问题?
- 截至目前,我已将其设置为客户端验证服务器(尽管它不工作)并发送数据并且服务器盲目地接受它。 如何让客户端发送证书(由客户端自签名)并且服务器只有在文件中有该特定证书时才会接受它。
- 在客户端我使用
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
消息中收到的证书检查此信息?如果是这样,我应该调用另一个函数来执行此检查吗?
- 一如既往,在问这个问题之前,我已经浏览了很多在线资源(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
数组 和 requestCert
和 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) {
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.createServer
的 ca
选项中的 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 可以找到一些代码 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;
:
:
}
目标:客户端将数据发送到受信任的服务器(通过自签名证书)和服务器相同。
我正在 运行 设置一个 Node.js TLS 服务器并有许多嵌入式客户端 运行 C 中的 openSSL TLS 客户端。我有整个设置工作,我可以看到数据已加密(通过 Wireshark),但我不相信我这样做是正确的(尤其是我处理证书的方式)。
到目前为止我有什么
- 在服务器上,我生成了一个2048的私钥(private-key.pem)和一个自签名证书(public-cert.pem)
- 已将自签名证书 (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;
}
观察
- 我将客户端上的证书更改为相同的随机证书,它仍然有效(服务器在 ServerHello TLS 握手期间发送的证书和使用 SSL_CTX_load_verify_locations 加载的客户端() 不同)。这让我想知道客户如何信任证书。 我该如何解决这个问题?
- 截至目前,我已将其设置为客户端验证服务器(尽管它不工作)并发送数据并且服务器盲目地接受它。 如何让客户端发送证书(由客户端自签名)并且服务器只有在文件中有该特定证书时才会接受它。
- 在客户端我使用
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
消息中收到的证书检查此信息?如果是这样,我应该调用另一个函数来执行此检查吗?
- 一如既往,在问这个问题之前,我已经浏览了很多在线资源(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
数组 和 requestCert
和 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) {
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.createServer
的 ca
选项中的 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 可以找到一些代码 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;
:
:
}