NGINX 配置多个证书一般有两种场景:
- 同一个域名下提供多种类型证书,如RSA,ECC证书
- 多个域名部署在同一个IP,使用多个不同证书, 根据客户端上送SNI选择证书
生成证书
使用 ssl_cert_chain_tool.sh 生成一组正式证书
RSA 类型证书
ssl_cert_chain_tool.sh -a RSA -o a_cert -n a.example.com -u
ssl_cert_chain_tool.sh -a RSA -o b_cert -n b.example.com -u
ssl_cert_chain_tool.sh -a RSA -o c_cert -n c.example.com -u
ECC 类型证书
ssl_cert_chain_tool.sh -a ECC -o d_cert -n d.example.com -u
ssl_cert_chain_tool.sh -a ECC -o e_cert -n e.example.com -u
支持多种类型证书
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 0.0.0.0:443 ssl;
ssl_protocols TLSv1.3 TLSv1.2;
server_name a.example.com b.example.com c.example.com d.example.com e.example.com;
ssl_prefer_server_ciphers on;
# RSA and ECC Certificates
ssl_certificate a_cert/server.cert.pem;
ssl_certificate_key a_cert/server.key.pem;
ssl_certificate e_cert/server.cert.pem;
ssl_certificate_key e_cert/server.key.pem;
ssl_certificate d_cert/server.cert.pem;
ssl_certificate_key d_cert/server.key.pem;
ssl_certificate c_cert/server.cert.pem;
ssl_certificate_key c_cert/server.key.pem;
ssl_certificate b_cert/server.cert.pem;
ssl_certificate_key b_cert/server.key.pem;
location / {
return 200 'Multi-certificates test\n';
}
}
}
客户端与服务端握手时根据签名算法选择合适的证书,
openssl 中使用tls_post_process_client_hello->tls_choose_sigalg 针对收到client hello报文后进行处理,上送的签名算法和服务端配置选择合适的证书。
openssl 使用状态机方式实现,由于要向前兼容要考虑很多中情况, 比如tls1.3 tls1.2兼容,如果套件中表明签名算法,从扩展栏位选, 扩展栏位没有从证书里找, 感觉维护起来都很复杂, 可以参考一下链接的源码分析,从中找到了一些线索。
https://hxndg.github.io/2020/09/07/OPENSSL%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB(3)/
tls1.2 套件包含了签名算法,指定RSA套件和扩展域 signature_algorithms , 会首先使用套件来优先选择对应的证书;
tls1.3 套件剥离了签名算法,可以通过扩展字段 signature_algorithms 来参与证书选择。
tls1.2的处理方式
# curl -ksv --tlsv1.2 --tls-max 1.2 --resolve a.example.com:443:127.0.0.1 https://a.example.com
* Added a.example.com:443:127.0.0.1 to DNS cache
* Hostname a.example.com was found in DNS cache
* Trying 127.0.0.1:443...
* Connected to a.example.com (127.0.0.1) port 443
* ALPN: curl offers http/1.1
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384 / X25519 / id-ecPublicKey
* ALPN: server accepted http/1.1
* Server certificate:
* subject: C=CN; O=TestOrg; CN=d.example.com
可以看到他选择了ecc证书d_cert, 如果指定套件可以看到, 则选择了RSA b_cert,
# curl -ksv --tlsv1.2 --tls-max 1.2 --resolve a.example.com:443:127.0.0.1 https://a.example.com --ciphers ECDHE-RSA-AES128-GCM-SHA256
* Added a.example.com:443:127.0.0.1 to DNS cache
* Hostname a.example.com was found in DNS cache
* Trying 127.0.0.1:443...
* Connected to a.example.com (127.0.0.1) port 443
* ALPN: curl offers http/1.1
* Cipher selection: ECDHE-RSA-AES128-GCM-SHA256
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / X25519 / RSASSA-PSS
* ALPN: server accepted http/1.1
* Server certificate:
* subject: C=CN; O=TestOrg; CN=b.example.com
可以看到tls1.2 套件起了的作用,如果设置了套件会用套件指定签名算法取选择证书, 而使用的扩展栏位是一样的。
Extension: signature_algorithms (len=42)
Type: signature_algorithms (13)
Length: 42
Signature Hash Algorithms Length: 40
Signature Hash Algorithms (20 algorithms)
Signature Algorithm: ecdsa_secp256r1_sha256 (0x0403)
Signature Algorithm: ecdsa_secp384r1_sha384 (0x0503)
Signature Algorithm: ecdsa_secp521r1_sha512 (0x0603)
Signature Algorithm: ed25519 (0x0807)
Signature Algorithm: ed448 (0x0808)
Signature Algorithm: rsa_pss_pss_sha256 (0x0809)
Signature Algorithm: rsa_pss_pss_sha384 (0x080a)
Signature Algorithm: rsa_pss_pss_sha512 (0x080b)
Signature Algorithm: rsa_pss_rsae_sha256 (0x0804)
Signature Algorithm: rsa_pss_rsae_sha384 (0x0805)
Signature Algorithm: rsa_pss_rsae_sha512 (0x0806)
Signature Algorithm: rsa_pkcs1_sha256 (0x0401)
Signature Algorithm: rsa_pkcs1_sha384 (0x0501)
Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
Signature Algorithm: SHA224 ECDSA (0x0303)
Signature Algorithm: SHA224 RSA (0x0301)
Signature Algorithm: SHA224 DSA (0x0302)
Signature Algorithm: SHA256 DSA (0x0402)
Signature Algorithm: SHA384 DSA (0x0502)
Signature Algorithm: SHA512 DSA (0x0602)
由于curl不能指定签名算法, 可以使用openssl sclient验证一下, tls1.2 协议指定签名算法。
# openssl s_client -connect 127.0.0.1:443 -sigalgs "RSA+SHA256" -tls1_2
CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 C = CN, O = TestOrg, CN = b.example.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = CN, O = TestOrg, CN = b.example.com
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 C = CN, O = TestOrg, CN = b.example.com
verify return:1
---
Certificate chain
0 s:C = CN, O = TestOrg, CN = b.example.com
i:C = CN, O = TestOrg, CN = Intermediate CA
---
Server certificate
...
subject=C = CN, O = TestOrg, CN = b.example.com
issuer=C = CN, O = TestOrg, CN = Intermediate CA
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA
Server Temp Key: X25519, 253 bits
...
New, TLSv1.2, Cipher is ECDHE-RSA-AES256-GCM-SHA384
...
wireshark client hello
Extension: signature_algorithms (len=4)
Type: signature_algorithms (13)
Length: 4
Signature Hash Algorithms Length: 2
Signature Hash Algorithms (1 algorithm)
Signature Algorithm: rsa_pkcs1_sha256 (0x0401)
Signature Hash Algorithm Hash: SHA256 (4)
Signature Hash Algorithm Signature: RSA (1)
tls1.3的处理方式
如果使用Tls1.3 由于已经不支持RSA+SHA256, 这部分可以参考 RFC8446
https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.3, 虽然可以出现在整理里但是握手阶段已不支持, 可以使用 RSA-PSS+SHA256
# openssl s_client -connect 127.0.0.1:443 -sigalgs "RSA-PSS+SHA256"
CONNECTED(00000003)
Can't use SSL_get_servername
depth=0 C = CN, O = TestOrg, CN = b.example.com
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 C = CN, O = TestOrg, CN = b.example.com
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 C = CN, O = TestOrg, CN = b.example.com
verify return:1
---
Certificate chain
0 s:C = CN, O = TestOrg, CN = b.example.com
i:C = CN, O = TestOrg, CN = Intermediate CA
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=C = CN, O = TestOrg, CN = b.example.com
issuer=C = CN, O = TestOrg, CN = Intermediate CA
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 1453 bytes and written 319 bytes
Verification error: unable to verify the first certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
关于为啥选择最后的证书这部分就不研究了,应该是类似stack结构后进先出, 主要是我们可以看到,其实并没有返回我想要的指定域名的证书。而是通过签名算法选择了证书, 如果要根据不同域名选择不同证书,并完成多站代理可以用第二种方法
SNI多证书配置
nginx官方文档给的一个样例:
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate www.example.com.crt;
...
}
server {
listen 443 ssl;
server_name www.example.org;
ssl_certificate www.example.org.crt;
...
}
https://nginx.org/en/docs/http/configuring_https_servers.html#certificate_with_several_names
如果要将证书配置到同一个server通过 map
指令可以根据客户端提供的 SNI 动态加载对应的证书和私钥, 但是这样配置的话由于每次都要进行证书读取,IO消耗是比较大的, nginx plus 对于这种配置是有优化处理的 https://www.infoq.com/news/2019/04/nginx-plus-release-18/, 生产环境的话还是选择上一种配置。
示例配置:
http {
map $ssl_server_name $cert {
a.example.com a_cert/server.cert.pem;
b.example.com b_cert/server.cert.pem;
d.example.com d_cert/server.cert.pem;
e.example.com e_cert/server.cert.pem;
default c_cert/server.cert.pem;
}
map $ssl_server_name $key {
a.example.com a_cert/server.key.pem;
b.example.com b_cert/server.key.pem;
d.example.com d_cert/server.key.pem;
e.example.com e_cert/server.key.pem;
default c_cert/server.key.pem;
}
server {
listen 0.0.0.0:443 ssl;
ssl_protocols TLSv1.3 TLSv1.2;
server_name a.example.com b.example.com c.example.com d.example.com e.example.com;
ssl_certificate $cert;
ssl_certificate_key $key;
location / {
return 200 'Multi-certificates test\n';
}
}
}
验证一下, 可以看到是通过server_name选择证书a.example.com 选择 RSA 的a_cert, e.example.com 选择ECC e_cert
]# curl -ksv --resolve a.example.com:443:127.0.0.1 https://a.example.com
* Added a.example.com:443:127.0.0.1 to DNS cache
* Hostname a.example.com was found in DNS cache
* Trying 127.0.0.1:443...
* Connected to a.example.com (127.0.0.1) port 443
* ALPN: curl offers http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted http/1.1
* Server certificate:
* subject: C=CN; O=TestOrg; CN=a.example.com
curl -ksv --resolve e.example.com:443:127.0.0.1 https://e.example.com
* Added e.example.com:443:127.0.0.1 to DNS cache
* Hostname e.example.com was found in DNS cache
* Trying 127.0.0.1:443...
* Connected to e.example.com (127.0.0.1) port 443
* ALPN: curl offers http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / id-ecPublicKey
* ALPN: server accepted http/1.1
* Server certificate:
* subject: C=CN; O=TestOrg; CN=e.example.com
最后
关于多证书nginx用的就是这些,从上面看如果不是特殊需求nginx基本的配置能够满足,
在整理资料的时候在查看dh算法的时候偶然,看到了quantum-safe cryptographic algorithms.又是系列新的算法,后续有时间整理一下。
图片from王進明
Comments are closed.