All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.undefinedlabs.scope.deps.okhttp3.CertificatePinner Maven / Gradle / Ivy

Go to download

Scope is a APM for tests to give engineering teams unprecedented visibility into their CI process to quickly identify, troubleshoot and fix failed builds. This artifact contains dependencies for Scope.

There is a newer version: 0.14.0-beta.2
Show newest version
package com.undefinedlabs.scope.deps.okhttp3;

import com.undefinedlabs.scope.deps.okhttp3.internal.tls.CertificateChainCleaner;
import com.undefinedlabs.scope.deps.okio.ByteString;

import javax.net.ssl.SSLPeerUnverifiedException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;

import static com.undefinedlabs.scope.deps.okhttp3.internal.Util.equal;

public final class CertificatePinner {
    public static final CertificatePinner DEFAULT = new Builder().build();

    private final Set pins;
    private final CertificateChainCleaner certificateChainCleaner;

    CertificatePinner(Set pins, CertificateChainCleaner certificateChainCleaner) {
        this.pins = pins;
        this.certificateChainCleaner = certificateChainCleaner;
    }

    @Override public boolean equals(Object other) {
        if (other == this) return true;
        return other instanceof CertificatePinner
                && (equal(certificateChainCleaner, ((CertificatePinner) other).certificateChainCleaner)
                && pins.equals(((CertificatePinner) other).pins));
    }

    @Override public int hashCode() {
        int result = certificateChainCleaner != null ? certificateChainCleaner.hashCode() : 0;
        result = 31 * result + pins.hashCode();
        return result;
    }

    /**
     * Confirms that at least one of the certificates pinned for {@code hostname} is in {@code
     * peerCertificates}. Does nothing if there are no certificates pinned for {@code hostname}.
     * OkHttp calls this after a successful TLS handshake, but before the connection is used.
     *
     * @throws SSLPeerUnverifiedException if {@code peerCertificates} don't match the certificates
     * pinned for {@code hostname}.
     */
    public void check(String hostname, List peerCertificates)
            throws SSLPeerUnverifiedException {
        List pins = findMatchingPins(hostname);
        if (pins.isEmpty()) return;

        if (certificateChainCleaner != null) {
            peerCertificates = certificateChainCleaner.clean(peerCertificates, hostname);
        }

        for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
            X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);

            // Lazily compute the hashes for each certificate.
            ByteString sha1 = null;
            ByteString sha256 = null;

