简体   繁体   English

Android SSL 证书固定

[英]Android SSL Certificate pinning

I know there are many questions regarding pinning certificates in Android but I can't find what I am looking for...我知道有很多关于在 Android 中固定证书的问题,但我找不到我要找的东西......

I subclass SSLSocketFactory and override the checkServerTrusted() method.checkServerTrusted() SSLSocketFactory并覆盖了checkServerTrusted()方法。 In this method, I do the following:在这种方法中,我执行以下操作:

CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate ca = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(PUB_KEY.getBytes("UTF-8")));
for (X509Certificate cert : chain) {
    // Verifing by public key
    cert.verify(ca.getPublicKey());                      
}

One of the items in the chain verifies and the other doesn't (which throws an Exception ).链中的一项进行验证,而另一项不进行验证(这会引发Exception )。 I guess I can't get a grasp of how certificate chains work.我想我无法掌握证书链的工作原理。

Should the same public certificate verify with all certificates in the chain?同一个公共证书是否应该与链中的所有证书一起验证?

The easiest way I found to implement certificate pinning on Android is to use the OkHttp library.我发现在 Android 上实现证书固定的最简单方法是使用OkHttp库。

Here is anexcerpt from the documentation :以下是文档摘录

By default, OkHttp trusts the certificate authorities of the host platform.默认情况下,OkHttp 信任宿主平台的证书颁发机构。 This strategy maximizes connectivity, but it is subject to certificate authority attacks such as the 2011 DigiNotar attack .此策略可最大限度地提高连接性,但会受到证书颁发机构攻击,例如2011 DigiNotar 攻击 It also assumes your HTTPS servers' certificates are signed by a certificate authority.它还假定您的 HTTPS 服务器的证书是由证书颁发机构签署的。

Use CertificatePinner to constrain which certificate authorities are trusted.使用CertificatePinner来限制受信任的证书颁发机构。 Certificate pinning increases security, but limits your server team's abilities to update their TLS certificates.证书锁定可提高安全性,但会限制您的服务器团队更新其 TLS 证书的能力。 Do not use certificate pinning without the blessing of your server's TLS administrator!未经服务器 TLS 管理员的许可,请勿使用证书锁定!

  public CertificatePinning() {
    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());
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/robots.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    for (Certificate certificate : response.handshake().peerCertificates()) {
      System.out.println(CertificatePinner.pin(certificate));
    }
  }

And if you need to support a self-signed certificate, the answer to Does OkHttp support accepting self-signed SSL certs?如果您需要支持自签名证书,那么OkHttp 支持接受自签名 SSL 证书吗? will guide you.会指导你。

Should the same public certificate verify with all certificates in the chain?同一个公共证书是否应该与链中的所有证书一起验证?

Answer:- No.答案:- 没有。

Most public CAs don't sign server certificates directly.大多数公共 CA 不直接签署服务器证书。 Instead, they use their main CA certificate, referred to as the root CA , to sign intermediate CAs.相反,他们使用他们的主 CA 证书(称为root CA )来签署中间 CA。 They do this so the root CA can be stored offline to reduce risk of compromise.他们这样做是为了让根 CA 可以离线存储,以降低被入侵的风险。 However, operating systems like Android typically trust only root CAs directly, which leaves a short gap of trust between the server certificate—signed by the intermediate CA—and the certificate verifier, which knows the root CA.但是,像 Android 这样的操作系统通常trust only root CAs直接trust only root CAs ,这在服务器证书(由中间 CA 签名)与知道根 CA 的证书验证者之间留下了短暂的信任差距。

To solve this, the server doesn't send the client only it's certificate during the SSL handshake, but a chain of certificates from the server CA through any intermediates necessary to reach a trusted root CA.为了解决这个问题,服务器在 SSL 握手期间不会只向客户端发送它的证书,而是从服务器 CA 通过到达受信任的根 CA 所需的任何中间件的证书链。

Check this link for more information.查看此链接以获取更多信息。 Hope this will help users.希望这会对用户有所帮助。

Certificate and Public Key Pinning (aka Certificate Pinning) in a nutshell -证书和公钥固定(又名证书固定)简而言之 -

Normally, an app trusts all pre-installed CAs.通常,应用程序信任所有预安装的 CA。 If any of these CAs were to issue a fraudulent certificate, the app would be at risk from a man-in-the-middle attack( aka eavesdropping ).如果这些 CA 中的任何一个要颁发欺诈性证书,则该应用程序将面临中间人攻击(又名窃听)的风险。 Some apps choose to limit the set of certificates they accept by either limiting the set of CAs they trust or by certificate pinning.一些应用程序选择通过限制他们信任的 CA 集或通过证书锁定来限制他们接受的证书集。 Certificate pinning is done by providing a set of certificates by hash of the public key.证书固定是通过公钥散列提供一组证书来完成的。 Certificate Pinning is a method that depends on server certificate verification on the client-side.证书固定是一种依赖于客户端服务器证书验证的方法。

