Home | History | Annotate | Download | only in recipes
      1 /*
      2  * Copyright (C) 2015 Square, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.squareup.okhttp.recipes;
     17 
     18 import com.squareup.okhttp.CertificatePinner;
     19 import com.squareup.okhttp.Headers;
     20 import com.squareup.okhttp.OkHttpClient;
     21 import com.squareup.okhttp.Request;
     22 import com.squareup.okhttp.Response;
     23 import java.io.IOException;
     24 import java.io.InputStream;
     25 import java.security.GeneralSecurityException;
     26 import java.security.KeyStore;
     27 import java.security.SecureRandom;
     28 import java.security.cert.Certificate;
     29 import java.security.cert.CertificateFactory;
     30 import java.util.Collection;
     31 import javax.net.ssl.KeyManagerFactory;
     32 import javax.net.ssl.SSLContext;
     33 import javax.net.ssl.TrustManagerFactory;
     34 import okio.Buffer;
     35 
     36 public final class CustomTrust {
     37   private final OkHttpClient client;
     38 
     39   public CustomTrust() {
     40     client = new OkHttpClient();
     41     SSLContext sslContext = sslContextForTrustedCertificates(trustedCertificatesInputStream());
     42     client.setSslSocketFactory(sslContext.getSocketFactory());
     43   }
     44 
     45   public void run() throws Exception {
     46     Request request = new Request.Builder()
     47         .url("https://publicobject.com/helloworld.txt")
     48         .build();
     49 
     50     Response response = client.newCall(request).execute();
     51     if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
     52 
     53     Headers responseHeaders = response.headers();
     54     for (int i = 0; i < responseHeaders.size(); i++) {
     55       System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
     56     }
     57 
     58     System.out.println(response.body().string());
     59   }
     60 
     61   /**
     62    * Returns an input stream containing one or more certificate PEM files. This implementation just
     63    * embeds the PEM files in Java strings; most applications will instead read this from a resource
     64    * file that gets bundled with the application.
     65    */
     66   private InputStream trustedCertificatesInputStream() {
     67     // PEM files for root certificates of Comodo and Entrust. These two CAs are sufficient to view
     68     // https://publicobject.com (Comodo) and https://squareup.com (Entrust). But they aren't
     69     // sufficient to connect to most HTTPS sites including https://godaddy.com and https://visa.com.
     70     // Typically developers will need to get a PEM file from their organization's TLS administrator.
     71     String comodoRsaCertificationAuthority = ""
     72         + "-----BEGIN CERTIFICATE-----\n"
     73         + "MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB\n"
     74         + "hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n"
     75         + "A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV\n"
     76         + "BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5\n"
     77         + "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT\n"
     78         + "EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR\n"
     79         + "Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh\n"
     80         + "dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR\n"
     81         + "6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X\n"
     82         + "pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC\n"
     83         + "9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV\n"
     84         + "/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf\n"
     85         + "Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z\n"
     86         + "+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w\n"
     87         + "qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah\n"
     88         + "SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC\n"
     89         + "u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf\n"
     90         + "Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq\n"
     91         + "crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E\n"
     92         + "FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n"
     93         + "/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl\n"
     94         + "wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM\n"
     95         + "4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV\n"
     96         + "2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna\n"
     97         + "FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ\n"
     98         + "CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK\n"
     99         + "boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke\n"
    100         + "jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL\n"
    101         + "S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb\n"
    102         + "QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl\n"
    103         + "0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB\n"
    104         + "NVOFBkpdn627G190\n"
    105         + "-----END CERTIFICATE-----\n";
    106     String entrustRootCertificateAuthority = ""
    107         + "-----BEGIN CERTIFICATE-----\n"
    108         + "MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC\n"
    109         + "VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0\n"
    110         + "Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW\n"
    111         + "KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl\n"
    112         + "cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw\n"
    113         + "NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw\n"
    114         + "NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy\n"
    115         + "ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV\n"
    116         + "BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ\n"
    117         + "KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo\n"
    118         + "Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4\n"
    119         + "4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9\n"
    120         + "KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI\n"
    121         + "rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi\n"
    122         + "94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB\n"
    123         + "sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi\n"
    124         + "gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo\n"
    125         + "kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE\n"
    126         + "vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA\n"
    127         + "A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t\n"
    128         + "O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua\n"
    129         + "AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP\n"
    130         + "9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/\n"
    131         + "eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m\n"
    132         + "0vdXcDazv/wor3ElhVsT/h5/WrQ8\n"
    133         + "-----END CERTIFICATE-----\n";
    134     return new Buffer()
    135         .writeUtf8(comodoRsaCertificationAuthority)
    136         .writeUtf8(entrustRootCertificateAuthority)
    137         .inputStream();
    138   }
    139 
    140   /**
    141    * Returns a SSL context that trusts {@code certificates} and none other. HTTPS services whose
    142    * certificates have not been signed by these certificates will fail with a {@code
    143    * SSLHandshakeException}.
    144    *
    145    * <p>This can be used to replace the host platform's built-in trusted certificates with a custom
    146    * set. This is useful in development where certificate authority-trusted certificates aren't
    147    * available. Or in production, to avoid reliance on third-party certificate authorities.
    148    *
    149    * <p>See also {@link CertificatePinner}, which can limit trusted certificates while still using
    150    * the host platform's built-in trust store.
    151    *
    152    * <h3>Warning: Customizing Trusted Certificates is Dangerous!</h3>
    153    * Relying on your own trusted certificates limits your server team's ability to update their TLS
    154    * certificates. By installing a specific set of trusted certificates, you take on additional
    155    * operational complexity and limit your ability to migrate between certificate authorities. Do
    156    * not use custom trusted certificates in production without the blessing of your server's TLS
    157    * administrator.
    158    */
    159   public SSLContext sslContextForTrustedCertificates(InputStream in) {
    160     try {
    161       CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    162       Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
    163       if (certificates.isEmpty()) {
    164         throw new IllegalArgumentException("expected non-empty set of trusted certificates");
    165       }
    166 
    167       // Put the certificates a key store.
    168       char[] password = "password".toCharArray(); // Any password will work.
    169       KeyStore keyStore = newEmptyKeyStore(password);
    170       int index = 0;
    171       for (Certificate certificate : certificates) {
    172         String certificateAlias = Integer.toString(index++);
    173         keyStore.setCertificateEntry(certificateAlias, certificate);
    174       }
    175 
    176       // Wrap it up in an SSL context.
    177       KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
    178           KeyManagerFactory.getDefaultAlgorithm());
    179       keyManagerFactory.init(keyStore, password);
    180       TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
    181           TrustManagerFactory.getDefaultAlgorithm());
    182       trustManagerFactory.init(keyStore);
    183       SSLContext sslContext = SSLContext.getInstance("TLS");
    184       sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(),
    185           new SecureRandom());
    186       return sslContext;
    187     } catch (GeneralSecurityException e) {
    188       throw new RuntimeException(e);
    189     }
    190   }
    191 
    192   private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
    193     try {
    194       KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    195       InputStream in = null; // By convention, 'null' creates an empty key store.
    196       keyStore.load(in, password);
    197       return keyStore;
    198     } catch (IOException e) {
    199       throw new AssertionError(e);
    200     }
    201   }
    202 
    203   public static void main(String... args) throws Exception {
    204     new CustomTrust().run();
    205   }
    206 }
    207