web-dev-qa-db-fra.com

Enregistrement de plusieurs fichiers de clés dans JVM

J'ai deux applications fonctionnant dans la même machine virtuelle Java, et les deux utilisent des magasins de clés et des magasins de clés de confiance différents.

Une option viable serait d'utiliser un seul fichier de clés et d'importer tous les autres dans le fichier de clés partagé (par exemple, keytool -import), mais cela aiderait vraiment mes besoins si je pouvais utiliser des fichiers de clés distincts pour des applications distinctes fonctionnant dans le même jvm.

Je pourrais définir le keystore et les truststores à utiliser comme paramètres jvm ou propriétés système comme suit:

Java -Djavax.net.ssl.keyStore=serverKeys 
-Djavax.net.ssl.keyStorePassword=password 
-Djavax.net.ssl.trustStore=serverTrust 
-Djavax.net.ssl.trustStorePassword=password SSLApplication

ou

System.setProperty("javax.net.ssl.keyStore","serverKeys")

Mais le problème avec cette approche est qu'elle spécifie le keystore/truststore à utiliser au niveau de la JVM, ainsi toutes les applications s'exécutant dans la même JVM obtiennent le même keystore/truststore.

J'ai également essayé de créer un SSLContext personnalisé et de le définir comme valeur par défaut, mais il définit également le contexte pour toutes les applications exécutées dans la même JVM.

SSLContext context = SSLContext.getInstance("SSL");
context.init(kms, tms, null);
SSLContext.setDefault(context);

Je veux pouvoir utiliser différents keystores/truststores sans modifier les codes d'application individuels.

ne solution qui peut enregistrer dynamiquement plusieurs magasins de clés en plus du magasin de clés/certificats par défaut en jre dans jvm serait formidable.

La solution fonctionnera de cette façon:

  • Lorsque la machine virtuelle Java démarre, elle charge tous les certificats/magasins de clés par défaut à partir du dossier jre/certs (par défaut Java lorsqu'aucun magasin de clés n'est spécifié).
  • Lorsque l'application 1 se charge, elle enregistre ses fichiers de clés,
  • puis lorsque l'application 2 se charge, elle enregistre ses magasins de clés ...

Veuillez me faire part de vos idées ou solutions. Merci d'avance!

49
Raz

Après avoir joué avec le code que j'ai reçu de ZZ Coder, sylvarking et Software Monkey, j'ai trouvé une solution qui fonctionne:

Tout d'abord, j'ai écrit un X509KeyManager qui fonctionne combine un keystore personnalisé et un keystore par défaut.

class MultiKeyStoreManager implements X509KeyManager {
 private static final Logger logger = Logger.getLogger(MultiKeyStoreManager.class); 
 private final X509KeyManager jvmKeyManager;
 private final X509KeyManager customKeyManager;

 public MultiKeyStoreManager(X509KeyManager jvmKeyManager, X509KeyManager customKeyManager ) {
  this.jvmKeyManager = jvmKeyManager;
  this.customKeyManager = customKeyManager;  
 }

 @Override
 public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
  // try the first key manager
  String alias = customKeyManager.chooseClientAlias(keyType, issuers, socket);
  if( alias == null ) {
   alias = jvmKeyManager.chooseClientAlias(keyType, issuers, socket);
   logger.warn("Reverting to JVM CLIENT alias : " + alias);
  }

  return alias;

 }

 @Override
 public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
  // try the first key manager
  String alias = customKeyManager.chooseServerAlias(keyType, issuers, socket);
  if( alias == null ) {
   alias =  jvmKeyManager.chooseServerAlias(keyType, issuers, socket);
   logger.warn("Reverting to JVM Server alias : " + alias);
  } 
  return alias;
 }

 @Override
 public X509Certificate[] getCertificateChain(String alias) {
  X509Certificate[] chain = customKeyManager.getCertificateChain(alias);
  if( chain == null || chain.length == 0) {
   logger.warn("Reverting to JVM Chain : " + alias);
   return jvmKeyManager.getCertificateChain(alias);
  } else {
   return chain;
  }  
 }

 @Override
 public String[] getClientAliases(String keyType, Principal[] issuers) {
  String[] cAliases = customKeyManager.getClientAliases(keyType, issuers);
  String[] jAliases = jvmKeyManager.getClientAliases(keyType, issuers);
  logger.warn("Supported Client Aliases Custom: " + cAliases.length + " JVM : " + jAliases.length);
  return ArrayUtils.join(cAliases,jAliases);
 }

 @Override
 public PrivateKey getPrivateKey(String alias) {
  PrivateKey key = customKeyManager.getPrivateKey(alias);
  if( key == null ) {
   logger.warn("Reverting to JVM Key : " + alias);
   return jvmKeyManager.getPrivateKey(alias);
  } else {
   return key;
  }
 }

 @Override
 public String[] getServerAliases(String keyType, Principal[] issuers) {
  String[] cAliases = customKeyManager.getServerAliases(keyType, issuers);
  String[] jAliases = jvmKeyManager.getServerAliases(keyType, issuers);
  logger.warn("Supported Server Aliases Custom: " + cAliases.length + " JVM : " + jAliases.length);
  return ArrayUtils.join(cAliases,jAliases);
 }

}

