Прием HTTPS-соединений с самозаверяющими сертификатами

Я пытаюсь сделать HTTPS-соединения, используя HttpClient lib, но проблема в том, что поскольку сертификат не подписан признанным центром сертификации (CA), например Verisign, GlobalSIgn и т.д., Указанным в наборе доверенных сертификатов Android, Я продолжаю получать javax.net.ssl.SSLException: Not trusted server certificate.

Я видел решения, в которых вы просто принимаете все сертификаты, но что, если я хочу спросить пользователя?

Я хочу получить диалог, похожий на диалог браузера, позволяющий пользователю решить продолжить или нет. Я предпочитаю использовать тот же сертификат, что и браузер. Есть идеи?

Ответ 1

Первое, что вам нужно сделать, это установить уровень проверки. Таких уровней не так много:

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

Хотя метод setHostnameVerifier() устарел для новой библиотеки apache, но для версии в Android SDK это нормально. И поэтому мы берем ALLOW_ALL_HOSTNAME_VERIFIER и устанавливаем его в методе factory SSLSocketFactory.setHostnameVerifier().

Далее, вам нужно установить наш factory для протокола https. Для этого просто вызовите метод SchemeRegistry.register().

Затем вам нужно создать DefaultHttpClient с SingleClientConnManager. Также в приведенном ниже коде вы можете увидеть, что по умолчанию также будет использоваться наш флаг (ALLOW_ALL_HOSTNAME_VERIFIER) методом HttpsURLConnection.setDefaultHostnameVerifier()

Ниже код работает для меня:

HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

DefaultHttpClient client = new DefaultHttpClient();

SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());

// Set verifier     
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

// Example send http request
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);

Ответ 2

Для обеспечения защищенного соединения от сертификационных органов требуются следующие основные шаги, которые не считаются доверенными платформой Android.

Как просили многие пользователи, я отразил наиболее важные части из статьи блога здесь:

  • Захватите все необходимые сертификаты (root и любые промежуточные центры сертификации)
  • Создайте хранилище ключей с keytool и BouncyCastle и импортируйте сертификаты
  • Загрузите хранилище ключей в приложение для Android и используйте его для защищенных соединений (я рекомендую использовать Apache HttpClient вместо стандартного java.net.ssl.HttpsURLConnection (проще понять, более результативно)

Захватите сертификаты

Вы должны получить все сертификаты, которые строят цепочку из сертификата конечной точки, вплоть до корневого ЦС. Это означает, что любые (если есть) промежуточные сертификаты CA, а также сертификат Root CA. Вам не нужно получать сертификат конечной точки.

Создайте хранилище ключей

Загрузите поставщик BouncyCastle и сохраните его в известном месте. Также убедитесь, что вы можете вызвать команду keytool (обычно находящуюся в папке bin вашей установки JRE).

Теперь импортируйте полученные сертификаты (не импортируйте сертификат конечной точки) в хранилище ключей формата BouncyCastle.

Я не тестировал его, но я думаю, что порядок импорта сертификатов важен. Это означает, что сначала импортируйте самый младший сертификат промежуточного ЦС, а затем весь путь до сертификата корневого ЦС.

С помощью следующей команды будет создано новое хранилище ключей (если оно еще не указано) с помощью пароля mysecret и будет импортирован сертификат промежуточного ЦС. Я также определил поставщика BouncyCastle, где его можно найти в моей файловой системе и в формате хранилища ключей. Выполните эту команду для каждого сертификата в цепочке.

keytool -importcert -v -trustcacerts -file "path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

Проверьте правильность импорта сертификатов в хранилище ключей:

keytool -list -keystore "res/raw/myKeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

Должна выводить целую цепочку:

RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93
IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43

Теперь вы можете скопировать хранилище ключей в качестве исходного ресурса в своем приложении для Android в res/raw/

Используйте хранилище ключей в своем приложении

Прежде всего, мы должны создать пользовательский Apache HttpClient, который использует наше хранилище ключей для соединений HTTPS:

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.mykeystore);
          try {
              // Initialize the keystore with the provided trusted certificates
              // Also provide the password of the keystore
              trusted.load(in, "mysecret".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);
      }
  }
}

