intermediate CA certificate 中间证书

中间证书

中间证书的使用是随着公共密钥基础设施(PKI)的发展而逐渐普及。

早期数字证书体系中根证书直接签发终端(客户端)证书,这种方式存在的问题

  • 根证书安全风险: 一旦根证书泄露整个信任体系就会崩溃;
  • 证书管理的灵活性:根证书通常有大型认证机构管理,工作不能合理分发;
  • 证书撤销机制:仅用根证书签发,一旦撤销将影响整个信任链;

 

相关标准

 

X.509 证书标准:

X.509 v3 证书标准中, 引入了更多扩展字段,使得中间证书可以更有效地运作。它允许通过设置 basicConstraints 扩展来定义证书是否是一个证书颁发机构(CA)证书,并引入路径长度约束(pathlen),以确保中间证书不能签发超出其级别的证书。

X.509 v3 是证书链中的关键标准,它允许将证书分层,使得根证书签发中间证书,中间证书再签发终端证书。这一机制在 1996 年被纳入 X.509 v3 标准。

互联网工程任务组 (IETF) 标准:RFC 5280:

RFC 5280 是互联网标准的一个重要文档,它详细说明了证书路径验证的标准,定义了如何使用 X.509 证书和证书吊销列表(CRL)来建立信任链。

X.509 v3 侧重于定义证书和结构, RFC 5280 侧重于在互联网应用中的使用,包括如何处理证书路径验证和证书撤销。

 

记得刚工作时正赶上银行收单系统一机一密改造,为每个终端生成终端密钥,与PKI体系不同,当时的pos和收单系统终端大多用的还是对称加密体系。

收单终端签到会根据其对应终端主密钥生成工作密钥,用于保护用户密码和用于报文校验。最早的终端密钥用的都是各省一个, 后来为每个终端生成一个终端主密钥, 防止主密钥泄露导致全省密钥泄露。

其实大体改造思路和中间正式类似。

 

 

实际使用

看下实际网站证书使用情况

# openssl s_client -connect baidu.com:443 -showcerts
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, CN = DigiCert Secure Site Pro CN CA G3
verify return:1
depth=0 C = CN, ST = \E5\8C\97\E4\BA\AC\E5\B8\82, O = "BeiJing Baidu Netcom Science Technology Co., Ltd", CN = www.baidu.cn
verify return:1
---
Certificate chain
 0 s:C = CN, ST = \E5\8C\97\E4\BA\AC\E5\B8\82, O = "BeiJing Baidu Netcom Science Technology Co., Ltd", CN = www.baidu.cn
   i:C = US, O = DigiCert Inc, CN = DigiCert Secure Site Pro CN CA G3
-----BEGIN CERTIFICATE-----
....PMdeTgwOzn2nJhPcJFZ5yUGPZ8thorOYjMLHQ4DKsy7mnGMnKLwmz17L/0Tx
8GU=
-----END CERTIFICATE-----
 1 s:C = US, O = DigiCert Inc, CN = DigiCert Secure Site Pro CN CA G3
   i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert Global Root CA
-----BEGIN CERTIFICATE-----
....+AgqbDB91mdnxIr1MuTtHGn/Q1YE2x9OSTb2f2k0ArhFxFdcjKTfLLewG1xD
-----END CERTIFICATE-----
---
Server certificate
subject=C = CN, ST = \E5\8C\97\E4\BA\AC\E5\B8\82, O = "BeiJing Baidu Netcom Science Technology Co., Ltd", CN = www.baidu.cn

issuer=C = US, O = DigiCert Inc, CN = DigiCert Secure Site Pro CN CA G3

---
  • 根证书(depth=2):由 DigiCert 签发,是整个信任链的起点,受操作系统或浏览器信任。
  • 中间证书(depth=1):DigiCert 的中间 CA,用于签发百度的终端服务器证书。
  • 服务器证书(depth=0):这是百度的具体服务器证书,表明它属于 www.baidu.cn,并由 DigiCert 的中间 CA 签发。

 

一些问题

实际应用中间证书遇到问题主要配上的一些问题。

