HTTPS协议是用于确保我们的连接安全的公认标准。 理解此协议的工作原理不是问题,并且从2000年起可以使用相应的RFC文档 。
尽管HTTPS的使用如此广泛,但您仍然可以找到一种无需不必要的复杂性就无法处理此协议的软件。 不幸的是,在使用该语言进行相互身份验证的过程中遇到了问题,这一点都不会让我感到惊讶。 它是Java 。
HTTPS如何工作?
在描述实现过程中遇到的问题之前,我将描述相互认证的工作原理。 HTTPS协议使用TLS / SSL协议来保护连接。 TLS / SSL协议定义了身份验证握手,该握手允许以安全方式将任何客户端与服务器连接。 在握手过程中,执行以下步骤:
- 客户端发送消息以启动连接。
- 服务器将其证书发送给客户端。
- 客户端使用由可信机构颁发的证书来验证证书。
- 服务器发送对客户端证书的请求。
- 客户端将其证书发送到服务器。
- 服务器验证客户端的证书。
- 服务器和客户端交换在数据加密期间使用的主密钥。
- 建立连接。
我们和队友一起尝试用Java实现HTTPS客户端。 结合我们对TLS / SSL握手的了解以及对curl
进行手动测试的经验,我们假设实现客户端只需要三个文件: 一个客户端的证书 , 一个客户端的私钥和一个用于验证服务器证书的受信任证书 。
哦,我们这么认为是多么错误。
Java –问题,解决方案以及为什么如此困难
由于每天使用相互身份验证是很不常见的,因此我们要求世界上最好的消息来源提供少量帮助。 初看Google叔叔提供的结果并没有揭示实现背后的复杂性,但是每次点击结果都使我们找到了越来越混乱的解决方案(其中一些来自90年代)。 更糟的是,我们不得不使用Apache HttpComponents来实现我们的连接,但是大多数提议的解决方案都基于纯Java库。
来自互联网的知识使我们能够确定:
- Java不能直接使用任何证书或私钥(例如
curl
) - Java需要单独的文件( Java Keystores ),其中可以包含原始证书和密钥。
- 我们需要一个受信任的密钥库,其中包含每个HTTPS连接的服务器证书验证所需的证书。
- 我们需要一个密钥密钥库,其中包含客户端的证书和客户端的私钥以进行相互认证。
首先,我们必须创建受信任的密钥库。 我们使用keytool
命令使用证书创建了密钥库:
$ keytool -import -alias trusted_certificate -keystore trusted.jks -file trusted.crt
我们将证书trusted.crt
存储在密钥库文件trusted.jks
中,别名为trusted_certificate
。 在执行此命令期间,要求我们输入此密钥库的密码。 我们稍后使用此密码来访问密钥库文件。
要创建密钥库,需要执行一些其他步骤。 在大多数情况下,您可能会从公司获得两个文件,这些文件会颁发客户的证书。 第一个文件将是pem
格式的客户证书。 该证书将被发送到服务器。 第二个文件是客户端的私钥(也是pem
格式),在握手期间使用该私钥来确认您是客户端证书的所有者。
不幸的是, Java仅支持PKCS12
格式。 因此,我们必须将我们的证书和私钥转换为PKCS12
格式。 我们可以使用OpenSSL做到这一点。
$ openssl pkcs12 -export \-in client.crt \-inkey client.key \-out key.p12 \-name client
我们从文件client.crt
和client.key
生成了文件key.p12
。 再次需要输入密码。 该密码用于保护私钥。
通过将PKCS12
导入新的密钥库,我们可以从PKCS12
格式的文件中生成另一个密钥库:
$ keytool -importkeystore \-destkeystore key.jks \-deststorepass <<keystore_password>> \-destkeypass <<key_password_in_keystore>> \-alias client \-srckeystore key.p12 \-srcstoretype PKCS12 \-srcstorepass <<original_password_of_PKCS12_file>>
这个命令看起来有点复杂,但是解密起来却很容易。 在命令的开头,我们声明名为key.jks
的新密钥库的参数。 我们定义了密钥库的密码和此密钥库将使用的私钥的密码。 我们还将私钥分配给密钥库中的某些别名(在本例中为client
)。 接下来,我们指定源文件( key.p12
),该文件的格式和原始密码。
使用trusted.jks
和key.jks
我们已经准备好进行编码。 在第一步中,我们必须描述我们如何使用密钥库。
File trustedKeystoreFile = new File("trusted.jks");
File keystoreFile = new File("key.jks");SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustedKeystoreFile, "<<trusted_keystore_password>>".toCharArray()).loadKeyMaterial(keystoreFile, "<<keystore_password>>".toCharArray(), "<<original_password_of_PKCS12_file>>".toCharArray()).build();SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslcontext,new String[]{"TLSv1.2"},null,SSLConnectionSocketFactory.getDefaultHostnameVerifier());
我们获取了密钥库文件,并构建了SSL上下文。 接下来,我们创建了套接字工厂,该套接字工厂为我们的请求提供了正确的HTTPS连接。
最后,我们可以从Java调用端点:
try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build()) {HttpGet httpGet = new HttpGet("https://ourserver.com/our/endpoint");try (CloseableHttpResponse response = httpclient.execute(httGet)) {HttpEntity entity = response.getEntity();System.out.println(response.getStatusLine());EntityUtils.consume(entity);}
}
做完了 在创建了两个额外的文件(密钥库)之后,这些文件等效于我们的原始证书和私钥,我们使用Java实现了相互身份验证 。 用Java实现HTTPS连接也许有一定道理,但是现在这只是一个头痛的事情。
翻译自: https://www.javacodegeeks.com/2016/03/mutual-problems-2.html