新しいSSLソケットファクトリを作成する必要があると思いますか?また、明らかな理由から、グローバルSSLコンテキスト( https://github.com/square/okhttp/issues/184 )を使用したくありません。
ありがとう!
編集:
Okhttp 2.1.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で使用する概念を変更することができました。グローバルSSLコンテキストの使用を避けたい場合は、少なくともバージョン2.0を使用する必要があります。
この変更は、OkHttpの現在のインスタンスにのみ適用され、そのインスタンスを変更してonly指定された証明書からの証明書を受け入れます。他の証明書(Twitterの証明書など)を受け入れたい場合は、以下で説明する変更を行わずに、新しいOkHttpインスタンスを作成するだけです。
証明書を固定するには、最初にこの証明書を含むトラストストアを作成する必要があります。トラストストアを作成するには、この目的のためにわずかに変更された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つのことが必要です。
keytool
(Android SDK)に含まれる)が$ PATHにあることを確認してください。スクリプトを実行します
./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass
「yes」と入力して証明書を信頼します。完了すると、mytruststore.bks
が現在のディレクトリに生成されます。
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
をシェルスクリプトに渡したパスワードに設定してください。 OkHttpインスタンスは、指定した証明書のみを受け入れるようになります。
これは、OkHttpで思っていたよりも簡単です。
次の手順を実行します:
1。 sha1の公開キーを取得します。OkHttp documentation を使用すると、サンプルコードを使用してこれを明確に行うことができます。消えた場合は、ここに貼り付けます:
たとえば、 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/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で実行している場合、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());
それだけです!
ドメインへのアクセス権がなく(アクセス制限など)、偽のハッシュをテストできないが、証明書ファイルがある場合は、opensslを使用して取得できます。
openssl x509 -in cert.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
サンプル ソースコード @ Michael-barany sharedを拡張するために、いくつかのテストを行いましたが、誤解を招くコードサンプルのようです。サンプルコードでは、例外は証明書チェーンの例外からの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」を使用できます。
このリンクの不明な認証局セクションで言及されている例を見つけました 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);