比如说类似下面的报错。

javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at sun.security.ssl.TransportContext.fatal(TransportContext.java:331)
        at sun.security.ssl.TransportContext.fatal(TransportContext.java:274)
        at sun.security.ssl.TransportContext.fatal(TransportContext.java:269)
        at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:654)
        at sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:473)
        at sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:369)
        at sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:377)
        at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:444)
        at sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:422)
        at sun.security.ssl.TransportContext.dispatch(TransportContext.java:182)
        at sun.security.ssl.SSLTransport.decode(SSLTransport.java:152)
        at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1401)
        at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1309)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:440)
        at SSLClient.main(SSLClient.java:34)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:456)
        at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:323)
        at sun.security.validator.Validator.validate(Validator.java:271)
        at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:315)
        at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:223)
        at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:129)
        at sun.security.ssl.CertificateMessage$T12CertificateConsumer.checkServerCerts(CertificateMessage.java:638)
        ... 11 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:148)
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:129)
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:451)
        ... 17 more

 

Traceback (most recent call last):
  File "client.py", line 18, in <module>
    wrapped_socket.connect((server_address, server_port))
  File "/usr/lib64/python3.8/ssl.py", line 1342, in connect
    self._real_connect(addr, False)
  File "/usr/lib64/python3.8/ssl.py", line 1333, in _real_connect
    self.do_handshake()
  File "/usr/lib64/python3.8/ssl.py", line 1309, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)f

 

复现问题

使用openssl可以创建证书,搭建环境验证一下

创建openssl.cnf

# openssl.cnf
[ req ]
default_bits       = 2048
distinguished_name = req_distinguished_name
prompt             = no
req_extensions     = v3_req

[ req_distinguished_name ]
C  = US
ST = California
L  = San Francisco
O  = MyCompany
OU = MyDept
CN = localhost

# 根CA扩展配置
[ v3_ca_root ]
basicConstraints    = critical, CA:TRUE, pathlen:1
keyUsage            = critical, keyCertSign, cRLSign

# 中间CA扩展配置
[ v3_ca_intermediate ]
basicConstraints    = critical, CA:TRUE, pathlen:0
keyUsage            = critical, keyCertSign, cRLSign

# 服务器证书扩展配置
[ v3_req ]
basicConstraints    = CA:FALSE
keyUsage            = critical, digitalSignature, keyEncipherment
extendedKeyUsage    = serverAuth

生成根CA证书

# 生成根CA私钥
openssl genrsa -out rootCA.key 2048

# 生成根CA证书
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 1024 -out rootCA.crt -config openssl.cnf -subj "/C=US/ST=California/L=San Francisco/O=MyCompany/OU=RootCA/CN=MyRootCA" -extensions v3_ca_root

生成中间CA证书

# 生成中间CA私钥
openssl genrsa -out intermediateCA.key 2048

# 生成中间CA证书签署请求 (CSR)
openssl req -new -key intermediateCA.key -out intermediateCA.csr -config openssl.cnf -subj "/C=US/ST=California/L=San Francisco/O=MyCompany/OU=IntermediateCA/CN=MyIntermediateCA"

# 生成中间CA证书
openssl x509 -req -in intermediateCA.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out intermediateCA.crt -days 500 -sha256 -extensions v3_ca_intermediate -extfile openssl.cnf

生成服务器证书

# 生成服务器私钥
openssl genrsa -out server.key 2048

# 生成服务器证书签署请求 (CSR)
openssl req -new -key server.key -out server.csr -config openssl.cnf -subj "/C=US/ST=California/L=San Francisco/O=MyCompany/OU=Server/CN=localhost"

# 生成服务器证书
openssl x509 -req -in server.csr -CA intermediateCA.crt -CAkey intermediateCA.key -CAcreateserial -out server.crt -days 500 -sha256 -extensions v3_req -extfile openssl.cnf

验证证书链,

openssl verify -CAfile rootCA.crt -untrusted intermediateCA.crt server.crt

server.crt: OK

启动 OpenSSL 服务器

