web-dev-qa-db-fra.com

Java certificats clients via HTTPS / SSL

J'utilise Java 6 et j'essaie de créer un HttpsURLConnection sur un serveur distant, à l'aide d'un certificat client.
Le serveur utilise un certificat racine auto-signé et nécessite la présentation d'un certificat client protégé par mot de passe. J'ai ajouté le certificat racine du serveur et le certificat client à un magasin de clés Java que j'ai trouvé dans /System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home/lib/security/cacerts (OSX 10.5). Le nom du fichier de clés semble suggérer que le certificat client n'est pas censé y être inséré?

Quoi qu'il en soit, l'ajout du certificat racine à ce magasin a résolu l'infâme javax.net.ssl.SSLHandshakeException: Sun.security.validator.ValidatorException: PKIX path building failed' problem.

Cependant, je suis maintenant bloqué sur l'utilisation du certificat client. J'ai essayé deux approches et aucune ne me mène nulle part.
Premièrement, et de préférence, essayez:

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

J'ai essayé de sauter la classe HttpsURLConnection (pas idéal car je veux parler HTTP avec le serveur), et le faire à la place:

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

Je ne suis même pas sûr que le certificat client soit le problème ici.

112
Jan

Finalement résolu le;). Vous avez un indice fort ici (la réponse de Gandalfs a également touché un peu). Les liens manquants étaient (principalement) le premier des paramètres ci-dessous et, dans une certaine mesure, j'ai négligé la différence entre les magasins de clés et les magasins de clés de confiance.

Le certificat de serveur auto-signé doit être importé dans un fichier de clés certifiées:

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

Ces propriétés doivent être définies (soit sur la ligne de commande, soit dans le code):

-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

Exemple de code de travail:

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

Bien que cela ne soit pas recommandé, vous pouvez également désactiver la validation du certificat SSL dans son intégralité:

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

Avez-vous défini les propriétés du système KeyStore et/ou TrustStore?

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

ou de avec le code

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

Pareil avec javax.net.ssl.trustStore

21
Gandalf

Si vous traitez un appel de service Web utilisant la structure Axis, la réponse est beaucoup plus simple. Si tout ce que vous voulez, c'est que votre client puisse appeler le service Web SSL et ignorer les erreurs de certificat SSL, il suffit de mettre cette déclaration avant d'appeler un service Web:

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

Les avertissements habituels selon lesquels il s'agit d'une très mauvaise chose à faire dans un environnement de production s'appliquent.

J'ai trouvé ceci à le wiki Axis .

13
Mark Meuer

Pour moi, c’est ce qui a fonctionné avec 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();
    }

Le fichier P12 contient le certificat client et la clé privée client créée avec 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();
}
5
EpicPandaForce

J'utilise le paquet HTTP Client Apache commons pour le faire dans mon projet actuel et cela fonctionne bien avec SSL et un certificat auto-signé (après l'avoir installé dans cacerts, comme vous l'avez mentionné). S'il vous plaît jetez un coup d'oeil ici:

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

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

4
Taylor Leese

Je pense que vous avez un problème avec votre certificat de serveur, ce n'est pas un certificat valide (je pense que c'est ce que "handshake_failure" signifie dans ce cas):

Importez votre certificat de serveur dans votre magasin de clés trustcacerts sur le JRE du client. Ceci est facilement fait avec keytool :

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

Utiliser le code ci-dessous

-Djavax.net.ssl.keyStoreType=pkcs12

ou

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

n'est pas du tout requis. De plus, il n'est pas nécessaire de créer votre propre fabrique SSL personnalisée.

J'ai également rencontré le même problème. Dans mon cas, il existait un problème qui chaîne de certificats complète n'était pas importé dans les magasins de clés de confiance. Importer des certificats à l'aide de l'utilitaire keytool droit du certificat racine, vous pouvez également ouvrir le fichier cacerts dans le bloc-notes et voir si la chaîne de certificats complète est importée ou non. Vérifiez le nom d'alias que vous avez fourni lors de l'importation de certificats, ouvrez les certificats et voyez combien il en contient, le même nombre de certificats devrait figurer dans le fichier cacerts.

De plus, le fichier cacerts doit être configuré sur le serveur sur lequel vous exécutez votre application. Les deux serveurs s'authentifieront l'un l'autre à l'aide de clés publiques/privées.

1
Ankur Singhal