中间证书

中间证书的使用是随着公共密钥基础设施(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和收单系统终端大多用的还是对称加密体系。

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

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

 

 

实际使用

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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 签发。

 

一些问题

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

比如说类似下面的报错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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证书

1
2
3
4
5
# 生成根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证书

1
2
3
4
5
6
7
8
# 生成中间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

生成服务器证书

1
2
3
4
5
6
7
8
# 生成服务器私钥
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

验证证书链,

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

server.crt: OK

启动 OpenSSL 服务器

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

客户端验证

 

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

 

输出结果

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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)

证书链条,就是我们手工创建的证书。

1
2
3
4
5
6
----
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客户端验证一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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 格式(此时需要设置一个密码):

    1
    2
    3
    
    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:

    1
    
    keytool -importkeystore -srckeystore rootCA.p12 -srcstoretype PKCS12 -destkeystore rootCA.jks -deststoretype JKS

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

 

java客户端代码

SSLClient.java,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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只配置了服务器证书,那么要求客户端需要安装中间证书。

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

1
cat server.crt intermediateCA.crt > server_chain.crt

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[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

 

如果直接使用服务端证书

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

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

1
2
3
4
5
6
7
8
9
[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陳聯鳳