Home | History | Annotate | Download | only in internal
      1 /*
      2  * Copyright (C) 2012 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 
     17 package com.squareup.okhttp.internal;
     18 
     19 import java.io.IOException;
     20 import java.io.InputStream;
     21 import java.net.InetAddress;
     22 import java.net.UnknownHostException;
     23 import java.security.GeneralSecurityException;
     24 import java.security.KeyStore;
     25 import java.security.SecureRandom;
     26 import java.security.cert.Certificate;
     27 import java.security.cert.X509Certificate;
     28 import java.util.ArrayList;
     29 import java.util.List;
     30 import javax.net.ssl.KeyManagerFactory;
     31 import javax.net.ssl.SSLContext;
     32 import javax.net.ssl.TrustManagerFactory;
     33 
     34 /**
     35  * Constructs an SSL context for testing. This uses Bouncy Castle to generate a
     36  * self-signed certificate for a single hostname such as "localhost".
     37  *
     38  * <p>The crypto performed by this class is relatively slow. Clients should
     39  * reuse SSL context instances where possible.
     40  */
     41 public final class SslContextBuilder {
     42   private static SSLContext localhost; // Lazily initialized.
     43 
     44   /** Returns a new SSL context for this host's current localhost address. */
     45   public static synchronized SSLContext localhost() {
     46     if (localhost != null) return localhost;
     47 
     48     try {
     49       // Generate a self-signed cert for the server to serve and the client to trust.
     50       HeldCertificate heldCertificate = new HeldCertificate.Builder()
     51           .serialNumber("1")
     52           .commonName(InetAddress.getByName("localhost").getHostName())
     53           .build();
     54 
     55       localhost = new SslContextBuilder()
     56           .certificateChain(heldCertificate)
     57           .addTrustedCertificate(heldCertificate.certificate)
     58           .build();
     59 
     60       return localhost;
     61     } catch (GeneralSecurityException e) {
     62       throw new RuntimeException(e);
     63     } catch (UnknownHostException e) {
     64       throw new RuntimeException(e);
     65     }
     66   }
     67 
     68   private HeldCertificate[] chain;
     69   private List<X509Certificate> trustedCertificates = new ArrayList<>();
     70 
     71   /**
     72    * Configure the certificate chain to use when serving HTTPS responses. The first certificate
     73    * in this chain is the server's certificate, further certificates are included in the handshake
     74    * so the client can build a trusted path to a CA certificate.
     75    */
     76   public SslContextBuilder certificateChain(HeldCertificate... chain) {
     77     this.chain = chain;
     78     return this;
     79   }
     80 
     81   /**
     82    * Add a certificate authority that this client trusts. Servers that provide certificate chains
     83    * signed by these roots (or their intermediates) will be accepted.
     84    */
     85   public SslContextBuilder addTrustedCertificate(X509Certificate certificate) {
     86     trustedCertificates.add(certificate);
     87     return this;
     88   }
     89 
     90   public SSLContext build() throws GeneralSecurityException {
     91     // Put the certificate in a key store.
     92     char[] password = "password".toCharArray();
     93     KeyStore keyStore = newEmptyKeyStore(password);
     94 
     95     if (chain != null) {
     96       Certificate[] certificates = new Certificate[chain.length];
     97       for (int i = 0; i < chain.length; i++) {
     98         certificates[i] = chain[i].certificate;
     99       }
    100       keyStore.setKeyEntry("private", chain[0].keyPair.getPrivate(), password, certificates);
    101     }
    102 
    103     for (int i = 0; i < trustedCertificates.size(); i++) {
    104       keyStore.setCertificateEntry("cert_" + i, trustedCertificates.get(i));
    105     }
    106 
    107     // Wrap it up in an SSL context.
    108     KeyManagerFactory keyManagerFactory =
    109         KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    110     keyManagerFactory.init(keyStore, password);
    111     TrustManagerFactory trustManagerFactory =
    112         TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    113     trustManagerFactory.init(keyStore);
    114     SSLContext sslContext = SSLContext.getInstance("TLS");
    115     sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(),
    116         new SecureRandom());
    117     return sslContext;
    118   }
    119 
    120   private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
    121     try {
    122       KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    123       InputStream in = null; // By convention, 'null' creates an empty key store.
    124       keyStore.load(in, password);
    125       return keyStore;
    126     } catch (IOException e) {
    127       throw new AssertionError(e);
    128     }
    129   }
    130 }
    131