How can I use different certificates on specific connections?

后端 未结 5 2002
星月不相逢
星月不相逢 2020-11-22 01:22

A module I\'m adding to our large Java application has to converse with another company\'s SSL-secured website. The problem is that the site uses a self-signed certificate.

相关标签:
5条回答
  • 2020-11-22 01:26

    We copy the JRE's truststore and add our custom certificates to that truststore, then tell the application to use the custom truststore with a system property. This way we leave the default JRE truststore alone.

    The downside is that when you update the JRE you don't get its new truststore automatically merged with your custom one.

    You could maybe handle this scenario by having an installer or startup routine that verifies the truststore/jdk and checks for a mismatch or automatically updates the truststore. I don't know what happens if you update the truststore while the application is running.

    This solution isn't 100% elegant or foolproof but it's simple, works, and requires no code.

    0 讨论(0)
  • 2020-11-22 01:29

    I read through LOTS of places online to solve this thing. This is the code I wrote to make it work:

    ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes());
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream);
    String alias = "alias";//cert.getSubjectX500Principal().getName();
    
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(null);
    trustStore.setCertificateEntry(alias, cert);
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(trustStore, null);
    KeyManager[] keyManagers = kmf.getKeyManagers();
    
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
    tmf.init(trustStore);
    TrustManager[] trustManagers = tmf.getTrustManagers();
    
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(keyManagers, trustManagers, null);
    URL url = new URL(someURL);
    conn = (HttpsURLConnection) url.openConnection();
    conn.setSSLSocketFactory(sslContext.getSocketFactory());
    

    app.certificateString is a String that contains the Certificate, for example:

    static public String certificateString=
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" +
            "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" +
            ... a bunch of characters...
            "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" +
            "-----END CERTIFICATE-----";
    

    I have tested that you can put any characters in the certificate string, if it is self signed, as long as you keep the exact structure above. I obtained the certificate string with my laptop's Terminal command line.

    0 讨论(0)
  • 2020-11-22 01:29

    I've had to do something like this when using commons-httpclient to access an internal https server with a self-signed certificate. Yes, our solution was to create a custom TrustManager that simply passed everything (logging a debug message).

    This comes down to having our own SSLSocketFactory that creates SSL sockets from our local SSLContext, which is set up to have only our local TrustManager associated with it. You don't need to go near a keystore/certstore at all.

    So this is in our LocalSSLSocketFactory:

    static {
        try {
            SSL_CONTEXT = SSLContext.getInstance("SSL");
            SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Unable to initialise SSL context", e);
        } catch (KeyManagementException e) {
            throw new RuntimeException("Unable to initialise SSL context", e);
        }
    }
    
    public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
        LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) });
    
        return SSL_CONTEXT.getSocketFactory().createSocket(host, port);
    }
    

    Along with other methods implementing SecureProtocolSocketFactory. LocalSSLTrustManager is the aforementioned dummy trust manager implementation.

    0 讨论(0)
  • 2020-11-22 01:33

    If creating a SSLSocketFactory is not an option, just import the key into the JVM

    1. Retrieve the public key: $openssl s_client -connect dev-server:443, then create a file dev-server.pem that looks like

      -----BEGIN CERTIFICATE----- 
      lklkkkllklklklklllkllklkl
      lklkkkllklklklklllkllklkl
      lklkkkllklk....
      -----END CERTIFICATE-----
      
    2. Import the key: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem. Password: changeit

    3. Restart JVM

    Source: How to solve javax.net.ssl.SSLHandshakeException?

    0 讨论(0)
  • 2020-11-22 01:50

    Create an SSLSocket factory yourself, and set it on the HttpsURLConnection before connecting.

    ...
    HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
    conn.setSSLSocketFactory(sslFactory);
    conn.setMethod("POST");
    ...
    

    You'll want to create one SSLSocketFactory and keep it around. Here's a sketch of how to initialize it:

    /* Load the keyStore that includes self-signed cert as a "trusted" entry. */
    KeyStore keyStore = ... 
    TrustManagerFactory tmf = 
      TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(null, tmf.getTrustManagers(), null);
    sslFactory = ctx.getSocketFactory();
    

    If you need help creating the key store, please comment.


    Here's an example of loading the key store:

    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(trustStore, trustStorePassword);
    trustStore.close();
    

    To create the key store with a PEM format certificate, you can write your own code using CertificateFactory, or just import it with keytool from the JDK (keytool won't work for a "key entry", but is just fine for a "trusted entry").

    keytool -import -file selfsigned.pem -alias server -keystore server.jks
    
    0 讨论(0)
提交回复
热议问题