我很新HTTPS/SSL/TLS
,我对使用证书进行身份验证时客户端应该提供的内容有点困惑.
我正在编写一个Java客户端,需要为POST
特定的数据执行简单的数据URL
.那部分工作正常,唯一的问题是应该完成它HTTPS
.该HTTPS
部分相当容易处理(使用HTTPclient
或使用Java的内置HTTPS
支持),但我仍然坚持使用客户端证书进行身份验证.我注意到这里已经有一个非常类似的问题,我还没有用我的代码试过(很快就会这么做).我当前的问题是 - 无论我做什么 - Java客户端永远不会发送证书(我可以使用PCAP
转储检查).
我想知道客户端在使用证书进行身份验证时应该向服务器提供什么内容(特别是对于Java - 如果这一点很重要)?这是一个JKS
文件,还是PKCS#12
?什么应该在他们身上; 只是客户端证书,还是密钥?如果是这样,哪个关键?对于所有不同类型的文件,证书类型等存在相当多的混淆.
正如我之前所说,我是新手,HTTPS/SSL/TLS
所以我也会欣赏一些背景信息(不必是一篇文章;我会接受好文章的链接).
最后设法解决了所有问题,所以我会回答我自己的问题.这些是我用来设置的文件/文件,以解决我的特定问题;
该客户端的密钥库是一个PKCS#12格式文件包含
客户端的公共证书(在此实例中由自签名CA签名)
客户端的私有密钥
为了生成它,我使用了OpenSSL的pkcs12
命令,例如;
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"
提示:确保您获得最新的OpenSSL,而不是版本0.9.8h,因为它似乎遇到了一个错误,它不允许您正确生成PKCS#12文件.
当客户端明确请求客户端进行身份验证时,Java客户端将使用此PKCS#12文件向服务器提供客户端证书.有关客户端证书身份验证协议实际工作原理的概述,请参阅维基百科有关TLS的文章(也解释了为什么我们需要客户端的私钥).
所述客户机的信任是直向前JKS格式包含该文件的根或中间CA证书.这些CA证书将确定允许您与哪些端点通信,在这种情况下,它将允许您的客户端连接到任何服务器提供由其中一个信任库CA签署的证书.
例如,要生成它,您可以使用标准的Java keytool;
keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca
使用此信任库,您的客户端将尝试与提供由CA标识的CA签名的证书的所有服务器进行完整的SSL握手myca.crt
.
以上文件仅供客户使用.如果要设置服务器,服务器还需要自己的密钥和信任库文件.可以在本网站上找到为Java客户端和服务器(使用Tomcat)设置完整工作示例的一个很好的演练.
问题/备注/提示
客户端证书身份验证只能由服务器强制执行.
(重要!)当服务器请求客户端证书(作为TLS握手的一部分)时,它还将提供可信CA的列表作为证书请求的一部分.当您希望提交用于身份验证的客户端证书未由其中一个CA签名时,它将不会被呈现(在我看来,这是奇怪的行为,但我确信这是有原因的).这是我的问题的主要原因,因为另一方没有正确配置他们的服务器以接受我的自签名客户端证书,并且我们认为问题是因为我没有在请求中正确提供客户端证书.
获取Wireshark.它具有出色的SSL/HTTPS数据包分析功能,可以帮助您调试和发现问题.-Djavax.net.debug=ssl
如果您对Java SSL调试输出感到不舒服,它类似于但更结构化(可以说)更容易理解.
完全可以使用Apache httpclient库.如果要使用httpclient,只需使用HTTPS等效项替换目标URL,并添加以下JVM参数(对于任何其他客户端都是相同的,无论您要使用哪个库通过HTTP/HTTPS发送/接收数据) :
-Djavax.net.debug=ssl -Djavax.net.ssl.keyStoreType=pkcs12 -Djavax.net.ssl.keyStore=client.p12 -Djavax.net.ssl.keyStorePassword=whatever -Djavax.net.ssl.trustStoreType=jks -Djavax.net.ssl.trustStore=client-truststore.jks -Djavax.net.ssl.trustStorePassword=whatever
其他答案显示了如何全局配置客户端证书.但是,如果要以编程方式为一个特定连接定义客户端密钥,而不是在JVM上运行的每个应用程序上全局定义它,那么您可以配置自己的SSLContext,如下所示:
String keyPassphrase = ""; KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray()); SSLContext sslContext = SSLContexts.custom() .loadKeyMaterial(keyStore, null) .build(); HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));
它们JKS文件只是证书和密钥对的容器.在客户端身份验证方案中,密钥的各个部分将位于此处:
该客户端的商店将包含客户端的私有和公共密钥对.它被称为密钥库.
该服务器的商店将包含客户端的公共密钥.它被称为信任库.
信任库和密钥库的分离不是强制性的,但建议使用.它们可以是相同的物理文件.
要设置两个存储的文件系统位置,请使用以下系统属性:
-Djavax.net.ssl.keyStore=clientsidestore.jks
并在服务器上:
-Djavax.net.ssl.trustStore=serversidestore.jks
要将客户端的证书(公钥)导出到文件,以便将其复制到服务器,请使用
keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks
要将客户端的公钥导入服务器的密钥库,请使用(如提到的海报,这已由服务器管理员完成)
keytool -import -file publicclientkey.cer -store serversidestore.jks
Maven pom.xml:
4.0.0 some.examples sslcliauth 1.0-SNAPSHOT jar sslcliauth org.apache.httpcomponents httpclient 4.4
Java代码:
package some.examples; import java.io.FileInputStream; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.SSLContext; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.ssl.SSLContexts; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.apache.http.entity.InputStreamEntity; public class SSLCliAuthExample { private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName()); private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS"; private static final String CA_KEYSTORE_PATH = "./cacert.jks"; private static final String CA_KEYSTORE_PASS = "changeit"; private static final String CLIENT_KEYSTORE_TYPE = "PKCS12"; private static final String CLIENT_KEYSTORE_PATH = "./client.p12"; private static final String CLIENT_KEYSTORE_PASS = "changeit"; public static void main(String[] args) throws Exception { requestTimestamp(); } public final static void requestTimestamp() throws Exception { SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory( createSslCustomContext(), new String[]{"TLSv1"}, // Allow TLSv1 protocol only null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) { HttpPost req = new HttpPost("https://changeit.com/changeit"); req.setConfig(configureRequest()); HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin")); req.setEntity(ent); try (CloseableHttpResponse response = httpclient.execute(req)) { HttpEntity entity = response.getEntity(); LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine()); EntityUtils.consume(entity); LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString()); } } } public static RequestConfig configureRequest() { HttpHost proxy = new HttpHost("changeit.local", 8080, "http"); RequestConfig config = RequestConfig.custom() .setProxy(proxy) .build(); return config; } public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException { // Trusted CA keystore KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE); tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray()); // Client keystore KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE); cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray()); SSLContext sslcontext = SSLContexts.custom() //.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate .build(); return sslcontext; } }
对于那些只想设置双向身份验证(服务器和客户端证书)的人来说,这两个链接的组合可以帮助您:
双向身份验证设置:
https://linuxconfig.org/apache-web-server-ssl-authentication
您不需要使用他们提到的openssl配置文件; 只是用
$ openssl genrsa -des3 -out ca.key 4096
$ openssl req -new -x509 -days 365 -key ca.key -out ca.crt
生成您自己的CA证书,然后通过以下方式生成并签署服务器和客户端密钥:
$ openssl genrsa -des3 -out server.key 4096
$ openssl req -new -key server.key -out server.csr
$ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 100 -out server.crt
和
$ openssl genrsa -des3 -out client.key 4096
$ openssl req -new -key client.key -out client.csr
$ openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 101 -out client.crt
其余部分请按照链接中的步骤操作.管理Chrome的证书与上面提到的firefox示例相同.
接下来,通过以下方式设置服
https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-apache-for-ubuntu-14-04
请注意,您已经创建了服务器.crt和.key,因此您不必再执行该步骤.