Ensuite, vous pouvez utiliser ce gestionnaire de magasin de clés lors de la création d'un contexte SSL ou SocketFactory. Le code a besoin d'être refactorisé et rangé, mais il fonctionne parfaitement.

 /**
  * Returns an array of KeyManagers, set up to use the required keyStore.
  * This method does the bulk of the work of setting up the custom trust managers.
  * 
  * @param props 
  * 
  * @return an array of KeyManagers set up accordingly.
  */
 private static KeyManager[] getKeyManagers(Properties props) throws IOException, GeneralSecurityException {
  // First, get the default KeyManagerFactory.
  String alg = KeyManagerFactory.getDefaultAlgorithm();
  KeyManagerFactory kmFact = KeyManagerFactory.getInstance(alg);   
  // Next, set up the KeyStore to use. We need to load the file into
  // a KeyStore instance.
  FileInputStream fis = new FileInputStream(props.getProperty(SSL_KEYSTORE));
  logger.info("Loaded keystore");
  KeyStore ks = KeyStore.getInstance("jks");
  String keyStorePassword = props.getProperty(SSL_KEYSTORE_PASSWORD);
  ks.load(fis, keyStorePassword.toCharArray());
  fis.close();
  // Now we initialise the KeyManagerFactory with this KeyStore
  kmFact.init(ks, keyStorePassword.toCharArray());

  // default
  KeyManagerFactory dkmFact = KeyManagerFactory.getInstance(alg); 
  dkmFact.init(null,null);  

  // Get the first X509KeyManager in the list
  X509KeyManager customX509KeyManager = getX509KeyManager(alg, kmFact);
  X509KeyManager jvmX509KeyManager = getX509KeyManager(alg, dkmFact);

  KeyManager[] km = { new MultiKeyStoreManager(jvmX509KeyManager, customX509KeyManager) };   
  logger.debug("Number of key managers registered:" + km.length);  
  return km;
 }


 /**
  * Find a X509 Key Manager compatible with a particular algorithm
  * @param algorithm
  * @param kmFact
  * @return
  * @throws NoSuchAlgorithmException
  */
 private static X509KeyManager getX509KeyManager(String algorithm, KeyManagerFactory kmFact)
   throws NoSuchAlgorithmException {
  KeyManager[] keyManagers = kmFact.getKeyManagers();

  if (keyManagers == null || keyManagers.length == 0) {
   throw new NoSuchAlgorithmException("The default algorithm :" + algorithm + " produced no key managers");
  }

  X509KeyManager x509KeyManager = null;

  for (int i = 0; i < keyManagers.length; i++) {
   if (keyManagers[i] instanceof X509KeyManager) {
    x509KeyManager = (X509KeyManager) keyManagers[i];
    break;
   }
  }

  if (x509KeyManager == null) {
   throw new NoSuchAlgorithmException("The default algorithm :"+ algorithm + " did not produce a X509 Key manager");
  }
  return x509KeyManager;
 }




 private static void initialiseManager(Properties props) throws IOException, GeneralSecurityException { 
  // Next construct and initialise a SSLContext with the KeyStore and
  // the TrustStore. We use the default SecureRandom.
  SSLContext context = SSLContext.getInstance("SSL");
  context.init(getKeyManagers(props), getTrustManagers(props), null);
  SSLContext.setDefault(context);

 }

Faites-moi savoir si quelqu'un a des questions ou a besoin de codes de démonstration.

17
Raz

