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/

6voto

hans Points 69

Pour ceux d'entre vous qui souhaitent simplement mettre en place une authentification bidirectionnelle (certificats du serveur et du client), une combinaison de ces deux liens vous permettra d'y parvenir :

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 ; utilisez simplement

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

  • $ openssl req -new -x509 -days 365 -key ca.key -out ca.crt

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

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

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

  • $ openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 100 -out server.crt

y

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

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

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

Pour le reste, suivez les étapes indiquées dans le lien. La gestion des certificats pour Chrome fonctionne de la même manière que dans l'exemple pour Firefox qui est 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éé les .crt et .key du serveur, vous n'avez donc plus besoin de faire cette étape.

1 votes

Je pense qu'il y a une faute de frappe dans l'étape de génération du CSR du serveur : il faut utiliser server.key pas client.key

4voto

GKislin Points 254

Je me suis connecté à la banque avec SSL bidirectionnel (certificat client et serveur) avec Spring Boot. Je décris ici toutes mes étapes, en espérant que cela aidera quelqu'un (la solution la plus simple que j'ai trouvée) :

  1. Générer une demande de certificat :

    • Générer une clé privée :

      openssl genrsa -des3 -passout pass:MY_PASSWORD -out user.key 2048
    • Générer une demande de certificat :

      openssl req -new -key user.key -out user.csr -passin pass:MY_PASSWORD

    Gardez user.key (et mot de passe) et envoyer la demande de certificat user.csr à la banque pour mon certificat

  2. Recevoir 2 certificats : mon client Certificat racine clientId.crt et le certificat racine de la banque : bank.crt

  3. Créez le keystore Java (entrez le mot de passe de la clé et définissez le mot de passe du keystore) :

    openssl pkcs12 -export -in clientId.crt -inkey user.key -out keystore.p12 -name clientId -CAfile ca.crt -caname root

    Ne faites pas attention à la sortie : unable to write 'random state' . Java PKCS12 keystore.p12 créé.

  4. Ajouter dans le keystore bank.crt (pour simplifier, j'ai utilisé un seul keystore) :

    keytool -import -alias banktestca -file banktestca.crt -keystore keystore.p12 -storepass javaops

    Vérifiez les certificats du keystore en :

    keytool -list -keystore keystore.p12
  5. Prêt pour le code Java :) J'ai utilisé Spring Boot RestTemplate avec ajout org.apache.httpcomponents.httpcore dépendance :

    @Bean("sslRestTemplate")
    public RestTemplate sslRestTemplate() throws Exception {
      char[] storePassword = appProperties.getSslStorePassword().toCharArray();
      URL keyStore = new URL(appProperties.getSslStore());
    
      SSLContext sslContext = new SSLContextBuilder()
            .loadTrustMaterial(keyStore, storePassword)
      // use storePassword twice (with key password do not work)!!
            .loadKeyMaterial(keyStore, storePassword, storePassword) 
            .build();
    
      // Solve "Certificate doesn't match any of the subject alternative names"
      SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
    
      CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
      HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(client);
      RestTemplate restTemplate = new RestTemplate(factory);
      // restTemplate.setMessageConverters(List.of(new Jaxb2RootElementHttpMessageConverter()));
      return restTemplate;
    }

0 votes

Vous pouvez tout faire avec le keytool. Il n'y a aucun besoin d'OpenSSL dans ce cas.

3voto

iloveretards Points 1130

Il existe un meilleur moyen que de devoir naviguer manuellement vers https://url , de savoir quel bouton cliquer dans quel navigateur, de savoir où et comment enregistrer le fichier "certificat" et enfin de connaître l'incantation magique du keytool pour l'installer localement.

Fais juste ça :

  1. Enregistrez le code ci-dessous dans InstallCert.java
  2. Ouvrez la ligne de commande et exécutez : javac InstallCert.java
  3. Courir comme ça : java InstallCert <host>[:port] [passphrase] (le port et la phrase de passe sont facultatifs)

Voici le code pour InstallCert, notez l'année dans l'en-tête, il faudra modifier certaines parties pour les versions "ultérieures" de java :

