Domanda

Un modulo Sto aggiungendo alla nostra applicazione Java di grandi dimensioni deve dialogare con il sito protetto da SSL di un'altra azienda. Il problema è che il sito utilizza un certificato auto-firmato. Ho una copia del certificato per verificare che non sto incontrando un attacco man-in-the-middle, e ho bisogno di incorporare questo certificato nel nostro codice in modo tale che la connessione al server avrà successo.

Ecco il codice di base:

void sendRequest(String dataPacket) {
  String urlStr = "https://host.example.com/";
  URL url = new URL(urlStr);
  HttpURLConnection conn = (HttpURLConnection)url.openConnection();
  conn.setMethod("POST");
  conn.setRequestProperty("Content-Length", data.length());
  conn.setDoOutput(true);
  OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
  o.write(data);
  o.flush();
}

Senza alcun ulteriore trattamento in atto per il certificato auto-firmato, questa muore a conn.getOutputStream () con la seguente eccezione:

Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Idealmente, il mio codice ha bisogno di insegnare Java di accettare questo certificato auto-firmato, per questo un punto nella domanda, e non altrove.

So che posso importare il certificato nell'archivio autorità di certificazione del JRE, e che permetterà Java per accettarlo. Questo non è un approccio voglio prendere se posso aiutare; sembra molto invasivo da fare su tutte le macchine dei nostri clienti per un modulo che non possono utilizzare; interesserebbe tutte le altre applicazioni Java che utilizzano lo stesso JRE, e non mi piace che, anche se le probabilità di qualsiasi altra applicazione Java mai accesso a questo sito sono pari a zero. Non è anche un'operazione banale:. Su UNIX Devo ottenere i diritti di accesso per modificare il JRE in questo modo

Ho visto anche che io possa creare un'istanza TrustManager che fa un po 'di controllo su ordinazione. Sembra che potrei anche essere in grado di creare un TrustManager che delega al vero TrustManager in tutti i casi tranne questo certificato. Ma sembra che TrustManager viene installato a livello globale, e presumo colpirebbe tutti gli altri collegamenti da nostra applicazione, e che non sente l'odore giusto per me, neanche.

Qual è la, o meglio di serie preferita di creare un'applicazione Java di accettare un certificato auto-firmato? Posso raggiungere tutti gli obiettivi che ho in mente di cui sopra, o sto andando a scendere a compromessi? C'è la possibilità che coinvolgono i file e le directory e le impostazioni di configurazione, e poco a nessun codice?

È stato utile?

Soluzione

Crea un fabbrica SSLSocket te stesso, e impostarlo sulla HttpsURLConnection prima del collegamento.

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

Ti consigliamo di crearne uno SSLSocketFactory e tenerlo intorno. Ecco un abbozzo di come inizializzare esso:

/* 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();

Se avete bisogno di aiuto per la creazione di chiavi, si prega di commento.


Ecco un esempio di caricamento delle chiavi:

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

Per creare l'archivio chiavi con un certificato formato PEM, è possibile scrivere il proprio codice utilizzando CertificateFactory, o semplicemente importarlo con keytool dal JDK (keytool non di lavoro per una "voce chiave", ma va bene per una "voce di fiducia").

keytool -import -file selfsigned.pem -alias server -keystore server.jks

Altri suggerimenti

Ho letto attraverso un sacco di posti on-line per risolvere questa cosa. Questo è il codice che ho scritto per farlo funzionare:

                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 è una stringa che contiene il certificato, ad esempio:

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

Ho provato che si può mettere tutti i caratteri della stringa del certificato, se si tratta di auto-firmato, fino a quando si mantiene la struttura esatta. Ho conseguito la stringa certificato con la linea di comando del Terminale del mio portatile.

Se la creazione di un SSLSocketFactory non è un'opzione, basta importare la chiave nella JVM

  1. recuperare la chiave pubblica: $openssl s_client -connect dev-server:443, quindi creare un file dev-server.pem che assomiglia

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

  3. Riavvia JVM

Fonte: Come risolvere javax.net.ssl. SSLHandshakeException?

Copiamo truststore del JRE e aggiungere i nostri certificati personalizzati per quel truststore, poi dire l'applicazione per utilizzare il truststore personalizzato con una proprietà di sistema. In questo modo si lascia il truststore di default JRE da solo.

Il rovescio della medaglia è che quando si aggiorna il JRE non si ottiene il suo nuovo truststore automaticamente fuse con la vostra abitudine uno.

Si potrebbe forse gestire questo scenario da avere una routine di installazione o di avvio che consente di verificare il truststore / JDK e controlli per una mancata corrispondenza o aggiorna il truststore automaticamente. Non so che cosa succede se si aggiorna il truststore mentre l'applicazione è in esecuzione.

Questa soluzione non è elegante, 100% o infallibile ma è semplice, funziona, e non richiede alcun codice.

Ho dovuto fare qualcosa di simile quando si utilizza commons-HttpClient per accedere a un server HTTPS interna con un certificato auto-firmato. Sì, la nostra soluzione era quella di creare un TrustManager personalizzato che semplicemente passato tutto (la registrazione di un messaggio di debug).

Questa scende a avere il nostro SSLSocketFactory che crea socket SSL dal nostro SSLContext locale, che è impostato per avere solo il nostro TrustManager locale ad esso associati. Non hai bisogno di andare nei pressi di un archivio di chiavi / certstore a tutti.

Quindi questo è nel nostro 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);
}

Con altri metodi di applicazione SecureProtocolSocketFactory. LocalSSLTrustManager è il già citato implementazione manichino Gestore affidabilità.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top