276 votes

Authentification par certificat du client Java HTTPS

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

J'écris un client Java qui doit faire une simple POST de données à un URL . Cette partie fonctionne bien, le seul problème c'est qu'elle est censée être faite sur HTTPS . Le site HTTPS est assez facile à gérer (soit avec HTTPclient ou en utilisant la fonction intégrée de Java HTTPS ), mais je suis bloqué sur l'authentification avec les certificats des clients. J'ai remarqué qu'il y a déjà une question très similaire ici, que je n'ai pas encore testée avec mon code (je le ferai bientô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 PCAP dumps).

J'aimerais savoir ce que le client est exactement censé présenter au serveur lors de l'authentification avec des certificats (spécifiquement pour Java - si cela a une quelconque importance) ? S'agit-il d'un JKS ou PKCS#12 ? Qu'est-ce qui est censé s'y trouver : juste le certificat du client, ou une clé ? Si oui, quelle clé ? Il y a beaucoup de confusion au sujet des différents types de fichiers, des types de certificats et autres.

Comme je l'ai déjà dit, je suis nouveau dans le domaine de la santé. HTTPS/SSL/TLS J'apprécierais donc d'avoir quelques informations de base (pas forcément un essai ; je me contenterai de liens vers de bons articles).

0 votes

J'ai reçu deux certificats du client comment identifier celui qui doit être ajouté dans le keystore et le truststore pourriez-vous s'il vous plaît m'aider à identifier ce problème comme vous l'avez déjà fait dans le même genre de problème, ce problème que j'ai soulevé en fait je n'ai pas d'indice sur ce qu'il faut faire. stackoverflow.com/questions/61374276/

276voto

tmbrggmn Points 2522

J'ai finalement réussi à résoudre tous les problèmes, je vais donc répondre à ma propre question. Voici les paramètres/fichiers que j'ai utilisés pour parvenir à résoudre mon ou mes problèmes particuliers ;

El keystore du client est un Format PKCS#12 contenant

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

Pour le générer, j'ai utilisé l'outil OpenSSL pkcs12 par exemple ;

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

Conseil : assurez-vous d'avoir la dernière version d'OpenSSL, no version 0.9.8h car elle semble souffrir d'un bug qui ne permet pas de générer correctement les fichiers PKCS#12.

Ce fichier PKCS#12 sera utilisé par le client Java pour présenter le certificat du client au serveur lorsque le serveur a explicitement demandé au client de s'authentifier. Voir le Article de Wikipedia sur TLS pour une vue d'ensemble du fonctionnement du protocole d'authentification du certificat du client (qui explique également pourquoi nous avons besoin de la clé privée du client).

El le truststore du client est un simple Format JKS contenant le Racine o certificats d'AC intermédiaires . Ces certificats d'autorité de certification détermineront les points d'extrémité avec lesquels vous serez autorisé à communiquer. Dans ce cas, votre client pourra se connecter à n'importe quel serveur présentant un certificat signé par l'une des autorités de certification du truststore.

Pour le générer, vous pouvez utiliser l'outil standard Java keytool, 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 truststore, votre client essaiera d'effectuer une poignée de main SSL complète avec tous les serveurs qui présentent un certificat signé par l'AC identifiée par myca.crt .

Les fichiers ci-dessus sont strictement réservés au client. Si vous souhaitez également configurer un serveur, ce dernier a besoin de ses propres fichiers de clés et de confiance. Un excellent exemple de configuration d'un client et d'un serveur Java (utilisant Tomcat) peut être trouvé sur le site suivant ce site web .

Questions/Remarques/Tips

  1. Authentification par certificat du client ne peut être appliquée que par le serveur.
  2. ( Important ! ) Lorsque le serveur demande un certificat client (dans le cadre de la poignée de main TLS), il fournit également une liste d'autorités de certification fiables dans le cadre de la demande de certificat. Lorsque le certificat client que vous souhaitez présenter pour l'authentification est no signé par l'un de ces CA, il ne sera pas présenté du tout (à mon avis, c'est un comportement bizarre, mais je suis sûr qu'il y a une raison à cela). C'était la principale cause de mes problèmes, car l'autre partie n'avait pas configuré son serveur correctement pour accepter mon certificat client auto-signé et nous avons supposé que le problème était de mon côté pour ne pas fournir correctement le certificat client dans la demande.
  3. Procurez-vous Wireshark. Il offre une excellente analyse des paquets SSL/HTTPS et sera d'une aide précieuse pour déboguer et trouver le problème. Il est similaire à -Djavax.net.debug=ssl mais il 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, il suffit de remplacer l'URL de destination par son équivalent HTTPS et d'ajouter les arguments JVM suivants (qui sont les mêmes 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