openssl s_server -accept 4433 -cert server.crt -key server.key -CAfile intermediateCA.crt -www

客户端验证

 

openssl s_client -connect localhost:4433 -CAfile rootCA.crt -showcerts

 

输出结果

CONNECTED(00000003)
Can't use SSL_get_servername
depth=2 C = US, ST = California, L = San Francisco, O = MyCompany, OU = RootCA, CN = MyRootCA
verify return:1
depth=1 C = US, ST = California, L = San Francisco, O = MyCompany, OU = IntermediateCA, CN = MyIntermediateCA
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = MyCompany, OU = Server, CN = localhost
verify return:1
---
Certificate chain
 0 s:C = US, ST = California, L = San Francisco, O = MyCompany, OU = Server, CN = localhost
   i:C = US, ST = California, L = San Francisco, O = MyCompany, OU = IntermediateCA, CN = MyIntermediateCA
-----BEGIN CERTIFICATE-----
MIIDtjCCAp6gAwIBAgIUXRjYuUAjaXYOsO4Jejyk8HH6gxgwDQYJKoZIhvcNAQEL
BQAwgYIxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
DA1TYW4gRnJhbmNpc2NvMRIwEAYDVQQKDAlNeUNvbXBhbnkxFzAVBgNVBAsMDklu
dGVybWVkaWF0ZUNBMRkwFwYDVQQDDBBNeUludGVybWVkaWF0ZUNBMB4XDTI0MTAx
MTE1MDMzMVoXDTI2MDIyMzE1MDMzMVowczELMAkGA1UEBhMCVVMxEzARBgNVBAgM
CkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCU15
Q29tcGFueTEPMA0GA1UECwwGU2VydmVyMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRiaafyXKpH7oA2h5O2QaHm/k+
1qcM+ooiDLaB/VtFU5qlcx78Tf+YKdamVLZXNCaech1xtsELUBMxc7QPJcsBceRA
KSetptL8nHsnzoy7MGR7i1ODV5mz4oAQFohT9SMvdHo/y4WflZYDY1g/Vjgsv0qn
pA9EfUYfPkumR39WFcSIK8l/iUzCwVRrDZ2mehcGU5ekxWBVVchISw8P8QofldI2
YhHDFUJphLcYAYRgcESsGzdK1Ev2/luV9MsyKlcBBZK+p5XOulmKILHwhsb/e89t
/bT79oSGuoMbUE3kcAai3Pz03qyH7dpNDozHYnCqJ9RqPQlq+RK/n7AnFZJvAgMB
AAGjMjAwMAkGA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG
AQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQB3u8VjA0UoGQms1CLuVBfhAh2/7KBH
2QyFz6lT9FFZivKtm4uP7tmWYgUx6sRRriugxD4h9IwMF/Eh/b/FD9yy2xhE4bFE
OcBgQpACXzaZcyNBF6muG6hMYsgB4U1DD6+L6bXFKwer65QvmRdodd6HxRz7r90h
bgGK/z1gSWvRgC7oYP/YRRoTsTkEc6WePcXMswPU0ss2qyJ4RaBEab4UsYD8VaDR
UZB2p64Mbe14+Lfe2d7Tq+2oZUwLLSqbqpLJaSj8/aTFX8ENc0dhudW8yAey6yBo
WucFYAIlAqeh7b9onIwfu39LU5Qr6y55xXMIv84sGQDdo8ntWdefH60l
-----END CERTIFICATE-----
 1 s:C = US, ST = California, L = San Francisco, O = MyCompany, OU = IntermediateCA, CN = MyIntermediateCA
   i:C = US, ST = California, L = San Francisco, O = MyCompany, OU = RootCA, CN = MyRootCA