Мы создали наш пользовательский HttpClient, теперь мы можем просто использовать его для защищенных соединений. Например, когда мы делаем GET-вызов ресурсу REST.

// Instantiate the custom HttpClient
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
// Execute the GET call and obtain the response
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();

Что это;)

Ответ 3

Если у вас есть собственный/самозаверяющий сертификат на сервере, которого нет на устройстве, вы можете использовать нижеприведенный класс для его загрузки и использовать его на стороне клиента в Android:

Поместите сертификат *.crt в файл /res/raw, чтобы он был доступен из R.raw.*

Используйте класс ниже, чтобы получить HTTPClient или HttpsURLConnection, который будет иметь сокет factory, используя этот сертификат:

package com.example.customssl;

import android.content.Context;
import org.apache.http.client.HttpClient;
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.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

public class CustomCAHttpsProvider {

    /**
     * Creates a {@link org.apache.http.client.HttpClient} which is configured to work with a custom authority
     * certificate.
     *
     * @param context       Application Context
     * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
     * @param allowAllHosts If true then client will not check server against host names of certificate.
     * @return Http Client.
     * @throws Exception If there is an error initializing the client.
     */
    public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {


        // build key store with ca certificate
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // init ssl socket factory with key store
        SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);

        // skip hostname security check if specified
        if (allowAllHosts) {
            sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        // basic http params for client
        HttpParams params = new BasicHttpParams();

        // normal scheme registry with our ssl socket factory for "https"
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

        // create connection manager
        ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

        // create http client
        return new DefaultHttpClient(cm, params);
    }

    /**
     * Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority
     * certificate.
     *
     * @param urlString     remote url string.
     * @param context       Application Context
     * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
     * @param allowAllHosts If true then client will not check server against host names of certificate.
     * @return Http url connection.
     * @throws Exception If there is an error initializing the connection.
     */
    public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
                                                           boolean allowAllHosts) throws Exception {

        // build key store with ca certificate
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        // Create an SSLContext that uses our TrustManager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);

        // Create a connection from url
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());

        // skip hostname security check if specified
        if (allowAllHosts) {
            urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        return urlConnection;
    }

    private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        // init a default key store
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);

        // read and add certificate authority
        Certificate cert = readCert(context, certRawResId);
        keyStore.setCertificateEntry("ca", cert);

        return keyStore;
    }

    private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException {

        // read certificate resource
        InputStream caInput = context.getResources().openRawResource(certResourceId);

        Certificate ca;
        try {
            // generate a certificate
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            ca = cf.generateCertificate(caInput);
        } finally {
            caInput.close();
        }

        return ca;
    }

}

Ключевые моменты:

  • Certificate объекты генерируются из .crt файлов.
  • Создан по умолчанию KeyStore.
  • keyStore.setCertificateEntry("ca", cert) добавляет сертификат в хранилище ключей под псевдонимом "ca". Вы изменяете код, чтобы добавить больше сертификатов (промежуточный ЦС и т.д.).
  • Основная задача - создать SSLSocketFactory, который затем может использоваться HTTPClient или HttpsURLConnection.
  • SSLSocketFactory можно настроить далее, например, чтобы пропустить проверку имени узла и т.д.

Дополнительная информация по адресу: http://developer.android.com/training/articles/security-ssl.html

Ответ 4

Я был расстроен, пытаясь подключить свое приложение для Android к моей службе RESTful с помощью https. Кроме того, я был немного недоволен всеми ответами, которые предлагали полностью отключить проверку сертификатов. Если вы это сделаете, какова точка https?

