Certificats clients Java via HTTPS / SSL

116

J'utilise Java 6 et j'essaie de créer un HttpsURLConnectioncontre un serveur distant, en utilisant un certificat client.
Le serveur utilise un certificat racine auto-signé et requiert 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 keystore java par défaut 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 keystore semble suggérer que le certificat client n'est pas censé y entrer?

Quoi qu'il en soit, l'ajout du certificat racine à ce magasin a résolu le tristement célèbre javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed' problem.

Cependant, je suis maintenant bloqué sur la façon d'utiliser le certificat client. J'ai essayé deux approches et aucune ne m'amène nulle part.
Tout d'abord, 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 faites ceci à 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.

Jan
la source
J'ai donné deux certificat de client à identifier dont on a besoin d'ajouter à keystore et truststore pourriez - vous aider à identifier ce problème que vous avez déjà passé par le même genre de problème stackoverflow.com/questions/61374276/...
henrycharles

Réponses:

100

Enfin résolu;). Vous avez un indice fort ici (la réponse de Gandalfs en 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 keystores et les truststores.

Le certificat de serveur auto-signé doit être importé dans un truststore:

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);
}
Jan
la source
J'ai utilisé une URL comme: localhost: 8443 / Application_Name / getAttributes . J'ai une méthode avec le mappage d'url / getAttribute. Cette méthode renvoie une liste d'éléments. J'ai utilisé HttpsUrlConnection, le code de réponse de connexion est 200, mais il ne me donne pas la liste d'attributs lorsque j'utilise inputStream, il me donne le contenu html de ma page de connexion. J'ai effectué l'authentification et défini le type de contenu sur JSON. S'il vous plaît suggérer
Deepak
83

Bien que cela ne soit pas recommandé, vous pouvez également désactiver complètement la validation des certificats 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) {}
  }
}
neu242
la source
72
Il est à noter que la désactivation de la validation de certificat de cette manière ouvre la connexion à d'éventuelles attaques MITM: ne pas utiliser en production .
Bruno
3
Le code ne compile pas, heureusement. Cette «solution» est radicalement peu sûre.
Marquis de Lorne
5
@ neu242, non, cela ne dépend pas vraiment de l'utilisation que vous en faites. Si vous souhaitez utiliser SSL / TLS, vous souhaitez sécuriser votre connexion contre les attaques MITM, c'est tout l'intérêt. L'authentification du serveur n'est pas nécessaire si vous pouvez garantir que personne ne pourra modifier le trafic, mais les situations dans lesquelles vous pensez qu'il peut y avoir des écoutes indiscrètes qui ne seraient pas également en mesure de modifier le trafic réseau sont assez rares.
Bruno
1
@ neu242 Merci pour l'extrait de code. Je pense en fait à l'utiliser en production dans un but très spécifique (exploration du Web), et j'ai mentionné votre implémentation dans une question ( stackoverflow.com/questions/13076511/… ). Si vous avez le temps, pourriez-vous l'examiner et me faire savoir s'il y a des risques de sécurité que j'ai manqués?
Sal
1
@Bruno Si je ne fais que cingler le serveur, est-ce que cela m'affecterait vraiment du tout, par des attaques MITM?
PhoonOne
21

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 avec le code

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

Idem avec javax.net.ssl.trustStore

Gandalf
la source
13

Si vous traitez avec un appel de service Web utilisant le framework Axis, il existe une réponse 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, inscrivez simplement cette déclaration avant d'appeler des services Web:

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

Les clauses de non-responsabilité habituelles selon lesquelles il s'agit d'une très mauvaise chose à faire dans un environnement de production s'appliquent.

J'ai trouvé ceci sur le wiki d'Axis .