-----BEGIN CERTIFICATE-----
MIIDqTCCApGgAwIBAgIUHyJzgt2DD8Ymr9bkQMPVBhLrt4AwDQYJKoZIhvcNAQEL
BQAwcjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
DVNhbiBGcmFuY2lzY28xEjAQBgNVBAoMCU15Q29tcGFueTEPMA0GA1UECwwGUm9v
dENBMREwDwYDVQQDDAhNeVJvb3RDQTAeFw0yNDEwMTExNTAxMTZaFw0yNjAyMjMx
NTAxMTZaMIGCMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG
A1UEBwwNU2FuIEZyYW5jaXNjbzESMBAGA1UECgwJTXlDb21wYW55MRcwFQYDVQQL
DA5JbnRlcm1lZGlhdGVDQTEZMBcGA1UEAwwQTXlJbnRlcm1lZGlhdGVDQTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfUmPuPeSHszYUoVyw7m4kaQfYf
ZjapPB/UKPr3VVLq64LQoDskJq/MF1gSKW3UxDoG5jCoDzPpLhbR68vGiPOirIOs
bRfNJGXivvEBkv1NeDJUjvWsjXqCYdl0aIDQxfCA1iJDV7Mq5/qmPhee+8y7dUbD
1Ujx2WKKyDX+0jZNHto1cWSIWRZqqESG0RGHxez0c2UbG3Mgr401uvEPq2bcD4dy
9ociGzVAryyiXCipXvqyIE30bHQNgRSrxz/999d3uZFcNIrkrdycFD/Z+gfrzjI6
oDoopesc3XYOMYCfS/G27mvRFCR230KI2i+sN4jc85aCi1y29yFoAS+JpbUCAwEA
AaMmMCQwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI
hvcNAQELBQADggEBAIskEpqFr8JRGFq9Bp4BNtR8TmGu3c+0gAzPIjeNGb34RbfH
qYKGvWgUO4xN5liBzwkZCryDpn3SWhWydUETa+6z0p5r9lmegYoCa1P9l/0kSORJ
XaLrw8hiSrSz5mkRxesHt6Y1QhBRSVJSNnm0jN9uafFlpmPWtX6OH+GWwqJjBqM+
D26PlrIPwmb+j/M7XRc/dQw0kWvmIR2hh+4wFb+5Y4Ew/XzSLXVCsOIaVNI8MZ49
DAvi7gd15o4hsHzP1IYct97VAdTwSGNwtQG1IGGxX76/t681kGdDqgkHePXLdraB
1f3TVbuyyc5CzuRoxz36ngEqygPFpK6NLjnT5Eo=
-----END CERTIFICATE-----
---
Server certificate
subject=C = US, ST = California, L = San Francisco, O = MyCompany, OU = Server, CN = localhost

issuer=C = US, ST = California, L = San Francisco, O = MyCompany, OU = IntermediateCA, CN = MyIntermediateCA