6 votes

"Si le certificat client que vous souhaitez présenter pour l'authentification n'est pas signé par l'une de ces autorités de certification, il ne sera pas présenté du tout". Les certificats ne sont pas présentés parce que le client sait qu'ils ne seront pas acceptés par le serveur. De plus, votre certificat peut être signé par une AC intermédiaire "ICA", et le serveur peut présenter à votre client l'AC racine "RCA", et votre navigateur web vous laissera toujours choisir votre certificat même s'il est signé par ICA et non RCA.

2 votes

Pour illustrer le commentaire ci-dessus, considérons une situation où vous avez une AC racine (RCA1) et deux AC intermédiaires (ICA1 et ICA2). Sur Apache Tomcat, si vous importez RCA1 dans le magasin de confiance, votre navigateur web présentera TOUS les certificats signés par ICA1 et ICA2, même s'ils ne sont pas dans votre magasin de confiance. C'est parce que c'est la chaîne qui compte et non les certificats individuels.

2 votes

"à mon avis, c'est un comportement bizarre, mais je suis sûr qu'il y a une raison à cela". La raison est que c'est ce qui est dit dans la RFC 2246. Il n'y a rien d'étrange à cela. Permettre aux clients de présenter des certificats qui ne seront pas acceptés par le serveur est ce qui serait bizarre, et une perte totale de temps et d'espace.

108voto

Magnus Points 5093

D'autres réponses montrent comment configurer globalement les certificats clients. Cependant, si vous souhaitez définir par programme la clé client pour une connexion particulière, plutôt que de la définir globalement pour 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"));

0 votes

J'ai dû utiliser sslContext = SSLContexts.custom().loadTrustMaterial(keyFile, PASSWORD).build(); . Je n'ai pas réussi à le faire fonctionner avec loadKeyMaterial(...) .

6 votes

@ConorSvensson Le matériel de confiance est pour le client qui fait confiance au certificat du serveur distant, le matériel de clé est pour le serveur qui fait confiance au client.

4 votes

J'aime beaucoup cette réponse concise et pertinente. Au cas où les gens seraient intéressés, je fournis un exemple travaillé ici avec des instructions de construction. stackoverflow.com/a/46821977/154527

35voto

mhaller Points 10002

Le fichier JKS est simplement 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 :

  • El client Le magasin du client contiendra l'adresse privé et public paire de clés. Il s'agit d'un keystore .
  • El serveur Le magasin du client contiendra l'adresse public clé. Il s'agit d'un truststore .

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

Pour définir les emplacements du 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 (clé publique) du client vers un fichier, afin de pouvoir le copier sur le serveur, utilisez la commande suivante

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

Pour importer la clé publique du client dans le keystore du serveur, utilisez (comme l'a mentionné le poster, cela a déjà été fait par les administrateurs du serveur)

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

1 votes

Je devrais probablement mentionner que je n'ai aucun contrôle sur le serveur. Le serveur a importé notre certificat public. Les administrateurs de ce système m'ont dit que je devais fournir explicitement le certificat afin qu'il puisse être envoyé lors de la poignée de main (leur serveur le demande explicitement).

0 votes

Vous aurez besoin des clés publiques et privées de votre certificat public (celui qui est connu du serveur) sous forme de fichier JKS.

0 votes

Merci pour l'exemple de code. Dans le code ci-dessus, qu'est-ce que "mykey-public.cer" exactement ? Est-ce le certificat public du client (nous utilisons des certificats auto-signés) ?

14voto

wildloop Points 2814

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;
}

}

0 votes

Si vous préférez que le certificat soit disponible pour toutes les applications qui utilisent une installation JVM particulière, suivez les instructions suivantes cette réponse à la place.

0 votes

Est la méthode configureRequest() pour définir le proxy du projet client, n'est-ce pas ?

0 votes

Oui, c'est la configuration du client http et c'est la configuration du proxy dans ce cas.

10voto

Johannes Brodwall Points 3469

Étant donné un fichier p12 contenant à la fois le certificat et la clé privée (générés par openssl, par exemple), le code suivant l'utilisera pour une connexion HttpsURLC spécifique :

    KeyStore keyStore = KeyStore.getInstance("pkcs12");
    keyStore.load(new FileInputStream(keyStorePath), keystorePassword.toCharArray());
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, keystorePassword.toCharArray());
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(kmf.getKeyManagers(), null, null);
    SSLSocketFactory sslSocketFactory = ctx.getSocketFactory();

    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    connection.setSSLSocketFactory(sslSocketFactory);

El SSLContext prend un certain temps pour s'initialiser, donc vous pouvez vouloir le mettre en cache.

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