La réponse de Raz a été un excellent début, mais n'était pas assez flexible pour répondre à mes besoins. Le MultiStoreKeyManager vérifie explicitement le KeyManager personnalisé, puis revient au jvm KeyManager si une opération échoue. En fait, je veux d'abord vérifier les certificats jvm; la meilleure solution devrait être en mesure de gérer les deux cas. En outre, la réponse ne parvient pas à fournir un TrustManager fonctionnel.

J'ai écrit quelques classes plus flexibles, CompositeX509KeyManager et CompositeX509TrustManager, qui ajoutent la prise en charge d'un certain nombre de magasins de clés dans un ordre arbitraire.

CompositeX509KeyManager

package com.mycompany.ssl;

import Java.net.Socket;
import Java.security.Principal;
import Java.security.PrivateKey;
import Java.security.cert.X509Certificate;
import Java.util.List;

import javax.annotation.Nullable;
import javax.net.ssl.X509KeyManager;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;

/**
 * Represents an ordered list of {@link X509KeyManager}s with most-preferred managers first.
 *
 * This is necessary because of the fine-print on {@link SSLContext#init}:
 *     Only the first instance of a particular key and/or trust manager implementation type in the
 *     array is used. (For example, only the first javax.net.ssl.X509KeyManager in the array will be used.)
 *
 * @author codyaray
 * @since 4/22/2013
 * @see http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm
 */
public class CompositeX509KeyManager implements X509KeyManager {

  private final List keyManagers;

  /**
   * Creates a new {@link CompositeX509KeyManager}.
   *
   * @param keyManagers the X509 key managers, ordered with the most-preferred managers first.
   */
  public CompositeX509KeyManager(List keyManagers) {
    this.keyManagers = ImmutableList.copyOf(keyManagers);
  }

  /**
   * Chooses the first non-null client alias returned from the delegate
   * {@link X509TrustManagers}, or {@code null} if there are no matches.
   */
  @Override
  public @Nullable String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
    for (X509KeyManager keyManager : keyManagers) {
      String alias = keyManager.chooseClientAlias(keyType, issuers, socket);
      if (alias != null) {
        return alias;
      }
    }
    return null;
  }

  /**
   * Chooses the first non-null server alias returned from the delegate
   * {@link X509TrustManagers}, or {@code null} if there are no matches.
   */
  @Override
  public @Nullable String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
    for (X509KeyManager keyManager : keyManagers) {
      String alias = keyManager.chooseServerAlias(keyType, issuers, socket);
      if (alias != null) {
        return alias;
      }
    }
    return null;
  }

  /**
   * Returns the first non-null private key associated with the
   * given alias, or {@code null} if the alias can't be found.
   */
  @Override
  public @Nullable PrivateKey getPrivateKey(String alias) {
    for (X509KeyManager keyManager : keyManagers) {
      PrivateKey privateKey = keyManager.getPrivateKey(alias);
      if (privateKey != null) {
        return privateKey;
      }
    }
    return null;
  }

  /**
   * Returns the first non-null certificate chain associated with the
   * given alias, or {@code null} if the alias can't be found.
   */
  @Override
  public @Nullable X509Certificate[] getCertificateChain(String alias) {
    for (X509KeyManager keyManager : keyManagers) {
      X509Certificate[] chain = keyManager.getCertificateChain(alias);
      if (chain != null && chain.length > 0) {
        return chain;
      }
    }
    return null;
  }

  /**
   * Get all matching aliases for authenticating the client side of a
   * secure socket, or {@code null} if there are no matches.
   */
  @Override
  public @Nullable String[] getClientAliases(String keyType, Principal[] issuers) {
    ImmutableList.Builder aliases = ImmutableList.builder();
    for (X509KeyManager keyManager : keyManagers) {
      aliases.add(keyManager.getClientAliases(keyType, issuers));
    }
    return emptyToNull(Iterables.toArray(aliases.build(), String.class));
  }

  /**
   * Get all matching aliases for authenticating the server side of a
   * secure socket, or {@code null} if there are no matches.
   */
  @Override
  public @Nullable String[] getServerAliases(String keyType, Principal[] issuers) {
    ImmutableList.Builder aliases = ImmutableList.builder();
    for (X509KeyManager keyManager : keyManagers) {
      aliases.add(keyManager.getServerAliases(keyType, issuers));
    }
    return emptyToNull(Iterables.toArray(aliases.build(), String.class));
  }

  @Nullable
  private static <T> T[] emptyToNull(T[] arr) {
    return (arr.length == 0) ? null : arr;
  }

}

