Authentification de certificat client Java HTTPS


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

J'écris un client Java qui doit faire un simple POST de données sur une URL particulière. Cette partie fonctionne bien, le seul problème est qu'elle est censée être effectuée via HTTPS. La partie HTTPS est assez facile à gérer (avec HttpClient ou en utilisant le support HTTPS intégré de Java), mais je suis bloqué sur l'authentification avec les 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 (je le ferai assez tôt). Mon problème actuel est que - quoi que je fasse-le client Java n'envoie jamais le long du certificat (je peux le vérifier avec les vidages PCAP).

Je voudrais savoir exactement ce que le client est censé présenter au serveur lors de l'authentification avec des certificats (spécifiquement pour Java - si cela compte du tout)? Est-ce un fichier JKS, ou PKCS#12? Ce qui est censé être dans eux; juste le certificat client, ou une clé? Si oui, quelle touche? Il y a un peu de confusion sur tous les différents types de fichiers, types de certificats et autres.

Comme je l'ai déjà dit, je suis nouveau sur HTTPS/SSL/TLS, donc j'apprécierais également quelques informations générales (ne doit pas être un essai; je me contenterai de liens vers de bons articles).

Author: Vertexwahn, 2009-11-03

6 answers

A finalement réussi à résoudre tous les problèmes, donc je vais répondre à ma propre question. Ce sont les paramètres / fichiers que j'ai utilisés pour résoudre mon (mes) problème (s) particulier (s);

Le client du magasin de clés est un format PKCS#12 fichier 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, pour exemple;

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

Astuce: assurez-vous d'avoir la dernière version OpenSSL, pas version 0.9.8 h car cela semble souffrir d'un bug qui ne vous 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 client au serveur lorsque le serveur a explicitement demandé au client de s'authentifier. Voir l'article Wikipedia sur TLS pour un aperçu de la façon dont le protocole pour le certificat client l'authentification fonctionne réellement (explique également pourquoi nous avons besoin de la clé privée du client ici).

Le client truststore est un simple JKS format fichier contenant les racine ou intermédiaire de certificats d'autorité de certification. Ces certificats d'autorité de certification détermineront les points de terminaison avec lesquels vous serez autorisé à communiquer, dans ce cas, ils permettront à votre client de se connecter au serveur qui présente un certificat signé par l'un des truststore CA's.

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 truststore, votre client essaiera de faire une prise de contact SSL complète avec tous les serveurs qui présentent 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 key et truststore. Une excellente présentation pour configurer un exemple entièrement fonctionnel pour un client Java et serveur (utilisant Tomcat) peut être trouvé sur ce site Web.

Questions/Remarques/Conseils

  1. L'authentification du certificat client ne peut être appliquée que par le serveur.
  2. (Important!) Lorsque le serveur demande un certificat client (dans le cadre de la prise de contact TLS), il fournit également une liste d'autorité de certification de confiance dans le cadre de la demande de certificat. Lorsque le certificat du client que vous souhaitez présenter pour l'authentification est pas 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é correctement son serveur pour accepter mon certificat client auto-signé et nous avons supposé que le problème était à ma fin pour ne pas fournir correctement le certificat client dans la demande.
  3. Obtenez Wireshark. Il a une grande analyse de paquets SSL/HTTPS et sera une aide précieuse pour le débogage et de trouver le problème. Il est similaire à -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 l'équivalent HTTPS et ajoutez 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 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
 200
Author: tmbrggmn, 2009-11-10 20:28:20

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 chaque application s'exécutant 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"));
 41
Author: Magnus, 2015-09-10 23:54:04

Ils fichier JKS est juste 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 clientcontiendra la paire de clés privée et publique du client. Il est appelé un keystore .
  • Le magasin servercontiendra la clé public du client. C'est ce qu'on appelle un truststore.

La séparation de truststore et keystore n'est pas obligatoire mais recommandé. 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 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'affiche l'a mentionné, cela a déjà été fait par les administrateurs du serveur)

keytool -import -file publicclientkey.cer -store serversidestore.jks
 27
Author: mhaller, 2009-11-04 08:24:04

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
Author: wildloop, 2015-03-10 20:39:10

Pour ceux d'entre vous qui veulent tout simplement de configurer une authentification bidirectionnelle (certificats serveur et client), une combinaison de ces deux liens vous y :

Configuration d'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; utilisez simplement

  • $ openssl genrsa -des3 -out ca.clé 4096

  • $ openssl req-nouveau-x509-jours 365 -la clé de l'autorité de certification.-clé d'ac.crt

Pour générer votre propre certificat CA, puis générer et signer les clés serveur et client via:

  • $ le serveur openssl genrsa-des3-out.clé 4096

  • $ openssl req-nouveau-client clé.key-out server.la rse

  • $ vous avez besoin d'un serveur openssl x509-req-jours 365-dans le serveur.rse -CA.crt-CAkey ca.clé-set_serial 100-out serveur.crt

Et

  • $ vous avez besoin d'un logiciel de gestion de compte.clé 4096

  • $ openssl req-nouveau-client clé.clé du client.la rse

  • $ openssl x509-req-jours 365-dans le client.rse -CA.crt-CAkey ca.clé -set_serial 101 -client.crt

Pour le reste, suivez les étapes du lien. La gestion des certificats pour Chrome fonctionne de la même manière que dans l'exemple pour 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 .clé pour que vous n'ayez plus à faire cette étape.

 6
Author: hans, 2017-05-02 12:52:05

Je pense que le correctif ici était le type keystore, 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 extrémité.

 0
Author: ObiWanKenobi, 2015-09-23 17:48:48