2009-05-17 31 views
103

Sto utilizzando Java 6 e sto cercando di creare un HttpsURLConnection su un server remoto, utilizzando un certificato client.
Il server utilizza un certificato radice autofirmato e richiede che venga presentato un certificato client protetto da password. Ho aggiunto il certificato di root del server e il certificato del client a un keystore java predefinito che ho trovato in /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts (OSX 10.5). Il nome del file di archivio chiavi sembra suggerire che il certificato client non deve essere inserito lì?Certificati client Java su HTTPS/SSL

In ogni caso, aggiungendo il certificato principale di questo negozio risolto il famigerato javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Tuttavia, ora sto bloccato su come utilizzare il certificato client. Ho provato due approcci e nessuno mi porta da nessuna parte.
In primo luogo, e preferito, provate:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
URL url = new URL("https://somehost.dk:3049"); 
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); 
conn.setSSLSocketFactory(sslsocketfactory); 
InputStream inputstream = conn.getInputStream(); 
// The last line fails, and gives: 
// javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 

Ho provato saltare la classe HttpsURLConnection (non è l'ideale in quanto voglio parlare HTTP con il server), e fare questo, invece:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("somehost.dk", 3049); 
InputStream inputstream = sslsocket.getInputStream(); 
// do anything with the inputstream results in: 
// java.net.SocketTimeoutException: Read timed out 

Non sono nemmeno sicuro che il certificato del cliente sia il problema qui.

risposta

82

finalmente risolto esso;). Ho un forte suggerimento here (la risposta di Gandalfs ha anche toccato un po '). I collegamenti mancanti erano (principalmente) il primo dei parametri seguenti, e in una certa misura ho trascurato la differenza tra i keystore ei truststores.

Il certificato del server autofirmato deve essere importato in un truststore:

keytool -import -alias gridserver -file gridserver.crt -storepass $ pass -keystore gridserver.chiavi

Queste proprietà devono essere impostato (sia dalla linea di comando, o in codice):

-Djavax.net.ssl.keyStoreType=pkcs12 
-Djavax.net.ssl.trustStoreType=jks 
-Djavax.net.ssl.keyStore=clientcertificate.p12 
-Djavax.net.ssl.trustStore=gridserver.keystore 
-Djavax.net.debug=ssl # very verbose debug 
-Djavax.net.ssl.keyStorePassword=$PASS 
-Djavax.net.ssl.trustStorePassword=$PASS 

lavoro codice di esempio:

SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); 
URL url = new URL("https://gridserver:3049/cgi-bin/ls.py"); 
HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); 
conn.setSSLSocketFactory(sslsocketfactory); 
InputStream inputstream = conn.getInputStream(); 
InputStreamReader inputstreamreader = new InputStreamReader(inputstream); 
BufferedReader bufferedreader = new BufferedReader(inputstreamreader); 

String string = null; 
while ((string = bufferedreader.readLine()) != null) { 
    System.out.println("Received " + string); 
} 
+0

Ho usato un URL come: https: // localhost: 8443/Nome_applicazione/getAttributes. Ho un metodo con/getAttribute url mapping. Questo metodo restituisce un elenco di elementi. Ho usato HttpsUrlConnection, il codice di risposta della connessione è 200, ma non mi fornisce l'elenco degli attributi quando uso inputStream, mi dà il contenuto html della mia pagina di accesso. Ho fatto l'autenticazione e impostare il tipo di contenuto come JSON. Si prega di suggerire – Deepak

4

Io uso il pacchetto client HTTP di Apache commons per farlo nel mio progetto corrente e funziona perfettamente con SSL e un certificato autofirmato (dopo averlo installato in cacerts come hai detto). Si prega di dare un'occhiata qui:

http://hc.apache.org/httpclient-3.x/tutorial.html

http://hc.apache.org/httpclient-3.x/sslguide.html

+1

Questo sembra un pacchetto abbastanza carino, ma la classe che dovrebbe far funzionare tutto 'AuthSSLProtocolSocketFactory' apparentemente non fa parte della distribuzione ufficiale, né in 4.0beta (nonostante le note di rilascio che lo dichiarano), o in 3.1. Ho fatto un po 'di pratica con esso e ora sembra essere permanentemente bloccato con una sospensione di 5 minuti prima che la connessione si interrompa. È davvero strano - se carico la CA e il client cert in qualsiasi browser, vola solo. – Jan

+1

