171 votes

Comment puis-je utiliser des certificats différents sur des connexions spécifiques ?

Un module que je suis en train d'ajouter à notre grande application Java doit converser avec le site Web sécurisé par SSL d'une autre société. Le problème est que le site utilise un certificat auto-signé. Je dispose d'une copie du certificat pour vérifier que je ne suis pas confronté à une attaque de type "man-in-the-middle", et je dois intégrer ce certificat dans notre code de manière à ce que la connexion au serveur soit réussie.

Voici le code de 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();
}

Sans traitement supplémentaire en place pour le certificat auto-signé, il meurt à conn.getOutputStream() avec l'exception suivante :

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

Idéalement, mon code doit apprendre à Java à accepter ce seul certificat auto-signé, pour cet endroit précis de l'application, et nulle part ailleurs.

Je sais que je peux importer le certificat dans le magasin de l'autorité de certification du JRE, ce qui permettra à Java de l'accepter. Ce n'est pas une approche que je veux adopter si je peux l'aider ; cela semble très invasif à faire sur toutes les machines de nos clients pour un module qu'ils n'utilisent peut-être pas ; cela affecterait toutes les autres applications Java utilisant le même JRE, et je n'aime pas cela, même si les chances qu'une autre application Java accède à ce site sont nulles. Ce n'est pas non plus une opération triviale : sous UNIX, je dois obtenir des droits d'accès pour modifier le JRE de cette manière.

J'ai également vu que je pouvais créer une instance de TrustManager qui effectue des contrôles personnalisés. Il semble même que je puisse créer un TrustManager qui délègue au TrustManager réel dans toutes les instances, sauf pour ce certificat. Mais il semble que ce TrustManager soit installé globalement, et je présume qu'il affecterait toutes les autres connexions de notre application, et cela ne me semble pas tout à fait correct non plus.

Quelle est la méthode préférée, standard ou optimale pour configurer une application Java afin qu'elle accepte un certificat auto-signé ? Puis-je atteindre tous les objectifs que j'ai à l'esprit ci-dessus, ou vais-je devoir faire des compromis ? Existe-t-il une option impliquant des fichiers, des répertoires et des paramètres de configuration, et peu ou pas de code ?

0 votes

Petite réparation qui fonctionne : rgagnon.com/javadetails/

23 votes

@Hasenpriester : s'il vous plaît, ne suggérez pas cette page. Elle désactive toute vérification de confiance. Vous n'allez pas seulement accepter le certificat auto-signé que vous voulez, vous allez aussi accepter n'importe quel certificat qu'un attaquant MITM vous présentera.

174voto

erickson Points 127945

Créer un SSLSocket l'usine vous-même, et le mettre sur le HttpsURLConnection avant de se connecter.

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

Vous devez créer un SSLSocketFactory et le garder autour. Voici un croquis de la façon de l'initialiser :

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

Si vous avez besoin d'aide pour créer le magasin de clés, veuillez commenter.


Voici un exemple de chargement du magasin de clés :

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

Pour créer le magasin de clés avec un certificat au format PEM, vous pouvez écrire votre propre code en utilisant CertificateFactory ou simplement l'importer avec keytool du JDK (keytool ne le fera pas ne fonctionne pas pour une "entrée clé", mais convient parfaitement pour une "entrée de confiance").

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

4 votes

Merci beaucoup ! Les autres gars m'ont aidé à me diriger dans la bonne direction, mais en fin de compte, c'est votre approche que j'ai adoptée. J'ai eu la tâche exténuante de convertir le fichier de certificat PEM en un fichier keystore Java JKS, et j'ai trouvé de l'aide pour cela ici : stackoverflow.com/questions/722931/

2 votes

Je suis content que ça ait marché. Je suis désolé que vous ayez eu du mal avec le magasin de clés ; j'aurais dû l'inclure dans ma réponse. Ce n'est pas trop difficile avec CertificateFactory. En fait, je pense que je vais faire une mise à jour pour ceux qui viendront plus tard.

0 votes

Wow. Ce moyen de convertir le PEM en JKS est nettement plus facile que ce que j'ai fait ! Merci ! :)

14voto

user454322 Points 1172

Si vous créez un SSLSocketFactory n'est pas une option, il suffit d'importer la clé dans la JVM

  1. Récupérer la clé publique : $openssl s_client -connect dev-server:443 puis créer un fichier dev-server.pem qui ressemble à

    -----BEGIN CERTIFICATE----- 
    lklkkkllklklklklllkllklkl
    lklkkkllklklklklllkllklkl
    lklkkkllklk....
    -----END CERTIFICATE-----
  2. Importez la clé : #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem . Mot de passe : changeit

  3. Redémarrer la JVM

Source : Comment résoudre l'exception javax.net.ssl.SSLHandshakeException ?

1 votes

Je ne pense pas que cela résout la question originale, mais cela a résolu mi problème, donc merci !

1 votes

Ce n'est pas non plus une bonne idée de faire cela : vous avez maintenant ce certificat auto-signé aléatoire et supplémentaire pour l'ensemble du système qui todo Les processus Java feront confiance par défaut.

12voto

araqnid Points 33350

J'ai dû faire quelque chose comme cela en utilisant commons-httpclient pour accéder à un serveur https interne avec un certificat auto-signé. Oui, notre solution était de créer un TrustManager personnalisé qui transmettait simplement tout (en enregistrant un message de débogage).

Cela revient à avoir notre propre SSLSocketFactory qui crée des sockets SSL à partir de notre SSLContext local, qui est configuré pour avoir uniquement notre TrustManager local associé à lui. Il n'est pas du tout nécessaire de s'approcher d'un keystore/certstore.

C'est donc dans notre 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);
}

Avec d'autres méthodes implémentant SecureProtocolSocketFactory. LocalSSLTrustManager est l'implémentation du gestionnaire de confiance fictif susmentionné.

9 votes

Si vous désactivez toute vérification de la confiance, il est inutile d'utiliser SSL/TLS. C'est acceptable pour les tests locaux, mais pas si vous voulez vous connecter à l'extérieur.

0 votes

J'obtiens cette exception lorsque je l'exécute sur Java 7. javax.net.ssl.SSLHandshakeException : no cipher suites in common Can you assist ?

12voto

Nous copions le truststore du JRE et ajoutons nos certificats personnalisés à ce truststore, puis nous indiquons à l'application d'utiliser le truststore personnalisé avec une propriété système. De cette façon, nous laissons le truststore par défaut du JRE tranquille.

L'inconvénient est que lorsque vous mettez à jour le JRE, son nouveau truststore n'est pas automatiquement fusionné avec votre truststore personnalisé.

Vous pourriez peut-être gérer ce scénario en ayant un installateur ou une routine de démarrage qui vérifie le truststore/jdk et vérifie s'il y a un décalage ou met automatiquement à jour le truststore. Je ne sais pas ce qui se passe si vous mettez à jour le truststore pendant l'exécution de l'application.

Cette solution n'est pas entièrement élégante ou infaillible, mais elle est simple, fonctionne et ne nécessite aucun code.

0voto

Leo RK Points 9

L'installation du certificat du client dans le keystore fonctionne bien, mais à chaque fois qu'un nouveau certificat est installé, le client doit mettre à jour le keystore avec le nouveau certificat et redémarrer la JVM. Avoir un SSLHandler personnalisé serait très utile (réponse d'Araqnid).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X