web-dev-qa-db-fra.com

Authentification du certificat client Java HTTPS

Je suis assez nouveau sur HTTPS/SSL/TLS et je suis un peu confus quant à ce que les clients sont supposés présenter lors de l'authentification avec des certificats.

J'écris un client Java qui doit effectuer un simple POST de données vers une URL particulière. Cette partie fonctionne bien, le seul problème est que cela est supposé se faire sur HTTPS. La partie HTTPS est assez facile à gérer (avec HTTPclient ou en utilisant la prise en charge HTTPS intégrée de Java), mais je ne parviens pas à m'authentifier à l'aide de certificats clients. J'ai remarqué qu'il y a déjà une question très similaire ici, que je n'ai pas encore essayée avec mon code (le fera assez tôt). Mon problème actuel est que - quoi que je fasse - le client Java n'envoie jamais le certificat (je peux le vérifier avec les sauvegardes PCAP).

Je voudrais savoir ce que le client est censé présenter au serveur lors de l'authentification avec des certificats (en particulier pour Java - si cela compte vraiment)? Est-ce un fichier JKS ou PKCS # 12? Qu'est-ce qui est censé être en eux? juste le certificat du client, ou une clé? Si oui, quelle clé? Il y a pas mal de confusion à propos des différents types de fichiers, types de certificats, etc.

Comme je l'ai déjà dit, je suis nouveau sur HTTPS/SSL/TLS et j'apprécierais donc également quelques informations de base (il n'est pas nécessaire que ce soit un essai; je vais me contenter de liens vers de bons articles).

200
tmbrggmn

Enfin réussi à résoudre tous les problèmes, alors je vais répondre à ma propre question. Ce sont les paramètres/fichiers que j'ai utilisés pour résoudre mes problèmes particuliers;

Le magasin de clés du client} _ est un fichier format PKCS # 12 contenant

  1. Le certificat public du client (dans ce cas, signé par une autorité de certification auto-signée)
  2. La clé privée du client

Pour le générer, j'ai utilisé la commande pkcs12 d'OpenSSL, par exemple;

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "Whatever"

Conseil: assurez-vous de disposer de la dernière version d'OpenSSL, not version 0.9.8h, car elle semble souffrir d'un bogue empêchant la génération correcte de fichiers PKCS # 12.

Ce fichier PKCS # 12 sera utilisé par le client Java pour présenter le certificat du client au serveur lorsque celui-ci a explicitement demandé au client de s’authentifier. Voir l'article Wikipedia sur TLS pour un aperçu du fonctionnement réel du protocole d'authentification par certificat client (explique également pourquoi nous avons besoin de la clé privée du client ici).

Le magasin de clés de confiance du client _ est un simple fichier format JKS contenant le racine ou certificats de CA intermédiaires. Ces certificats d'autorité de certification détermineront les points de terminaison avec lesquels vous serez autorisé à communiquer. Dans ce cas, votre client pourra se connecter au serveur qui présente un certificat signé par l'une des autorités de certification du magasin de clés de confiance.

Pour le générer, vous pouvez utiliser le keytool Java standard, par exemple;

keytool -genkey -dname "cn=CLIENT" -alias truststorekey -keyalg RSA -keystore ./client-truststore.jks -keypass whatever -storepass whatever
keytool -import -keystore ./client-truststore.jks -file myca.crt -alias myca

En utilisant ce fichier de clés certifiées, votre client essaiera d’établir une liaison SSL complète avec tous les serveurs présentant un certificat signé par l’autorité de certification identifiée par myca.crt.