Apache HTTP Client 4 può prendere direttamente un 'SSLContext', così puoi configurare tutto questo in questo modo, invece di usare' AuthSSLProtocolSocketFactory'. – Bruno

18

Avete impostare il keystore e/o proprietà TrustStore sistema?

java -Djavax.net.ssl.keyStore=pathToKeystore -Djavax.net.ssl.keyStorePassword=123456 

o dal con il codice

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore); 

Stessa cosa con javax.net.ssl.trustStore

3

penso che tu abbia un problema con il certificato del server, non è un certificato valido (I pensa che questo è ciò che "handshake_failure" significa in questo caso):

Importare il certificato del server nel keystore trustcacerts sul client JRE. Questo è fatto facilmente con keytool:

keytool 
    -import 
    -alias <provide_an_alias> 
    -file <certificate_file> 
    -keystore <your_path_to_jre>/lib/security/cacerts 
+0

Ho provato a pulire e ricominciare da capo, e l'errore di handshake è andato via. Ora ho solo 5 minuti di silenzio prima che la connessione venga interrotta: o – Jan

78

Anche se non consigliato, è anche possibile disattivare SSL convalida cert alltogether:

import javax.net.ssl.*; 
import java.security.SecureRandom; 
import java.security.cert.X509Certificate; 

public class SSLTool { 

    public static void disableCertificateValidation() { 
    // Create a trust manager that does not validate certificate chains 
    TrustManager[] trustAllCerts = new TrustManager[] { 
     new X509TrustManager() { 
     public X509Certificate[] getAcceptedIssuers() { 
      return new X509Certificate[0]; 
     } 
     public void checkClientTrusted(X509Certificate[] certs, String authType) {} 
     public void checkServerTrusted(X509Certificate[] certs, String authType) {} 
    }}; 

    // Ignore differences between given hostname and certificate hostname 
    HostnameVerifier hv = new HostnameVerifier() { 
     public boolean verify(String hostname, SSLSession session) { return true; } 
    }; 

    // Install the all-trusting trust manager 
    try { 
     SSLContext sc = SSLContext.getInstance("SSL"); 
     sc.init(null, trustAllCerts, new SecureRandom()); 
     HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); 
     HttpsURLConnection.setDefaultHostnameVerifier(hv); 
    } catch (Exception e) {} 
    } 
} 
+0

Puoi fare la stessa cosa in un modo ancora più semplice se stai usando il framework Axis. Vedi la mia risposta qui sotto. –

+62

Va notato che la disabilitazione della convalida del certificato come questa apre la connessione a possibili attacchi MITM: ** non utilizzare in produzione **. – Bruno

+1

Il codice non viene compilato, per fortuna. Questa 'soluzione' è radicalmente insicura. – EJP

14

Se si tratta di un servizio web chiama usando il framework Axis, c'è una risposta molto più semplice. Se tutto vogliamo è per il vostro cliente di essere in grado di chiamare il servizio Web SSL e ignorare gli errori del certificato SSL, basta mettere questa dichiarazione prima di richiamare qualsiasi servizio web:

System.setProperty("axis.socketSecureFactory", "org.apache.axis.components.net.SunFakeTrustSocketFactory");

Le solite esclusioni di responsabilità su questo essere un Molto Si applica una cosa cattiva da fare in un ambiente di produzione.

Ho trovato questo a the Axis wiki.

+0

L'OP ha a che fare con un HttpsURLConnection, non con l'asse – neu242

+2

, ho capito. Non intendevo implicare che la mia risposta fosse migliore nel caso generale. È solo che * se * usi il framework Axis, potresti avere la domanda dell'OP in quel contesto. (Ecco come ho trovato questa domanda in primo luogo.) In tal caso, il modo in cui ho fornito è più semplice. –

+1

@MarkMeuer Grazie! Ha funzionato perfettamente –

1

Usando sottostante Codice

-Djavax.net.ssl.keyStoreType=pkcs12 

o

System.setProperty("javax.net.ssl.keyStore", pathToKeyStore); 

non è affatto necessario. Inoltre non è necessario creare il proprio factory SSL personalizzato.

Ho riscontrato anche lo stesso problema, nel mio caso c'era un problema nel fatto che la catena di certificati completa non è stata importata in truststores. Importare i certificati utilizzando il certificato di root del programma di utilità keytool, inoltre è possibile aprire il file cacerts nel blocco note e verificare se l'intera catena di certificati è importata o meno. Verificare il nome dell'alias fornito durante l'importazione dei certificati, aprire i certificati e vedere quanti ne contiene, lo stesso numero di certificati dovrebbe essere presente nel file cacerts.

