Регистрация нескольких хранилищ ключей в JVM
Проблема с использованием различных хранилищ ключей в одной 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 ответ(ов)
В результате работы с кодом, который я получил от 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);
}
Если у кого-либо есть вопросы или нужны демонстрационные коды, дайте знать!
Что такое параметры -Xms и -Xmx при запуске JVM?
Как определить, запущен ли код в 64-разрядной или 32-разрядной JVM (изнутри программы)?
Почему компилятор Java 11 использует invokevirtual для вызова приватных методов?
Безопасно ли использовать -XX:MaxRAMFraction=1 в продакшене в контейнеризованной среде?
"Как задать количество потоков/ЦП для Java VM?"