            for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
                Pin pin = pins.get(p);
                if (pin.hashAlgorithm.equals("sha256/")) {
                    if (sha256 == null) sha256 = sha256(x509Certificate);
                    if (pin.hash.equals(sha256)) return; // Success!
                } else if (pin.hashAlgorithm.equals("sha1/")) {
                    if (sha1 == null) sha1 = sha1(x509Certificate);
                    if (pin.hash.equals(sha1)) return; // Success!
                } else {
                    throw new AssertionError();
                }
            }
        }

        // If we couldn't find a matching pin, format a nice exception.
        StringBuilder message = new StringBuilder()
                .append("Certificate pinning failure!")
                .append("\n  Peer certificate chain:");
        for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
            X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
            message.append("\n    ").append(pin(x509Certificate))
                    .append(": ").append(x509Certificate.getSubjectDN().getName());
        }
        message.append("\n  Pinned certificates for ").append(hostname).append(":");
        for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
            Pin pin = pins.get(p);
            message.append("\n    ").append(pin);
        }
        throw new SSLPeerUnverifiedException(message.toString());
    }

    /** @deprecated replaced with {@link #check(String, List)}. */
    public void check(String hostname, Certificate... peerCertificates)
            throws SSLPeerUnverifiedException {
        check(hostname, Arrays.asList(peerCertificates));
    }

    /**
     * Returns list of matching certificates' pins for the hostname. Returns an empty list if the
     * hostname does not have pinned certificates.
     */
    List findMatchingPins(String hostname) {
        List result = Collections.emptyList();
        for (Pin pin : pins) {
            if (pin.matches(hostname)) {
                if (result.isEmpty()) result = new ArrayList<>();
                result.add(pin);
            }
        }
        return result;
    }

    /** Returns a certificate pinner that uses {@code certificateChainCleaner}. */
    CertificatePinner withCertificateChainCleaner(CertificateChainCleaner certificateChainCleaner) {
        return equal(this.certificateChainCleaner, certificateChainCleaner)
                ? this
                : new CertificatePinner(pins, certificateChainCleaner);
    }

    /**
     * Returns the SHA-256 of {@code certificate}'s public key.
     *
     * 

In OkHttp 3.1.2 and earlier, this returned a SHA-1 hash of the public key. Both types are * supported, but SHA-256 is preferred. */ public static String pin(Certificate certificate) { if (!(certificate instanceof X509Certificate)) { throw new IllegalArgumentException("Certificate pinning requires X509 certificates"); } return "sha256/" + sha256((X509Certificate) certificate).base64(); } static ByteString sha1(X509Certificate x509Certificate) { return ByteString.of(x509Certificate.getPublicKey().getEncoded()).sha1(); } static ByteString sha256(X509Certificate x509Certificate) { return ByteString.of(x509Certificate.getPublicKey().getEncoded()).sha256(); } static final class Pin { private static final String WILDCARD = "*."; /** A hostname like {@code example.com} or a pattern like {@code *.example.com}. */ final String pattern; /** The canonical hostname, i.e. {@code EXAMPLE.com} becomes {@code example.com}. */ final String canonicalHostname; /** Either {@code sha1/} or {@code sha256/}. */ final String hashAlgorithm; /** The hash of the pinned certificate using {@link #hashAlgorithm}. */ final ByteString hash; Pin(String pattern, String pin) { this.pattern = pattern; this.canonicalHostname = pattern.startsWith(WILDCARD) ? HttpUrl.parse("http://" + pattern.substring(WILDCARD.length())).host() : HttpUrl.parse("http://" + pattern).host(); if (pin.startsWith("sha1/")) { this.hashAlgorithm = "sha1/"; this.hash = ByteString.decodeBase64(pin.substring("sha1/".length())); } else if (pin.startsWith("sha256/")) { this.hashAlgorithm = "sha256/"; this.hash = ByteString.decodeBase64(pin.substring("sha256/".length())); } else { throw new IllegalArgumentException("pins must start with 'sha256/' or 'sha1/': " + pin); } if (this.hash == null) { throw new IllegalArgumentException("pins must be base64: " + pin); } } boolean matches(String hostname) { if (pattern.startsWith(WILDCARD)) { int firstDot = hostname.indexOf('.'); return hostname.regionMatches(false, firstDot + 1, canonicalHostname, 0, canonicalHostname.length()); } return hostname.equals(canonicalHostname); } @Override public boolean equals(Object other) { return other instanceof Pin && pattern.equals(((Pin) other).pattern) && hashAlgorithm.equals(((Pin) other).hashAlgorithm) && hash.equals(((Pin) other).hash); } @Override public int hashCode() { int result = 17; result = 31 * result + pattern.hashCode(); result = 31 * result + hashAlgorithm.hashCode(); result = 31 * result + hash.hashCode(); return result; } @Override public String toString() { return hashAlgorithm + hash.base64(); } } /** Builds a configured certificate pinner. */ public static final class Builder { private final List pins = new ArrayList<>(); /** * Pins certificates for {@code pattern}. * * @param pattern lower-case host name or wildcard pattern such as {@code *.example.com}. * @param pins SHA-256 or SHA-1 hashes. Each pin is a hash of a certificate's Subject Public Key * Info, base64-encoded and prefixed with either {@code sha256/} or {@code sha1/}. */ public Builder add(String pattern, String... pins) { if (pattern == null) throw new NullPointerException("pattern == null"); for (String pin : pins) { this.pins.add(new Pin(pattern, pin)); } return this; } public CertificatePinner build() { return new CertificatePinner(new LinkedHashSet<>(pins), null); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy