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



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



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



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

685   12  

12 ответов:

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

  • 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);

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

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

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

возьмите сертификаты

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

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

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

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

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

С помощью следующей команды новое хранилище ключей (если уже не существует) с паролем mysecret будет создан и промежуточный сертификат CA будет импортирован. Я также определил поставщика 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();

вот именно ;)

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

место сертификат на /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. по умолчанию это.
  3. keyStore.setCertificateEntry("ca", cert) добавить сертификат в хранилище ключей под псевдонимом "ка". Вы изменяете код, чтобы добавить больше сертификатов (промежуточный CA и т. д.).
  4. основная цель состоит в том, чтобы создать SSLSocketFactory который затем может быть использован HTTPClient или HttpsURLConnection.
  5. SSLSocketFactory можно настроить дополнительно, например, пропустить проверку имени хоста и т. д.

Подробнее : http://developer.android.com/training/articles/security-ssl.html

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

после того, как погуглил по теме некоторое время, я наконец нашел этой решение, где внешние банки не нужны,просто Android API. Спасибо Эндрю Смиту, который опубликовал его в июле 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;
    }
}

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

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

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

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

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

и Never NUKE SSL сертификаты (никогда!!!).

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

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

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

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

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

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

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

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

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

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

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

?>

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

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

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

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

ошибка "нет сертификата сверстника" в Android 2.3, но не в 4

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

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

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

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

нажмите на кнопку "дополнительная информация"

нажмите на кнопку "Просмотр сертификата"

нажмите на кнопку "подробности"

нажмите на "Экспортный..."

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

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

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

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

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

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

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

Comments

    Ничего не найдено.