---
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 2456 bytes and written 369 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Session-ID: 2EC761154438F854423BD876391EE5876CB898267045F43DC6CA14AF3B90FCB6
    Session-ID-ctx:
    Resumption PSK: CA7F44075220AA22B83B0E2CAA1559812BCAB3A5D8E46190D6885C8C8BCAF13EDBD53DE945511DA854CEFE81BEF55EC4
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 7200 (seconds)
    TLS session ticket:
    0000 - 32 2b ef 01 94 00 9f 82-78 95 75 42 c2 60 00 c3   2+......x.uB.`..
    0010 - f3 d5 f1 80 79 c3 e5 1c-90 ed 32 a3 c5 58 b1 1b   ....y.....2..X..
    0020 - 26 b3 b3 bf 7b fc 51 cc-e0 7c 47 7e 9e 62 45 2b   &...{.Q..|G~.bE+
    0030 - da 16 7b a8 b6 4f aa 7a-38 47 0d dd e1 d4 ae 6f   ..{..O.z8G.....o
    0040 - 12 7d b2 f1 ca 4e 98 3d-66 0b c7 3a 28 bc e7 31   .}...N.=f..:(..1
    0050 - 6f af 0a d4 56 6f 45 6b-f5 fa 0f 2e 35 c6 6a d6   o...VoEk....5.j.
    0060 - 36 a3 9f f7 3d ba f9 f2-ae b0 6a b6 db fc fe 0b   6...=.....j.....
    0070 - 9e 98 d8 d4 04 97 be 94-3b eb 3b 7a f7 43 10 da   ........;.;z.C..
    0080 - 42 32 2f 21 f4 7b 82 bb-68 7c ed cb 25 5a ff 19   B2/!.{..h|..%Z..
    0090 - 8f 3f 78 e7 a1 73 34 58-8e f1 82 b0 e9 62 c8 7d   .?x..s4X.....b.}
    00a0 - 4c da 9e ea a2 41 40 e0-f5 27 ed ff 9d d0 49 dd   L....A@..'....I.
    00b0 - 42 cf 3c d0 a7 19 c9 fd-6d 7d fb 80 7a 6f 98 de   B.<.....m}..zo..

    Start Time: 1728685261
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
    Max Early Data: 0
---
read R BLOCK
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Session-ID: D22D61BF964422B64B281B55817C7F612DEDEF2FC8E8DAC0A7A6A81D8AA299CA
    Session-ID-ctx:
    Resumption PSK: 9D7264E4F4DCAD28200D7D91AAC9CDA47E57A349D5015F2CD8B58937AEFF6BB9F5182113B069CB26A22C0AF985EC42D0
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 7200 (seconds)
    TLS session ticket:
    0000 - 32 2b ef 01 94 00 9f 82-78 95 75 42 c2 60 00 c3   2+......x.uB.`..
    0010 - fc 7d 9d b6 ab 54 a9 db-fc b4 19 cb b7 51 80 1a   .}...T.......Q..
    0020 - 7e 8f f4 b4 9a 46 9f 6f-65 e3 e0 d2 2e 72 63 8b   ~....F.oe....rc.
    0030 - 6c 7a 3a 59 c7 63 84 65-89 c2 f9 39 cd a2 a5 e8   lz:Y.c.e...9....
    0040 - cb 0a 66 e6 25 ef d9 15-64 92 df a3 52 7d b4 0d   ..f.%...d...R}..
    0050 - 10 2e 2b 1c 06 59 bc 36-93 a8 60 9e fe 14 cb 86   ..+..Y.6..`.....
    0060 - 4d ba 72 c9 44 30 cc 92-cd e0 52 a9 3c 87 2e 56   M.r.D0....R.<..V
    0070 - f0 fd ce f3 0a 61 8a e5-54 63 9f 12 0a e7 f2 f1   .....a..Tc......
    0080 - 35 5b b4 ee 3b 84 61 2c-e8 f6 a1 58 1d 23 6e da   5[..;.a,...X.#n.
    0090 - 61 a6 a5 17 80 60 8b 1f-77 4a b1 37 9d fb 16 39   a....`..wJ.7...9
    00a0 - 97 90 a7 a9 e3 9a 67 12-a8 3f 76 1d 6a 7a 27 0e   ......g..?v.jz'.
    00b0 - 27 2a cf c8 7e b6 fc a5-52 95 8f 4a 58 65 46 99   '*..~...R..JXeF.

    Start Time: 1728685261
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
    Extended master secret: no
    Max Early Data: 0
---
read R BLOCK
可以看到验证结果 Verify return code: 0 (ok)
证书链条,就是我们手工创建的证书。
----
depth=2 C = US, ST = California, L = San Francisco, O = MyCompany, OU = RootCA, CN = MyRootCA
verify return:1
depth=1 C = US, ST = California, L = San Francisco, O = MyCompany, OU = IntermediateCA, CN = MyIntermediateCA
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = MyCompany, OU = Server, CN = localhost

 

使用代理配置

实际使用中如果是互联网环境一般会将证书链配置到DMZ代理服务或区的负载设备上,代理负载将卸载ssl,请求转发到应用服务。

真实环境一般客户都一般不需要加载CA,使用系统内置的 CA 证书来验证服务器证书,如果中间证书客户端没安装,也可以在服务配置证书链。

下面使用nginx配置,java客户端验证一下。

user  root;
worker_processes  1;

