How can I use certificate authentication with HttpsURLConnection?

前端 未结 3 988
遥遥无期
遥遥无期 2020-12-13 21:39

I\'m trying to connect to an HTTPS URL, but I need to use client authentication with a certificate placed on my system by third party software.

I haven\'t the slight

相关标签:
3条回答
  • 2020-12-13 22:18

    Okay, so your question's title is How can I use certificate authentication with HttpsURLConnection? I have a working example for that. For it to work it has one prerequisite:

    1. you have to have a key store which has your certificate file in it. (I know that your scenario not exactly permits this, but please just follow me here a bit so that we can narrow your problem down a little bit, because it's a bit too complicated to answer right off the bat.)

    So, first get your hand on the actual certificate file. If you're on Windows 7 this can be done by the following steps:

    1. open Internet Explorer,
    2. open Tools (in Internet Explorer 9 it is the cog icon),
    3. click Internet Options,
    4. go to the Content tab,
    5. click Certificates,
    6. find the certificate at hand,
    7. click on it and click Export and save it to a file (DER encoded binary X.509).

    (After exporting it delete it from among the other certificates, making sure Java won't use it one way or another. I don't know if it can use it, but it couldn't hurt.)

    Now, you have to create a key store file and import the exported certificate into it, which can be done by the following.

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

    (Obviously, keytool has to be on your path to work. It's part of the JDK.)

    It'll prompt you for a password (the key store's password; it doesn't have to do anything with the certificate), which I don't know how to set to "" right now, so set let it be password or whatever.

    After this, with the following steps you can establish a secure connection to your endpoint via a proxy.

    1. First, load the key store file.

      InputStream trustStream = new FileInputStream("path/to/<keystore>");
      char[] trustPassword = "<password>".toCharArray();
      
    2. Initialize a KeyStore.

      KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
      trustStore.load(trustStream, trustPassword);
      
    3. Initialize TrustManager objects. (I think these handle certificate resolution or something like that, however as far as I'm concerned this is magic.)

      TrustManagerFactory trustFactory =
          TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
      trustFactory.init(trustStore);
      TrustManager[] trustManagers = trustFactory.getTrustManagers();
      
    4. Create a new SSLContext, load the TrustManager objects into it and set it as default. Take care, because SSLContext.getDefault() returns a non-modifiable instance of the class (or more like the default one can't be re-initialized, but whatever), that's why we have to use SSLContext.getInstance("SSL"). Also, don't forget to set this new SSLContext as the default, because without that the code goes poof.

      SSLContext sslContext = SSLContext.getInstance("SSL");
      sslContext.init(null, trustManagers, null);
      SSLContext.setDefault(sslContext);
      
    5. Create your proxy and setup authentication for it. (Instead of using System.setProperty(...) use the Proxy class. Oh and don't be mislead by Type.HTTP.)

      Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("<host>", <port>));
      
    6. Setup authentication for your proxy. (I've used a free proxy which didn't require authentication so I couldn't test that part of the problem right now.)

      Authenticator.setDefault(new Authenticator() {
      
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
          return new PasswordAuthentication("<user>", "<password>".toCharArray());
        }
      });
      
    7. Connect to your end point by passing the previously created proxy to the connection. (I've used one of my company's service's URL, which asks for a certificate—which certificate I imported in my own key store of course.)

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

    If it doesn't work like this (you get errors) then try it with HttpsURLConnection.

       HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
       httpsConnection.setAllowUserInteraction(true);
       httpsConnection.setRequestMethod("POST");
    

    Basically, the setAllowUserInteraction thingy kicks in if the server (where the resource pointed by the URL you connect to is located) asks for credentials, right? Now, I couldn't test just that per se, but as I see if you can get this baby working with a server that doesn't require authentication to access its resources, then you're good to go, because the server will ask you to authenticate yourself only after the connection is already established.

    If after these you still receive some error, then please post it.

    0 讨论(0)
  • 2020-12-13 22:19

    From what I remember from my last attempt of doing that, you can't use HttpsURLConnection. You can have a look to the Apache HttpClient library that has support for this.

    Here is a code sample giving an idea of the process:

    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);
    

    Edit (regarding Tom comment):

    Here are some thoughts on how you can obtain the certificates stored in the Windows key store:

    • You need to use the Sun Cryptography suite (ie, a Sun Java 6 JDK)
    • You can obtain the KeyStore like this: ks = KeyStore.getInstance("Windows-MY");
    • You can load it this way: ks.load(null, null);. The JVM will load the Windos keystore and take care of asking for the keystore password.
    • You can then navigate the keystore like any other keystore.
    0 讨论(0)
  • 2020-12-13 22:19

    Turned out to be a Private Key issue since it was set as not exportable. As this meant I could only get the private key from the windows store, I caved and "Fixed" the issue with a lot of messing around to get the necessary jdk6 classes working without influencing the rest of the application too much.

    0 讨论(0)
提交回复
热议问题