无法在 NGINX 反向代理中仅使用 CommonName 来验证 TLS 证书

无法在 NGINX 反向代理中仅使用 CommonName 来验证 TLS 证书

我想为我的 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_CHAINX509_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 证书,因此信任就此破裂。这就是我放弃的地方,但希望这些笔记能对其他人有所帮助。

相关内容