CompositeX509TrustManager

package com.mycompany.ssl;

import Java.security.cert.CertificateException;
import Java.security.cert.X509Certificate;
import Java.util.List;

import javax.net.ssl.X509TrustManager;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;

/**
 * Represents an ordered list of {@link X509TrustManager}s with additive trust. If any one of the
 * composed managers trusts a certificate chain, then it is trusted by the composite manager.
 *
 * This is necessary because of the fine-print on {@link SSLContext#init}:
 *     Only the first instance of a particular key and/or trust manager implementation type in the
 *     array is used. (For example, only the first javax.net.ssl.X509KeyManager in the array will be used.)
 *
 * @author codyaray
 * @since 4/22/2013
 * @see http://stackoverflow.com/questions/1793979/registering-multiple-keystores-in-jvm
 */
public class CompositeX509TrustManager implements X509TrustManager {

  private final List trustManagers;

  public CompositeX509TrustManager(List trustManagers) {
    this.trustManagers = ImmutableList.copyOf(trustManagers);
  }

  @Override
  public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    for (X509TrustManager trustManager : trustManagers) {
      try {
        trustManager.checkClientTrusted(chain, authType);
        return; // someone trusts them. success!
      } catch (CertificateException e) {
        // maybe someone else will trust them
      }
    }
    throw new CertificateException("None of the TrustManagers trust this certificate chain");
  }

  @Override
  public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    for (X509TrustManager trustManager : trustManagers) {
      try {
        trustManager.checkServerTrusted(chain, authType);
        return; // someone trusts them. success!
      } catch (CertificateException e) {
        // maybe someone else will trust them
      }
    }
    throw new CertificateException("None of the TrustManagers trust this certificate chain");
  }

  @Override
  public X509Certificate[] getAcceptedIssuers() {
    ImmutableList.Builder certificates = ImmutableList.builder();
    for (X509TrustManager trustManager : trustManagers) {
      certificates.add(trustManager.getAcceptedIssuers());
    }
    return Iterables.toArray(certificates.build(), X509Certificate.class);
  }

}

Usage

Pour le cas standard d'un keystore + jvm keystore, vous pouvez le câbler comme ceci. J'utilise à nouveau Guava, mais cette fois dans un emballage Guicey:

@Provides @Singleton
SSLContext provideSSLContext(KeyStore keystore, char[] password) {
  String defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
  X509KeyManager customKeyManager = getKeyManager("SunX509", keystore, password);
  X509KeyManager jvmKeyManager = getKeyManager(defaultAlgorithm, null, null);
  X509TrustManager customTrustManager = getTrustManager("SunX509", keystore);
  X509TrustManager jvmTrustManager = getTrustManager(defaultAlgorithm, null);

  KeyManager[] keyManagers = { new CompositeX509KeyManager(ImmutableList.of(jvmKeyManager, customKeyManager)) };
  TrustManager[] trustManagers = { new CompositeX509TrustManager(ImmutableList.of(jvmTrustManager, customTrustManager)) };

  SSLContext context = SSLContext.getInstance("SSL");
  context.init(keyManagers, trustManagers, null);
  return context;
}

private X509KeyManager getKeyManager(String algorithm, KeyStore keystore, char[] password) {
  KeyManagerFactory factory = KeyManagerFactory.getInstance(algorithm);
  factory.init(keystore, password);
  return Iterables.getFirst(Iterables.filter(
      Arrays.asList(factory.getKeyManagers()), X509KeyManager.class), null);
}

private X509TrustManager getTrustManager(String algorithm, KeyStore keystore) {
  TrustManagerFactory factory = TrustManagerFactory.getInstance(algorithm);
  factory.init(keystore);
  return Iterables.getFirst(Iterables.filter(
      Arrays.asList(factory.getTrustManagers()), X509TrustManager.class), null); 
}

J'ai extrait cela de mon article de blog à propos de ce problème qui a un peu plus de détails, de motivation, etc. Tout le code est là, donc c'est autonome. :)

30
Cody A. Ray

Découvrez ma réponse à cette question,

Comment puis-je avoir plusieurs certificats SSL pour un Java

Si vous utilisez MyKeyManager, vous pouvez avoir plusieurs magasins de clés ou vous pouvez utiliser un seul magasin de clés pour plusieurs contextes.

1
ZZ Coder