我想为我的 WiFi 路由器创建一个 NGINX 反向代理,并验证连接。我的路由器使用自签名证书,该证书将域列为tplinkwifi.net
主题通用名称 (CN),但不包含任何主题备用名称 (SAN)。
我已经尝试过用 Caddy 来实现这个功能了,但是支持文档很明显,Caddy 不会考虑 CommonName。这可能是合理的,因为它自 2000 年左右以来就被弃用了,但由于我只能使用旧证书,所以我正在寻找一种解决方法。
我不知道 NGINX 是否会支持这一点。代码中有一些遗留路径提到回到 commonname,但较新的版本依赖于 OpenSSL。我还没有进行代码深入研究来确认。
有问题的证书:
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIJAO2y2LjMMgMJMA0GCSqGSIb3DQEBCwUAMCYxCzAJBgNV
BAYTAkNOMRcwFQYDVQQDDA50cGxpbmt3aWZpLm5ldDAeFw0xMDAxMDEwMDAwMDBa
Fw0zMDEyMzEwMDAwMDBaMCYxCzAJBgNVBAYTAkNOMRcwFQYDVQQDDA50cGxpbmt3
aWZpLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKxYm7686e9c
Dc9R37jKfJe5MRbexde4bgANyIUwGd2ZyXQhTYcAWUNnj+AA5JDfDUUwhqO0GJWE
wj77xplSjS6iiKoT0pMffyPOdZtM6vHm+pKSC3trpRaw7HUlhDOJC+8Vw+NWOz5i
6R8dWihd9+atKpuiPcU0zEn7JFXgCtXKksyiA73FXZa7td2N/laBUtww7zn0d7QJ
ahYi0AJMFvvqtmUU6lCAs4DVaLbvMt9NQtkGHnk9PdVJDUMkPZ9LhGy9LT4GDSlG
4n0dFyKB+1fcsAHDoil9zo5D6ObkZudvhZvl0HLm+81MKTmZJs/0/pc4ZajkWS2/
ZB8GBFBQtF8CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl
blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFI4D3y7RVWbZUAeT
J0ejh3/dz7iTMB8GA1UdIwQYMBaAFF0ec0ZdWbKTStafLg1Ufi01LbEkMA0GCSqG
SIb3DQEBCwUAA4IBAQBb2TIgM5f4F0MxrY8/GXVrOkz50g5qHb0lOBvQigHIx20I
KVeJ47t0bjbffaspUS9CV2a1gbmf0cbNmk+KenUY4eW6HJ9ZOy8kHVGm1NtnLEAq
/Sarb4OWxfq45PNpcZbR7CU3+SnueV+b3NZ8CpIifl3RtTsuYNsGQKsnPtEp1SaA
HuZZNznNWxVKU7yyoQIFDXFBwHVDJoke00x/gxBJgHXBqPEHtcXa9HrYeGKkuHfH
FvnnUtD1VIOQT3R9oAWMgkYQenox/zmBshpiGSXLQaGOVtM9UeXHSCjDceOZ5VNq
auGL6Br1aRq9/rBUb0V5Z4RE7Ey659XRqjW/uxEw
-----END CERTIFICATE-----
$ openssl x509 -in cert.pem -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
[...]
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = CN, CN = tplinkwifi.net
Validity
Not Before: Jan 1 00:00:00 2010 GMT
Not After : Dec 31 00:00:00 2030 GMT
Subject: C = CN, CN = tplinkwifi.net
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
[...]
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
[...]
X509v3 Authority Key Identifier:
[...]
Signature Algorithm: sha256WithRSAEncryption
[...]
curl
如果我使用服务器的证书,我就可以进行身份验证和连接--cacert
:
$ curl --cacert cert.pem https://tplinkwifi.net/
<?xml version="1.0" encoding="utf-8"?>
[...]
但不是 OpenSSL 命令行:
$ openssl s_client -servername tplinkwifi.net -connect tplinkwifi.net:443 -CAfile cert.pem
CONNECTED(00000003)
depth=0 C = CN, CN = tplinkwifi.net
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = CN, CN = tplinkwifi.net
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 C = CN, CN = tplinkwifi.net
verify return:1
---
Certificate chain
0 s:C = CN, CN = tplinkwifi.net
i:C = CN, CN = tplinkwifi.net
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIJAO2y2LjMMgMJMA0GCSqGSIb3DQEBCwUAMCYxCzAJBgNV
BAYTAkNOMRcwFQYDVQQDDA50cGxpbmt3aWZpLm5ldDAeFw0xMDAxMDEwMDAwMDBa
Fw0zMDEyMzEwMDAwMDBaMCYxCzAJBgNVBAYTAkNOMRcwFQYDVQQDDA50cGxpbmt3
aWZpLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKxYm7686e9c
Dc9R37jKfJe5MRbexde4bgANyIUwGd2ZyXQhTYcAWUNnj+AA5JDfDUUwhqO0GJWE
wj77xplSjS6iiKoT0pMffyPOdZtM6vHm+pKSC3trpRaw7HUlhDOJC+8Vw+NWOz5i
6R8dWihd9+atKpuiPcU0zEn7JFXgCtXKksyiA73FXZa7td2N/laBUtww7zn0d7QJ
ahYi0AJMFvvqtmUU6lCAs4DVaLbvMt9NQtkGHnk9PdVJDUMkPZ9LhGy9LT4GDSlG
4n0dFyKB+1fcsAHDoil9zo5D6ObkZudvhZvl0HLm+81MKTmZJs/0/pc4ZajkWS2/
ZB8GBFBQtF8CAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl
blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFI4D3y7RVWbZUAeT
J0ejh3/dz7iTMB8GA1UdIwQYMBaAFF0ec0ZdWbKTStafLg1Ufi01LbEkMA0GCSqG
SIb3DQEBCwUAA4IBAQBb2TIgM5f4F0MxrY8/GXVrOkz50g5qHb0lOBvQigHIx20I
KVeJ47t0bjbffaspUS9CV2a1gbmf0cbNmk+KenUY4eW6HJ9ZOy8kHVGm1NtnLEAq
/Sarb4OWxfq45PNpcZbR7CU3+SnueV+b3NZ8CpIifl3RtTsuYNsGQKsnPtEp1SaA
HuZZNznNWxVKU7yyoQIFDXFBwHVDJoke00x/gxBJgHXBqPEHtcXa9HrYeGKkuHfH
FvnnUtD1VIOQT3R9oAWMgkYQenox/zmBshpiGSXLQaGOVtM9UeXHSCjDceOZ5VNq
auGL6Br1aRq9/rBUb0V5Z4RE7Ey659XRqjW/uxEw
-----END CERTIFICATE-----
---
Server certificate
subject=C = CN, CN = tplinkwifi.net
issuer=C = CN, CN = tplinkwifi.net
[...]
Verify return code: 21 (unable to verify the first certificate)
[...]
我怀疑问题在于我正在使用-CAfile
并且证书有CA:FALSE
。
在 NGINX 中,我尝试了以下操作:
events {
}
error_log /dev/stdout debug;
http {
upstream tplinkwifi.net {
server tplinkwifi.net:443;
}
server {
access_log /dev/stdout,severity=debug;
listen 80;
server_name router;
location / {
proxy_pass https://tplinkwifi.net;
proxy_ssl_trusted_certificate /cert.pem;
proxy_ssl_verify on;
proxy_ssl_server_name on;
proxy_ssl_name tplinkwifi.net;
proxy_ssl_verify_depth 10;
}
}
}
当我连接时,我得到一个502 Bad Gateway
日志输出:
2023/04/27 07:16:51 [error] 29#29: *1 upstream SSL certificate verify error: (21:unable to verify the first certificate) while SSL handshaking to upstream, client: 172.17.0.1, server: router, request: "GET / HTTP/1.1", upstream: "https://192.168.0.1:443/", host: "router"
这看起来与我看到的 OpenSSL 命令行行为一致(两者都无法验证证书)。
curl -k
我知道我可以忽略NGINX 的证书proxy_ssl_verify off;
,但我希望真正验证连接,并且我更愿意固定我的路由器使用的证书。我知道我可以换一个不同的路由器,但路由器已经是新的了。我也不想改变路由器。
如果有其他反向代理软件可以支持我的路由器使用的旧版 CommonName 专用证书,我愿意使用其他反向代理软件。虽然证书存在问题,但我认为如果我可以将反向代理固定到证书,我就能获得经过身份验证的连接。
答案1
我最终使它基本完成了工作。
我追踪了 NGINX 错误X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE
。结果发现,该证书实际上不是自签名的。它是由私有 CA 签名的,但该根证书未包含在路由器提供的链中。我也没有看到下载根证书的方法。
接下来,我在 OpenSSL 方面取得了进展。部分链验证允许我根据自身验证叶证书,而不需要找到完整链并从根开始信任:
openssl verify -partial_chain -CAfile tplink.pem tplink.pem
tplink.pem: OK
我跟踪-partial_chain
了 OpenSSL 代码,发现OPT_V_PARTIAL_CHAIN
并X509_V_FLAG_PARTIAL_CHAIN
。有趣的是,Curl使用部分链验证默认情况下。Envoy 支持此标志,但我想继续使用 NGINX。
我修补了 NGINX 并对其进行了编译:
diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c
index 9cc202c9..8fc78ed0 100644
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -4993,6 +4993,12 @@ ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf)
return NGX_ERROR;
}
+ {
+ // Patch: Permit partial chain verification
+ X509_STORE *store = SSL_CTX_get_cert_store(plcf->upstream.ssl->ctx);
+ X509_STORE_set_flags(store, X509_V_FLAG_PARTIAL_CHAIN);
+ }
+
cln = ngx_pool_cleanup_add(cf->pool, 0);
if (cln == NULL) {
ngx_ssl_cleanup_ctx(plcf->upstream.ssl);
这使得 NGINX 能够根据自身验证叶证书,并且成功了!
不幸的是,我的 tplink 路由器每次重启都会生成新的 TLS 证书,因此信任就此破裂。这就是我放弃的地方,但希望这些笔记能对其他人有所帮助。