我们正在尝试创建一个node.js应用程序,它应该通过HTTPS(>TLS v1.2)与服务器交互。我们得到了一些密钥、证书文件的列表来建立与服务器的连接。节点 HTTPS 需要 CA、证书、密钥文件,即 CA 文件、客户端证书和密钥文件。当提供这些时,我们收到以下错误:
Error: unhandled critical extension.
在互联网上花了一些时间后,我们发现在 TLS 握手时收到的服务器证书有一些自定义扩展。后来当我们这样做时
openssl verify -CAfile ca_file.pem client_cert.pem
我们可以重现它:
error 34 at 0 depth lookup:unhandled critical extension
OK
所以,这似乎与 OpenSSL 有关。我们如何让 OpenSSL 理解我们的自定义扩展?这些自定义扩展也很重要,因此我们不能通过设置
-ignore_critical
来忽略错误。
编辑:
CA 证书的
X509v3 extensions
部分包含以下内容,
X509v3 extensions:
X509v3 Subject Key Identifier:
A1:*:*:*...
X509v3 Authority Key Identifier:
keyid:1D:*:*:*
X509v3 Basic Constraints: critical
CA:TRUE, pathlen:0
X509v3 Key Usage:
Digital Signature, Certificate Sign, CRL Sign
服务器证书的
X509v3 extensions
部分包含以下内容,
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Cert Type:
SSL Client
X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
X509v3 Subject Key Identifier:
E1:*:*...
X509v3 Authority Key Identifier:
keyid:F9:*:*:*...
X509v3 Extended Key Usage:
TLS Web Client Authentication
1.3.*.*.*...: critical
02.
TYPE:TEST..FCC_ID:12345..DP_ID:54321
1.3.*.*.*...: critical
..NULL:TRUE
CA 证书没有
X509v3 Extended Key Usage
部分。
多年后这个问题仍然存在,并且没有(令人满意的)答案......
[长话;博士]
来自 OpenSSL 文档X509_VERIFY_PARAM_set_flags:
[对于 OpenSSL,] 默认情况下,证书中的任何 未处理的关键扩展 [...] 都会导致 致命错误。
在 Node.js HTTPS/TLS 服务器中,解决方法是将
rejectUnauthorized
选项设置为 false。如果客户端证书被拒绝(在请求期间从 req.client.authorized
检查),我们可以手动重新检查(可能使用 pkijs、node-forge 或其他 x509 证书感知包),忽略有问题的扩展。
经过漫长的旅程才得出这个结论:
在 Node.js HTTPS/TLS 服务器中,控制客户端证书验证的选项是
requestCert
。
追踪到“crypto_tls.cc”文件,该选项由SSL_set_verify()
使用:
// extracted from 'src/crypto/crypto_tls.cc', for 'requestCert' = true
void TLSWrap::SetVerifyMode(const FunctionCallbackInfo<Value>& args) {
// ...
verify_mode = SSL_VERIFY_PEER;
// ...
SSL_set_verify(wrap->ssl_.get(), verify_mode, VerifyCallback);
}
证书验证将在
crypto_common.cc
中完成。函数 SSL_get_peer_certificate
将调用 VerifyCallback
。
// extracted from 'src/crypto/crypto_common.cc'
long VerifyPeerCertificate( // NOLINT(runtime/int)
const SSLPointer& ssl,
long def) { // NOLINT(runtime/int)
long err = def; // NOLINT(runtime/int)
if (X509* peer_cert = SSL_get_peer_certificate(ssl.get())) {
X509_free(peer_cert);
err = SSL_get_verify_result(ssl.get());
// ...
}
return err;
}
来自 OpenSSL 文档SSL_set_verify:
verify_callback 函数用于控制设置 SSL_VERIFY_PEER 标志时的行为。它必须由应用程序提供并接收两个参数: preverify_ok 指示相关证书的验证是否通过 (preverify_ok=1) 或未通过 (preverify_ok=0)。 x509_ctx 是指向用于证书链验证的完整上下文的指针。
但是在 Node.js 'crypto_util.cc' 文件中:
// extracted from 'src/crypto/crypto_util.cc'
int VerifyCallback(int preverify_ok, X509_STORE_CTX* ctx) {
return 1;
}
所以:
X509_V_FLAG_IGNORE_CRITICAL
标志),ctx
检索错误信息来查看发生了哪个错误(没有 JS 回调)。注意:引自 Node.js 版本 13.18.0 和 OpenSSL 版本 3.1.2