259 votes

Accepter le certificat ssl auto-signé du serveur dans le client Java

Cela ressemble à une question standard, mais je n'ai pas trouvé de directives claires.

J'ai un code java qui essaie de se connecter à un serveur avec un certificat probablement auto-signé (ou expiré). Le code signale l'erreur suivante :

[HttpMethodDirector] I/O exception (javax.net.ssl.SSLHandshakeException) caught 
when processing request: sun.security.validator.ValidatorException: PKIX path 
building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target

Si je comprends bien, je dois utiliser keytool et dire à Java que c'est bon d'autoriser cette connexion.

Toutes les instructions pour résoudre ce problème supposent que je maîtrise parfaitement keytool, par exemple

générer une clé privée pour le serveur et l'importer dans le keystore

Y a-t-il quelqu'un qui pourrait poster des instructions détaillées ?

Je suis sous unix, donc bash script serait le mieux.

Je ne sais pas si c'est important, mais le code a été exécuté dans Jboss.

2 votes

Voir Comment accepter un certificat auto-signé avec une HttpsURLConnection Java ? . Évidemment, il serait préférable que vous puissiez faire en sorte que le site utilise un certificat valide.

2 votes

Merci pour le lien, je ne l'avais pas vu en cherchant. Mais les deux solutions proposées impliquent un code spécial pour envoyer une requête et j'utilise un code existant (amazon ws client for java). Respectivement, c'est leur site que je connecte et je ne peux pas résoudre ses problèmes de certificat.

5 votes

@MatthewFlaschen - "Évidemment, ce serait mieux si vous pouviez faire en sorte que le site utilise un cert valide..." - Un certificat auto-signé est un certificat valide si le client lui fait confiance. Beaucoup pensent que le fait de confier la confiance au cartel CA/Navigateur est un défaut de sécurité.

1voto

user1076662 Points 34

La réponse acceptée nécessite une option 3

AUSSI L'option 2 est TERRIBLE . Elle ne devrait JAMAIS être utilisée (surtout en production) car elle procure un faux sentiment de sécurité. Utilisez simplement HTTP au lieu de l'option 2.

OPTION 3

Utilisez le certificat auto-signé pour établir la connexion Https.

Voici un exemple :

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.KeyStore;

/*
 * Use a SSLSocket to send a HTTP GET request and read the response from an HTTPS server.
 * It assumes that the client is not behind a proxy/firewall
 */

public class SSLSocketClientCert
{
    private static final String[] useProtocols = new String[] {"TLSv1.2"};
    public static void main(String[] args) throws Exception
    {
        URL inputUrl = null;
        String certFile = null;
        if(args.length < 1)
        {
            System.out.println("Usage: " + SSLSocketClient.class.getName() + " <url>");
            System.exit(1);
        }
        if(args.length == 1)
        {
            inputUrl = new URL(args[0]);
        }
        else
        {
            inputUrl = new URL(args[0]);
            certFile = args[1];
        }
        SSLSocket sslSocket = null;
        PrintWriter outWriter = null;
        BufferedReader inReader = null;
        try
        {
            SSLSocketFactory sslSocketFactory = getSSLSocketFactory(certFile);

            sslSocket = (SSLSocket) sslSocketFactory.createSocket(inputUrl.getHost(), inputUrl.getPort() == -1 ? inputUrl.getDefaultPort() : inputUrl.getPort());
            String[] enabledProtocols = sslSocket.getEnabledProtocols();
            System.out.println("Enabled Protocols: ");
            for(String enabledProtocol : enabledProtocols) System.out.println("\t" + enabledProtocol);

            String[] supportedProtocols = sslSocket.getSupportedProtocols();
            System.out.println("Supported Protocols: ");
            for(String supportedProtocol : supportedProtocols) System.out.println("\t" + supportedProtocol + ", ");

            sslSocket.setEnabledProtocols(useProtocols);

            /*
             * Before any data transmission, the SSL socket needs to do an SSL handshake.
             * We manually initiate the handshake so that we can see/catch any SSLExceptions.
             * The handshake would automatically  be initiated by writing & flushing data but
             * then the PrintWriter would catch all IOExceptions (including SSLExceptions),
             * set an internal error flag, and then return without rethrowing the exception.
             *
             * This means any error messages are lost, which causes problems here because
             * the only way to tell there was an error is to call PrintWriter.checkError().
             */
            sslSocket.startHandshake();
            outWriter = sendRequest(sslSocket, inputUrl);
            readResponse(sslSocket);
            closeAll(sslSocket, outWriter, inReader);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            closeAll(sslSocket, outWriter, inReader);
        }
    }

