如何在HttpsURLConnection中使用证书身份验证?

时间:2011-12-01 09:41:56

标签: java https x509certificate authentication

我正在尝试连接到HTTPS网址,但我需要使用客户端身份验证和第三方软件在我的系统上放置的证书。

我一点也不知道我应该如何找到或使用它,而我所要做的只是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的等价物。

更新

我正在建立的连接是依赖于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..y.M.
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将会出现。)< / p>

更多更新

添加以前的更新,我得出结论,我的问题必须在于我的CA不在Java的cacerts文件中,因此它从服务器获取可信CA的列表,但不是识别它们,然后不再发送单个证书导致连接失败。 所以问题仍然存在,我如何让Java使用它的密钥库作为信任库或以编程方式向cacerts添加证书(不需要文件路径)?因为如果那些不可能只留给我秘密选项C, voodoo 。为了以防万一,我会开始用针刺伤Duke娃娃。

3 个答案:

答案 0 :(得分:11)

好的,所以你的问题的标题是如何使用HttpsURLConnection进行证书身份验证?我有一个工作示例。要使它工作,它有一个先决条件:

  1. 您必须拥有一个包含证书文件的密钥库。 (我知道你的场景并不完全允许这样做,但请稍微关注我这里,以便我们可以稍微缩小你的问题,因为它有点太复杂了,无法立即回答。
  2. 因此,首先要了解实际的证书文件。如果您使用的是Windows 7,则可以通过以下步骤完成:

    1. 打开 Internet Explorer
    2. 打开工具(在Internet Explorer 9中,它是 cog 图标),
    3. 点击互联网选项
    4. 转到内容标签,
    5. 点击证书
    6. 找到手头的证书,
    7. 单击它并单击导出并将其保存到文件( DER编码二进制X.509 )。
    8. 导出后将其从其他证书中删除,确保Java不会以某种方式使用它。我不知道它是否可以使用它,但它不能伤。

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

      > keytool -importcert -file <certificate> -keystore <keystore> -alias <alias>
      

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

      它会提示您输入密码(密钥库的密码;它不必与证书一起任何),我不知道如何设置为{{1现在,所以设置为""或其他任何内容。

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

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

        password
      2. 初始化 KeyStore

        InputStream trustStream = new FileInputStream("path/to/<keystore>");
        char[] trustPassword = "<password>".toCharArray();
        
      3. 初始化 TrustManager 对象。 (我认为这些可以处理证书解析或类似的事情,但就我而言,这是神奇的。

        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(trustStream, trustPassword);
        
      4. 创建新的 SSLContext ,将 TrustManager 对象加载到其中并将其设置为默认值。请注意,因为 SSLContext.getDefault()会返回一个不可修改的类实例(或者更像是默认的实例重新初始化,但无论如何) ,这就是我们必须使用 SSLContext.getInstance(“SSL”)的原因。另外,不要忘记将此新的 SSLContext 设置为默认值,因为如果没有,则代码将 poof

        TrustManagerFactory trustFactory =
            TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustFactory.init(trustStore);
        TrustManager[] trustManagers = trustFactory.getTrustManagers();
        
      5. 为其创建代理并设置身份验证。 (而不是使用System.setProperty(...)使用Proxy类。哦,不要被Type.HTTP误导。

        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustManagers, null);
        SSLContext.setDefault(sslContext);
        
      6. 为您的代理设置身份验证。 (我使用了a free proxy,它不需要身份验证,所以我现在无法测试问题的这一部分。

        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("<host>", <port>));
        
      7. 通过将先前创建的代理传递给连接来连接到您的终点。 (我使用了我公司服务的一个URL,要求提供证书 - 当然我在自己的密钥库中导入了哪个证书。

        Authenticator.setDefault(new Authenticator() {
        
          @Override
          protected PasswordAuthentication getPasswordAuthentication() {
            return new PasswordAuthentication("<user>", "<password>".toCharArray());
          }
        });
        
      8. 如果它不起作用(你得到错误),那么用 HttpsURLConnection 尝试。

        URL url = new URL("<endpoint>");
        URLConnection connection = url.openConnection(proxy);
        connection.connect();
        

        基本上,如果服务器(您连接到的URL指向的资源所在的位置)要求提供凭据, setAllowUserInteraction 就会启动,对吗?现在,我无法测试那个本身,但是当我看到你是否可以让这个婴儿使用不需要身份验证来访问其资源的服务器时,那么你就是好的go,因为只有连接已经建立后,服务器才会要求您自己验证

        如果在这些之后仍然收到一些错误,请发布。

答案 1 :(得分:0)

根据我上次尝试的记忆,你不能使用HttpsURLConnection。您可以查看支持此功能的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密钥库并负责询问密钥库密码。
  • 然后,您可以像任何其他密钥库一样导航密钥库。

答案 2 :(得分:0)

原来是私钥问题,因为它被设置为不可导出。因为这意味着我只能从windows商店获取私钥,我陷入困境并“修复”了这个问题,为了让必要的jdk6类工作而不会过多影响应用程序的其余部分,需要进行大量的处理。