Anche il file cacerts deve essere configurato nel server su cui si sta eseguendo l'applicazione, i due server si autenticano a vicenda con chiavi pubbliche/private.

+0

La creazione del proprio factory SSL personalizzato è molto più complicata e soggetta a errori rispetto all'impostazione di due proprietà di sistema. – EJP

5

Per me, questo è ciò che ha funzionato usando Apache HttpComponents ~ HttpClient 4.x:

KeyStore keyStore = KeyStore.getInstance("PKCS12"); 
    FileInputStream instream = new FileInputStream(new File("client-p12-keystore.p12")); 
    try { 
     keyStore.load(instream, "helloworld".toCharArray()); 
    } finally { 
     instream.close(); 
    } 

    // Trust own CA and all self-signed certs 
    SSLContext sslcontext = SSLContexts.custom() 
     .loadKeyMaterial(keyStore, "helloworld".toCharArray()) 
     //.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()) //custom trust store 
     .build(); 
    // Allow TLSv1 protocol only 
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
     sslcontext, 
     new String[] { "TLSv1" }, 
     null, 
     SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //TODO 
    CloseableHttpClient httpclient = HttpClients.custom() 
     .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) //TODO 
     .setSSLSocketFactory(sslsf) 
     .build(); 
    try { 

     HttpGet httpget = new HttpGet("https://localhost:8443/secure/index"); 

     System.out.println("executing request" + httpget.getRequestLine()); 

     CloseableHttpResponse response = httpclient.execute(httpget); 
     try { 
      HttpEntity entity = response.getEntity(); 

      System.out.println("----------------------------------------"); 
      System.out.println(response.getStatusLine()); 
      if (entity != null) { 
       System.out.println("Response content length: " + entity.getContentLength()); 
      } 
      EntityUtils.consume(entity); 
     } finally { 
      response.close(); 
     } 
    } finally { 
     httpclient.close(); 
    } 

Il file P12 contiene il certificato del client e la chiave privata del cliente, creata con BouncyCastle:

public static byte[] convertPEMToPKCS12(final String keyFile, final String cerFile, 
    final String password) 
    throws IOException, CertificateException, KeyStoreException, NoSuchAlgorithmException, 
    NoSuchProviderException 
{ 
    // Get the private key 
    FileReader reader = new FileReader(keyFile); 

    PEMParser pem = new PEMParser(reader); 
    PEMKeyPair pemKeyPair = ((PEMKeyPair)pem.readObject()); 
    JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter().setProvider("BC"); 
    KeyPair keyPair = jcaPEMKeyConverter.getKeyPair(pemKeyPair); 

    PrivateKey key = keyPair.getPrivate(); 

    pem.close(); 
    reader.close(); 

    // Get the certificate 
    reader = new FileReader(cerFile); 
    pem = new PEMParser(reader); 

    X509CertificateHolder certHolder = (X509CertificateHolder) pem.readObject(); 
    java.security.cert.Certificate x509Certificate = 
     new JcaX509CertificateConverter().setProvider("BC") 
      .getCertificate(certHolder); 

    pem.close(); 
    reader.close(); 

    // Put them into a PKCS12 keystore and write it to a byte[] 
    ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
    KeyStore ks = KeyStore.getInstance("PKCS12", "BC"); 
    ks.load(null); 
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(), 
     new java.security.cert.Certificate[]{x509Certificate}); 
    ks.store(bos, password.toCharArray()); 
    bos.close(); 
    return bos.toByteArray(); 
} 
+0

Il 'keyStore' è ciò che contiene la chiave privata e il certificato. – EpicPandaForce

+1

È necessario includere queste 2 dipendenze affinché il codice convertPEMtoP12 funzioni: org.BouncyCastle bcprov-jdk15on 1,53 org.bouncycastle bcpkix-jdk15on 1,53 BirdOfPrey

+0

@EpicPandaForce ottengo un errore: Catturato: org.codehaus.groovy. runtime.typehandling.GroovyCastException: impossibile eseguire il cast dell'oggetto con la classe 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject' alla classe 'in t 'in line ks.setKeyEntry - tutti gli indizi cosa potrebbe essere sbagliato –

Problemi correlati