53

新しい SSL Socket Factory を作成する必要があると思いますか? また、明らかな理由から、グローバル SSL コンテキスト ( https://github.com/square/okhttp/issues/184 )を使用したくありません。

ありがとう!

編集:

okhttp 2.1.0 以降では、証明書を非常に簡単にピン留めできます。

開始するには、こちらのソースコードを参照してください

4

5 に答える 5

89

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

ここから先はすべて OKHTTP の古い (2.x) バージョン用です

このブログ投稿を読んだ後、OkHttp で使用するために概念を変更することができました。グローバル SSL コンテキストの使用を避けたい場合は、少なくともバージョン 2.0 を使用する必要があります。

この変更は、OkHttp の現在のインスタンスにのみ適用され、指定された証明書からの証明書のみを受け入れるようにそのインスタンスを変更します。他の証明書 (Twitter からの証明書など) を受け入れたい場合は、以下で説明する変更を行わずに新しい OkHttp インスタンスを作成するだけです。

1.トラストストアの作成

証明書を固定するには、まずこの証明書を含むトラストストアを作成する必要があります。トラストストアを作成するには、nelenkov の次の便利なスクリプトを目的に合わせて少し変更して使用します。

#!/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..."

このスクリプトを実行するには、次の 3 つが必要です。

  1. keytool(Android SDK に含まれています) が $PATH にあることを確認してください。
  2. スクリプトと同じディレクトリに最新の BouncyCastle jar ファイルがダウンロードされていることを確認してください。(ダウンロードはこちら)
  3. ピン留めする証明書。

スクリプトを実行します

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

「yes」と入力して証明書を信頼します。完了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_PASSWORDOkHttp インスタンスは、指定した証明書のみを受け入れるようになります。

于 2014-06-03T05:10:39.030 に答える
27

これは、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: 証明書の固定に失敗しました!
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 外部 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)

例外からの公開鍵ハッシュを証明書 pinner の構成に貼り付けてフォローアップします。

補足: Android でこれを行う場合、UI スレッドでこれを行うと別の例外が発生するため、必ずバックグラウンド スレッドで行うようにしてください。

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

それだけです!

于 2015-08-18T21:34:04.713 に答える
6

@Michael-barany が共有したサンプルソース コードを拡張するために、いくつかのテストを行ったところ、誤解を招くコード サンプルのようです。コードのサンプルでは、​​証明書チェーン例外からの 4 つの sha1 ハッシュが例外として記録されています。

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

ただし、実行してコードを確認したテストでは、最初の有効なもののみが解釈されるため、返されたハッシュの 1 つだけを含めるのが最適です。正確なサイト証明書に最も具体的なハッシュ「DmxUShsZuNiqPQsX2Oi9uv2sCnw」を使用できます...または、希望するセキュリティ体制に基づいて、CA ルートに最も広範なハッシュ「T5x9IXmcrQ7YuQxXnxoCmeeQ84c」を使用できます。

于 2015-05-11T03:01:49.077 に答える
3

このリンクdeveloper.android.com/training/articles/security-sslの不明な認証局セクションに記載されている例が非常に役立つことがわかりました。

context.getSocketFactory() で返された SSLSocketFactory を使用して、setSslSocketFactory() メソッドで OkHttpClient に設定できます。

注 : [不明な認証局] セクションには、このコードを使用して確認するための証明書ファイルをダウンロードするためのリンクも記載されています。

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);
于 2015-10-13T14:51:11.463 に答える