如何使用Square OKHTTP固定证书?

Mic*_*any 45 android okhttp

我想我需要创建一个新的SSL套接字工厂?此外,出于显而易见的原因,我不想使用全局SSL上下文(https://github.com/square/okhttp/issues/184).

谢谢!

编辑:

从okhttp 2.1.0开始,您可以非常轻松地修复证书.

请参阅此处的源代码以开始使用

Mar*_*cny 72

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();
Run Code Online (Sandbox Code Playgroud)

这将失败,因为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)
Run Code Online (Sandbox Code Playgroud)

确保将这些添加到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();
Run Code Online (Sandbox Code Playgroud)

这里的所有东西都是OKHTTP的旧版本(2.x)版本

阅读此博客文章后,我能够修改与OkHttp一起使用的概念.如果要避免使用全局SSL上下文,则应至少使用2.0版.

此修改仅适用于OkHttp的当前实例,并更改该实例,以便它只接受来自指定证书的证书.如果您想要接受其他证书(例如来自Twitter的证书),您只需创建一个新的OkHttp实例,而不需要下面描述的修改.

1.创建TrustStore

为了固定证书,首先需要创建一个包含此证书的信任库.为了创建信任库,我们将使用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..."
Run Code Online (Sandbox Code Playgroud)

要运行此脚本,您需要3件事:

  1. 确保keytool(包含在Android SDK中)位于$ PATH上.
  2. 确保您在与脚本相同的目录中下载最新的BouncyCastle jar文件.(在这里下载)
  3. 你想要的证书.

现在运行脚本

./gentruststore.sh your_cert.pem bcprov-jdk15on-150.jar your_secret_pass
Run Code Online (Sandbox Code Playgroud)

键入"是"以信任证书,并在完成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);

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我们实例化了一个新的实例OkHttpClient和调用setSslSocketFactory,并SSLSocketFactory使用我们的自定义信任库传递.确保设置TRUST_STORE_PASSWORD为传递给shell脚本的密码.您的OkHttp实例现在应该只接受您指定的证书.

  • 请不要为每个请求创建一个`OkHttpClient`.创建**单个实例**并为每个请求重复使用它. (19认同)
  • 这是示例代码,当然您不会为每个请求执行此操作 - 请注意,url也是硬编码的. (3认同)
  • 据我了解,证书不是您应该保密的东西。您的服务器在每次 SSL 握手时将其发送到客户端。在这种情况下,需要密码,因为“keytool”通常用于存储私钥,而不是证书。不幸的是,没有办法禁用密码,所以使用任何你喜欢的。 (3认同)
  • java.security.cert.CertificateException:java.security.cert.CertPathValidatorException:未找到证书路径的信任锚。 (2认同)
  • @SebastianRoth openssl x509 -inform der -in <certificate> -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha1 -binary | 如果你正在寻找的话,openssl enc -base64会在你公钥的base64中输出sha1 ... (2认同)

spi*_*ce7 26

这比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();   
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这会因证书固定异常而失败:

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());
Run Code Online (Sandbox Code Playgroud)

这里的所有都是它的!

  • 我得到的是这个异常而不是 sha256 的异常,知道如何补救吗?`javax.net.ssl.SSLHandshakeException:java.security.cert.CertPathValidatorException:未找到证书路径的信任锚。` (2认同)

小智 10

如果您无法访问域(例如限制访问)并且无法测试虚假哈希,但是您有证书文件,则可以使用openssl来检索它:

openssl x509 -in cert.pem -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
Run Code Online (Sandbox Code Playgroud)

  • 如果您的证书已经是 der(例如,如果从 chrome 获取),则命令是 `openssl x509 -inform der -in certificate.cer -fingerprint -sha256 -noout | openssl dgst -sha256 -binary | openssl enc -base64` (2认同)

小智 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
Run Code Online (Sandbox Code Playgroud)

然后将所有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();
Run Code Online (Sandbox Code Playgroud)

但是,给定测试我已经执行并查看代码,只会解释第一个有效的代码,因此您最适合仅包含返回的其中一个哈希值.您可以使用最具体的散列"DmxUShsZuNiqPQsX2Oi9uv2sCnw"来获取精确的站点证书...或者您可以根据所需的安全状态使用最宽泛的散列"T5x9IXmcrQ7YuQxXnxoCmeeQ84c"作为CA Root.