Certificati client Java su HTTPS / SSL


116

Sto usando Java 6 e sto cercando di creare un HttpsURLConnectioncontro 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 radice del server e il certificato 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 keystore sembra suggerire che il certificato client non dovrebbe entrare lì?

Ad ogni modo, l'aggiunta del certificato radice a questo negozio ha risolto il famigerato javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Tuttavia, ora sono bloccato su come utilizzare il certificato client. Ho provato due approcci e nessuno dei due mi porta da nessuna parte.
Innanzitutto, e preferito, prova:

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 a saltare la classe HttpsURLConnection (non ideale dato che voglio parlare HTTP con il server), e invece faccio questo:

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 problema qui sia il certificato client.


ho fornito due certificati dal cliente come identificare quale è necessario aggiungere nel keystore e nel truststore potresti per favore aiutarci a identificare questo problema dato che hai già affrontato un tipo simile di problema stackoverflow.com/questions/61374276/…
henrycharles

Risposte:


100

Finalmente risolto;). Ho un forte suggerimento qui (anche la risposta di Gandalfs lo ha toccato un po '). I collegamenti mancanti erano (principalmente) il primo dei parametri seguenti e in una certa misura ho trascurato la differenza tra keystore e truststore.

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

keytool -import -alias gridserver -file gridserver.crt -storepass $ PASS -keystore gridserver.keystore

Queste proprietà devono essere impostate (sulla riga di comando o nel 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

Codice di esempio funzionante:

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

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

83

Sebbene non sia consigliato, puoi anche disabilitare completamente la convalida del certificato SSL:

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) {}
  }
}

72
Va notato che disabilitare la convalida del certificato in questo modo apre la connessione a possibili attacchi MITM: non utilizzare in produzione .
Bruno

3
Il codice non si compila, per fortuna. Questa "soluzione" è radicalmente insicura.
Marchese di Lorne

5
@ neu242, no, non dipende davvero da cosa lo usi. Se vuoi usare SSL / TLS, vuoi proteggere la tua connessione dagli attacchi MITM, questo è il punto. L'autenticazione del server non è necessaria se puoi garantire che nessuno sarà in grado di alterare il traffico, ma le situazioni in cui sospetti che possano esserci intercettatori che non sarebbero in grado di alterare anche il traffico di rete sono piuttosto rare.
Bruno

1
@ neu242 Grazie per lo snippet di codice. In realtà sto pensando di utilizzarlo in produzione per uno scopo molto specifico (scansione del Web) e ho menzionato la tua implementazione in una domanda ( stackoverflow.com/questions/13076511/… ). Se hai tempo, potresti per favore guardarlo e fammi sapere se ci sono rischi per la sicurezza che ho perso?
Sal

1
@Bruno Se sto solo eseguendo un ping al server, ciò mi influenzerebbe davvero, dagli attacchi MITM?
PhoonOne

21

Hai impostato le proprietà del sistema KeyStore e / o TrustStore?

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

o da con il codice

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

Lo stesso con javax.net.ssl.trustStore


13

Se hai a che fare con una chiamata a un servizio Web utilizzando il framework Axis, c'è una risposta molto più semplice. Se tutto ciò che vogliamo è che il tuo client sia in grado di chiamare il servizio web SSL e ignorare gli errori del certificato SSL, inserisci questa dichiarazione prima di richiamare qualsiasi servizio web:

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

Si applicano le solite clausole di esclusione di responsabilità su questa cosa da fare in un ambiente di produzione.

L'ho trovato sul wiki di Axis .


L'OP ha a che fare con una connessione HttpsURLC, non con Axis
neu242

2
Capisco. Non intendevo implicare che la mia risposta fosse migliore nel caso generale. E 'solo che se si sta utilizzando il framework Axis, si può avere domanda del PO in quel contesto. (È così che ho trovato questa domanda in primo luogo.) In tal caso, il modo in cui ho fornito è più semplice.
Mark Meuer

5

Per me, questo è ciò che ha funzionato utilizzando 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 client e la chiave privata del client, creati 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();
}

Il keyStoreè quello che contiene la chiave privata e il certificato.
EpicPandaForce

1
È necessario includere queste 2 dipendenze affinché il codice convertPEMtoP12 funzioni: <dependency> <groupId> org.bouncycastle </groupId> <artifactId> bcprov-jdk15on </artifactId> <version> 1.53 </version> </dependency> < dependency> <groupId> org.bouncycastle </groupId> <artifactId> bcpkix-jdk15on </artifactId> <version> 1.53 </version> </dependency>
BirdOfPrey

@EpicPandaForce Ottengo un errore: Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Impossibile eseguire il cast di un oggetto con la classe "org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject" alla classe "int" nella riga ks. setKeyEntry - eventuali indizi su cosa potrebbe essere sbagliato
Vishal Biyani

Sì, stai usando Groovy invece di un linguaggio strettamente digitato. (tecnicamente quel metodo richiede un ID e un certificato, non solo un certificato)
EpicPandaForce

4

Uso il pacchetto client HTTP di Apache commons per farlo nel mio progetto attuale e funziona bene con SSL e un certificato autofirmato (dopo averlo installato in cacert come hai menzionato). 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
Sembra un pacchetto abbastanza accurato, 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 affermino che lo è), né in 3.1. Ho hackerato un po 'con esso e ora sembra che sia permanentemente bloccato con un blocco di 5 minuti prima che interrompa la connessione. È davvero strano: se carico la CA e il certificato del client in qualsiasi browser, vola.
Jan

1
Apache HTTP Client 4 può prendere SSLContextdirettamente un , quindi puoi configurare tutto in questo modo, invece di usare AuthSSLProtocolSocketFactory.
Bruno

1
Esiste un modo per eseguire tutte le operazioni relative ai certificati client in memoria anziché tramite un keystore esterno?
Sridhar Sarnobat

4

Penso che tu abbia un problema con il tuo certificato del server, non è un certificato valido (penso che questo sia il significato di "handshake_failure" in questo caso):

Importa il certificato del tuo server nel tuo keystore trustcacerts sul JRE del client. Questo è facilmente realizzabile con keytool :

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

Ho provato a ripulire e ricominciare da capo, e il fallimento della stretta di mano è andato via. Ora ho solo 5 minuti di silenzio assoluto prima che la connessione viene terminata: o
gen

1

Utilizzando il codice sottostante

-Djavax.net.ssl.keyStoreType=pkcs12

o

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

non è affatto richiesto. Inoltre, non è necessario creare la propria fabbrica SSL personalizzata.

Ho anche riscontrato lo stesso problema, nel mio caso si è verificato un problema per cui l' intera catena di certificati non veniva importata nei truststore. Importa i certificati utilizzando l'utilità keytool direttamente dal certificato radice, inoltre puoi aprire il file cacerts nel blocco note e vedere se la catena di certificati completa è importata o meno. Controlla il nome alias che hai fornito durante l'importazione dei certificati, apri i certificati e vedi quanti ne contiene, lo stesso numero di certificati dovrebbe essere presente nel file cacerts.

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


La creazione della propria factory SSL personalizzata è molto più complicata e soggetta a errori rispetto all'impostazione di due proprietà di sistema.
Marchese di Lorne
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.