    private static PrintWriter sendRequest(SSLSocket sslSocket, URL inputUrl) throws IOException
    {
        PrintWriter outWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(sslSocket.getOutputStream())));
        outWriter.println("GET " + inputUrl.getPath() + " HTTP/1.1");
        outWriter.println("Host: " + inputUrl.getHost());
        outWriter.println("Connection: Close");
        outWriter.println();
        outWriter.flush();
        if(outWriter.checkError())        // Check for any PrintWriter errors
            System.out.println("SSLSocketClient: PrintWriter error");
        return outWriter;
    }

    private static void readResponse(SSLSocket sslSocket) throws IOException
    {
        BufferedReader inReader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
        String inputLine;
        while((inputLine = inReader.readLine()) != null)
            System.out.println(inputLine);
    }

    // Terminate all streams
    private static void closeAll(SSLSocket sslSocket, PrintWriter outWriter, BufferedReader inReader) throws IOException
    {
        if(sslSocket != null) sslSocket.close();
        if(outWriter != null) outWriter.close();
        if(inReader != null) inReader.close();
    }

    // Create an SSLSocketFactory based on the certificate if it is available, otherwise use the JVM default certs
    public static SSLSocketFactory getSSLSocketFactory(String certFile)
        throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException
    {
        if (certFile == null) return (SSLSocketFactory) SSLSocketFactory.getDefault();
        Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new FileInputStream(new File(certFile)));

        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);
        keyStore.setCertificateEntry("server", certificate);

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagerFactory.getTrustManagers(), null);

        return sslContext.getSocketFactory();
    }
}

0voto

VSK Points 139

Il ne s'agit pas d'une solution à l'ensemble du problème, mais Oracle dispose d'une bonne documentation détaillée sur la façon d'utiliser cet outil de clé. Ceci explique comment

  1. utiliser keytool.
  2. générer des certs/certs auto-signés en utilisant keytool.
  3. importer les certs générés vers les clients java.

https://docs.oracle.com/cd/E54932_01/doc.705/e54936/cssg_create_ssl_cert.htm#CSVSG178

0voto

user66660 Points 1

Au lieu d'utiliser keytool comme suggéré par le commentaire du haut, sur RHEL vous pouvez utiliser update-ca-trust à partir des versions plus récentes de RHEL 6. Vous aurez besoin d'avoir le cert au format pem. Puis

trust anchor <cert.pem>

Editez /etc/pki/ca-trust/source/cert.p11-kit et changez "certificate category : other-entry" en "certificate category : authority". (Ou utilisez sed pour faire cela dans un script.) Puis faites

update-ca-trust

Quelques mises en garde :

  • Je n'ai pas trouvé "trust" sur mon serveur RHEL 6 et yum ne m'a pas proposé de l'installer. J'ai fini par l'utiliser sur un serveur RHEL 7 et par copier le fichier .p11-kit.
  • Pour que cela fonctionne pour vous, vous devrez peut-être faire ce qui suit update-ca-trust enable . Cela remplacera /etc/pki/java/cacerts par un lien symbolique pointant vers /etc/pki/ca-trust/extracted/java/cacerts. (Vous voudrez donc peut-être sauvegarder le premier en premier).
  • Si votre client java utilise des cacerts stockés dans un autre emplacement, vous devrez le remplacer manuellement par un lien symbolique vers /etc/pki/ca-trust/extracted/java/cacerts, ou le remplacer par ce fichier.

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