Подтвердить что ты не робот

Как я могу привязать сертификат с помощью Square OKHTTP?

Мне кажется, мне нужно создать новый SSL-разъем Factory? Кроме того, я не хочу использовать глобальный контекст SSL (https://github.com/square/okhttp/issues/184) по понятным причинам.

спасибо!

EDIT:

По сравнению с okhttp 2.1.0 вы можете легко списать сертификаты.

Смотрите исходный код здесь, чтобы начать работу

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ ДЛЯ OKHTTP 3.0

OKHTTP 3.0 имеет встроенную поддержку для закрепления сертификатов. Начните с вставки следующего кода:

 String hostname = "yourdomain.com";
 CertificatePinner certificatePinner = new CertificatePinner.Builder()
     .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
     .build();
 OkHttpClient client = OkHttpClient.Builder()
     .certificatePinner(certificatePinner)
     .build();

 Request request = new Request.Builder()
     .url("https://" + hostname)
     .build();
 client.newCall(request).execute();

Это не удастся, потому что AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA не является допустимым хешем вашего сертификата. Исправленное исключение будет иметь правильные хэши вашего сертификата:

 javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
   Peer certificate chain:
     sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject.com, OU=PositiveSSL
     sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA
     sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority
     sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root
   Pinned certificates for publicobject.com:
     sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
   at okhttp3.CertificatePinner.check(CertificatePinner.java)
   at okhttp3.Connection.upgradeToTls(Connection.java)
   at okhttp3.Connection.connect(Connection.java)
   at okhttp3.Connection.connectAndSetOwner(Connection.java)

Убедитесь, что вы добавили их в свой объект CertificatePinner, и вы успешно привязали свой сертификат:

 CertificatePinner certificatePinner = new CertificatePinner.Builder()
   .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
   .add("publicobject.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
   .add("publicobject.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
   .add("publicobject.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")
   .build();

ВСЕ ПРОШЛОЕ ЗДЕСЬ ДЛЯ ПОСЛЕ (2.x) ВЕРСИЙ OKHTTP

После чтения этого сообщения в блоге Я смог изменить концепцию для использования с OkHttp. Вы должны использовать хотя бы версию 2.0, если хотите избежать использования глобального контекста SSL.

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

1. Создание TrustStore

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

#!/bin/bash

if [ "$#" -ne 3 ]; then
  echo "Usage: importcert.sh <CA cert PEM file> <bouncy castle jar> <keystore pass>"
  exit 1
fi

CACERT=$1
BCJAR=$2
SECRET=$3

TRUSTSTORE=mytruststore.bks
ALIAS=`openssl x509 -inform PEM -subject_hash -noout -in $CACERT`

if [ -f $TRUSTSTORE ]; then
    rm $TRUSTSTORE || exit 1
fi

echo "Adding certificate to $TRUSTSTORE..."
keytool -import -v -trustcacerts -alias $ALIAS \
      -file $CACERT \
      -keystore $TRUSTSTORE -storetype BKS \
      -providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
      -providerpath $BCJAR \
      -storepass $SECRET

echo "" 
echo "Added '$CACERT' with alias '$ALIAS' to $TRUSTSTORE..."

Для запуска этого script вам понадобятся 3 вещи:

  • Убедитесь, что keytool (входит в Android SDK) находится на вашем $PATH.
  • Удостоверьтесь, что у вас есть последняя версия файла jar файла BouncyCastle в том же каталоге, что и script. (Загрузить здесь)
  • Сертификат, который вы хотите вывести.

Теперь запустите script

./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass

Введите "да", чтобы доверять сертификату, и когда полный mytruststore.bks будет сгенерирован в вашем текущем каталоге.

2. Примените свой TrustStore к проекту Android

Создайте каталог raw в папке res. Скопируйте mytruststore.bks здесь.

Теперь вот очень простой класс, который связывает ваш сертификат с OkHttp

import android.content.Context;
import android.util.Log;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.InputStream;
import java.io.Reader;
import java.security.KeyStore;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;


/**
 * Created by martin on 02/06/14.
 */
public class Pinning {

    Context context;
    public static String TRUST_STORE_PASSWORD = "your_secret";
    private static final String ENDPOINT = "https://api.yourdomain.com/";

    public Pinning(Context c) {
        this.context = c;
    }

    private SSLSocketFactory getPinnedCertSslSocketFactory(Context context) {
        try {
            KeyStore trusted = KeyStore.getInstance("BKS");
            InputStream in = context.getResources().openRawResource(R.raw.mytruststore);
            trusted.load(in, TRUST_STORE_PASSWORD.toCharArray());
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
                    TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(trusted);
            sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            Log.e("MyApp", e.getMessage(), e);
        }
        return null;
    }

    public void makeRequest() {
        try {
            OkHttpClient client = new OkHttpClient();
            client.setSslSocketFactory(getPinnedCertSslSocketFactory(context));

            Request request = new Request.Builder()
                    .url(ENDPOINT)
                    .build();

            Response response = client.newCall(request).execute();

            Log.d("MyApp", response.body().string());

        } catch (Exception e) {
            Log.e("MyApp", e.getMessage(), e);

        }
    }
}

Как вы можете видеть, мы создаем экземпляр нового экземпляра OkHttpClient и вызываем setSslSocketFactory, передавая в SSLSocketFactory наш собственный магазин доверия. Убедитесь, что вы установили TRUST_STORE_PASSWORD пароль, который вы передали в оболочку script. Ваш экземпляр OkHttp теперь должен принимать только указанный вами сертификат.

Ответ 2

Это проще, чем я думал с помощью OkHttp.

Выполните следующие действия:

1. Получить общедоступные ключи sha1. Документация OkHttp дает нам четкий способ сделать это в комплекте с образцом кода. В случае, если он уходит, здесь он вставлен ниже:

Например, чтобы вывести https://publicobject.com, начните с разбитого Конфигурация:

String hostname = "publicobject.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
    .add(hostname, "sha1/BOGUSPIN")
    .build();
OkHttpClient client = new OkHttpClient();
client.setCertificatePinner(certificatePinner);

Request request = new Request.Builder()
    .url("https://" + hostname)
    .build();
client.newCall(request).execute();   

Как и ожидалось, это приводит к сбою исключения сертификата:

javax.net.ssl.SSLPeerUnverifiedException: отказ при получении сертификата!
Цепочка сертификатов сверстников: sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw =: CN = publicobject.com, OU = PositiveSSL sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw =: CN = проверка домена COMODO RSA Secure Server CA sha1/blhOM3W9V/bVQhsWAcLYwPU6n24 =: CN = Сертификационный центр COMODO RSA sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c =: CN = AddTrust Внешний корень CA

Прикрепленные сертификаты для publicobject.com:

sha1/BOGUSPIN
на com.squareup.okhttp.CertificatePinner.check(CertificatePinner.java)
на com.squareup.okhttp.Connection.upgradeToTls(Connection.java)
на com.squareup.okhttp.Connection.connect(Connection.java)
на com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java)

Последовательность действий, вставив хэши открытого ключа из исключения в конфигурацию сервера пиннера:

Примечание: если вы делаете это на Android, вы получите отдельное исключение, если вы делаете это в потоке пользовательского интерфейса, поэтому убедитесь, что вы делаете это в фоновом потоке.

2. Настройте клиента OkHttp:

OkHttpClient client = new OkHttpClient();
client.setCertificatePinner(new CertificatePinner.Builder()
       .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
       .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
       .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
       .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
       .build());

Это все, что нужно!

Ответ 3

Чтобы расширить пример исходный код @Michael-barany поделился, я провел некоторое тестирование и, похоже, является вводящим в заблуждение образцом кода. В образце кода исключение отметило 4 шай-хэша из исключения цепочки сертификатов:

javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Peer certificate chain:
sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=: CN=publicobject.com, OU=PositiveSSL
sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=: CN=COMODO RSA Domain Validation Secure Server CA
sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=: CN=COMODO RSA Certification Authority
sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=: CN=AddTrust External CA Root

а затем добавили все 4 хэша открытых ключей ключа sha1 в CertificatePinner Builder.

CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
.add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
.add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
.add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
.build();

Однако, учитывая тесты, которые я выполнил и просмотрел код, будет интерпретирован только первый действительный, поэтому вам лучше всего включить только один из возвращенных хешей. Вы можете использовать наиболее специфичный хэш "DmxUShsZuNiqPQsX2Oi9uv2sCnw" для точного сертификата сайта... или вы можете использовать самый широкий хэш "T5x9IXmcrQ7YuQxXnxoCmeeQ84c" для корня CA на основе желаемой позы безопасности.

Ответ 4

Если у вас нет доступа к домену (например, ограниченный доступ) и невозможно проверить хеш-фикцию, но у вас есть файл сертификата, вы можете использовать openssl для его получения:

openssl x509 -in cert.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Ответ 5

Я нашел пример, упомянутый в разделе Неизвестный центр сертификации этой ссылки developer.android.com/training/articles/security-ssl, очень полезный.

SSLSocketFactory, возвращаемый в context.getSocketFactory(), может затем использоваться для установки в OkHttpClient в методе setSslSocketFactory().

Примечание. В разделе Неизвестный центр сертификации также упоминается ссылка для загрузки файла сертификата для его использования и проверки этого кода.

Вот пример, который я написал для получения SSLSocketFactory

private SSLSocketFactory getSslSocketFactory() {
    try {
        // Load CAs from an InputStream
        // (could be from a resource or ByteArrayInputStream or ...)
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        // From https://www.washington.edu/itconnect/security/ca/load-der.crt
        InputStream caInput = getApplicationContext().getResources().openRawResource(R.raw.loadder);
        Certificate ca = null;
        try {
            ca = cf.generateCertificate(caInput);
            System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
        } catch (CertificateException e) {
            e.printStackTrace();
        } finally {
            caInput.close();
        }

        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        if (ca == null)
            return 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);

        return context.getSocketFactory();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (KeyStoreException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

Позже я просто устанавливаю это на OkHttpClient, как это

httpClient.setSslSocketFactory(sslSocketFactory);

а затем сделайте вызов https

httpClient.newCall(requestBuilder.build()).enqueue(callback);