web-dev-qa-db-fra.com

Comment utiliser un certificat client SSL avec Apache HttpClient?

En Python, j'utilisais des requêtes comme celle-ci:

requests.put(
              webdavURL, 
              auth=(tUsername, tPassword), 
              data=webdavFpb, 
              verify=False, 
              cert=("/path/to/file.pem", "/path/to/file.key"))

C'est de la tarte. 

Maintenant, je dois implémenter la même chose en Java avec Apache HttpClient. Comment puis-je passer un certificat client lors de requêtes utilisant HttpClient?

17
daniels

Je pense que la principale différence est qu'en Java, vous mettez habituellement la clé et le certificat dans un magasin de clés et l'utilisez à partir de là. Comme vous le mentionnez souvent, les utilisateurs souhaitent utiliser une bibliothèque distincte, comme httpcomponents client (tout comme vous utilisez request library dans votre exemple python).

Voici un exemple d'utilisation d'un certificat client à partir d'un magasin de clés, à l'aide de la bibliothèque mentionnée précédemment:

import org.Apache.http.HttpEntity;
import org.Apache.http.HttpResponse;
import org.Apache.http.client.HttpClient;
import org.Apache.http.client.methods.HttpGet;
import org.Apache.http.impl.client.HttpClients;
import org.Apache.http.ssl.SSLContexts;
import org.Apache.http.util.EntityUtils;
import org.junit.Test;

import javax.net.ssl.SSLContext;
import Java.io.InputStream;
import Java.security.KeyStore;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class MyClientCertTest {

    private static final String KEYSTOREPATH = "/clientkeystore.jks"; // or .p12
    private static final String KEYSTOREPASS = "keystorepass";
    private static final String KEYPASS = "keypass";

    KeyStore readStore() throws Exception {
        try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
            KeyStore keyStore = KeyStore.getInstance("JKS"); // or "PKCS12"
            keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray());
            return keyStore;
        }
    }
    @Test
    public void readKeyStore() throws Exception {
        assertNotNull(readStore());
    }
    @Test
    public void performClientRequest() throws Exception {
        SSLContext sslContext = SSLContexts.custom()
                .loadKeyMaterial(readStore(), KEYPASS.toCharArray()) // use null as second param if you don't have a separate key password
                .build();

        HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
        HttpResponse response = httpClient.execute(new HttpGet("https://slsh.iki.fi/client-certificate/protected/"));
        assertEquals(200, response.getStatusLine().getStatusCode());
        HttpEntity entity = response.getEntity();

        System.out.println("----------------------------------------");
        System.out.println(response.getStatusLine());
        EntityUtils.consume(entity);
    }
}

Maven Pom pour les versions de dépendance:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.Apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.Apache.org/POM/4.0.0 http://maven.Apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.acme</groupId>
    <artifactId>httptests</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.Apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.Apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.9</version>
                <!-- this is not needed, but useful if you want to debug what's going
                     on with your connection -->
                <configuration>
                    <argLine>-Djavax.net.debug=all</argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

J'ai également publié une simple page de test pour tester un certificat client.


Juste pour démontrer que cela peut être fait, voici un exemple d'utilisation d'un certificat client utilisant simplement une API Java standard, sans bibliothèques supplémentaires.

import org.junit.Test;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import Java.io.BufferedReader;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import Java.net.URL;
import Java.security.KeyStore;

public class PlainJavaHTTPS2Test {

    @Test
    public void testJKSKeyStore() throws Exception {
        final String KEYSTOREPATH = "clientkeystore.jks";
        final char[] KEYSTOREPASS = "keystorepass".toCharArray();
        final char[] KEYPASS = "keypass".toCharArray();

        try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
            setSSLFactories(storeStream, "JKS", KEYSTOREPASS, KEYPASS);
        }
        testPlainJavaHTTPS();
    }
    @Test
    public void testP12KeyStore() throws Exception {
        final String KEYSTOREPATH = "clientkeystore.p12";
        final char[] KEYSTOREPASS = "keystorepass".toCharArray();
        final char[] KEYPASS = "keypass".toCharArray();

        try (InputStream storeStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
            setSSLFactories(storeStream, "PKCS12", KEYSTOREPASS, KEYPASS);
        }
        testPlainJavaHTTPS();
    }
    private static void setSSLFactories(InputStream keyStream, String keystoreType, char[] keyStorePassword, char[] keyPassword) throws Exception
    {
        KeyStore keyStore = KeyStore.getInstance(keystoreType);

        keyStore.load(keyStream, keyStorePassword);

        KeyManagerFactory keyFactory =
                KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

        keyFactory.init(keyStore, keyPassword);

        KeyManager[] keyManagers = keyFactory.getKeyManagers();

        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(keyManagers, null, null);
        SSLContext.setDefault(sslContext);
    }

    public void testPlainJavaHTTPS() throws Exception {
        String httpsURL = "https://slsh.iki.fi/client-certificate/protected/";
        URL myUrl = new URL(httpsURL);
        HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
        try (InputStream is = conn.getInputStream()) {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            String inputLine;

            while ((inputLine = br.readLine()) != null) {
                System.out.println(inputLine);
            }
        }
    }
}

Et voici une troisième version avec le moins de code, mais qui repose sur le fait que le magasin de clés est un fichier sur disque, pas dans le fichier jar, et b) le mot de passe de clé doit être identique au mot de passe de magasin de clés.

import org.junit.BeforeClass;
import org.junit.Test;

import Java.net.URL;
import Java.io.*;
import javax.net.ssl.HttpsURLConnection;

public class PlainJavaHTTPSTest {

    @BeforeClass
    public static void setUp() {
        System.setProperty("javax.net.ssl.keyStore", "/full/path/to/clientkeystore-samepassword.jks");
        System.setProperty("javax.net.ssl.keyStorePassword", "keystorepass");
    }

    @Test
    public void testPlainJavaHTTPS() throws Exception {
        String httpsURL = "https://slsh.iki.fi/client-certificate/protected/";
        URL myUrl = new URL(httpsURL);
        HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
        try (InputStream is = conn.getInputStream()) {
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);

            String inputLine;

            while ((inputLine = br.readLine()) != null) {
                System.out.println(inputLine);
            }
        }
    }
}

Les propriétés définies ci-dessus dans le code peuvent bien sûr aussi être données en tant que paramètres de démarrage, -Djavax.net.ssl.keyStore=/full/path/to/clientkeystore-samepassword.jks et -Djavax.net.ssl.keyStorePassword=keystorepass.

17
eis

Si vous souhaitez utiliser le client HTTP Apache au lieu du client HTTP Java, vous devez fournir à SSLFactory votre fichier de clés et configurer DefaultHTTPClient pour qu'il l'utilise dans le protocole HTTPS.

Vous pouvez trouver un exemple de travail ici .

J'espère que ça aide.

11
Bosko Mijin

dans Apache HttpClient 4.5.5

Comment gérer un certificat SSL non valide avec le client Apache 4.5.5?

HttpClient httpClient = HttpClients
            .custom()
            .setSSLContext(new SSLContextBuilder().loadTrustMaterial(null, TrustAllStrategy.INSTANCE).build())
            .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
            .build();
0
aaron