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

Val*_*ion 13 java authentication https x509certificate

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

我一点也不知道我应该如何找到或使用它,而我所要做的只是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;
Run Code Online (Sandbox Code Playgroud)

然后它只是遍历WS2_store_Certs CertificateCollection并以这种方式检查它们.再进一步,它设置这样的证书:

HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url_string);
httpWebRequest.ClientCertificates = SSC_Certs;
Run Code Online (Sandbox Code Playgroud)

这一切看起来都很合理,即使我不知道它是如何找到证书的,但我仍然无法找到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..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
Run Code Online (Sandbox Code Playgroud)

我用来建立连接的代码是

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();
Run Code Online (Sandbox Code Playgroud)

(我还没有尝试过HttpClient,因为我不知道如何找到证书文件,我也不确定在每个客户端系统上是否总是这样.)

另一个更新

我已经在WINDOWS-ROOT密钥库中找到了我需要的证书的CA(并使用.verify()进行检查以查看它们都已检出),我已将它们添加到java密钥库中,但仍然没有任何变化.我猜他们应该进入TrustStore,但我还没有找到一种方法来编程.(我宁愿不依赖最终用户来做这种事情,因为我可以向他们保证,由于在这个荒谬的长问题开头提到的第三方软件,证书和CA将会出现.)

更多更新

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

Koh*_*ert 12

好的,所以您的问题的标题是如何使用HttpsURLConnection进行证书身份验证?我有一个有效的例子.为了它的工作,它有一个先决条件:

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

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

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

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

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

> keytool -importcert -file <certificate> -keystore <keystore> -alias <alias>
Run Code Online (Sandbox Code Playgroud)

(显然,keytool必须走上你的工作之路.它是JDK的一部分.)

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

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

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

    InputStream trustStream = new FileInputStream("path/to/<keystore>");
    char[] trustPassword = "<password>".toCharArray();
    
    Run Code Online (Sandbox Code Playgroud)
  2. 初始化KeyStore.

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

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

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

    Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("<host>", <port>));
    
    Run Code Online (Sandbox Code Playgroud)
  6. 为您的代理设置身份验证.(我使用了一个不需要身份验证的免费代理,所以我现在无法测试这部分问题.)

    Authenticator.setDefault(new Authenticator() {
    
      @Override
      protected PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication("<user>", "<password>".toCharArray());
      }
    });
    
    Run Code Online (Sandbox Code Playgroud)
  7. 通过将先前创建的代理传递给连接来连接到您的终点.(我使用了我公司服务的一个URL,要求提供证书 - 当然我在自己的密钥库中导入了哪个证书.)

    URL url = new URL("<endpoint>");
    URLConnection connection = url.openConnection(proxy);
    connection.connect();
    
    Run Code Online (Sandbox Code Playgroud)

如果它不能像这样工作(你得到错误),那么尝试使用HttpsURLConnection.

   HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
   httpsConnection.setAllowUserInteraction(true);
   httpsConnection.setRequestMethod("POST");
Run Code Online (Sandbox Code Playgroud)

基本上,如果服务器(您连接到的URL指向的资源所在的位置)要求提供凭据,setAllowUserInteraction就会启动,对吧?现在,我不能只是测试本身,而是因为我看你能不能得到这个宝宝不需要身份验证访问其资源的服务器的工作,那么你是好去,因为服务器会问只有在建立连接才能对自己进行身份验证.

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


Val*_*ion 0

原来是私钥问题,因为它被设置为不可导出。因为这意味着我只能从 Windows 商店获取私钥,所以我放弃并“修复”了这个问题,并进行了大量的修改,以使必要的 jdk6 类正常工作,而不会过多影响应用程序的其余部分。