Android Open Source - cert-pinner Cert Pinner






From Project

Back to project page cert-pinner.

License

The source code is released under:

Apache License

If you think the Android project cert-pinner listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package org.nick.certpinner;
//from   w w w  .  j a v  a 2 s .  c o m
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXCertPathValidatorResult;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.util.Base64;
import android.util.Log;

public class CertPinner {

    private static final String TAG = CertPinner.class.getSimpleName();

    private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6',
            '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
            'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
            'x', 'y', 'z' };

    private static final char[] UPPER_CASE_DIGITS = { '0', '1', '2', '3', '4',
            '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
            'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
            'V', 'W', 'X', 'Y', 'Z' };

    private static final String PINLIST_CERTIFICATE_KEY = "config_update_certificate";

    private static final String EXTRA_CONTENT_PATH = "CONTENT_PATH";
    private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH";
    private static final String EXTRA_SIGNATURE = "SIGNATURE";
    private static final String EXTRA_VERSION_NUMBER = "VERSION";

    public static final String PIN_LIST_SIGNING_CERT = ""
            + "MIICqDCCAZACCQDM8+2I0Zho4zANBgkqhkiG9w0BAQUFADAWMRQwEgYDVQQDDAtj"
            + "ZXJ0LXBpbm5lcjAeFw0xMjEyMDUwNzQ3MjhaFw0xMzEyMDUwNzQ3MjhaMBYxFDAS"
            + "BgNVBAMMC2NlcnQtcGlubmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC"
            + "AQEAvLQnLWQxBmguxvmJYASTCZjeSX107T/HKSA4memibzJBlDUKhC4Mak560eT8"
            + "XA6bfF2ituTddddzHsndzlKWOLq1qUpcuQS1kYJqJpIPx3j9x2lAITH06I0KspAL"
            + "EgrqUCLmTuI3irCGxm8SlNILmbI0ZX0U0kG13T75L6dKpF+t/af4xwHTvnp++6R1"
            + "DIo7AVd4waMYmX7DgC2NXHJSfow7ovpzbHPHQ8SJb14JTiowHS98VLcFbpW1UQLp"
            + "hiDI4wQy/V9NaHmXGtl2gdK8pACAvaym5SQZG9ETYS+joMWaGFdd29hIZEIarM7b"
            + "rVawQYYGzOJqUgLnG0kOIWO0swIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQBG299R"
            + "FuwjLkZ21MYydPDUnyUkWSTT+JmAROkjs/y3KgnKaOXD2uGd98HovXRtMtkplNNw"
            + "84j6eD4u4TeUxlmLzBQ6BtUGPIj8j2NLVyTTugJaUfJRcZVZRkWyIX9euJg9AqwG"
            + "pWPsAmGVOhxY/j3wfyQ3upCKJh/kkc4UkIY7WJunJYC1PPBjDNWUBF306buUMT9T"
            + "n2pOFCJpLdHmSRpKJKp7vidAH6JjHTbbapqCc5enmrBTnG9OziFhWoTmc4gw0/r9"
            + "bP7uvPGbwkn0cbpIncQvzG9Gf0w2Qxgbcr/djnD3XmiPCkZeFGKqRINt+rT+XCgO"
            + "e5++IXfVqRSDSmsH";

    private static final String PIN_LIST_SIGNING_KEY = ""
            + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8tCctZDEGaC7G"
            + "+YlgBJMJmN5JfXTtP8cpIDiZ6aJvMkGUNQqELgxqTnrR5PxcDpt8XaK25N1113Me"
            + "yd3OUpY4urWpSly5BLWRgmomkg/HeP3HaUAhMfTojQqykAsSCupQIuZO4jeKsIbG"
            + "bxKU0guZsjRlfRTSQbXdPvkvp0qkX639p/jHAdO+en77pHUMijsBV3jBoxiZfsOA"
            + "LY1cclJ+jDui+nNsc8dDxIlvXglOKjAdL3xUtwVulbVRAumGIMjjBDL9X01oeZca"
            + "2XaB0rykAIC9rKblJBkb0RNhL6OgxZoYV13b2EhkQhqsztutVrBBhgbM4mpSAucb"
            + "SQ4hY7SzAgMBAAECggEAMgdVJ6ybbsZqOGhp6mHsFaxIqpUvTcMN6zJWrz+IyBA7"
            + "4K4bRqXqtrhtyX37BfD9egBdJj4RFK/1HmGIg63Tk+C0TtifMpI0DQrVV7p7onfK"
            + "WHboAKT8+DaEcojL1pG8Q1itVJaXARcB9FP4SipR1wKu74U04vV24NxUNjUVDfS2"
            + "Yy7ekb8DXWvEI9ZgfHNAwQBZZ2rrKpX/8zrwCH9iKoAKfq0fmaoqezUcFI0b//1w"
            + "CY0BLqX7tksf6nFTBvR1fnIktKmzrfi70DwiBzVk5GWa85BimZXyB55m2eQ8SE+e"
            + "sM/UixqiQLjVa4L16AG2ggBhuujkDJGw4Nl2NWwPKQKBgQD6eahLnlQaWUlsHXs4"
            + "I7CxT5pu1qJE/Ii1Ih9iiyK6dF/0Co84O7TuIyNFeiOqShfVAAxydxQWAEq9ZXuK"
            + "8Vomdv61StHRE2MwxydAigyVSzt032Xct2X2HQCiDQ0wz1lwpm0+WPeiPMYRA2It"
            + "e4w7VEtQuC7DBWcJxXUSFwPDRQKBgQDA3bHEhIVHOzaPp4F5VeJB3PsMIakyLpo6"
            + "I19Jqe+fcBVtdyfR59xbmsd+1tgWqByNIqHIYQt+7yCxrPNkDtpmj1Q57L7Hq9HC"
            + "D2QWmDRNXPJgjkRXbqMbFviouTUOBBfxglIBh2Xwyl2UsyDl9mPE67agZzO0p/KU"
            + "yvEvo9pblwKBgFxLeeUrWUhAQFrTXjUoiZI8h+Zxtmd/OoysHy57oHdeLIFLZszM"
            + "y3W4guW2BPBZzwBQvUVsdX1J7EBv5Z8kIhjsXhzFjhzhbPprWB5jABH/H9CIBQvY"
            + "lHyk4TfVYVfr/8QPv09rDwy8IivguEuUK+8st3ft9mUsV3R1Sxc4Xc2VAoGAFVRv"
            + "ZJyDYO1bi2erGhA1hbM60IyoebRNukBPOYZhyfBLbl/PN5e89ySXC6AXJepRvgom"
            + "elLBQriPlRbblCVQYidX2VAliU+nUx8Aor8SibvN0n/pbwH9Z/GSbpaNF4+8Vilj"
            + "iGfBDnBTCS8GZGhrgEvRVswTG9e3LF2Fbw9gBuECgYEA8Iwjm4Ij58FMkRTv++0V"
            + "XT3NJEcmIvLgZDyedQ4eeFeg/pHPbowJzBAurx4xjcHFa90P8FXTNymIfITuzJXb"
            + "P44yGhcyRIvVo0FLehx8Fp9JhGgY7ZJzcR3VzsudIGeBZSax7K96DYAwjir4B/a3"
            + "tUxiEotCBVJAaEOH7vJ9SV4=";

    private Context ctx;

    private Signature signer;

    private KeyStore systemTrustStore;
    private PKIXParameters pkixParams;
    private CertPathValidator certPathValidator;
    private CertificateFactory certificateFactory;

    public CertPinner(Context ctx) {
        this.ctx = ctx;
        try {
            signer = Signature.getInstance("SHA512withRSA");

            systemTrustStore = loadSystemTrustStre();
            pkixParams = createPkixParams(systemTrustStore);
            certificateFactory = CertificateFactory.getInstance("X509");
            certPathValidator = CertPathValidator.getInstance("PKIX");
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    public void setPinListSigningCertificate(boolean overwrite)
            throws Exception {
        String cert = Settings.Secure.getString(ctx.getContentResolver(),
                PINLIST_CERTIFICATE_KEY);
        if (cert == null || overwrite) {
            X509Certificate c = createCertificate();
            Log.d(TAG, "Trying to put pin list signing certificate in "
                    + PINLIST_CERTIFICATE_KEY + ": "
                    + c.getSubjectDN().getName());
            putCertificate();
        }
        Log.d(TAG, PINLIST_CERTIFICATE_KEY + "= " + cert);
    }

    public String getNextVersion() throws Exception {
        int currentVersion = Integer.parseInt(readCurrentVersion());
        return Integer.toString(currentVersion + 1);
    }

    // cannot read w/ default perms. Use su
    private String readCurrentVersion() throws Exception {
        String metadataFilename = "/data/misc/keychain/metadata/version";
        if (SuShell.canGainSu(ctx)) {
            List<String> result = SuShell.runWithSu("ls " + metadataFilename);
            if (result.isEmpty()) {
                return "0";
            }

            if (result.get(0).contains("No such file")) {
                return "0";
            }

            result = SuShell.runWithSu("cat " + metadataFilename);
            // shouldn't really happen...
            if (result.isEmpty()) {
                return "0";
            }

            return result.get(0).trim();
        }
        // this will most probably fail, but try anyway
        else {
            File f = new File(metadataFilename);
            if (!f.exists()) {
                return "0";
            }

            return readFileAsString(f.getAbsolutePath());
        }
    }

    private void putCertificate() {
        Settings.Secure.putString(ctx.getContentResolver(),
                PINLIST_CERTIFICATE_KEY, PIN_LIST_SIGNING_CERT);
    }

    private static String getCurrentHash(String content) throws Exception {
        if (content == null) {
            return "0";
        }
        MessageDigest dgst = MessageDigest.getInstance("SHA512");
        byte[] encoded = content.getBytes();
        byte[] fingerprint = dgst.digest(encoded);
        return bytesToHexString(fingerprint, false);
    }

    public static String bytesToHexString(byte[] bytes, boolean upperCase) {
        char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
        char[] buf = new char[bytes.length * 2];
        int c = 0;
        for (byte b : bytes) {
            buf[c++] = digits[(b >> 4) & 0xf];
            buf[c++] = digits[b & 0xf];
        }
        return new String(buf);
    }

    public static String getHashOfCurrentContent() throws Exception {
        String content = readFileAsString("/data/misc/keychain/pins");
        return getCurrentHash(content);
    }

    public static String readFileAsString(String path) throws IOException {
        File f = new File(path);
        if (!f.exists()) {
            return null;
        }

        FileInputStream fis = new FileInputStream(path);
        byte[] data = new byte[fis.available()];
        fis.read(data);
        fis.close();

        return new String(data, "ASCII");
    }

    private static PrivateKey createKey() throws Exception {
        byte[] derKey = Base64.decode(PIN_LIST_SIGNING_KEY.getBytes(),
                Base64.DEFAULT);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(derKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        return keyFactory.generatePrivate(keySpec);
    }

    private X509Certificate createCertificate() throws Exception {
        return createCertificate(PIN_LIST_SIGNING_CERT);
    }

    private static X509Certificate createCertificate(String certStr)
            throws Exception {
        byte[] derCert = Base64.decode(certStr.getBytes(), Base64.DEFAULT);
        InputStream istream = new ByteArrayInputStream(derCert);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        return (X509Certificate) cf.generateCertificate(istream);
    }

    @SuppressWarnings("deprecation")
    @SuppressLint("WorldReadableFiles")
    public String makeTemporaryContentFile(String content) throws Exception {
        FileOutputStream fw = ctx.openFileOutput("content.txt",
                Context.MODE_WORLD_READABLE);
        fw.write(content.getBytes(), 0, content.length());
        fw.close();

        return ctx.getFilesDir() + "/content.txt";
    }

    public String createSignature(String content, String version,
            String requiredHash) throws Exception {
        signer.initSign(createKey());
        signer.update(content.trim().getBytes());
        signer.update(version.trim().getBytes());
        signer.update(requiredHash.getBytes());
        String sig = new String(Base64.encode(signer.sign(), Base64.DEFAULT));

        return sig;
    }

    public boolean verifySignature(String content, String version,
            String requiredPrevious, String signature, X509Certificate cert)
            throws Exception {
        signer.initVerify(cert);
        signer.update(content.trim().getBytes());
        signer.update(version.trim().getBytes());
        signer.update(requiredPrevious.trim().getBytes());

        return signer
                .verify(Base64.decode(signature.getBytes(), Base64.DEFAULT));
    }

    public void sendIntent(String contentPath, String version, String required,
            String sig) {
        Intent i = new Intent();
        i.setAction("android.intent.action.UPDATE_PINS");
        i.putExtra(EXTRA_CONTENT_PATH, contentPath);
        i.putExtra(EXTRA_VERSION_NUMBER, version);
        i.putExtra(EXTRA_REQUIRED_HASH, required);
        i.putExtra(EXTRA_SIGNATURE, sig);
        ctx.sendBroadcast(i);
    }

    public static String getFingerprint(X509Certificate cert)
            throws NoSuchAlgorithmException {
        MessageDigest dgst = MessageDigest.getInstance("SHA512");
        byte[] encoded = cert.getPublicKey().getEncoded();
        byte[] fingerprint = dgst.digest(encoded);

        return bytesToHexString(fingerprint, false);
    }

    public static String getChromeFingerprint(X509Certificate cert)
            throws NoSuchAlgorithmException {
        MessageDigest dgst = MessageDigest.getInstance("SHA1");
        byte[] encoded = cert.getPublicKey().getEncoded();
        byte[] fingerprint = dgst.digest(encoded);

        return "sha1/" + Base64.encodeToString(fingerprint, Base64.DEFAULT);
    }

    public X509Certificate getTrustAnchor(X509Certificate[] chain)
            throws CertificateException {
        try {
            CertPath certPath = certificateFactory.generateCertPath(Arrays
                    .asList(chain));
            PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) certPathValidator
                    .validate(certPath, pkixParams);

            if (result == null) {
                return null;
            }

            return result.getTrustAnchor().getTrustedCert();
        } catch (CertPathValidatorException e) {
            return null;
        } catch (InvalidAlgorithmParameterException e) {
            throw new CertificateException(e);
        }
    }

    private static KeyStore loadSystemTrustStre() {
        try {
            KeyStore result = KeyStore.getInstance("AndroidCAStore");
            result.load(null, null);

            return result;
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static PKIXParameters createPkixParams(KeyStore trustStore) {
        try {
            HashSet<TrustAnchor> trusted = new HashSet<TrustAnchor>();
            for (Enumeration<String> aliases = trustStore.aliases(); aliases
                    .hasMoreElements();) {
                String alias = aliases.nextElement();
                X509Certificate cert = (X509Certificate) trustStore
                        .getCertificate(alias);

                if (cert != null) {
                    trusted.add(new TrustAnchor(cert, null));
                }
            }

            PKIXParameters result = new PKIXParameters(trusted);
            result.setRevocationEnabled(false);

            return result;
        } catch (InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        } catch (KeyStoreException e) {
            throw new RuntimeException(e);
        }
    }

}




Java Source Code List

org.nick.certpinner.CertPinner.java
org.nick.certpinner.MainActivity.java
org.nick.certpinner.SuShell.java