如何在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? 我有一个有效的例子。 为了它的工作,它有一个先决条件:

  1. 你必须有一个密钥库,其中包含你的证书文件。 ( 我知道你的场景并不完全允许这样做,但请稍微关注我这里,以便我们可以缩小你的问题一点点,因为它有点太复杂,无法立即回答。

因此,首先要了解实际的证书文件。 如果您使用的是Windows 7,则可以通过以下步骤完成:

  1. 打开Internet Explorer
  2. 打开工具 (在Internet Explorer 9中它是cog图标),
  3. 单击Internet选项
  4. 转到“ 内容”标签,
  5. 单击证书
  6. 找到手边的证书,
  7. 单击它并单击“ 导出”并将其保存到文件( DER编码二进制X.509 )。

导出后将其从其他证书中删除,确保Java不会以这种或那种方式使用它。我不知道它是否可以使用它,但它不会受到伤害。

现在,您必须创建密钥库文件并将导出的证书导入其中,这可以通过以下方式完成。

 > keytool -importcert -file  -keystore  -alias  

显然,keytool必须在你的工作路径上。它是JDK的一部分。

它会提示你输入密码(密钥库的密码;它不需要对证书做任何事情 ),我现在不知道如何设置为"" ,所以设置为password或其他任何password

在此之后,通过以下步骤,您可以通过代理建立到端点的安全连接。

  1. 首先,加载密钥库文件。

     InputStream trustStream = new FileInputStream("path/to/"); char[] trustPassword = "".toCharArray(); 
  2. 初始化KeyStore

     KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(trustStream, trustPassword); 
  3. 初始化TrustManager对象。 ( 我认为这些可以处理证书解析或类似的事情,但就我而言,这是神奇的。

     TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustFactory.init(trustStore); TrustManager[] trustManagers = trustFactory.getTrustManagers(); 
  4. 创建一个新的SSLContext ,将TrustManager对象加载到其中并将其设置为默认值。 请注意,因为SSLContext.getDefault()返回一个不可修改的类实例(或更像是默认的实例,不能重新初始化 ,但无论如何),这就是为什么我们必须使用SSLContext.getInstance(“SSL”) ) 。 另外,不要忘记将这个新的SSLContext设置为默认值,因为没有它,代码就会失败

     SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustManagers, null); SSLContext.setDefault(sslContext); 
  5. 为它创建代理并设置身份validation。 ( 而不是使用System.setProperty(…)使用Proxy类。哦,不要被Type.HTTP误导。

     Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("", )); 
  6. 为您的代理设置身份validation。 ( 我使用了一个不需要身份validation的免费代理 ,所以我现在无法测试这部分问题。

     Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("", "".toCharArray()); } }); 
  7. 通过将先前创建的代理传递给连接来连接到您的端点。 ( 我使用了我公司服务的一个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类工作,而不会过多地影响应用程序的其余部分。