0

Регистрация нескольких хранилищ ключей в JVM

14

Проблема с использованием различных хранилищ ключей в одной JVM

У меня есть два приложения, работающих в одной и той же виртуальной машине Java, и оба используют разные хранилища ключей и доверенные хранилища.

Один из возможных вариантов — использовать одно общее хранилище ключей и импортировать в него все остальные (например, с помощью keytool -import), но было бы очень удобно, если бы я смог использовать отдельные хранилища ключей для каждого приложения.

Я мог бы задать хранилище ключей и доверенное хранилище в качестве параметров JVM или системных свойств следующим образом:

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

либо

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

Но проблема с этим подходом заключается в том, что он задает хранилище ключей/доверенное хранилище на уровне JVM, что приводит к тому, что все приложения, работающие в одной и той же JVM, используют одно и то же хранилище ключей и доверенное хранилище.

Я также пытался создать собственный SSLContext и установить его в качестве значения по умолчанию, но это тоже задает контекст для всех приложений, работающих в одной JVM:

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

Я хочу иметь возможность использовать разные хранилища ключей и доверенные хранилища без изменения кода отдельных приложений.

Отличным решением было бы динамически регистрировать несколько хранилищ ключей в дополнение к хранилищу по умолчанию в JRE для JVM.

Решение должно работать следующим образом:

  • При загрузке JVM загружаются все сертификаты/хранилища ключей по умолчанию из папки jre/certs (поведение по умолчанию для Java, когда хранилища не указаны).
  • Когда приложение 1 загружается, оно регистрирует свои хранилища ключей.
  • Затем, когда приложение 2 загружается, оно также регистрирует свои хранилища ключей...

Пожалуйста, дайте знать свои идеи или предложения по решению данной проблемы. Заранее спасибо!

1 ответ(ов)

0

В результате работы с кодом, который я получил от ZZ Coder, sylvarking и Software Monkey, я нашел решение, которое работает:

Сначала я написал класс X509KeyManager, который объединяет пользовательский хранилище ключей (keystore) и стандартное хранилище ключей.

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) {
  // пробуем первым ключевым менеджером
  String alias = customKeyManager.chooseClientAlias(keyType, issuers, socket);
  if( alias == null ) {
   alias = jvmKeyManager.chooseClientAlias(keyType, issuers, socket);
   logger.warn("Возврат к клиентскому alias JVM: " + alias);
  }

  return alias;
 }

 @Override
 public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
  // пробуем первым ключевым менеджером
  String alias = customKeyManager.chooseServerAlias(keyType, issuers, socket);
  if( alias == null ) {
   alias =  jvmKeyManager.chooseServerAlias(keyType, issuers, socket);
   logger.warn("Возврат к серверному alias JVM: " + alias);
  } 
  return alias;
 }

 @Override
 public X509Certificate[] getCertificateChain(String alias) {
  X509Certificate[] chain = customKeyManager.getCertificateChain(alias);
  if( chain == null || chain.length == 0) {
   logger.warn("Возврат к цепочке сертификатов JVM : " + 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("Поддерживаемые клиентские alias: Пользовательский: " + 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("Возврат к ключу JVM: " + 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("Поддерживаемые серверные alias: Пользовательский: " + cAliases.length + ", JVM: " + jAliases.length);
  return ArrayUtils.join(cAliases, jAliases);
 }
}

Затем вы можете использовать этот менеджер ключей при создании SSL-контекста или SocketFactory. Код нуждается в некотором рефакторинге и упорядочивании, но он работает идеально.

private static KeyManager[] getKeyManagers(Properties props) throws IOException, GeneralSecurityException {
  // Сначала получаем стандартный KeyManagerFactory.
  String alg = KeyManagerFactory.getDefaultAlgorithm();
  KeyManagerFactory kmFact = KeyManagerFactory.getInstance(alg);   
  
  // Устанавливаем хранилище ключей, загружая файл в экземпляр KeyStore.
  FileInputStream fis = new FileInputStream(props.getProperty(SSL_KEYSTORE));
  logger.info("Загружено хранилище ключей");
  KeyStore ks = KeyStore.getInstance("jks");
  String keyStorePassword = props.getProperty(SSL_KEYSTORE_PASSWORD);
  ks.load(fis, keyStorePassword.toCharArray());
  fis.close();
  
  // Инициализируем KeyManagerFactory с этим хранилищем ключей
  kmFact.init(ks, keyStorePassword.toCharArray());
  
  // Создаем стандартный KeyManagerFactory
  KeyManagerFactory dkmFact = KeyManagerFactory.getInstance(alg); 
  dkmFact.init(null, null);  

  // Получаем первого X509KeyManager из списка
  X509KeyManager customX509KeyManager = getX509KeyManager(alg, kmFact);
  X509KeyManager jvmX509KeyManager = getX509KeyManager(alg, dkmFact);

  KeyManager[] km = { new MultiKeyStoreManager(jvmX509KeyManager, customX509KeyManager) };   
  logger.debug("Количество зарегистрированных менеджеров ключей: " + km.length);  
  return km;
}

// Другие вспомогательные методы...

private static void initialiseManager(Properties props) throws IOException, GeneralSecurityException { 
  // Конструируем и инициализируем SSLContext с хранилищем ключей и хранилищем доверия.
  SSLContext context = SSLContext.getInstance("SSL");
  context.init(getKeyManagers(props), getTrustManagers(props), null);
  SSLContext.setDefault(context);
}

Если у кого-либо есть вопросы или нужны демонстрационные коды, дайте знать!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь