прием HTTPS-соединений с самозаверяющими сертификатами - PullRequest
147 голосов
/ 06 января 2010

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

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

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

Ответы [ 12 ]

169 голосов
/ 11 октября 2010

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

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

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

Далее вам нужно установить нашу фабрику для протокола 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);
121 голосов
/ 22 октября 2010

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

По просьбе многих пользователей я отразил наиболее важные части из моей статьи в блоге здесь:

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

Хватай сертификаты

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

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

Загрузите BouncyCastle Provider и сохраните его в известном месте. Также убедитесь, что вы можете вызвать команду 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();

Вот и все;)

16 голосов
/ 16 мая 2014

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

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

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

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;
    }

}

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

  1. Certificate объекты создаются из .crt файлов.
  2. По умолчанию KeyStore создано.
  3. keyStore.setCertificateEntry("ca", cert) добавляет сертификат в хранилище ключей под псевдонимом "ca". Вы изменяете код для добавления дополнительных сертификатов (промежуточный CA и т. Д.).
  4. Основная цель - создать SSLSocketFactory, который затем может быть использован HTTPClient или HttpsURLConnection.
  5. SSLSocketFactory можно настроить далее, например, чтобы пропустить проверку имени хоста и т. Д.

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

6 голосов
/ 18 октября 2016

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

А также, НИКОГДА NUKE SSL-сертификаты (НИКОГДА !!!).

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

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

Вот еще один Gist , который может помочь для создания самоподписанных сертификатов SSL для настройки на вашем сервере, а также для использования сертификата в вашем приложении. Очень важно: Вы должны скопировать файл .crt, созданный с помощью приведенного выше сценария, в каталог «raw» из вашего проекта Android.

6 голосов
/ 28 января 2016

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

После того, как некоторое время гуглил по теме, я наконец нашел это решение, где внешние jar-файлы не нужны, только 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;
    }
}

Это хорошо сработало для моего приложения-макета.

6 голосов
/ 15 августа 2014

Верхний ответ не работал для меня. После некоторого расследования я нашел необходимую информацию о «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 является лишь примером, и ее использование в продуктивной среде может создать серьезную угрозу безопасности!

4 голосов
/ 17 июня 2011

Вот как вы можете добавить дополнительные сертификаты в хранилище ключей, чтобы избежать этой проблемы: Доверие всем сертификатам с использованием HttpClient через HTTPS

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

2 голосов
/ 27 июля 2017

Самый простой способ создания SSL-сертификата

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

Посетите свой сайт разработки с самозаверяющим сертификатом SSL.

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

Нажмите «Подробнее»

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

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

Нажмите «Экспорт ...»

Выберите «Сертификат X.509 с цепочкой (PEM)», выберите папку и имя, чтобы сохранить ее, и нажмите «Сохранить»

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

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

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

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

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

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

1 голос
/ 18 февраля 2017

Ни одно из этих исправлений не помогло моей платформе разработки, ориентированной на 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;
}

?>
1 голос
/ 31 марта 2016

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

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

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

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