如何在HttpsURLConnection中使用证书身份validation?
我正在尝试连接到HTTPS URL,但我需要使用客户端身份validation和第三方软件在我的系统上放置的证书。
我一点也不知道我应该如何找到或使用它,而我所要做的只是C#示例代码,它与我发现的所有Java答案有很大不同。 (例如,KeyStore显然需要某种密码?)
这是我的C#示例代码
System.Security.Cryptography.X509Certificates.X509CertificateCollection SSC_Certs = new System.Security.Cryptography.X509Certificates.X509CertificateCollection(); Microsoft.Web.Services2.Security.X509.X509CertificateStore WS2_store = Microsoft.Web.Services2.Security.X509.X509CertificateStore.CurrentUserStore( Microsoft.Web.Services2.Security.X509.X509CertificateStore.MyStore); WS2_store.OpenRead(); Microsoft.Web.Services2.Security.X509.X509CertificateCollection WS2_store_Certs = WS2_store.Certificates;
然后它只是迭代WS2_store_Certs CertificateCollection并以这种方式检查它们。 再进一步,它设置这样的证书:
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url_string); httpWebRequest.ClientCertificates = SSC_Certs;
这一切看起来都很合理,即使我不知道它是如何找到证书的,但我仍然无法找到Java的等价物。
UPDATE
我正在建立的连接是依赖于JDK 5的更大应用程序的一部分,但我已经设法使用sunmscapi jar来查找我正在寻找的证书。 当我尝试使用Windows密钥库进行连接时出现错误,所以我认为通过从Windows应用程序获取我需要的证书并将其插入默认的java文件中我解决了这个问题。 现在我收到一个EOFException,然后是SSLHandshakeException,说“握手期间远程主机关闭连接”。 ssl调试跟踪不会向我显示直接问题,因为我需要的证书显示在此处的证书链中。
它完成了整个ClientKeyExchange的事情,说它已经完成,然后我从调试日志中得到的最后一条消息就是
[write] MD5 and SHA1 hashes: len = 16 0000: 14 00 00 0C D3 E1 E7 3D C2 37 2F 41 F9 38 26 CC .......=.7/A.8&. Padded plaintext before ENCRYPTION: len = 32 0000: 14 00 00 0C D3 E1 E7 3D C2 37 2F 41 F9 38 26 CC .......=.7/A.8&. 0010: CB 10 05 A1 3D C3 13 1C EC 39 ED 93 79 9E 4D B0 ....=....9..yM AWT-EventQueue-1, WRITE: TLSv1 Handshake, length = 32 [Raw write]: length = 37 0000: 16 03 01 00 20 06 B1 D8 8F 9B 70 92 F4 AD 0D 91 .... .....p..... 0010: 25 9C 7D 3E 65 C1 8C A7 F7 DA 09 C0 84 FF F4 4A %..>e..........J 0020: CE FD 4D 65 8D ..Me. AWT-EventQueue-1, received EOFException: error
我用来建立连接的代码是
KeyStore jks = KeyStore.getInstance(KeyStore.getDefaultType()); jks.load(null, null); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); jks.setCertificateEntry("alias", cert1); //X509Certificate obtained from windows keystore kmf.init(jks, new char[0]); SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(kmf.getKeyManagers(), new TrustManager[]{tm}, null); sslsocketfactory = sslContext.getSocketFactory(); System.setProperty("https.proxyHost", proxyurl); System.setProperty("https.proxyPort", proxyport); Authenticator.setDefault(new MyAuthenticator("proxyID", "proxyPassword")); URL url = new URL(null, urlStr, new sun.net.www.protocol.https.Handler()); HttpsURLConnection uc = (HttpsURLConnection) url.openConnection(); uc.setSSLSocketFactory(sslsocketfactory); uc.setAllowUserInteraction(true); uc.setRequestMethod("POST"); uc.connect();
(我还没有尝试过HttpClient,因为我不知道如何找到证书文件,我也不确定在每个客户端系统上是否总是相同。)
另一个更新
我在WINDOWS-ROOT密钥库中找到了我需要的证书的CA(并使用.verify()检查它们是否全部检出),我已经将它们添加到java密钥库中,但仍然没有任何变化。 我猜他们应该进入TrustStore,但我还没有找到一种方法来编程。 (我宁愿不依赖最终用户来做这种事情,因为我可以向他们保证,由于在这个非常长的问题开头提到的第三方软件,证书和CA将会出现。)
更多更新
添加以前的更新,我得出结论,我的问题必须在于我的CA不在Java的cacerts文件中,因此它从服务器获取可信CA的列表,但不识别它们和随后不会发回单个证书导致连接失败。 所以问题仍然存在,我如何让Java使用它的密钥库作为信任库或以编程方式将证书添加到cacerts(不需要文件路径)? 因为如果那些不可能只留给我秘密选项C, 伏都教 。 为了以防万一,我会开始用针刺伤Duke娃娃。
好的,所以您的问题的标题是如何使用HttpsURLConnection进行证书身份validation? 我有一个有效的例子。 为了它的工作,它有一个先决条件:
- 你必须有一个密钥库,其中包含你的证书文件。 ( 我知道你的场景并不完全允许这样做,但请稍微关注我这里,以便我们可以缩小你的问题一点点,因为它有点太复杂,无法立即回答。 )
因此,首先要了解实际的证书文件。 如果您使用的是Windows 7,则可以通过以下步骤完成:
- 打开Internet Explorer ,
- 打开工具 (在Internet Explorer 9中它是cog图标),
- 单击Internet选项 ,
- 转到“ 内容”标签,
- 单击证书 ,
- 找到手边的证书,
- 单击它并单击“ 导出”并将其保存到文件( DER编码二进制X.509 )。
( 导出后将其从其他证书中删除,确保Java不会以这种或那种方式使用它。我不知道它是否可以使用它,但它不会受到伤害。 )
现在,您必须创建密钥库文件并将导出的证书导入其中,这可以通过以下方式完成。
> keytool -importcert -file -keystore -alias
( 显然,keytool必须在你的工作路径上。它是JDK的一部分。 )
它会提示你输入密码(密钥库的密码;它不需要对证书做任何事情 ),我现在不知道如何设置为""
,所以设置为password
或其他任何password
。
在此之后,通过以下步骤,您可以通过代理建立到端点的安全连接。
-
首先,加载密钥库文件。
InputStream trustStream = new FileInputStream("path/to/
"); char[] trustPassword = " ".toCharArray(); -
初始化KeyStore 。
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(trustStream, trustPassword);
-
初始化TrustManager对象。 ( 我认为这些可以处理证书解析或类似的事情,但就我而言,这是神奇的。 )
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustFactory.init(trustStore); TrustManager[] trustManagers = trustFactory.getTrustManagers();
-
创建一个新的SSLContext ,将TrustManager对象加载到其中并将其设置为默认值。 请注意,因为SSLContext.getDefault()返回一个不可修改的类实例(或更像是默认的实例,不能重新初始化 ,但无论如何),这就是为什么我们必须使用SSLContext.getInstance(“SSL”) ) 。 另外,不要忘记将这个新的SSLContext设置为默认值,因为没有它,代码就会失败 。
SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustManagers, null); SSLContext.setDefault(sslContext);
-
为它创建代理并设置身份validation。 ( 而不是使用System.setProperty(…)使用Proxy类。哦,不要被Type.HTTP误导。 )
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("
", )); -
为您的代理设置身份validation。 ( 我使用了一个不需要身份validation的免费代理 ,所以我现在无法测试这部分问题。 )
Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("
", " ".toCharArray()); } }); -
通过将先前创建的代理传递给连接来连接到您的端点。 ( 我使用了我公司服务的一个URL,要求提供证书 – 我当然在我自己的密钥库中导入了哪个证书。 )
URL url = new URL("
"); URLConnection connection = url.openConnection(proxy); connection.connect();
如果它不能像这样工作(你得到错误),那么用HttpsURLConnection尝试它。
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection; httpsConnection.setAllowUserInteraction(true); httpsConnection.setRequestMethod("POST");
基本上,如果服务器(您连接到的URL指向的资源所在的位置)要求提供凭据, setAllowUserInteraction就会启动,对吧? 现在,我无法测试本身,但是我看到你是否可以让这个婴儿使用不需要身份validation来访问其资源的服务器,那么你很高兴,因为服务器会问只有在已建立连接后才能对自己进行身份validation。
如果在这些之后你仍然收到一些错误,那么请发布它。
根据我上次尝试的记忆,你不能使用HttpsURLConnection
。 您可以查看支持此function的Apache HttpClient库。
这是一个代码示例,提供了该过程的概念:
String server = "example.com"; int port = 443; EasySSLProtocolSocketFactory psf = new EasySSLProtocolSocketFactory(); InputStream is = readFile("/path/to/certificate"); KeyMaterial km = new KeyMaterial(is, "certpasswd".toCharArray()); easy.setKeyMaterial(km); Protocol proto = new Protocol("https", (ProtocolSocketFactory) psf, port); HttpClient httpclient = new HttpClient(); httpclient.getHostConfiguration().setHost(server, port, proto);
编辑(关于汤姆评论):
以下是有关如何获取存储在Windows密钥库中的证书的一些想法:
- 您需要使用Sun Cryptography套件(即Sun Java 6 JDK)
- 您可以像这样获取KeyStore:
ks = KeyStore.getInstance("Windows-MY");
- 你可以这样加载它:
ks.load(null, null);
。 JVM将加载Windos密钥库并负责询问密钥库密码。 - 然后,您可以像任何其他密钥库一样导航密钥库。
您可以在Https连接上设置HostNameVerifier
http://docs.oracle.com/javase/6/docs/api/javax/net/ssl/class-use/HostnameVerifier.html
例如http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/
原来是私钥问题,因为它被设置为不可导出。 因为这意味着我只能从Windows商店获取私钥,我陷入困境并“修复”了这个问题,并且需要进行大量的处理以使必要的jdk6类工作,而不会过多地影响应用程序的其余部分。