Mon client SSL (Java) n'envoie pas de certificat au serveur dans une prise de contact SSL bidirectionnelle


Dans une application Java 1.7 fonctionnant sous Windows 7, j'essaie de faire SSL 2 voies avec un serveur (un jeton de carte à puce fournit mes certificats client via openSC). Le certificat du serveur est très bien vérifié par le client, mais le client ne répond pas à la demande de certificat du serveur. Je crois que c'est parce que le client n'est pas en mesure de faire une chaîne de mon certificat à l'un de ceux demandés par le serveur (même si une telle chaîne existe).

Voici le débogage SSL de la demande de certificat du serveur et la réponse vide des clients:

*** CertificateRequest
Cert Types: RSA, DSS, ECDSA
Cert Authorities:
<CN=c4isuite-SDNI-DC02-CA, DC=c4isuite, DC=local>
<CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US>
    ...
*** ServerHelloDone
*** Certificate chain
***

Mon certificat client est le suivant:

found key for : Certificate for PIV Authentication
chain [0] = [
[
  Version: V3
  Subject: CN=<...>, OU=CONTRACTOR, OU=PKI, OU=DoD, O=U.S. Government, C=US
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 2048 bits

  Issuer: CN=DOD CA-30, OU=PKI, OU=DoD, O=U.S. Government, C=US
  SerialNumber: [    05bf13]

Via key-tool, j'ai également installé dans le truststore (fichier java cacerts), quel devrait être le lien entre l'émetteur de mon certificat, DOD CA-30, et ce que le serveur demande, DOD Root CA 2.

Du débogage SSL:

adding as trusted cert:
  Subject: CN=DOD CA-30, OU=PKI, OU=DoD, O=U.S. Government, C=US
  Issuer:  CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US
  Algorithm: RSA; Serial number: 0x1b5
  Valid from Thu Sep 08 10:59:24 CDT 2011 until Fri Sep 08 10:59:24 CDT 2017

adding as trusted cert:
  Subject: CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US
  Issuer:  CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US
  Algorithm: RSA; Serial number: 0x5
  Valid from Mon Dec 13 09:00:10 CST 2004 until Wed Dec 05 09:00:10 CST 2029

La question est donc, pourquoi le client ne peut-il pas créer la chaîne de certificats pour la réponse? Voici le code pertinent:

    // Create the keyStore from the SmartCard certs
    Provider provider = new sun.security.pkcs11.SunPKCS11(configName);

    Security.addProvider(provider);
    keyStore = KeyStore.getInstance("PKCS11", "SunPKCS11-SCR3310test");
    char[] pin = PIN.toCharArray();
    keyStore.load(null, pin);

        // Init the trustmanager
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);

        // Create the client key manager
        LOG.info("Installing keystore with pin");
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(clientKeyStore, clientKeyPassword.toCharArray());        
        
        sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), null);

        // Init SSL context
        SSLSocketFactory socketFactory = sslContext.getSocketFactory();
        

        URL url = new URL(urlString);
        URLConnection connection = url.openConnection();
        if (connection instanceof HttpsURLConnection) {
            LOG.info("Connection is HTTPS");
            ((HttpsURLConnection) connection).setSSLSocketFactory(socketFactory);
        }
        
        // Send the request.
        connection.connect();

        InputStreamReader in = new InputStreamReader((InputStream) connection.getContent());
        ...

Et l'erreur que j'obtiens retour est que le serveur renvoie un 403. Très probablement parce que le client ne lui a pas envoyé de certificat client.

Author: HoldOffHunger, 2012-07-31

2 answers

Même s'il semble que vous n'ayez copié qu'une partie de la liste CA envoyée par le serveur dans cette question, je suppose que CN=DOD CA-30, OU=PKI, OU=DoD, O=U.S. Government, C=US ne figure pas dans cette liste.

Ce qui semble manquer dans la chaîne est ce certificat (que vous mentionnez plus tard):

  Subject: CN=DOD CA-30, OU=PKI, OU=DoD, O=U.S. Government, C=US
  Issuer:  CN=DoD Root CA 2, OU=PKI, OU=DoD, O=U.S. Government, C=US
  Algorithm: RSA; Serial number: 0x1b5
  Valid from Thu Sep 08 10:59:24 CDT 2011 until Fri Sep 08 10:59:24 CDT 2017

L'importation de certificats dans le truststore de votre client n'a absolument aucun effet sur le certificat envoyé par le client. Le certificat client (et sa clé privée) doit être configuré dans le magasin de clés client . Outre, si vous souhaitez envoyer une chaîne de certificats client (ce qui sera requis ici, si le serveur n'offre pas ce certificat d'autorité de certification intermédiaire dans sa liste), vous devrez associer la chaîne complète à cette entrée de certificat. Il ne suffit pas de mettre les autres certificats dans le magasin de clés.

Pour résoudre ce problème, vous devez configurer votre entrée keystore avec la chaîne de certificats client. Cela peut être effectué comme décrit dans cette réponse. Cependant, il est possible que le fait que ce soit un le jeton matériel accessible via PKCS # 11 pourrait rendre cela un peu plus compliqué (il existe peut-être un autre outil de gestion des certificats fourni avec la carte, éventuellement indépendant de Java).

 3
Author: Bruno, 2017-05-23 12:25:13

Puisque je sais quel certificat je dois utiliser pour l'authentification au serveur, je peux forcer le client à envoyer ce certificat spécifique en étendant X509ExtendedKeyManager et en remplaçant la méthode chooseClientAlias() pour toujours renvoyer l'alias de ce certificat. Code:

public class MyX509KeyManager extends X509ExtendedKeyManager
  {
    X509KeyManager defaultKeyManager;

    public MyX509KeyManager(X509KeyManager inKeyManager) {
        defaultKeyManager = inKeyManager;
    }

    public String chooseEngineClientAlias(String[] keyType,
            Principal[] issuers, SSLEngine engine) {
        return "<Alias of my cert>";
    }

    @Override
    public String chooseClientAlias(String[] strings, Principal[] prncpls, Socket socket) {
        return "<Alias of my cert>";
    }

    @Override
    public String[] getClientAliases(String string, Principal[] prncpls) {
        return defaultKeyManager.getClientAliases(string, prncpls);
    }

    @Override
    public String[] getServerAliases(String string, Principal[] prncpls) {
        return defaultKeyManager.getServerAliases(string, prncpls);
    }

    ...

Comme vous pouvez le voir, je prends un defaultKeyManager auquel je m'en remets pour tout sauf ce que je veux remplacer. Ensuite, pour l'utiliser dans votre sslContext, procédez comme suit:

// clientKeyStore is initialized elsewhere from the SmartCard
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(clientKeyStore, clientKeyPassword.toCharArray());

MyX509KeyManager customKeyManager = new MyX509KeyManager((X509KeyManager) keyManagerFactory.getKeyManagers()[0]);
sslContext.init(new KeyManager[] {customKeyManager}, tmf.getTrustManagers(), null);
 3
Author: PaulP, 2012-08-01 16:18:03