Les fichiers ci-dessus sont strictement réservés au client. Lorsque vous souhaitez également configurer un serveur, celui-ci a besoin de ses propres fichiers de clés et de fichiers de clés certifiées. Vous trouverez un excellent guide pour configurer un exemple pleinement fonctionnel pour un client et un serveur Java (à l'aide de Tomcat) sur ce site Web .

_ {Issues/Remarks/Tips} _

  1. Authentification par certificat client ne peut être appliqué que par le serveur.
  2. (Important!) Lorsque le serveur demande un certificat client (dans le cadre de la négociation TLS), il fournit également une liste des autorités de certification approuvées dans le cadre de la demande de certificat. Lorsque le certificat client que vous souhaitez présenter pour l'authentification est non signé par l'une de ces autorités de certification, il ne sera pas présenté du tout (à mon avis, il s'agit d'un comportement étrange, mais je suis sûr qu'il existe une raison pour cela). C’était la principale cause de mes problèmes, car l’autre partie n’avait pas configuré correctement son serveur pour accepter mon certificat client auto-signé et nous supposions que le problème venait de moi pour ne pas avoir correctement fourni le certificat client dans la demande.
  3. Obtenez Wireshark. Il offre une excellente analyse de paquets SSL/HTTPS et sera une aide précieuse pour le débogage et la recherche du problème. Il ressemble à -Djavax.net.debug=ssl mais est plus structuré et (sans doute) plus facile à interpréter si vous n'êtes pas à l'aise avec la sortie de débogage Java SSL.
  4. Il est parfaitement possible d'utiliser la bibliothèque Apache httpclient. Si vous souhaitez utiliser httpclient, remplacez simplement l'URL de destination par son équivalent HTTPS et ajoutez les arguments JVM suivants (identiques pour tout autre client, quelle que soit la bibliothèque que vous souhaitez utiliser pour envoyer/recevoir des données via HTTP/HTTPS). :

    -Djavax.net.debug=ssl
    -Djavax.net.ssl.keyStoreType=pkcs12
    -Djavax.net.ssl.keyStore=client.p12
    -Djavax.net.ssl.keyStorePassword=whatever
    -Djavax.net.ssl.trustStoreType=jks
    -Djavax.net.ssl.trustStore=client-truststore.jks
    -Djavax.net.ssl.trustStorePassword=whatever
210
tmbrggmn

D'autres réponses montrent comment configurer globalement les certificats client . Cependant, si vous souhaitez définir par programme la clé client pour une connexion particulière plutôt que de la définir globalement dans toutes les applications exécutées sur votre JVM, vous pouvez configurer votre propre SSLContext comme suit :

String keyPassphrase = "";

KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("cert-key-pair.pfx"), keyPassphrase.toCharArray());

SSLContext sslContext = SSLContexts.custom()
        .loadKeyMaterial(keyStore, null)
        .build();

HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build();
HttpResponse response = httpClient.execute(new HttpGet("https://example.com"));
42
Magnus

Le fichier JKS n’est qu’un conteneur pour les certificats et les paires de clés . Dans un scénario d’authentification côté client, les différentes parties des clés seront situées ici:

  • Le magasin client contiendra la paire de clés private et public du client. Cela s'appelle un keystore.
  • Le magasin de serveur contiendra la clé public du client. Cela s'appelle un truststore.

La séparation du fichier de clés certifiées et du fichier de clés n'est pas obligatoire mais recommandée. Ils peuvent être le même fichier physique.

Pour définir les emplacements de système de fichiers des deux magasins, utilisez les propriétés système suivantes:

-Djavax.net.ssl.keyStore=clientsidestore.jks

et sur le serveur:

-Djavax.net.ssl.trustStore=serversidestore.jks

Pour exporter le certificat du client (clé publique) dans un fichier afin de pouvoir le copier sur le serveur, utilisez

keytool -export -alias MYKEY -file publicclientkey.cer -store clientsidestore.jks

Pour importer la clé publique du client dans le magasin de clés du serveur, utilisez (comme l'a indiqué l'affiche, cela a déjà été fait par les administrateurs du serveur).

keytool -import -file publicclientkey.cer -store serversidestore.jks
30
mhaller

Maven pom.xml:

<?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>some.examples</groupId>
    <artifactId>sslcliauth</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>sslcliauth</name>
    <dependencies>
        <dependency>
            <groupId>org.Apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.4</version>
        </dependency>
    </dependencies>
</project>

Code Java:

package some.examples;

import Java.io.FileInputStream;
import Java.io.IOException;
import Java.security.KeyManagementException;
import Java.security.KeyStore;
import Java.security.KeyStoreException;
import Java.security.NoSuchAlgorithmException;
import Java.security.UnrecoverableKeyException;
import Java.security.cert.CertificateException;
import Java.util.logging.Level;
import Java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.Apache.http.HttpEntity;
import org.Apache.http.HttpHost;
import org.Apache.http.client.config.RequestConfig;
import org.Apache.http.client.methods.CloseableHttpResponse;
import org.Apache.http.client.methods.HttpPost;
import org.Apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.Apache.http.ssl.SSLContexts;
import org.Apache.http.impl.client.CloseableHttpClient;
import org.Apache.http.impl.client.HttpClients;
import org.Apache.http.util.EntityUtils;
import org.Apache.http.entity.InputStreamEntity;

public class SSLCliAuthExample {

private static final Logger LOG = Logger.getLogger(SSLCliAuthExample.class.getName());

private static final String CA_KEYSTORE_TYPE = KeyStore.getDefaultType(); //"JKS";
private static final String CA_KEYSTORE_PATH = "./cacert.jks";
private static final String CA_KEYSTORE_PASS = "changeit";

private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
private static final String CLIENT_KEYSTORE_PATH = "./client.p12";
private static final String CLIENT_KEYSTORE_PASS = "changeit";

public static void main(String[] args) throws Exception {
    requestTimestamp();
}

public final static void requestTimestamp() throws Exception {
    SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(
            createSslCustomContext(),
            new String[]{"TLSv1"}, // Allow TLSv1 protocol only
            null,
            SSLConnectionSocketFactory.getDefaultHostnameVerifier());
    try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(csf).build()) {
        HttpPost req = new HttpPost("https://changeit.com/changeit");
        req.setConfig(configureRequest());
        HttpEntity ent = new InputStreamEntity(new FileInputStream("./bytes.bin"));
        req.setEntity(ent);
        try (CloseableHttpResponse response = httpclient.execute(req)) {
            HttpEntity entity = response.getEntity();
            LOG.log(Level.INFO, "*** Reponse status: {0}", response.getStatusLine());
            EntityUtils.consume(entity);
            LOG.log(Level.INFO, "*** Response entity: {0}", entity.toString());
        }
    }
}

public static RequestConfig configureRequest() {
    HttpHost proxy = new HttpHost("changeit.local", 8080, "http");
    RequestConfig config = RequestConfig.custom()
            .setProxy(proxy)
            .build();
    return config;
}

public static SSLContext createSslCustomContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException {
    // Trusted CA keystore
    KeyStore tks = KeyStore.getInstance(CA_KEYSTORE_TYPE);
    tks.load(new FileInputStream(CA_KEYSTORE_PATH), CA_KEYSTORE_PASS.toCharArray());

    // Client keystore
    KeyStore cks = KeyStore.getInstance(CLIENT_KEYSTORE_TYPE);
    cks.load(new FileInputStream(CLIENT_KEYSTORE_PATH), CLIENT_KEYSTORE_PASS.toCharArray());

    SSLContext sslcontext = SSLContexts.custom()
            //.loadTrustMaterial(tks, new TrustSelfSignedStrategy()) // use it to customize
            .loadKeyMaterial(cks, CLIENT_KEYSTORE_PASS.toCharArray()) // load client certificate
            .build();
    return sslcontext;
}

}
9
wildloop