После того, как я искал тему на некоторое время, я наконец нашел это решение, где внешние банки не нужны, просто API Android. Спасибо Эндрю Смиту, который опубликовал его в июле 2014 года.

 /**
 * Set up a connection to myservice.domain using HTTPS. An entire function
 * is needed to do this because myservice.domain has a self-signed certificate.
 * 
 * The caller of the function would do something like:
 * HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca");
 * InputStream in = urlConnection.getInputStream();
 * And read from that "in" as usual in Java
 * 
 * Based on code from:
 * https://developer.android.com/training/articles/security-ssl.html#SelfSigned
 */
public static HttpsURLConnection setUpHttpsConnection(String urlString)
{
    try
    {
        // Load CAs from an InputStream
        // (could be from a resource or ByteArrayInputStream or ...)
        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        // My CRT file that I put in the assets folder
        // I got this file by following these steps:
        // * Go to https://littlesvr.ca using Firefox
        // * Click the padlock/More/Security/View Certificate/Details/Export
        // * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))
        // The MainActivity.context is declared as:
        // public static Context context;
        // And initialized in MainActivity.onCreate() as:
        // MainActivity.context = getApplicationContext();
        InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt"));
        Certificate ca = cf.generateCertificate(caInput);
        System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());

        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        // Create an SSLContext that uses our TrustManager
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        // Tell the URLConnection to use a SocketFactory from our SSLContext
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
        urlConnection.setSSLSocketFactory(context.getSocketFactory());

        return urlConnection;
    }
    catch (Exception ex)
    {
        Log.e(TAG, "Failed to establish SSL connection to server: " + ex.toString());
        return null;
    }
}

Это сработало для моего приложения mockup.

Ответ 5

Главный ответ не работал у меня. После некоторого расследования я нашел необходимую информацию о "Android Developer": https://developer.android.com/training/articles/security-ssl.html#SelfSigned

Создание пустой реализации X509TrustManager выполнило трюк:

private static class MyTrustManager implements X509TrustManager
{

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType)
         throws CertificateException
    {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException
    {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers()
    {
        return null;
    }

}

...

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
try
{
    // Create an SSLContext that uses our TrustManager
    SSLContext context = SSLContext.getInstance("TLS");
    TrustManager[] tmlist = {new MyTrustManager()};
    context.init(null, tmlist, null);
    conn.setSSLSocketFactory(context.getSocketFactory());
}
catch (NoSuchAlgorithmException e)
{
    throw new IOException(e);
} catch (KeyManagementException e)
{
    throw new IOException(e);
}
conn.setRequestMethod("GET");
int rcode = conn.getResponseCode();

Помните, что эта пустая реализация TustManager является лишь примером, и использование ее в продуктивной среде может вызвать серьезную угрозу безопасности!

Ответ 6

Здесь вы можете добавить дополнительные сертификаты в свой KeyStore, чтобы избежать этой проблемы: Доверять всем сертификатам, используя HttpClient через HTTPS

Он не будет запрашивать пользователя, как вы просите, но он будет менее вероятным, чтобы пользователь выполнил ошибку "Недопустимый сертификат сервера".

Ответ 7

Google рекомендует использовать Android Volley для HTTP/HTTPS-соединений, так как HttpClient устарел. Итак, вы знаете правильный выбор:).

Кроме того, НИКОГДА НЕ СКАЧАТЬ SSL-сертификаты (НИКОГДА!!!).

Сертификаты SSL для nuke полностью противоречат цели SSL, которая способствует безопасности. Нет смысла использовать SSL, если вы планируете бомбить все SSL-сертификаты, которые поступают. Лучшим решением было бы, не используя SSL, или лучшее решение, было бы создание пользовательского TrustManager в вашем приложении + с помощью Android Volley для HTTP/HTTPS-соединений.

Здесь Gist, который я создал с помощью базового LoginApp, выполняющего HTTPS-подключения, используя самоподписанный сертификат на сервере -side, принятый в приложении.

