各对等机构各自部署一套服务平台(称作节点peer),要求在各机构平台之间内部通信使用ssl加密。支持自签名方式或ca认证方式。
win10,jdk1.8.0_261。
keytool
命令;server.ssl
服务端配置;restTemplate
客户端集成ssl配置;-Djavax.net.debug=ssl:handshake
,便于分析ssl。/api/doSomething
接口,内部再用restTemplate
调用peer2的https接口https://peer2/interact/reply/hello
;keytool
为peer1和peer2分别生成秘钥库以及信任秘钥库,并配置进ssl服务端和客户端;http://peer1/api/doSomething
;完整项目点这里:
https://github.com/oaHeZgnoS/ssl-peer
com.szh.peer.ctrl.SystemCtrl.doSomething()
com.szh.peer.ctrl.InteractCtrl.replyHello()
peer1和peer2两个机构各自生成自己秘钥库,并将对方配置在自己的信任秘钥库中,从而达到互认互信加密通信。信任秘钥库可以合并在秘钥库,也可以是独立于秘钥库的另一个文件中。
## 信任密钥库合并在密钥库
一、peer1生成密钥库以及导出公钥证书
1、生成peer1的密钥库peer1.jks
keytool -genkeypair -alias peer1 -keystore peer1.jks -storepass passwd1 -dname CN=peer1,OU=peer1,O=peer1,L=peer1,C=CN
2、查看密钥库详情
keytool -list -keystore peer1.jks -storepass passwd1 -v
3、peer1导出公钥证书
keytool -export -alias peer1 -file peer1.cer -keystore peer1.jks -storepass passwd1
二、peer2生成密钥库以及导出公钥证书
1、生成peer2的密钥库peer2.jks
keytool -genkeypair -alias peer2 -keystore peer2.jks -storepass passwd2 -dname CN=peer2,OU=peer2,O=peer2,L=peer2,C=CN
2、查看密钥库详情
keytool -list -keystore peer2.jks -storepass passwd2 -v
3、peer2导出公钥证书
keytool -export -alias peer2 -file peer2.cer -keystore peer2.jks -storepass passwd2
三、peer1/peer2互相导入对方密钥库,建立信任
1、peer2的证书导入peer1密钥库
keytool -import -alias peer2 -file peer2.cer -keystore peer1.jks -storepass passwd1
2、peer1的证书导入peer2密钥库
keytool -import -alias peer1 -file peer1.cer -keystore peer2.jks -storepass passwd2
四、检验peer1/peer2是否具有自己的privateKey和对方的cert
1、检验peer1是否具有自己的privateKey和peer2的cert
keytool -list -keystore peer1.jks -storepass passwd1
2、检验peer2是否具有自己的privateKey和peer1的cert
keytool -list -keystore peer2.jks -storepass passwd2
五、转换JKS格式为P12(便于在postman单向测试证书)
1、转换peer2.jks->peer2.p12
keytool -importkeystore -srckeystore peer2.jks -destkeystore peer2.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass passwd2 -deststorepass passwd2 -srckeypass passwd2 -destkeypass passwd2 -srcalias peer2 -destalias peer2 -noprompt
2、转换peer1.jks->peer1.p12
keytool -importkeystore -srckeystore peer1.jks -destkeystore peer1.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass passwd1 -deststorepass passwd1 -srckeypass passwd1 -destkeypass passwd1 -srcalias peer1 -destalias peer1 -noprompt
## 信任密钥库独立于密钥库
一、peer1生成密钥库以及导出公钥证书
1、生成peer1的密钥库peer1.jks
keytool -genkeypair -alias peer1 -keystore peer1.jks -storepass passwd1 -dname CN=peer1,OU=peer1,O=peer1,L=peer1,C=CN
2、查看密钥库详情
keytool -list -keystore peer1.jks -storepass passwd1 -v
3、peer1导出公钥证书
keytool -export -alias peer1 -file peer1.cer -keystore peer1.jks -storepass passwd1
二、peer2生成密钥库以及导出公钥证书
1、生成peer2的密钥库peer2.jks
keytool -genkeypair -alias peer2 -keystore peer2.jks -storepass passwd2 -dname CN=peer2,OU=peer2,O=peer2,L=peer2,C=CN
2、查看密钥库详情
keytool -list -keystore peer2.jks -storepass passwd2 -v
3、peer2导出公钥证书
keytool -export -alias peer2 -file peer2.cer -keystore peer2.jks -storepass passwd2
三、peer1/peer2互相导入对方信任密钥库
1、peer2的证书导入peer1信任密钥库
keytool -import -alias peer1Trust -file peer2.cer -keystore peer1Trust.jks -storepass passwd1
2、peer1的证书导入peer2信任密钥库
keytool -import -alias peer2Trust -file peer1.cer -keystore peer2Trust.jks -storepass passwd2
四、检验peer1/peer2是否具有对方的cert
1、检验peer1的信任密钥库是否有peer2的cert
keytool -list -keystore peer1Trust.jks -storepass passwd1
2、检验peer2的信任密钥库是否有peer1的cert
keytool -list -keystore peer2Trust.jks -storepass passwd2
peer1和peer2两个机构各自生成自己秘钥库jks
,再生成csr
证书请求文件,再通过同一个CA签发生成证书。再将ca和自身证书配置在自己的秘钥库和信任秘钥库中,从而达到互认互信加密通信。信任秘钥库可以合并在秘钥库,也可以是独立于秘钥库的另一个文件中。
CA的方式更灵活一些,不像自签名的方式,一旦加入了peer3,那么peer1和peer2都要再把peer3的证书导入到自己信任秘钥库中,一般需要重启服务;而CA方式避免了这个问题。
# CA jks
keytool -genkeypair -alias ca -dname "cn=Local Network - Development" -validity 10000 -keyalg RSA -keysize 2048 -ext bc:c -keystore ca.jks -keypass passwdca -storepass passwdca
# CA cert
keytool -exportcert -noprompt -rfc -alias ca -file ca.crt -keystore ca.jks -storepass passwdca
keytool -exportcert -noprompt -rfc -alias ca -file ca.pem -keystore ca.jks -storepass passwdca
#Peer1
# generate private keys (for peer1)
keytool -genkeypair -alias peer1 -dname cn=peer1 -validity 10000 -keyalg RSA -keysize 2048 -keystore peer1.jks -keypass passwd1 -storepass passwd1
# generate a certificate for peer1 signed by ca (ca -> peer1)
# generate csr
keytool -certreq -noprompt -alias peer1 -sigalg SHA256withRSA -file peer1.csr -keypass passwd1 -keystore peer1.jks -dname "CN=peer1" -storepass passwd1
# sign csr
keytool -gencert -noprompt -infile peer1.csr -outfile peer1.crt -alias ca -sigalg SHA256withRSA -validity 10000 -ext ku:c=dig,keyEnc -ext "san=dns:localhost,ip:127.0.0.1" -ext eku=sa,ca -keypass passwdca -keystore ca.jks -storepass passwdca -rfc
# import ca.crt into peer1.jks
keytool -keystore peer1.jks -storepass passwd1 -importcert -trustcacerts -noprompt -alias ca -file ca.crt
# import peer1.crt into peer1.jks (complete crt chain ca->peer1 imported)
keytool -keystore peer1.jks -storepass passwd1 -importcert -noprompt -alias peer1 -file peer1.crt
#Peer2
# generate private keys (for peer2)
keytool -genkeypair -alias peer2 -dname cn=peer2 -validity 10000 -keyalg RSA -keysize 2048 -keystore peer2.jks -keypass passwd2 -storepass passwd2
# generate a certificate for peer2 signed by ca (ca -> peer2)
# generate csr
keytool -certreq -noprompt -alias peer2 -sigalg SHA256withRSA -file peer2.csr -keypass passwd2 -keystore peer2.jks -dname "CN=peer2" -storepass passwd2
# sign csr
keytool -gencert -noprompt -infile peer2.csr -outfile peer2.crt -alias ca -sigalg SHA256withRSA -validity 10000 -ext ku:c=dig,keyEnc -ext "san=dns:localhost,ip:127.0.0.1" -ext eku=sa,ca -keypass passwdca -keystore ca.jks -storepass passwdca -rfc
# import ca.crt into peer2.jks
keytool -keystore peer2.jks -storepass passwd2 -importcert -trustcacerts -noprompt -alias ca -file ca.crt
# import peer2.crt into peer2.jks (complete crt chain ca->peer2 imported)
keytool -keystore peer2.jks -storepass passwd2 -importcert -noprompt -alias peer2 -file peer2.crt
可以单独把ca证书导入到独立的信任库。
# Trust Store containing ca.crt
keytool -importcert -noprompt -alias catrust -file ca.crt -keypass passwdtrust -keystore trust.jks -storepass passwdtrust
## ssl start
server.ssl.pure-key-store=peer1.jks
server.ssl.pure-trust-store=trust.jks
# 私钥库
server.ssl.enabled=true
server.ssl.key-store-type=JKS
server.ssl.key-store=classpath:${server.ssl.pure-key-store}
server.ssl.key-store-password=passwd1
server.ssl.key-alias=peer1
# 受信任密钥库
server.ssl.trust-store=classpath:${server.ssl.pure-trust-store}
server.ssl.trust-store-password=passwdtrust
server.ssl.trust-store-provider=SUN
server.ssl.trust-store-type=JKS
server.ssl.client-auth=need
## ssl end
客户端restTemplate底层实现选用httpClient,
org.apache.httpcomponents
httpclient
4.5.5
构造httpClient的sslContext,
@Configuration
@Slf4j
public class RestTemplateConfig {
@Value("${server.ssl.pure-key-store}")
public String keyStore;
@Value("${server.ssl.key-store-password}")
public String keyStorePassword;
@Value("${server.ssl.pure-trust-store}")
public String trustStore;
@Value("${server.ssl.trust-store-password}")
public String trustStorePassword;
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory httpComponentsClientHttpRequestFactory) {
RestTemplate restTemplate = new RestTemplate(httpComponentsClientHttpRequestFactory);
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
log.info("loading restTemplate");
return restTemplate;
}
@Bean("httpComponentsClientHttpRequestFactory")
public ClientHttpRequestFactory httpComponentsClientHttpRequestFactory() throws IOException, UnrecoverableKeyException,
CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
SSLContext sslContext = SSLContextBuilder
.create()
// 作为client,load自己密钥库;理论上当server的server.ssl.client-auth=need时,通过它可以获取client的公钥证书传递给server来验证
.loadKeyMaterial(new ClassPathResource(keyStore).getURL(),
trustStorePassword.toCharArray(), keyStorePassword.toCharArray())
// 作为client,load自己的信任库;在请求server之前,client先通过它判断server是否受信
.loadTrustMaterial(new ClassPathResource(trustStore).getURL(), trustStorePassword.toCharArray())
.build();
HttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) // 不需要主机验证
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(client);
return requestFactory;
}
}
自定义一个http端口,
server.http-port=8288
配置web容器也开启http端口,
@Component
public class TomcatServerCustomer implements WebServerFactoryCustomizer {
@Value("${server.http-port}")
public Integer httpPort;
@Override
public void customize(TomcatServletWebServerFactory factory) {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(httpPort);
factory.addAdditionalTomcatConnectors(connector);
}
}
服务启动后会打印https和http端口信息,
Tomcat started on port(s): 28288 (https) 8288 (http) with context path ''
http调用peer,触发内部https调用另一peer,成功返回,
curl http://127.0.0.1:8688/api/doSomething
hello, peer2
curl http://127.0.0.1:8288/api/doSomething
hello, peer1
当然也可以在postman中模拟一个客户端peer2,直接请求https接口https://peer1/interact/reply/hello
,只不过需要在Settings
-Certificates
-Client Certificates
导入peer2的证书即可。
从peer2的jks格式秘钥库中获取p12证书,
keytool -importkeystore -srckeystore peer2.jks -destkeystore peer2.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass passwd2 -deststorepass passwd2 -srckeypass passwd2 -destkeypass passwd2 -srcalias peer2 -destalias peer2 -noprompt
除此之外,还有其他命令,
查看帮助命令:
keytool -command_name -help
keytool -genkeypair -help
查看密钥库里面的信息:
keytool -list -keystore peer1.jks -v
查看指定证书文件的信息:
keytool -printcert -file peer1.cer -v
cer证书转为pem:
openssl x509 -in ca.cer -inform DER -out ca.pem -outform PEM
是指peer节点作为服务端,不用去认证客户端,这样每个客户端也不用发送自己的公钥证书给服务端了。对应设置server.ssl.client-auth=none
,再注释掉下面不需要的这行,
SSLContext sslContext = SSLContextBuilder
.create()
// 注释这行
/*.loadKeyMaterial(new ClassPathResource(keyStore).getURL(), trustStorePassword.toCharArray(), keyStorePassword.toCharArray())*/
...
可以看出来,单向认证只是客户端校验服务端证书是否合法。
双向认证还需要服务端校验客户端证书。
以上插图来自这里。
真实场景中使用,需要多测试不同jdk版本下的keytool生成的秘钥证书,在不同版本jvm下的表现。比如jdk11下生成的jks,在jdk8中运行可能报错“Invalid keytool format”。
这种情况下,因为服务端不再需要验证客户端证书,所以postman作为客户端也不需要导入Settings
-Certificates
-Client Certificates
。此时已经可以直接访问https接口了,只是postman里会标记提示“Unable to get local issuer certificate
”,如下,
同样,直接在浏览器敲“https://127.0.0.1:28288/interact/reply/hello?name=browser
”,也会先提示“NET::ERR_CERT_AUTHORITY_INVALID
”,代表服务端的证书可能是不安全的,需要手动继续访问。
那如果强行开启postman客户端的ssl认证,首先需要如下操作,
此时访问,则报错SSL Error: Unable to get local issuer certificate
,
所以还需要在Settings
-Certificates
-CA Certificates
导入ca.pem,此时再访问,可能报错SSL Error: Hostname/IP does not match certificate's altnames
,
这是因为生成节点证书时,通过-ext "san=dns:localhost"
指定了只允许通过serverName别名localhost来访问,127.0.0.1不能访问,所以要么请求ip改为localhost,要么在生成证书时,同时指定ip,就可以通过ip访问:-ext "san=dns:localhost,ip:127.0.0.1"
。
上面是ca->peer
,只有一级。多级root->ca->peer
可以这样,
## [root] generate root.jks
keytool -genkeypair -alias root -dname "cn=root" -validity 10000 -keyalg RSA -keysize 2048 -ext bc:c -keystore root.jks -keypass passwdroot -storepass passwdroot
## [root] self sign and generate root.pem
keytool -exportcert -rfc -keystore root.jks -alias root -storepass passwdroot > root.pem
## [ca] generate ca.jks
keytool -genkeypair -alias ca -dname "cn=ca" -validity 10000 -keyalg RSA -keysize 2048 -ext bc:c -keystore ca.jks -keypass passwdca -storepass passwdca
## [ca] generate ca.csr
keytool -keystore ca.jks -storepass passwdca -certreq -alias ca -file ca.csr
## [root] root sign and generate ca.pem (root -> ca)
keytool -keystore root.jks -storepass passwdroot -gencert -alias root -ext bc=0 -ext san=dns:ca -rfc -infile ca.csr > ca.pem
## [ca] import root.pem and ca.pem into ca.jks
keytool -keystore ca.jks -storepass passwdca -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore ca.jks -storepass passwdca -importcert -alias ca -file ca.pem
## [peer1] generate peer1.jks
keytool -genkeypair -alias peer1 -dname cn=peer1 -validity 10000 -keyalg RSA -keysize 2048 -keystore peer1.jks -keypass passwd1 -storepass passwd1
## [peer1] generate peer1.csr
keytool -keystore peer1.jks -storepass passwd1 -certreq -alias peer1 -file peer1.csr
## [ca] ca sign and generate peer1.pem (root -> ca -> peer1)
keytool -keystore ca.jks -storepass passwdca -gencert -alias ca -ext ku:c=dig,keyEnc -ext "san=dns:localhost,ip:127.0.0.1" -ext eku=sa,ca -rfc -infile peer1.csr > peer1.pem
## [peer1] import root.pem and ca.pem and peer1.pem into peer1.jks
keytool -keystore peer1.jks -storepass passwd1 -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore peer1.jks -storepass passwd1 -importcert -alias ca -file ca.pem
keytool -keystore peer1.jks -storepass passwd1 -importcert -alias peer1 -file peer1.pem
## [peer1] import root.pem and ca.pem and peer1.pem into peer1Trust.jks
keytool -keystore peer1Trust.jks -storepass passwd1 -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore peer1Trust.jks -storepass passwd1 -importcert -alias ca -file ca.pem
keytool -keystore peer1Trust.jks -storepass passwd1 -importcert -alias peer1 -file peer1.pem
## [peer1] peer1.jks -> peer1.p12
keytool -importkeystore -srckeystore peer1.jks -destkeystore peer1.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass passwd1 -deststorepass passwd1 -srckeypass passwd1 -destkeypass passwd1 -srcalias peer1 -destalias peer1 -noprompt
## [peer2] generate peer2.jks
keytool -genkeypair -alias peer2 -dname cn=peer2 -validity 10000 -keyalg RSA -keysize 2048 -keystore peer2.jks -keypass passwd2 -storepass passwd2
## [peer2] generate peer2.csr
keytool -keystore peer2.jks -storepass passwd2 -certreq -alias peer2 -file peer2.csr
## [ca] ca sign and generate peer2.pem (root -> ca -> peer2)
keytool -keystore ca.jks -storepass passwdca -gencert -alias ca -ext ku:c=dig,keyEnc -ext "san=dns:localhost,ip:127.0.0.1" -ext eku=sa,ca -rfc -infile peer2.csr > peer2.pem
## [peer2] import root.pem and ca.pem and peer2.pem into peer2.jks
keytool -keystore peer2.jks -storepass passwd2 -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore peer2.jks -storepass passwd2 -importcert -alias ca -file ca.pem
keytool -keystore peer2.jks -storepass passwd2 -importcert -alias peer2 -file peer2.pem
## [peer2] import root.pem and ca.pem and peer2.pem into peer2Trust.jks
keytool -keystore peer2Trust.jks -storepass passwd2 -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore peer2Trust.jks -storepass passwd2 -importcert -alias ca -file ca.pem
keytool -keystore peer2Trust.jks -storepass passwd2 -importcert -alias peer2 -file peer2.pem
## [peer2] peer2.jks -> peer2.p12
keytool -importkeystore -srckeystore peer2.jks -destkeystore peer2.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass passwd2 -deststorepass passwd2 -srckeypass passwd2 -destkeypass passwd2 -srcalias peer2 -destalias peer2 -noprompt