Pour ceux d'entre vous qui souhaitent simplement mettre en place une authentification à double sens (certificats serveur et client), une combinaison de ces deux liens vous y mènera: 

Configuration de l’authentification bidirectionnelle:

https://linuxconfig.org/Apache-web-server-ssl-authentication

Vous n'avez pas besoin d'utiliser le fichier de configuration openssl qu'ils mentionnent; juste utiliser 

  • $ openssl genrsa -des3 -out environ 4096

  • $ openssl demande -nouveau -x509 -jours 365-clé ca.key -out ca.crt

pour générer votre propre certificat d'autorité de certification, puis générer et signer les clés de serveur et de client via: 

  • $ openssl genrsa -des3 -out server.key 4096

  • $ openssl req -new -key server.key -out server.csr

  • $ openssl x509 -req -days 365 -en serveur.csr -CA ca.crt -CAcomme ca.key -set_serial 100 -out serveur.crt

et

  • $ openssl genrsa -des3 -out client.key 4096

  • $ openssl req -new -key client.key -out client.csr

  • $ openssl x509 -req -days 365 -en client.csr -CA ca.crt -CAcomme ca.key -set_serial 101 -out client.crt

Pour le reste suivez les étapes dans le lien. La gestion des certificats pour Chrome fonctionne de la même manière que dans l'exemple de Firefox mentionné. 

Ensuite, configurez le serveur via: 

https://www.digitalocean.com/community/tutorials/how-to-create-a-ssl-certificate-on-Apache-for-ubuntu-14-04

Notez que vous avez déjà créé le serveur .crt et .key afin que vous n'ayez plus à faire cette étape. 

7
hans

Je pense que le correctif ici était le type de magasin de clés, pkcs12 (pfx) a toujours une clé privée et le type JKS peut exister sans clé privée. Sauf si vous spécifiez dans votre code ou sélectionnez un certificat via un navigateur, le serveur n'a aucun moyen de savoir qu'il représente un client à l'autre bout.

0
ObiWanKenobi