简体   繁体   中英

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...

I subclass SSLSocketFactory and override the checkServerTrusted() method. 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 ). 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.

Here is anexcerpt from the documentation :

By default, OkHttp trusts the certificate authorities of the host platform. This strategy maximizes connectivity, but it is subject to certificate authority attacks such as the 2011 DigiNotar attack . It also assumes your HTTPS servers' certificates are signed by a certificate authority.

Use CertificatePinner to constrain which certificate authorities are trusted. Certificate pinning increases security, but limits your server team's abilities to update their TLS certificates. Do not use certificate pinning without the blessing of your server's TLS administrator!

  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? 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. Instead, they use their main CA certificate, referred to as the root CA , to sign intermediate CAs. They do this so the root CA can be stored offline to reduce risk of compromise. 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.

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.

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. 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 ). Some apps choose to limit the set of certificates they accept by either limiting the set of CAs they trust or by certificate pinning. 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 -


In particular to your question, you could configure the certificates by hash of the public key in NSC using <pin-set> tag.

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. 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.

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:

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.

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.

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. 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 . Just follow the instructions on the page.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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