我想我需要创建一个新的SSL套接字工厂?此外,出于显而易见的原因,我不想使用全局SSL上下文(https://github.com/square/okhttp/issues/184).
谢谢!
编辑:
从okhttp 2.1.0开始,您可以非常轻松地修复证书.
请参阅此处的源代码以开始使用
这比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域验证安全服务器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)
在玉米.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());
这里的所有都是它的!
如果您无法访问域(例如限制访问)并且无法测试虚假哈希,但是您有证书文件,则可以使用openssl来检索它:
openssl x509 -in cert.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
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的当前实例,并更改该实例,以便它只接受来自指定证书的证书.如果您想要接受其他证书(例如来自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上.
确保您在与脚本相同的目录中下载最新的BouncyCastle jar文件.(在这里下载)
你想要的证书.
现在运行脚本
./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass
键入"是"以信任证书,并在完成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
为传递给shell脚本的密码.您的OkHttp实例现在应该只接受您指定的证书.
为了扩展示例源代码 @ 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();
但是,给定测试我已经执行并查看代码,只会解释第一个有效的代码,因此您最适合仅包含返回的其中一个哈希值.您可以使用最具体的散列"DmxUShsZuNiqPQsX2Oi9uv2sCnw"来获取精确的站点证书...或者您可以根据所需的安全状态使用最宽泛的散列"T5x9IXmcrQ7YuQxXnxoCmeeQ84c"作为CA Root.