Mark Meuer
la source
L'OP traite d'une HttpsURLConnection, pas d'Axis
neu242
2
Je comprends. Je n'avais pas l'intention de laisser entendre que ma réponse était meilleure dans le cas général. Il est juste que si vous êtes en utilisant le cadre Axis, vous pouvez avoir dans ce contexte la question de l'OP. (C'est ainsi que j'ai trouvé cette question en premier lieu.) Dans ce cas, la façon dont j'ai fourni est plus simple.
Mark Meuer
5

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éés 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();
}
EpicPandaForce
la source
C'est keyStorece qui contient la clé privée et le certificat.
EpicPandaForce
1
Vous devez inclure ces 2 dépendances pour que le code convertPEMtoP12 fonctionne: <dependency> <groupId> org.bouncycastle </groupId> <artifactId> bcprov-jdk15on </artifactId> <version> 1.53 </version> </dependency> < dépendance> <groupId> org.bouncycastle </groupId> <artifactId> bcpkix-jdk15on </artifactId> <version> 1.53 </version> </dependency>
BirdOfPrey
@EpicPandaForce J'obtiens une erreur: Attrapé: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Impossible de convertir l'objet avec la classe 'org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject' vers la classe 'int' en ligne ks. setKeyEntry - tous les indices de ce qui pourrait être faux
Vishal Biyani
Ouais, vous utilisez Groovy au lieu d'un langage strictement typé. (techniquement, cette méthode prend un identifiant et un certificat, pas seulement un certificat)
EpicPandaForce
4

J'utilise le package Apache commons HTTP Client pour faire cela dans mon projet actuel et cela fonctionne bien avec SSL et un certificat auto-signé (après l'avoir installé dans des cacerts comme vous l'avez mentionné). Jetez-y un œil ici:

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

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

Taylor Leese
la source
1
Cela semble être un package assez soigné, mais la classe qui devrait tout faire fonctionner 'AuthSSLProtocolSocketFactory' ne fait apparemment pas partie de la distribution officielle, ni dans la version 4.0beta (malgré les notes de publication indiquant que c'est le cas), ni dans la version 3.1. J'ai piraté un peu avec lui et je semble maintenant être définitivement bloqué avec un blocage de 5 minutes avant qu'il ne coupe simplement la connexion. C'est vraiment étrange - si je charge l'autorité de certification et le certificat client dans n'importe quel navigateur, cela vole.
Jan
1
Apache HTTP Client 4 peut prendre un SSLContextdirectement, vous pouvez donc configurer tout cela de cette façon, au lieu d'utiliser AuthSSLProtocolSocketFactory.
Bruno le
1
Existe-t-il un moyen de faire tout le contenu du certificat client en mémoire plutôt que via un fichier de clés externe?
Sridhar Sarnobat
4

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 signifie «handshake_failure» dans ce cas):

Importez votre certificat de serveur dans votre keystore trustcacerts sur le JRE du client. Cela se fait facilement avec keytool :

keytool
    -import
    -alias <provide_an_alias>
    -file <certificate_file>
    -keystore <your_path_to_jre>/lib/security/cacerts
sourcerebels
la source
J'ai essayé de nettoyer et de recommencer, et l'échec de la poignée de main a disparu. Maintenant , je viens d' obtenir 5 minutes de silence avant que la connexion est terminée: o
Jan
1

Utilisation du code ci-dessous

-Djavax.net.ssl.keyStoreType=pkcs12

ou

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

n'est pas du tout nécessaire. Il n'est pas non plus nécessaire de créer votre propre usine SSL personnalisée.

J'ai également rencontré le même problème, dans mon cas, il y avait un problème qui complète la chaîne de certificats n'était pas importée dans les magasins de confiance. Importez des certificats à l'aide de l'utilitaire keytool à droite 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 être présent dans le fichier cacerts.

Le fichier cacerts doit également être configuré sur le serveur sur lequel vous exécutez votre application, les deux serveurs s'authentifieront mutuellement avec des clés publiques / privées.

Ankur Singhal
la source
La création de votre propre usine SSL personnalisée est beaucoup plus compliquée et sujette aux erreurs que la définition de deux propriétés système.
Marquis de Lorne