我认为我需要创建一个新的SSL Socket Factory?同时,出于明显原因,我不想使用全局SSL上下文(https://github.com/square/okhttp/issues/184)。
谢谢!
编辑:
从okhttp 2.1.0开始,您可以非常轻松地固定证书。
请参见这里的源代码以开始。
我认为我需要创建一个新的SSL Socket Factory?同时,出于明显原因,我不想使用全局SSL上下文(https://github.com/square/okhttp/issues/184)。
谢谢!
编辑:
从okhttp 2.1.0开始,您可以非常轻松地固定证书。
请参见这里的源代码以开始。
OKHTTP 3.0 具有证书锁定的内置支持(built-in support)。开始使用以下代码:
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实例,并更改该实例,以使其仅接受从指定证书中指定的证书。如果您想要接受其他证书(例如来自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..."
运行此脚本需要以下三个条件:
keytool
(包含在Android SDK中)在您的 $PATH 中。现在运行脚本即可。
./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass
输入“yes”以信任证书,完整后将在当前目录生成mytruststore.bks
。
2. 将您的TrustStore应用于Android项目
在res
文件夹下创建一个名为raw
的目录。将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
设置为您传递到 shell 脚本中的密码。现在,您的 OkHttp 实例应仅接受您指定的证书。
OkHttpClient
。创建一个单一实例并在每个请求中重复使用它。 - Jake Whartonkeytool
通常用于存储私钥而不是证书。不幸的是,无法禁用密码,因此请使用任何您喜欢的密码。 - Martin Konecny使用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: 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
publicobject.com 的固定证书:
sha1/BOGUSPIN
at com.squareup.okhttp.CertificatePinner.check(CertificatePinner.java)
at com.squareup.okhttp.Connection.upgradeToTls(Connection.java)
at com.squareup.okhttp.Connection.connect(Connection.java)
at 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());
就是这样了!
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
- Fonix如果您无法访问该域(例如受限制),并且无法测试伪哈希值,但您拥有证书文件,则可以使用openssl检索它:
openssl x509 -in cert.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
openssl x509 -inform der -in certificate.cer -fingerprint -sha256 -noout | openssl dgst -sha256 -binary | openssl enc -base64
。 - Tom为了详细说明@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
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();
我在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;
}
httpClient.setSslSocketFactory(sslSocketFactory);
然后进行https调用
httpClient.newCall(requestBuilder.build()).enqueue(callback);