https原理(四)双向实践(java客户端+tcp代理)
本文采用客户端与服务端共用一个密钥对
1
将https代理服务器(三)实践中的mkcert p12分解为一个公钥一个私钥
mac@macdeMacBook mkcert % openssl pkcs12 -clcerts -nokeys -out myhost.com.pem -in myhost.com.p12
Enter Import Password:
MAC verified OK
mac@macdeMacBook mkcert % openssl pkcs12 -nocerts -out myhost.com-key.pem -in myhost.com.p12
Enter Import Password:
MAC verified OK
Enter PEM pass phrase:
2 将公钥导入jks
keytool -import -file myhost.com.pem -keystore myhost.com-client.jks
密码123456
3 curl
mac@macdeMacBook mkcert % chmod 777 myhost.com-key.pem
mac@macdeMacBook mkcert % curl -k --cert myhost.com.pem --key myhost.com-key.pem https://myhost.com:8080/test/test -v
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to myhost.com (127.0.0.1) port 8080 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* unable to set private key file: 'myhost.com-key.pem' type PEM
* Closing connection 0
curl: (58) unable to set private key file: 'myhost.com-key.pem' type PEM
查下来是openssl分解可能存在很深的问题,不浪费时间
4 故从头开始
mkcert myhost.com
openssl pkcs12 -export -in myhost.com.pem -inkey myhost.com-key.pem -out myhost.com.p12
Enter Export Password:
Verifying - Enter Export Password:
把p12弄入springboot,作为keystore
keytool -import -file myhost.com.pem -keystore myhost.com-client(后改名未 -pub-capub).jks 作为truststore
curl -k --cert myhost.com.pem --key myhost.com-key.pem https://myhost.com:8080/test/test -v
成功
mac@macdeMacBook mkcert % curl -k --cert myhost.com.pem --key myhost.com-key.pem https://myhost.com:8080/test/test -v * Trying 127.0.0.1... * TCP_NODELAY set * Connected to myhost.com (127.0.0.1) port 8080 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem CApath: none * 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, Request CERT (13): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Certificate (11): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS handshake, CERT verify (15): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 * ALPN, server did not agree to a protocol * Server certificate: * subject: O=mkcert development certificate; OU=mac@macdeMacBook.local * start date: Feb 23 03:15:33 2023 GMT * expire date: May 23 03:15:33 2025 GMT * issuer: O=mkcert development CA; OU=mac@macdeMacBook.local; CN=mkcert mac@macdeMacBook.local * SSL certificate verify ok. > GET /test/test HTTP/1.1 > Host: myhost.com:8080 > User-Agent: curl/7.64.1 > Accept: */* > < HTTP/1.1 200 < Content-Type: text/plain;charset=UTF-8 < Content-Length: 4 < Date: Thu, 23 Feb 2023 03:23:38 GMT < * Connection #0 to host myhost.com left intact DONE* Closing connection 0
5 java httpclient
KeyStore keyStore = KeyStore.getInstance("PKCS12"); InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("mkcert/myhost.com.p12"); keyStore.load(inputStream, "changeit".toCharArray()); SSLContext sslcontext = SSLContexts.custom() //忽略掉对服务器端证书的校验 .loadTrustMaterial(new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { return true; } }) //加载服务端提供的truststore(如果服务器提供truststore的话就不用忽略对服务器端证书的校验了) //.loadTrustMaterial(new File("D:\\truststore.jks"), "123456".toCharArray(), // new TrustSelfSignedStrategy()) .loadKeyMaterial(keyStore, "changeit".toCharArray()) .build(); HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslcontext, hostnameVerifier);
Executing request GET https://myhost.com:8080/test/test HTTP/1.1
11:26:48.940 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: default
11:26:48.969 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
11:26:48.971 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: {s}->https://myhost.com:8080][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]
11:26:48.988 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {s}->https://myhost.com:8080][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20]
11:26:48.990 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Opening connection {s}->https://myhost.com:8080
11:26:48.995 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connecting to myhost.com/127.0.0.1:8080
11:26:49.005 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Connecting socket to myhost.com/127.0.0.1:8080 with timeout 0
11:26:49.034 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Enabled protocols: [TLSv1, TLSv1.1, TLSv1.2]
11:26:49.034 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Enabled cipher suites:[TLS_ECDHE_ECDSA_W。。。。。ENEGOTIATION_INFO_SCSV]
11:26:49.034 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Starting handshake
Exception in thread "main" javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
经查:
数据和安全②HTTPS单向和双向认证
https://www.codetd.com/article/11579403
5、将客户端证书添加到服务端jks中
## 将客户端证书导入服务端jks
keytool -import -v -file client.cer -keystore server.jks
### 查看证书文件 cer和crt证书文件一模一样,顶多去掉前缀
### 查看jks的证书文件,如下图
keytool -list -rfc -keystore server.jks -storepass 123456
## 服务端信任客户端证书、客户端证书信任服务端不行,要将根证书导入秘钥库
## 要将ca证书也导入证书库
keytool -import -file ca.crt -alias ca -keystore server.jks -storepass 123456
如果没有添加根证书到jks秘钥库,报错如下:
SSLHandshakeException: Received fatal alert: bad_certificate
跟着操作:
mkcert -CAROOT
keytool -import -file /Users/mac/Library/Application\ Support/mkcert/rootCA.pem -alias ca -keystore myhost.com-pub-capub.jks
查看ca证书是否已经在里面了:
mac@macdeMacBook mkcert % keytool -v -list -keystore myhost.com-pub-capub.jks 输入密钥库口令: 密钥库类型: JKS 密钥库提供方: SUN 您的密钥库包含 2 个条目 别名: ca 创建日期: 2023-2-23 条目类型: trustedCertEntry 所有者: CN=mkcert mac@macdeMacBook.local, OU=mac@macdeMacBook.local, O=mkcert development CA 发布者: CN=mkcert mac@macdeMacBook.local, OU=mac@macdeMacBook.local, O=mkcert development CA 序列号: 235d3f5c76501a7ed133bc9d0bc44bd3 有效期开始日期: Mon Dec 12 14:53:21 CST 2022, 截止日期: Sun Dec 12 14:53:21 CST 2032 别名: mykey 创建日期: 2023-2-23 条目类型: trustedCertEntry 所有者: OU=mac@macdeMacBook.local, O=mkcert development certificate 发布者: CN=mkcert mac@macdeMacBook.local, OU=mac@macdeMacBook.local, O=mkcert development CA 序列号: 6e414b515e161572f3da9edbaf732ba7 有效期开始日期: Thu Feb 23 11:15:33 CST 2023, 截止日期: Fri May 23 11:15:33 CST 2025
重启
成功
6 透明代理
将http代理服务器(三)fiddler【重点】中的透明代理拿来
请求成功
代理输出:
二月 23, 2023 11:35:15 上午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x5522c775] REGISTERED
二月 23, 2023 11:35:15 上午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x5522c775] BIND(0.0.0.0/0.0.0.0:1999)
二月 23, 2023 11:35:15 上午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x5522c775, /0:0:0:0:0:0:0:0:1999] ACTIVE
二月 23, 2023 11:35:25 上午 io.netty.handler.logging.LoggingHandler logMessage
信息: [id: 0x5522c775, /0:0:0:0:0:0:0:0:1999] RECEIVED: [id: 0x6b813dbd, /127.0.0.1:59079 => /127.0.0.1:1999]
--------------------cc----------------------
------------------------------------------
服务器输出:
个人证书信息:OU=mac@macdeMacBook.local, O=mkcert development certificate
说明经过了代理传递了证书,逻辑上 java httpclient与springboot直接ssl双向握手
7
自签名证书穿越want
keytool -genkeypair -alias "test1" -keyalg "RSA" -keystore "test.keystore"
keytool -importkeystore -srckeystore test.keystore -destkeystore test.p12 -srcstoretype jks -deststoretype pkcs12
可以穿越,但拿不到证书
8 ca证书穿越want
mkcert -pkcs12 testca
11:58:28.670 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Starting handshake
Exception in thread "main" javax.net.ssl.SSLHandshakeException: Received fatal alert: certificate_unknown
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
11:58:28.946 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: Shutdown connection
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:2023)
11:58:28.947 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Connection discarded
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1125)
at sun.security.ssl.SSLSocketImpl.waitForClose(SSLSocketImpl.java:1769)
11:58:28.947 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {s}->https://myhost.com:8080][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]
at sun.security.ssl.HandshakeOutStream.flush(HandshakeOutStream.java:124)
11:58:28.947 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager is shutting down
at sun.security.ssl.Handshaker.sendChangeCipherSpec(Handshaker.java:1130)
11:58:28.947 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager shut down
at sun.security.ssl.ClientHandshaker.sendChangeCipherAndFinish(ClientHandshaker.java:1216)
说明want一旦开始搞就会搞到底,不如不给客户端证书或自签名证书
9 mkcert myhost.com作为客户端证书
两种方式 直接mkcert p12 与mkcert pem再openssl合并,结果都同8 error
所以mkcert两次即使签发同一个cn,证书内容也不相同
10 不给truststore
家里所有请求都失败 java和curl
公司可以不知道为啥 (https原理(六)系统分析中解释了)
家里有truststore | 公司没有truststore | |
原始 | 通过有证书 | 通过有证书 |
自签名 | 通过但没证书 | 通过但没证书 |
ca不同cn | ssl失败 | / |
ca 相同cn p12 | ssl失败 | / |
ca 相同cn pem p12 | ssl失败 | / |
11 那么对于want
https://qa.1r1g.com/sf/ask/1047344861/
我的应用程序需要对特定URL进行客户端身份验证,在客户端身份验证成功后,应用程序本身也会对客户端证书主题进行一些验证(使用spring security x509过滤器).我想配置tomcat来强制特定URL的客户端身份验证(clientAuth = true),但基于这篇文章,似乎我不能只使用tomcat - 只为特定的URL模式配置tomcat进行客户端身份验证.
我的问题是,如果我使用clientAuth = want,当服务器请求证书时,以下内容如下:
- 如果设备具有身份证书但不受tomcat truststoreFile中配置的CA信任,则不会传递证书,并且请求将在spring安全过滤器中失败(证书将为null)
- 如果设备具有由tomcat truststoreFile中配置的CA信任的身份证书,但无效(不确定执行了哪些验证)或过期,则认证将在tomcat中失败(在安全过滤器之前)或在选项1中没有证书将传递并且请求将在spring安全过滤器中失败(证书将为null) 我这里直接ssl失败
使用此配置的want +安全过滤器,是否存在我可能缺少的安全漏洞?我想问题是 - 如果证书最终从设备传递到服务器,服务器将始终验证它(未过期,受信任等),即使使用clientAuth = want也不会允许客户端继续,如果证书是无效?没有证书通过的情况由安全过滤器覆盖,该过滤器将检查证书不为空.
您在1和2中的假设都是正确的.Tomcat不允许将不受信任或无效的证书通过您的应用程序.如果获得空证书,则可以假定未传递证书,或者传递了不受信任/无效的证书.
在我正在开发的项目上,我们有与您相同的要求:仅限某些URL的客户端证书.我们通过实验发现了"clientAuth = want"的工作原理.