error_log  logs/error.log  debug;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    log_format quic '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" "$http3"';

    access_log logs/access.log quic;

    server {
    listen 4433 ssl;
    server_name localhost;  # 替换为你的域名

    ssl_certificate /root/ssl/certs/server_chain.crt;  # 证书链文件
    #ssl_certificate /root/ssl/certs/server.crt;  # 证书链文件
    ssl_trusted_certificate /root/ssl/certs/intermediateCA.crt;  # 中间 CA 证书
    ssl_certificate_key /root/ssl/certs/server.key;  # 服务器私钥文件

    ssl_protocols TLSv1.2 TLSv1.3;  # 设置支持的TLS协议版本
    ssl_ciphers 'HIGH:!aNULL:!MD5';  # 设置加密套件

    location / {
        root /root/nginx-quic/html;  # 设置网站根目录
        index index.html index.htm;  # 设置默认首页
    }
}


}

 

JAVA客户端,Java 默认使用 JKS 格式,但如果你的证书文件使用的是 PEM 或其他格式,需要先转换为 JKS 格式

  1. 首先,将 CRT 转换为 PKCS12 格式(此时需要设置一个密码):
    openssl pkcs12 -export -in rootCA.crt -inkey rootCA.key -out rootCA.p12 -name rootCA
    Enter Export Password:
    Verifying - Enter Export Password:
    

    在此过程中,系统会要求您输入导出文件的密码。

  2. 将 PKCS12 转换为 JKS:使用 keytool 命令将 PKCS12 转换为 JKS:
    keytool -importkeystore -srckeystore rootCA.p12 -srcstoretype PKCS12 -destkeystore rootCA.jks -deststoretype JKS
    

    在此过程中,系统会要求您输入导出文件的密码。

 

java客户端代码

SSLClient.java,

import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;

public class SSLClient {
    public static void main(String[] args) {
        String caCertPath = "/root/ssl/certs/rootCA.jks";
        String password = "111111";
        String host = "localhost";
        int port = 4433;

        try {
            // 加载 KeyStore
            KeyStore keyStore = KeyStore.getInstance("JKS");
            try (InputStream keyStoreStream = new FileInputStream(caCertPath)) {
                keyStore.load(keyStoreStream, password.toCharArray());
            }

            // 创建 TrustManagerFactory
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);

            // 创建 SSLContext
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

            // 创建 SSL Socket
            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
            try (SSLSocket sslSocket = (SSLSocket) socketFactory.createSocket(host, port)) {
                sslSocket.setEnabledProtocols(new String[] {"TLSv1.2"});

                // 发送请求
                sslSocket.startHandshake();
                sslSocket.getOutputStream().write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n".getBytes());
                sslSocket.getOutputStream().flush();

                // 读取响应
                byte[] response = new byte[4096];
                int bytesRead = sslSocket.getInputStream().read(response);
                System.out.println(new String(response, 0, bytesRead));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

javac SSLClient.java

 

如果nginx只配置了服务器证书,那么要求客户端需要安装中间证书。

如果客户都没有安装中间证书,可以合并证书, 服务证书再上面, 做为服务端证书。

cat server.crt intermediateCA.crt > server_chain.crt

启动nginx, 执行java客户端返回。

[root@iZ8vbd88lmglnbsnad85q3Z certs]# java SSLClient
HTTP/1.1 200 OK
Server: nginx/1.25.4
Date: Fri, 11 Oct 2024 22:41:39 GMT
Content-Type: text/html
Content-Length: 1406
Last-Modified: Sun, 28 Jan 2024 02:07:47 GMT
Connection: keep-alive
ETag: "65b5b6f3-57e"
Accept-Ranges: bytes

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web

 

如果直接使用服务端证书

#ssl_certificate /root/ssl/certs/server_chain.crt;# 证书链文件 
ssl_certificate /root/ssl/certs/server.crt; # 证书链文件

直接报错, unable to find valid certification path to requested target

[root@iZ8vbd88lmglnbsnad85q3Z certs]# java SSLClient
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
         ... 11 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
        at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:148)
        at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:129)
        at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
        at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:451)
        ... 17 mored

 

图片from陳聯鳳

 

Comments are closed.