web-dev-qa-db-fra.com

Obtenir de manière programmée sur KeyStore de PEM

Comment peut-on obtenir de manière programmée d'un keyStore à partir d'un fichier PEM contenant à la fois un certificat et une clé privée? Je tente de fournir un certificat client à un serveur dans une connexion HTTPS. J'ai confirmé que le certificat client fonctionne si j'utilise OpenSSL et KeyTool pour obtenir un fichier JKS, que je charge de manière dynamique. Je peux même le faire fonctionner en lisant de manière dynamique dans un fichier P12 (PKCS12).

Je cherche à utiliser la classe Pemreader de Bouncycastle, mais je ne peux pas dépasser des erreurs. Je cours le Java avec The -DJAVAX.NET.DEBUG = TOUTES OPTION ET APACHE Web Server avec le débogage LOGLEVEL. Je ne sais pas quoi chercher cependant. L'erreur Apache Le journal indique:

...
OpenSSL: Write: SSLv3 read client certificate B
OpenSSL: Exit: error in SSLv3 read client certificate B
Re-negotiation handshake failed: Not accepted by client!?

Le fichier Java programme client indique:

...
main, WRITE: TLSv1 Handshake, length = 48
main, waiting for close_notify or alert: state 3
main, Exception while waiting for close Java.net.SocketException: Software caused connection abort: recv failed
main, handling exception: Java.net.SocketException: Software caused connection abort: recv failed
%% Invalidated:  [Session-3, TLS_RSA_WITH_AES_128_CBC_SHA]
main, SEND TLSv1 ALERT:  fatal, description = unexpected_message
...

Le code client:

public void testClientCertPEM() throws Exception {
    String requestURL = "https://mydomain/authtest";
    String pemPath = "C:/Users/myusername/Desktop/client.pem";

    HttpsURLConnection con;

    URL url = new URL(requestURL);
    con = (HttpsURLConnection) url.openConnection();
    con.setSSLSocketFactory(getSocketFactoryFromPEM(pemPath));
    con.setRequestMethod("GET");
    con.setDoInput(true);
    con.setDoOutput(false);  
    con.connect();

    String line;

    BufferedReader reader = new BufferedReader(new InputStreamReader(con.getInputStream()));

    while((line = reader.readLine()) != null) {
        System.out.println(line);
    }       

    reader.close();
    con.disconnect();
}

public SSLSocketFactory getSocketFactoryFromPEM(String pemPath) throws Exception {
    Security.addProvider(new BouncyCastleProvider());        
    SSLContext context = SSLContext.getInstance("TLS");

    PEMReader reader = new PEMReader(new FileReader(pemPath));
    X509Certificate cert = (X509Certificate) reader.readObject();        

    KeyStore keystore = KeyStore.getInstance("JKS");
    keystore.load(null);
    keystore.setCertificateEntry("alias", cert);

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(keystore, null);

    KeyManager[] km = kmf.getKeyManagers(); 

    context.init(km, null, null);

    return context.getSocketFactory();
} 

J'ai remarqué que le serveur émet SSLV3 dans le journal tandis que le client est TLSV1. Si j'ajoute la propriété système -dhttps.protocols = sslv3, le client utilisera également SSLV3, mais je reçois le même message d'erreur. J'ai aussi essayé d'ajouter -dsun.security.ssl.allowunsaferenegotiation = vrai sans changement de résultat.

J'ai googlé et la réponse habituelle pour cette question est de simplement utiliser OpenSSL et KeyTool d'abord. Dans mon cas, j'ai besoin de lire le PEM directement à la volée. Je portais réellement un programme C++ qui le fait déjà, et franchement, je suis très surpris de voir à quel point il est difficile de le faire en Java. Le code C++:

  curlpp::Easy request;
  ...
  request.setOpt(new Options::Url(myurl));
  request.setOpt(new Options::SslVerifyPeer(false));
  request.setOpt(new Options::SslCertType("PEM"));
  request.setOpt(new Options::SslCert(cert));
  request.perform();
26
Ryan

Je l'ai compris. Le problème est que le X509Certificate n'est pas suffisant. J'avais besoin de mettre la clé privée dans le magasin de clé généré de manière dynamique. Il ne semble pas que Bouncycastle Pemreader puisse gérer un fichier PEM avec cert et clé privée, tout en une fois, mais il peut gérer chaque pièce séparément. Je peux lire le PEM dans la mémoire moi-même et le casser en deux ruisseaux distincts, puis nourrir chacun un à un pemreader séparé. Puisque je sais que les fichiers PEM que je traite avec le certificat auront le certificat d'abord et la clé privée, je peux simplifier le code au coût de la robustesse. Je sais aussi que le délimiteur de certificat final sera toujours entouré de cinq traits d'union. La mise en œuvre qui fonctionne pour moi est la suivante:

protected static SSLSocketFactory getSocketFactoryPEM(String pemPath) throws Exception {        
    Security.addProvider(new BouncyCastleProvider());

    SSLContext context = SSLContext.getInstance("TLS");

    byte[] certAndKey = fileToBytes(new File(pemPath));

    String delimiter = "-----END CERTIFICATE-----";
    String[] tokens = new String(certAndKey).split(delimiter);

    byte[] certBytes = tokens[0].concat(delimiter).getBytes();
    byte[] keyBytes = tokens[1].getBytes();

    PEMReader reader;

    reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(certBytes)));
    X509Certificate cert = (X509Certificate)reader.readObject();        

    reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(keyBytes)));
    PrivateKey key = (PrivateKey)reader.readObject();        

    KeyStore keystore = KeyStore.getInstance("JKS");
    keystore.load(null);
    keystore.setCertificateEntry("cert-alias", cert);
    keystore.setKeyEntry("key-alias", key, "changeit".toCharArray(), new Certificate[] {cert});

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(keystore, "changeit".toCharArray());

    KeyManager[] km = kmf.getKeyManagers(); 

    context.init(km, null, null);

    return context.getSocketFactory();
}

Mise à jour: Il semble que cela puisse être fait sans bouncycastle:

    byte[] certAndKey = fileToBytes(new File(pemPath));
    byte[] certBytes = parseDERFromPEM(certAndKey, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
    byte[] keyBytes = parseDERFromPEM(certAndKey, "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");

    X509Certificate cert = generateCertificateFromDER(certBytes);              
    RSAPrivateKey key  = generatePrivateKeyFromDER(keyBytes);

...

protected static byte[] parseDERFromPEM(byte[] pem, String beginDelimiter, String endDelimiter) {
    String data = new String(pem);
    String[] tokens = data.split(beginDelimiter);
    tokens = tokens[1].split(endDelimiter);
    return DatatypeConverter.parseBase64Binary(tokens[0]);        
}

protected static RSAPrivateKey generatePrivateKeyFromDER(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException {
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);

    KeyFactory factory = KeyFactory.getInstance("RSA");

    return (RSAPrivateKey)factory.generatePrivate(spec);        
}

protected static X509Certificate generateCertificateFromDER(byte[] certBytes) throws CertificateException {
    CertificateFactory factory = CertificateFactory.getInstance("X.509");

    return (X509Certificate)factory.generateCertificate(new ByteArrayInputStream(certBytes));      
}
34
Ryan