Как создать Java Keystore в формате BKS (BouncyCastle), который содержит цепочку сертификатов клиента
Я пишу приложение для Android, которое требует аутентификации клиента SSL. Я знаю, как создать хранилище ключей JKS для настольного Java-приложения, но Android поддерживает только формат BKS. Каждый способ, которым я пытался создать хранилище ключей, приводит к следующей ошибке:handling exception: javax.net.ssl.SSLHandshakeException: null cert chain
Таким образом, похоже, что клиент никогда не отправляет правильную цепочку сертификатов, возможно, потому что я не создаю хранилище ключей должным образом. Я не могу включить отладку SSL, как я могу на рабочем столе, так что это делает это намного сложнее, чем должно быть.
Для справки ниже приведена команда, которая работает над созданием склада доверенных сертификатов BKS:keytool -importcert -v -trustcacerts -file "cacert.pem" -alias ca -keystore "mySrvTruststore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest
Вот команда, которую я пробовал, которая НЕ работает для создания хранилища ключей клиента BKS:
cat clientkey.pem clientcert.pem cacert.pem > client.pem
keytool -import -v -file <(openssl x509 -in client.pem) -alias client -keystore "clientkeystore" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest
7 ответов
Подробные пошаговые инструкции, которым я следовал, чтобы добиться этого
- Загрузите JAR-файл bouncycastle с http://repo2.maven.org/maven2/org/bouncycastle/bcprov-ext-jdk15on/1.46/bcprov-ext-jdk15on-1.46.jar или возьмите его из папки "doc".
- Настройте BouncyCastle для ПК, используя один из следующих способов.
- Добавление поставщика BC статически (рекомендуется)
- Скопируйте файл bcprov-ext-jdk15on-1.46.jar каждому
- D:\tools\jdk1.5.0_09\jre\lib\ext (JDK (в комплекте JRE)
- D: \ tools\jre1.5.0_09\ lib \ ext (JRE)
- C: \ (местоположение, которое будет использоваться в переменной env)
- Измените файл java.security в разделе
- D: \ Tools \ jdk1.5.0_09 \ JRE \ Lib \ безопасность
- D: \ Tools\jre1.5.0_09\ Lib \ безопасность
- и добавьте следующую запись
- security.provider.7=org.bouncycastle.jce.provider.BouncyCastleProvider
- Добавьте следующую переменную среды в разделе "Пользовательские переменные"
- CLASSPATH=%CLASSPATH%; C:\bcprov-доб-jdk15on-1.46.jar
- Скопируйте файл bcprov-ext-jdk15on-1.46.jar каждому
- Добавьте bcprov-ext-jdk15on-1.46.jar в CLASSPATH вашего проекта и добавьте следующую строку в ваш код
- Security.addProvider(новый BouncyCastleProvider());
- Добавление поставщика BC статически (рекомендуется)
- Сгенерируйте хранилище ключей, используя Bouncy Castle
- Запустите следующую команду
- keytool -genkey -alias myproject -keystore C:/myproject.keystore -storepass myproject -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
- Это создает файл C:\myproject.keystore
- Запустите следующую команду, чтобы проверить, правильно ли она сгенерирована или нет
- keytool -list -keystore C:\myproject.keystore -storetype BKS
- Запустите следующую команду
Настроить BouncyCastle для TOMCAT
Откройте D:\tools\apache-tomcat-6.0.35\conf\server.xml и добавьте следующую запись
Перезагрузите сервер после этих изменений.
- Настройте BouncyCastle для клиента Android
- Не нужно настраивать, поскольку Android внутренне поддерживает Bouncy Castle версии 1.46 в прилагаемом "android.jar".
- Просто реализуйте свою версию HTTP-клиента (MyHttpClient.java можно найти ниже) и установите следующее в коде
- SSLSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
- Если вы этого не сделаете, это дает исключение, как показано ниже
- javax.net.ssl.SSLException: имя хоста в сертификате не соответствует: <192.168.104.66>!=
- В рабочем режиме измените приведенный выше код на
- SSLSocketFactory.setHostnameVerifier (SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
MyHttpClient.java
package com.arisglobal.aglite.network;
import java.io.InputStream;
import java.security.KeyStore;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import com.arisglobal.aglite.activity.R;
import android.content.Context;
public class MyHttpClient extends DefaultHttpClient {
final Context context;
public MyHttpClient(Context context) {
this.context = context;
}
@Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// Register for port 443 our SSLSocketFactory with our keystore to the ConnectionManager
registry.register(new Scheme("https", newSslSocketFactory(), 443));
return new SingleClientConnManager(getParams(), registry);
}
private SSLSocketFactory newSslSocketFactory() {
try {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("BKS");
// Get the raw resource, which contains the keystore with your trusted certificates (root and any intermediate certs)
InputStream in = context.getResources().openRawResource(R.raw.aglite);
try {
// Initialize the keystore with the provided trusted certificates.
// Also provide the password of the keystore
trusted.load(in, "aglite".toCharArray());
} finally {
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible for the verification of the server certificate.
SSLSocketFactory sf = new SSLSocketFactory(trusted);
// Hostname verification from certificate
// http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
Как вызвать вышеуказанный код в вашем классе Activity:
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpResponse response = client.execute(...);
Не уверен, что вы решили эту проблему или нет, но вот как я это делаю, и это работает на Android:
- Используйте openssl для объединения сертификата клиента (сертификат должен быть подписан центром сертификации, который принят сервером) и закрытым ключом в пару ключей формата PCKS12: openssl pkcs12 -export -in clientcert.pem -inkey clientkey.pem -out client.p12
- Вам может потребоваться исправить вашу JRE, чтобы неограниченная сила шифрования зависела от силы вашего ключа: скопируйте файлы jar из файлов политики юрисдикции неограниченной силы JCE 5.0 и переопределите их в вашем JRE (например, C:\Program Files\Java\jre6\lib\security)
- Используйте инструмент Portecle, упомянутый выше, и создайте новое хранилище ключей в формате BKS
- Импортируйте пару ключей PCKS12, созданную на шаге 1, и сохраните ее как хранилище ключей BKS. Это хранилище ключей работает с аутентификацией клиента Android.
- Если вам нужно создать цепочку сертификатов, вы можете использовать этот инструмент IBM: KeyMan для объединения пары ключей PCKS12 клиента с сертификатом CA. Но он генерирует только хранилище ключей JKS, поэтому вам снова понадобится Protecle, чтобы преобразовать его в формат BKS.
Я не думаю, что ваша проблема с хранилищем ключей BouncyCastle; Я думаю, что проблема в сломанном пакете javax.net.ssl в Android. Хранилище ключей BouncyCastle вызывает крайнее раздражение, потому что Android изменил поведение Java по умолчанию, нигде не документировав его, и удалил провайдера по умолчанию, но он работает.
Обратите внимание, что для аутентификации SSL вам может потребоваться 2 хранилища ключей. Хранилище ключей "TrustManager", которое содержит сертификаты CA, и хранилище ключей "KeyManager", которое содержит открытые / закрытые ключи вашего сайта клиента. (Документация несколько расплывчата относительно того, что должно быть в хранилище ключей KeyManager.) Теоретически вам не нужно хранилище ключей TrustManager, если все ваши сертификаты подписаны "хорошо известными" органами сертификации, например, Verisign, Thawte, и так далее. Дайте мне знать, как это работает для вас. Вашему серверу также потребуется CA для того, что использовалось для подписи вашего клиента.
Я не мог создать соединение SSL, используя javax.net.ssl вообще. Я отключил клиентскую SSL-аутентификацию на стороне сервера и все еще не смог создать соединение. Поскольку моей конечной целью был HTTPS GET, я пробовал и пытался использовать HTTP-клиент Apache, который поставляется в комплекте с Android. Это вроде сработало. Я мог установить соединение HTTPS, но я все еще не мог использовать аутентификацию SSL. Если я включу SSL-аутентификацию клиента на моем сервере, соединение не будет установлено. Я не проверял код HTTP-клиента Apache, но подозреваю, что они используют собственную реализацию SSL и не используют javax.net.ssl.
Командная строка:
keytool -genseckey -alias aliasName -keystore truststore.bks -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath /path/to/jar/bcprov-jdk16-1.46.jar -storetype BKS
Ваша команда для создания хранилища ключей BKS выглядит правильно для меня.
Как вы инициализируете хранилище ключей.
Вы должны создать и передать свой собственный SSLSocketFactory. Вот пример, который использует Apache org.apache.http.conn.ssl.SSLSocketFactory
Но я думаю, что вы можете сделать то же самое на javax.net.ssl.SSLSocketFactory
private SSLSocketFactory newSslSocketFactory() {
try {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("BKS");
// Get the raw resource, which contains the keystore with
// your trusted certificates (root and any intermediate certs)
InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
try {
// Initialize the keystore with the provided trusted certificates
// Also provide the password of the keystore
trusted.load(in, "testtest".toCharArray());
} finally {
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible
// for the verification of the server certificate.
SSLSocketFactory sf = new SSLSocketFactory(trusted);
// Hostname verification from certificate
// http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
Пожалуйста, дайте мне знать, если это сработало.
Используйте это руководство http://blog.antoine.li/2010/10/22/android-trusting-ssl-certificates/ Это руководство действительно помогло мне. Важно соблюдать последовательность сертификатов в магазине. Например: сначала импортируйте самый нижний сертификат промежуточного ЦС, а затем до самого сертификата корневого ЦС.