У вас есть четыре возможных решения для исправления исключения «Не доверяйте» на Android с использованием httpclient:
- Доверяй всем сертификатам. Не делайте этого, если вы действительно не знаете, что делаете.
- Создайте собственный SSLSocketFactory, который доверяет только вашему сертификату. Это работает до тех пор, пока вы точно знаете, к каким серверам вы собираетесь подключаться, но как только вам потребуется подключиться к новому серверу с другим сертификатом SSL, вам нужно будет обновить приложение.
- Создайте файл хранилища ключей, который содержит «главный список» сертификатов Android, а затем добавьте свой собственный. Если срок действия какого-либо из этих сертификатов истекает, вы несете ответственность за их обновление в своем приложении. Я не могу придумать причину сделать это.
- Создайте пользовательский SSLSocketFactory, который использует встроенный сертификат KeyStore, но использует альтернативный KeyStore для всего, что не удается проверить по умолчанию.
В этом ответе используется решение № 4, которое мне кажется наиболее надежным.
Решение состоит в том, чтобы использовать SSLSocketFactory, который может принимать несколько хранилищ ключей, что позволяет вам предоставлять свой собственный хранилище ключей с собственными сертификатами. Это позволяет загружать дополнительные сертификаты верхнего уровня, такие как Thawte, которые могут отсутствовать на некоторых устройствах Android. Это также позволяет вам загружать собственные подписанные сертификаты. Сначала он будет использовать встроенные сертификаты устройства по умолчанию и использовать ваши дополнительные сертификаты только при необходимости.
Сначала вы захотите определить, какой сертификат отсутствует в вашем хранилище ключей. Выполните следующую команду:
openssl s_client -connect www.yourserver.com:443
И вы увидите вывод, подобный следующему:
Certificate chain
0 s:/O=www.yourserver.com/OU=Go to
https://www.thawte.com/repository/index.html/OU=Thawte SSL123
certificate/OU=Domain Validated/CN=www.yourserver.com
i:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
1 s:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
i:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c)
2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA
Как видите, наш корневой сертификат от Thawte. Зайдите на сайт вашего провайдера и найдите соответствующий сертификат. Для нас это было здесь , и вы можете видеть, что нам нужен был Copyright 2006.
Если вы используете самозаверяющий сертификат, вам не нужно было выполнять предыдущий шаг, поскольку у вас уже есть сертификат подписи.
Затем создайте файл хранилища ключей, содержащий недостающий сертификат подписи. У Crazybob есть подробностей о том, как это сделать на Android , но идея состоит в том, чтобы сделать следующее:
Если у вас его еще нет, загрузите библиотеку провайдера надувных замков по адресу: http://www.bouncycastle.org/latest_releases.html. Это будет указано в вашем пути к классам ниже.
Запустите команду для извлечения сертификата с сервера и создания файла pem. В этом случае mycert.pem.
echo | openssl s_client -connect ${MY_SERVER}:443 2>&1 | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > mycert.pem
Затем выполните следующие команды для создания хранилища ключей.
export CLASSPATH=/path/to/bouncycastle/bcprov-jdk15on-155.jar
CERTSTORE=res/raw/mystore.bks
if [ -a $CERTSTORE ]; then
rm $CERTSTORE || exit 1
fi
keytool \
-import \
-v \
-trustcacerts \
-alias 0 \
-file <(openssl x509 -in mycert.pem) \
-keystore $CERTSTORE \
-storetype BKS \
-provider org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath /path/to/bouncycastle/bcprov-jdk15on-155.jar \
-storepass some-password
Вы заметите, что приведенный выше скрипт помещает результат в res/raw/mystore.bks
. Теперь у вас есть файл, который вы загрузите в свое приложение для Android, которое предоставляет недостающие сертификаты.
Для этого зарегистрируйте свой SSLSocketFactory для схемы SSL:
final SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", createAdditionalCertsSSLSocketFactory(), 443));
// and then however you create your connection manager, I use ThreadSafeClientConnManager
final HttpParams params = new BasicHttpParams();
...
final ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params,schemeRegistry);
Чтобы создать свой SSLSocketFactory:
protected org.apache.http.conn.ssl.SSLSocketFactory createAdditionalCertsSSLSocketFactory() {
try {
final KeyStore ks = KeyStore.getInstance("BKS");
// the bks file we generated above
final InputStream in = context.getResources().openRawResource( R.raw.mystore);
try {
// don't forget to put the password used above in strings.xml/mystore_password
ks.load(in, context.getString( R.string.mystore_password ).toCharArray());
} finally {
in.close();
}
return new AdditionalKeyStoresSSLSocketFactory(ks);
} catch( Exception e ) {
throw new RuntimeException(e);
}
}
И, наконец, код AdditionalKeyStoresSSLSocketFactory, который принимает ваш новый KeyStore и проверяет, не удается ли встроенному KeyStore проверить сертификат SSL:
/**
* Allows you to trust certificates from additional KeyStores in addition to
* the default KeyStore
*/
public class AdditionalKeyStoresSSLSocketFactory extends SSLSocketFactory {
protected SSLContext sslContext = SSLContext.getInstance("TLS");
public AdditionalKeyStoresSSLSocketFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(null, null, null, null, null, null);
sslContext.init(null, new TrustManager[]{new AdditionalKeyStoresTrustManager(keyStore)}, null);
}
@Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
/**
* Based on http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager
*/
public static class AdditionalKeyStoresTrustManager implements X509TrustManager {
protected ArrayList<X509TrustManager> x509TrustManagers = new ArrayList<X509TrustManager>();
protected AdditionalKeyStoresTrustManager(KeyStore... additionalkeyStores) {
final ArrayList<TrustManagerFactory> factories = new ArrayList<TrustManagerFactory>();
try {
// The default Trustmanager with default keystore
final TrustManagerFactory original = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
original.init((KeyStore) null);
factories.add(original);
for( KeyStore keyStore : additionalkeyStores ) {
final TrustManagerFactory additionalCerts = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
additionalCerts.init(keyStore);
factories.add(additionalCerts);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
/*
* Iterate over the returned trustmanagers, and hold on
* to any that are X509TrustManagers
*/
for (TrustManagerFactory tmf : factories)
for( TrustManager tm : tmf.getTrustManagers() )
if (tm instanceof X509TrustManager)
x509TrustManagers.add( (X509TrustManager)tm );
if( x509TrustManagers.size()==0 )
throw new RuntimeException("Couldn't find any X509TrustManagers");
}
/*
* Delegate to the default trust manager.
*/
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
final X509TrustManager defaultX509TrustManager = x509TrustManagers.get(0);
defaultX509TrustManager.checkClientTrusted(chain, authType);
}
/*
* Loop over the trustmanagers until we find one that accepts our server
*/
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for( X509TrustManager tm : x509TrustManagers ) {
try {
tm.checkServerTrusted(chain,authType);
return;
} catch( CertificateException e ) {
// ignore
}
}
throw new CertificateException();
}
public X509Certificate[] getAcceptedIssuers() {
final ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
for( X509TrustManager tm : x509TrustManagers )
list.addAll(Arrays.asList(tm.getAcceptedIssuers()));
return list.toArray(new X509Certificate[list.size()]);
}
}
}