Здесь также еще один Gist, который может помочь, для создания самоподписанных SSL-сертификатов для настройки на вашем сервере, а также с помощью сертификат в вашем приложении. Очень важно: вы должны скопировать файл .crt, который был сгенерирован script выше, в "сырой" каталог из вашего проекта Android.

Ответ 8

Я написал небольшую библиотеку ssl-utils-android, чтобы доверять определенному сертификату на Android.

Вы можете просто загрузить любой сертификат, указав имя файла из каталога ресурсов.

Использование:

OkHttpClient client = new OkHttpClient();
SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
client.setSslSocketFactory(sslContext.getSocketFactory());

Ответ 9

Ни один из этих исправлений не работал для моей платформы разработки платформы SDK 16, выпуск 4.1.2, поэтому я нашел обходной путь.

Мои приложения хранят данные на сервере с помощью http://www.example.com/page.php?data=somedata"

Недавно page.php был перемещен в " https://www.secure-example.com/page.php", и я продолжаю получать "javax.net.ssl.SSLException: Не доверяется сертификат сервера".

Вместо того, чтобы принимать все сертификаты только для одной страницы, начиная с этого руководства, я решил свою проблему, написав мою собственную page.php, опубликованную на " http://www.example.com/page.php"

<?php

caronte ("https://www.secure-example.com/page.php");

function caronte($url) {
    // build curl request
    $ch = curl_init();
    foreach ($_POST as $a => $b) {
        $post[htmlentities($a)]=htmlentities($b);
    }
    curl_setopt($ch, CURLOPT_URL,$url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post));

    // receive server response ...
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $server_output = curl_exec ($ch);
    curl_close ($ch);

    echo $server_output;
}

?>

Ответ 10

Возможно, это будет полезно... он работает с java-клиентами, используя самозаверяющие сертификаты (проверка сертификата отсутствует). Будьте осторожны и используйте его только для случаев разработки, потому что это абсолютно безопасно!

Как игнорировать ошибки сертификата SSL в Apache HttpClient 4.0

Надеюсь, что он будет работать на Android, просто добавив библиотеку HttpClient... удачи!!

Ответ 11

Это проблема, вызванная отсутствием поддержки SNI (идентификация имени сервера) в A, ndroid 2.x. Я боролся с этой проблемой в течение недели, пока не столкнулся со следующим вопросом, который не только дает хороший запас проблемы, но также обеспечивает эффективное и эффективное решение, лишенное каких-либо дыр в безопасности.

Ошибка "нет равных сертификатов" в Android 2.3, но НЕ в 4

Ответ 12

Простейший способ создания сертификата SSL

Откройте Firefox (я полагаю, это также возможно с Chrome, но мне легче с FF)

Посетите свой сайт разработки с помощью самоподписанного SSL-сертификата.

Нажмите на сертификат (рядом с названием сайта)

Нажмите "Дополнительная информация"

Нажмите "Просмотреть сертификат"

Нажмите "Подробности"

Нажмите "Экспортировать..."

Выберите "X.509 Certificate whith chain (PEM)", выберите папку и имя, чтобы сохранить ее, и нажмите "Сохранить"

Перейдите в командную строку, в каталог, в который вы загрузили файл pem, и выполните "openssl x509 -inform PEM -outform DM -in.pem -out.crt"

Скопируйте файл .crt в корень папки /sdcard внутри вашего устройства Android В вашем устройстве Android выберите "Настройки" > "Безопасность" > "Установить из хранилища".

Он должен обнаружить сертификат и добавить его на устройство Перейдите на сайт разработки.

В первый раз он попросит вас подтвердить исключение безопасности. Все это.

Сертификат должен работать с любым браузером, установленным на вашем Android (браузер, Chrome, Opera, Dolphin...)

Помните, что если вы обслуживаете свои статические файлы из другого домена (все мы - суки скорости страницы), вам также нужно добавить сертификат для этого домена.