Мне нужно использовать пару ключей в одном ssl-соединении без изменений в клиентах.
Почему?
Поскольку один клиент использует атрибут CN вхранилище доверенных сертификатов для установления соединения и других клиентов, использующих другое значение в том же атрибуте для выполнения той же задачи таким же образом.
Поэтому мне нужно использовать хранилище двух ключей (личное) с различными атрибутами CN , а также псевдонимами и совместно используются два разных хранилища доверия (открытый ключ) с различными атрибутами CN, а также псевдонимами.
Описание ниже:
keyStore1
Тип хранилища ключей: JKS
Поставщик хранилища ключей: SUN
Имя псевдонима: identity1
Владелец: CN = app1 ...
Эмитент: CN = app1 ...
trustStore1
Псевдоним: identity1
Владелец: CN = app1 ...
Эмитент: CN = app1 ...
keyStore2
Псевдоним: identity2
Владелец: CN = app2 ...
Эмитент: CN = app2 ...
trustStore2
Псевдоним: identity2
Владелец: CN = app2 ...
Эмитент: CN = app2...
Я пытался реализовать эту функцию следующим образом:
KeyStore KeyStore1;
try {
String keyStoreFile1 = "privatekey1";
String keyStoreType1 = "jks";
char[] keyStorePwd1 = "password".toCharArray();
keyStore1 = KeyStore.getInstance(keyStoreType1);
keyStore1.load(new FileInputStream(keyStoreFile1), keyStorePwd1);
} catch (java.security.GeneralSecurityException thr) {
throw new IOException("Cannot load keystore (" + thr + ")");
}
KeyStore trustStore1;
try {
String trustStoreFile1 = "publickey1";
String trustStoreType1 = "jks";
char[] trustStorePwd1 = "password".toCharArray();
trustStore1 = KeyStore.getInstance(trustStoreType1);
trustStore.load(new FileInputStream(trustStoreFile1), trustStorePwd1);
} catch (java.security.GeneralSecurityException thr) {
throw new IOException("Cannot load truststore (" + thr + ")");
}
KeyStore keyStore2;
try {
String keyStoreFile2 = "privatekey2";
String keyStoreType2 = "jks";
char[] keyStorePwd2 = "anotherpass".toCharArray();
keyStore2 = KeyStore.getInstance(key2StoreType);
keyStore2.load(new FileInputStream(keyStoreFile2), keyStorePwd2);
} catch (java.security.GeneralSecurityException thr) {
throw new IOException("Cannot load keystore (" + thr + ")");
}
KeyStore trustStore2;
try {
String trustStoreFile2 = "publickey2";
String trustStoreType2 = "jks";
char[] trustStorePwd2 = "anotherpass".toCharArray();
trustStore2 = KeyStore.getInstance(trustStoreType2);
trustStore2.load(new FileInputStream(trustStoreFile2), trustStorePwd2);
} catch (java.security.GeneralSecurityException thr) {
throw new IOException("Cannot load truststore (" + thr + ")");
}
KeyManagerFactory kmfkey1 = KeyManagerFactory
.getInstance(KeyManagerFactory.getkey1Algorithm());
kmfkey1.init(keyStore1, "password".toCharArray());
TrustManagerFactory tmfkey1 =
TrustManagerFactory.getInstance(TrustManagerFactory.getkey1Algorithm());
tmfkey1.init(trustStore1);
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(kmfkey1.getKeyManagers(), tmfkey1.getTrustManagers(), null);
KeyManagerFactory kmfkey2 = KeyManagerFactory.
getInstance(KeyManagerFactory.getkey1Algorithm());
kmfkey2.init(keyStore2, "password".toCharArray());
TrustManagerFactory tmfkey2 = TrustManagerFactory
.getInstance(TrustManagerFactory.getkey1Algorithm());
tmfkey2.init(trustStore2);
SSLContext ctxkey2 = SSLContext.getInstance("SSL");
ctxkey2.init(kmfkey2.getKeyManagers(), tmfkey2.getTrustManagers(), null);
SSLServerSocketFactory sslSrvSockFact = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
serverSocket = sslSrvSockFact.createServerSocket(port);
... Но я получил это сообщение об ошибке
...
java.security.KeyStoreException: неинициализированное хранилище ключей в java.security.KeyStore.aliases (KeyStore.java:941) в com.sun.net.ssl.internal.ssl.SunX509KeyManagerImpl. (SunX509KeyManagerImp6):в com.sun.net.ssl.internal.ssl.KeyManagerFactoryImpl $ SunX509.engineInit (KeyManagerFactoryImpl.java:41) в javax.net.ssl.KeyManagerFactory.init (KeyManagerFactory.java:192)
1087* ...
Поэтому я действительно хочу знать, возможно ли использовать пары ключей в одном сокетном соединении или решить это другим способом, который я не могу увидеть или разобраться.
РЕДАКТИРОВАТЬ 1
Бруно, не могли бы вы дать мне полный или полный пример, пожалуйста?
Потому что мне не понятно ....
Я попробовал две вещи:
Одно из решений - поместить два ключа в хранилище закрытых ключей, следуя предыдущему предложению ... но не работает, и я получил сообщение ниже:
Исключение в потоке "main" java.lang.NoSuchMethodError: javax.net.ssl.SSLContext.setDefault (Ljavax / net / ssl / SSLContext;) в ... initialiseManager (499)
Вы были правы ... Мне все еще нужно выбрать один контекст или
javax.net.ssl.SSLException: ни один из доступных сертификатов или ключей не соответствует включенным комплектам шифров SSL.при попытке отправить сообщение на сервер ...
Во-вторых, я последовал вашему предложению ... но соединение через SSL Socket не запускается
Я реализовал это с помощью многих другихребята, которые показали свой код здесь на этом сайте ... спасибо за все
private KeyManager[] getKeyManagers() 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.
File keyFile = new File("privatekey1");
FileInputStream fis = new FileInputStream(keyFile);
LogManager.log("Loaded keystore privatekey1 " + keyFile.getAbsolutePath(),
LogManager.LOG_LOWEST_LEVEL);
KeyStore ks = KeyStore.getInstance("jks");
String keyStorePassword = "password";
ks.load(fis, keyStorePassword.toCharArray());
fis.close();
// Now we initialise the KeyManagerFactory with this KeyStore
kmFact.init(ks, keyStorePassword.toCharArray());
KeyManagerFactory dkmFact = KeyManagerFactory.getInstance(alg);
File keyFileTwo = new File("privatekey2");
FileInputStream fisTwo = new FileInputStream(keyFileTwo);
LogManager.log("Loaded keystore privatekey2 " + keyFileTwo.getAbsolutePath(), LogManager.LOG_LOWEST_LEVEL);
KeyStore ksTwo = KeyStore.getInstance("jks");
String keyStorePasswordTwo = "password";
ksTwo.load(fisTwo, keyStorePasswordTwo.toCharArray());
fisTwo.close();
// Now we initialise the KeyManagerFactory with this KeyStore
dkmFact.init(ksTwo, keyStorePasswordTwo.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)};
LogManager.log("Number of key managers registered:" + km.length, LogManager.LOG_LOWEST_LEVEL);
return km;
}
/**
* Find a X509 Key Manager compatible with a particular algorithm
* @param algorithm
* @param kmFact
* @return
* @throws NoSuchAlgorithmException
*/
private 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 void initialiseManager(int iPort) throws IOException, GeneralSecurityException {
// Next construct and initialise a SSLContext with the KeyStore and
// the TrustStore. We use the default SecureRandom.
// load your key store as a stream and initialize a KeyStore
File trustFile = new File("publicKey");
LogManager.log("Trust File Loaded " + trustFile.getAbsolutePath(), LogManager.LOG_LOWEST_LEVEL);
InputStream trustStream = new FileInputStream(trustFile);
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
// if your store is password protected then declare it (it can be null however)
char[] trustPassword = "password".toCharArray();
// load the stream to your store
trustStore.load(trustStream, trustPassword);
File trustFileTwo = new File("publicKeyTwo");
LogManager.log("Trust File Loaded " + trustFileTwo.getAbsolutePath(), LogManager.LOG_LOWEST_LEVEL);
InputStream trustStreamTwo = new FileInputStream(trustFileTwo);
KeyStore trustStoreTwo = KeyStore.getInstance(KeyStore.getDefaultType());
// if your store is password protected then declare it (it can be null however)
char[] trustPasswordTwo = "password".toCharArray();
// load the stream to your store
trustStoreTwo.load(trustStreamTwo, trustPasswordTwo);
// initialize a trust manager factory with the trusted store
TrustManagerFactory trustFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(trustStore);
trustFactory.init(trustStoreTwo);
// get the trust managers from the factory
TrustManager[] managers = trustFactory.getTrustManagers();
SSLContext context = SSLContext.getInstance("SSL");
context.init(getKeyManagers(), managers, new SecureRandom());
SSLContext.setDefault(context);
SSLServerSocketFactory sslSrvFact = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
serverSocket = sslSrvFact.createServerSocket(iPort);
// this method didn't create a Socket Connection correctly
}
class MultiKeyStoreManager implements X509KeyManager {
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);
LogManager.log("Reverting to JVM CLIENT alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
}
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);
LogManager.log("Reverting to JVM Server alias : " + alias ,LogManager.LOG_LOWEST_LEVEL);
}
return alias;
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
String[] cAliases = customKeyManager.getClientAliases(keyType, issuers);
String[] jAliases = jvmKeyManager.getClientAliases(keyType, issuers);
LogManager.log("Supported Client Aliases Custom: " + cAliases.length + " JVM : " + jAliases.length,
LogManager.LOG_LOWEST_LEVEL);
return (String[]) ArrayUtils.addAll(cAliases, jAliases);
}
@Override
public PrivateKey getPrivateKey(String alias) {
PrivateKey key = customKeyManager.getPrivateKey(alias);
if (key == null) {
System.out.println("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);
LogManager.log("Supported Server Aliases Custom: " + cAliases.length + " JVM : " + jAliases.length,
LogManager.LOG_LOWEST_LEVEL);
return (String[]) ArrayUtils.addAll(cAliases, jAliases);
}
@Override
public java.security.cert.X509Certificate[] getCertificateChain(String string) {
java.security.cert.X509Certificate[] chain = customKeyManager.getCertificateChain("alias_key1");
if (chain == null || chain.length == 0) {
LogManager.log("Reverting to JVM Chain : " + string, LogManager.LOG_LOWEST_LEVEL);
return jvmKeyManager.getCertificateChain("alias_key2");
} else {
return chain;
}
}
}
and this gave me this status
* 2012.02.09 18:47:00 Активация SSL-соединения
2012.02.09 18:47:00 [... :: run]
2012.02.09 18:47:00 Траст-файл загружен publicKey
2012.02.09 18:47:00 Траст-файл загружен publicKeyTwo
2012.02.09 18:47:00 Загруженное хранилище ключей privateKey privateKey
2012.02.09 18:47:00 Загруженное хранилище ключей privateKey2 privateKeyTwo
2012.02.09 18:47:00 Количество зарегистрированных менеджеров ключей: 1 *
Но ничего не произошло, когда я попытался отправить сообщение на сервер ...
Трудно найти пример, который действительно работает в этом случае.
РЕДАКТИРОВАТЬ 2
Привет, Бруно
На самом деле вы обладаете глубокими знаниями по этому вопросу, Java и SSH, и я ценю вашу помощь.Вся эта информация поможет мне лучше понять эту проблему, пока показывает мне путь ... Большое спасибо
И снова вы правы ... Я использую код для Java 6 на Java 5 JRE
Но, следуя твоему рецепту, я получил этот код:
// Load the key store: change store type if needed
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
FileInputStream fis = new FileInputStream("keyStore1");
char[] keyPass = "passw".toCharArray();
try {
ks.load(fis, keyPass);
} finally {
if (fis != null) {
fis.close();
}
}
// Get the default Key Manager
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, keyPass);
final X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
X509KeyManager km = new X509KeyManager() {
public String chooseServerAlias(String[] keyType, Principal[] issuers, Socket socket) {
String alias;
InetAddress remoteAddress = socket.getInetAddress();
if (remoteAddress.getHostAddress().equalsIgnoreCase("11.111.111.11")) {
alias = "alias1";
LogManager.log("Reverting to JVM CLIENT alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
} else {
alias = "alias2";
LogManager.log("Reverting to JVM CLIENT alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
}
return alias;
}
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
// try this key manager
String alias = origKm.chooseClientAlias(keyType, issuers, socket);
LogManager.log("Reverting to JVM CLIENT alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
return alias;
}
public String[] getClientAliases(String keyType, Principal[] issues) {
String[] cAliases = origKm.getClientAliases(keyType, issues);
LogManager.log("Supported Client Aliases : " + cAliases.length, LogManager.LOG_LOWEST_LEVEL);
return cAliases;
}
public String[] getServerAliases(String keyType, Principal[] issues) {
String[] sAliases = origKm.getServerAliases(keyType, issues);
LogManager.log("Supported Server Aliases: " + sAliases.length, LogManager.LOG_LOWEST_LEVEL);
return sAliases;
}
public String chooseServerAlias(String keyType, Principal[] issues, Socket socket) {
// try this key manager
String alias = origKm.chooseServerAlias(keyType, issues, socket);
LogManager.log("Reverting to JVM Server alias : " + alias, LogManager.LOG_LOWEST_LEVEL);
return alias;
}
public X509Certificate[] getCertificateChain(String keyType) {
// here I could specify my other keystore, keystore2 how I could do this?
// I'm thinking in the righ way when I implemented this code to get the correct private key?
X509Certificate[] chain = origKm.getCertificateChain("alias1");
if (chain == null || chain.length == 0) {
LogManager.log("Reverting to JVM Chain : " + keyType, LogManager.LOG_LOWEST_LEVEL);
return origKm.getCertificateChain("alias2");
} else {
return chain;
}
}
public PrivateKey getPrivateKey(String alias) {
PrivateKey key = origKm.getPrivateKey(alias);
// here I could get my other keystore according the alias, for example
// keystore2 how I could do this?
LogManager.log("Reverting to JVM Key : " + alias, LogManager.LOG_LOWEST_LEVEL);
return key;
}
};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(new KeyManager[]{km}, null, null);
SSLServerSocketFactory sslSrvFact = sslContext.getServerSocketFactory();
objServerSocket = sslSrvFact.createServerSocket(iPort);
Это именно то, что мне нужно для достижения моей цели?
РЕДАКТИРОВАТЬ 3
Используя этот подход, можно получить рукопожатие между клиентом и сервером, используя второй
хранилище ключей с псевдонимом 2 с использованием общего хранилища доверия 2 в клиенте
... но я все еще получал ошибку, когда пытался использовать trust store1 в клиенте
... получил сообщение от [addr = / 11.111.111.11]
Возврат к псевдониму сервера JVM: alias2
Возврат к ключу JVM: alias2
Ошибка при получении:
javax.net.ssl.SSLHandshakeException: получено фатальное предупреждение: certificate_unknown
Мой код не меняет сервер на использование закрытого ключа1 с псевдонимом1 ... когда клиент использует публичное хранилище доверенных сертификатов1 с сертификатом псевдонима1 ...
Что еще нужно сделать, чтобы решить эту проблему ... Я считаю, что она близка к окончательному решению ...
спасибо еще раз!
РЕДАКТИРОВАТЬ 4
Бруно, я изменил метод getCertificateChain, следуя вашему предложению, показанному ниже
public X509Certificate[] getCertificateChain(String alias) {
X509Certificate[] chain = origKm.getCertificateChain(alias);
return chain;
}
и метод
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
String alias;
также я удалил дублированный метод ...
А в зависимости клиенты, использующие старое хранилище доверия, не проверяют имя хоста
private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
Но для клиента, которому необходимо использовать второе хранилище доверия, выполните эту проверку, и это причина, потому что мне нужно иметь дело с сертификатами пары ключей ...
EDIT5
Я хотел бы знать, как я могу реализовать сокет сервера, чтобы он мог идентифицировать и использовать
правильный сертификат в соответствии с сертификатом, используемым клиентом для продолжения
рукопожатие связи с сервером.
Объясняя лучше, на стороне сервера это:
AppServerSideSocket.jar
- хранилище личных ключей: privateKeyApp (тип JKS, сгенерированный с помощью keytool)
- хранилище открытых ключей: publicKeyApp (тип JKS, общий для всех клиентов)
А на стороне клиента ...
AppClientSideSocket.jar
- хранилище открытых ключей: publicKeyApp
AppServerSideSocket.jar прослушивание запросов клиентов и один раз полученный процесс
информация, отправленная клиентами
AppClientSideSocket.jar соединяется с сервером по протоколу SSL с использованием publicKeyApp без
проверьте имя хоста сервера и после рукопожатия отправьте информацию для приложения AppServerSideSocket.
Теперь у меня есть другое клиентское приложение, AppClientSideSocketNEW.jar , и это проверяет имя хоста сервера, чтобы сделать
связь с сервером. В этом случае CN используется в публичном сертификате на стороне клиента
должно совпадать с именем хоста, где AppServerSideSocket.jar .
Первоначально соединение было настроено таким образом на стороне сервера:
if (usingSSLConn) {
System.setProperty("javax.net.ssl.keyStore", "privateKeyApp");
System.setProperty("javax.net.ssl.keyStorePassword", "privateKeyAppPassword");
SSLServerSocketFactory sslServerSocketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
ServerSocket serverSocketApp = sslServerSocketFactory.createServerSocket(Port);
} else
serverSocketApp = new ServerSocket(Port);
}
Все клиенты получили одно и то же publicKeyApp и подключаются к серверу без проверки имени хоста, поэтому не имеет значения
если сервер, на котором установлено приложение сокета сервера ( AppServerSideSocket.jar ) на сервере с именем хоста,
badServer1.com и CN ключа в privateKeyApp и publicKeyApp установлены с goodServer1.com, потому что все
клиенты не проверяют имя хоста или атрибут CN ключа.
Ниже показан кусок такого рода соединения
private static final HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
System.setProperty("javax.net.ssl.trustStore", publicKey1);
System.getProperties().setProperty("java.protocol.handler.pkgs", "javax.net.ssl.internal.www.protocol");
HttpsURLConnection.setDefaultHostnameVerifier(DO_NOT_VERIFY);
...
SOAPConnectionFactory soapConn = SOAPConnectionFactory.newInstance();
SOAPConnection connection = soapConn.createConnection();
...
URL endpoint = new URL(hostname + ":" + port);
Но новый клиент ( AppClientSideSocketNEW.jar ) выполняет эту проверку в обязательном порядке, теперь необходимо предоставить новый сертификат
для этого клиента с новым значением для атрибута CN, отражающего правильное имя хоста CN, где находится сокет сервера.
У меня нет доступа ко второму клиенту, и я уверен, что он выполняет проверку имени хоста.
Итак, я создал два новых сертификата пары ключей ( privateKeyAppNew и publicKeyAppNew ), и, очевидно, связь произошлас успехом между сервером, использующим эту новую пару ключей, и новым клиентом, использующим этот новый открытый ключ publicKeyAppNew, после того, как сервер настроен на использование этой новой пары ключей.
Но мне нужно продолжать использовать старую пару ключей для старых клиентов. Я хотел бы знать, как я могу справиться с этим.
Использование диспетчера ключей позволило мне проверить клиентский сертификат в приложении сервера, когда клиент пытается подключиться, и
выбрать подходящий и сделать рукопожатие, используя правильный сертификат?
Или мне нужно отдельное соединение сокета ssl в разных портах для какого типа клиентов?