Below are the 3 ways to implement Certificate Pinning on Android -以下是在 Android 上实现证书锁定的 3 种方法 -


In particular to your question, you could configure the certificates by hash of the public key in NSC using <pin-set> tag.特别是对于您的问题,您可以使用<pin-set>标签通过 NSC 中公钥的散列来配置证书。

Note that, when using certificate pinning, you should always include a backup key so that if you are forced to switch to new keys or change CAs (when pinning to a CA certificate or an intermediate of that CA), your app's connectivity is unaffected.请注意,在使用证书固定时,您应该始终包含一个备份密钥,以便如果您被迫切换到新密钥或更改 CA(固定到 CA 证书或该 CA 的中间证书时),您的应用程序的连接不受影响。 Otherwise, you must push out an update to the app to restore connectivity.否则,您必须向应用程序推送更新以恢复连接。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <pin-set expiration="2018-01-01">
            <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
            <!-- backup pin -->
            <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
        </pin-set>
    </domain-config>
</network-security-config>

Your Questions and Doubts您的问题和疑虑

Should the same public certificate verify with all certificates in the chain?同一个公共证书是否应该与链中的所有证书一起验证?

No, because each certificate in the chain (root, intermediate and leaf certificate)) was signed with a different private/public key pair.不,因为链中的每个证书(根证书、中间证书和叶证书)都使用不同的私钥/公钥对进行签名。

One of the items in the chain verifies and the other doesn't (which throws an Exception).链中的一项验证,另一个不验证(这会引发异常)。 I guess I can't get a grasp of how certificate chains work.我想我无法掌握证书链的工作原理。

That's because your certificate is the leaf one, thus you can only verify your public key against it, not against the root and intermediate certificate(s).那是因为您的证书是叶子证书,因此您只能根据它来验证您的公钥,而不能针对根证书和中间证书进行验证。

A Code Approach代码方法

I subclass SSLSocketFactory and override the checkServerTrusted() method.我继承了 SSLSocketFactory 并覆盖了 checkServerTrusted() 方法。

If you really want to code it yourself I would suggest you to use instead the built-in OkHttp Ceritficate Pinner, that you can build like this:如果您真的想自己编写代码,我建议您改用内置的 OkHttp Ceritficate Pinner,您可以像这样构建:

import okhttp3.CertificatePinner;

public class OkHttpPinnerService {

    // true if the Approov SDK initialized okay
    private boolean initialized;

    // cached OkHttpClient to use or null if not set
    private OkHttpClient okHttpClient;

    public synchronized OkHttpClient getOkHttpClient() {
        if (okHttpClient == null) {
            // build a new OkHttpClient on demand
            if (initialized) {
                // build the pinning configuration
                CertificatePinner.Builder pinBuilder = new CertificatePinner.Builder();
                Map<String, List<String>> pins = YourConfig.getPins("public-key-sha256");
                for (Map.Entry<String, List<String>> entry : pins.entrySet()) {
                    for (String pin : entry.getValue())
                        pinBuilder = pinBuilder.add(entry.getKey(), "sha256/" + pin);
                }

                // build the OkHttpClient with the correct pins preset and ApproovTokenInterceptor
                Log.i(TAG, "Building new Approov OkHttpClient");
                okHttpClient = okHttpBuilder.certificatePinner(pinBuilder.build()).build();
            } else {
                // if the Approov SDK could not be initialized then we can't pin or add Approov tokens
                Log.e(TAG, "Cannot build Approov OkHttpClient due to initialization failure");
                okHttpClient = okHttpBuilder.build();
            }
        }

        return okHttpClient;
    }    
}

The code was not tested for syntax errors or logical correctness.代码没有经过语法错误或逻辑正确性测试。 I just copied it from this repo and slightly adapted it.我只是从这个 repo 中复制了它并稍微修改了它。

A Codeless Approach无代码方法

Since Android API 24 it is possible to implement certificate pinning to the public key hash via the built-in security config file, that doesn't require any code to be written, just a properly configured network_security_config.xml file added to your project.由于 Android API 24 可以通过内置的安全配置文件将证书固定到公钥哈希,这不需要编写任何代码,只需将正确配置的network_security_config.xml文件添加到您的项目中即可。

To avoid mistakes while building the network_security_config.xml file I recommend you to use the Mobile Certificate Pinning Generator to extract the live pin being used by the domain you want to pin against and to build for you the correct configuration.为避免在构建network_security_config.xml文件时出错,我建议您使用移动证书锁定生成器来提取要锁定的域所使用的实时 pin,并为您构建正确的配置。 For example:例如:

从将域添加到固定

安卓网络安全配置

Now just copy paste the generated configuration the network_security_config.xml file in your project and add this same file to the AndroifManifest.xml .现在只需将生成的配置复制粘贴到项目中的network_security_config.xml文件中,并将该文件添加到AndroifManifest.xml Just follow the instructions on the page.只需按照页面上的说明操作即可。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM