https原理(四)双向实践(java客户端+tcp代理)

2023-05-26,,

本文采用客户端与服务端共用一个密钥对

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"的工作原理.

https原理(四)双向实践(java客户端+tcp代理)的相关教程结束。

《https原理(四)双向实践(java客户端+tcp代理).doc》

下载本文的Word格式文档,以方便收藏与打印。