/*
 * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.*;
import java.net.URL;

import java.security.*;
import java.security.cert.*;

import javax.net.ssl.*;

public class InstallCert {

    public static void main(String[] args) throws Exception {
  String host;
  int port;
  char[] passphrase;
  if ((args.length == 1) || (args.length == 2)) {
      String[] c = args[0].split(":");
      host = c[0];
      port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
      String p = (args.length == 1) ? "changeit" : args[1];
      passphrase = p.toCharArray();
  } else {
      System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
      return;
  }

  File file = new File("jssecacerts");
  if (file.isFile() == false) {
      char SEP = File.separatorChar;
      File dir = new File(System.getProperty("java.home") + SEP
        + "lib" + SEP + "security");
      file = new File(dir, "jssecacerts");
      if (file.isFile() == false) {
    file = new File(dir, "cacerts");
      }
  }
  System.out.println("Loading KeyStore " + file + "...");
  InputStream in = new FileInputStream(file);
  KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
  ks.load(in, passphrase);
  in.close();

  SSLContext context = SSLContext.getInstance("TLS");
  TrustManagerFactory tmf =
      TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  tmf.init(ks);
  X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
  SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
  context.init(null, new TrustManager[] {tm}, null);
  SSLSocketFactory factory = context.getSocketFactory();

  System.out.println("Opening connection to " + host + ":" + port + "...");
  SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
  socket.setSoTimeout(10000);
  try {
      System.out.println("Starting SSL handshake...");
      socket.startHandshake();
      socket.close();
      System.out.println();
      System.out.println("No errors, certificate is already trusted");
  } catch (SSLException e) {
      System.out.println();
      e.printStackTrace(System.out);
  }

  X509Certificate[] chain = tm.chain;
  if (chain == null) {
      System.out.println("Could not obtain server certificate chain");
      return;
  }

  BufferedReader reader =
    new BufferedReader(new InputStreamReader(System.in));

  System.out.println();
  System.out.println("Server sent " + chain.length + " certificate(s):");
  System.out.println();
  MessageDigest sha1 = MessageDigest.getInstance("SHA1");
  MessageDigest md5 = MessageDigest.getInstance("MD5");
  for (int i = 0; i < chain.length; i++) {
      X509Certificate cert = chain[i];
      System.out.println
        (" " + (i + 1) + " Subject " + cert.getSubjectDN());
      System.out.println("   Issuer  " + cert.getIssuerDN());
      sha1.update(cert.getEncoded());
      System.out.println("   sha1    " + toHexString(sha1.digest()));
      md5.update(cert.getEncoded());
      System.out.println("   md5     " + toHexString(md5.digest()));
      System.out.println();
  }

  System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
  String line = reader.readLine().trim();
  int k;
  try {
      k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
  } catch (NumberFormatException e) {
      System.out.println("KeyStore not changed");
      return;
  }

  X509Certificate cert = chain[k];
  String alias = host + "-" + (k + 1);
  ks.setCertificateEntry(alias, cert);

  OutputStream out = new FileOutputStream("jssecacerts");
  ks.store(out, passphrase);
  out.close();

  System.out.println();
  System.out.println(cert);
  System.out.println();
  System.out.println
    ("Added certificate to keystore 'jssecacerts' using alias '"
    + alias + "'");
    }

    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(byte[] bytes) {
  StringBuilder sb = new StringBuilder(bytes.length * 3);
  for (int b : bytes) {
      b &= 0xff;
      sb.append(HEXDIGITS[b >> 4]);
      sb.append(HEXDIGITS[b & 15]);
      sb.append(' ');
  }
  return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

  private final X509TrustManager tm;
  private X509Certificate[] chain;

  SavingTrustManager(X509TrustManager tm) {
      this.tm = tm;
  }

  public X509Certificate[] getAcceptedIssuers() {
      throw new UnsupportedOperationException();
  }

  public void checkClientTrusted(X509Certificate[] chain, String authType)
    throws CertificateException {
      throw new UnsupportedOperationException();
  }

  public void checkServerTrusted(X509Certificate[] chain, String authType)
    throws CertificateException {
      this.chain = chain;
      tm.checkServerTrusted(chain, authType);
  }
    }

}

0 votes

Puisque je suis un fan de la lecture et de la modification du code par des personnes paresseuses, si vous voulez télécharger l'ensemble de la version chaîne (ce que vous voulez absolument faire pour que l'authentification ssl fonctionne derrière des proxies d'entreprise) tout ce que vous devez faire est d'ajouter une boucle dans le code ci-dessus, pour boucler sur la chaîne et l'écrire dans cacerts ou jssecacerts ou autre (à vous de trouver).

-1voto

ObiWanKenobi Points 72

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

1 votes

Le format PKCS12 était traditionnellement utilisé pour les privatekey-AND-cert, mais Java depuis 8 en 2014 (plus d'un an avant cette réponse) supporte les PKCS12 contenant des cert(s) sans privatekey(s). Quel que soit le format du keystore, l'authentification du client nécessite effectivement privatekey-AND-cert. Je ne comprends pas votre deuxième phrase, mais le client Java peut sélectionner automatiquement un certificat et une clé de client si au moins une entrée appropriée est disponible ou un gestionnaire de clés peut être configuré pour en utiliser un spécifié.

1 votes

Votre deuxième phrase est totalement incorrecte. Le serveur fournit ses signataires de confiance, et le client soit meurt, soit ne fournit pas de certificat satisfaisant à cette contrainte. Automatiquement, pas via "dans votre code". C'est est le moyen pour le serveur de savoir qu'il représente un client.

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