OKHTTP 3.0 更新
OKHTTP 3.0 有内置支持 http://square.github.io/okhttp/3.x/okhttp/okhttp3/CertificatePinner.html用于固定证书。首先粘贴以下代码:
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
看完之后这篇博文 http://nelenkov.blogspot.co.uk/2011/12/using-custom-certificate-trust-store-on.html我能够修改与 OkHttp 一起使用的概念。如果您想避免使用全局 SSL 上下文,则应至少使用版本 2.0。
此修改仅适用于 OkHttp 的当前实例,并更改该实例,以便它only接受指定证书中的证书。如果您希望接受其他证书(例如来自 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..."
要运行这个脚本,你需要做三件事:
- 确保
keytool
(包含在 Android SDK 中)位于您的 $PATH 中。
- 确保您在与脚本相同的目录中下载了最新的 BouncyCastle jar 文件。 (下载here http://www.bouncycastle.org/download/bcprov-jdk15on-150.jar)
- 您想要固定的证书。
现在运行脚本
./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass
输入“yes”以信任该证书,完成后mytruststore.bks
将在您当前的目录中生成。
2. 将您的 TrustStore 应用到您的 Android 项目中
创建目录raw
在你的res
文件夹。复制mytruststore.bks
here.
现在这是一个非常简单的类,它将您的证书固定到 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
到您传递到 shell 脚本中的密码。您的 OkHttp 实例现在应该只接受您指定的证书。