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.math.BigInteger;
     22 import java.net.InetAddress;
     23 import java.net.UnknownHostException;
     24 import java.security.GeneralSecurityException;
     25 import java.security.KeyPair;
     26 import java.security.KeyPairGenerator;
     27 import java.security.KeyStore;
     28 import java.security.SecureRandom;
     29 import java.security.Security;
     30 import java.security.cert.Certificate;
     31 import java.security.cert.X509Certificate;
     32 import java.util.Date;
     33 import javax.net.ssl.KeyManagerFactory;
     34 import javax.net.ssl.SSLContext;
     35 import javax.net.ssl.TrustManagerFactory;
     36 import javax.security.auth.x500.X500Principal;
     37 import org.bouncycastle.jce.provider.BouncyCastleProvider;
     38 import org.bouncycastle.x509.X509V3CertificateGenerator;
     39 
     40 /**
     41  * Constructs an SSL context for testing. This uses Bouncy Castle to generate a
     42  * self-signed certificate for a single hostname such as "localhost".
     43  *
     44  * <p>The crypto performed by this class is relatively slow. Clients should
     45  * reuse SSL context instances where possible.
     46  */
     47 public final class SslContextBuilder {
     48   static {
     49     Security.addProvider(new BouncyCastleProvider());
     50   }
     51 
     52   private static final long ONE_DAY_MILLIS = 1000L * 60 * 60 * 24;
     53   private static SSLContext localhost; // Lazily initialized.
     54 
     55   private final String hostName;
     56   private long notBefore = System.currentTimeMillis();
     57   private long notAfter = System.currentTimeMillis() + ONE_DAY_MILLIS;
     58 
     59   /**
     60    * @param hostName the subject of the host. For TLS this should be the
     61    * domain name that the client uses to identify the server.
     62    */
     63   public SslContextBuilder(String hostName) {
     64     this.hostName = hostName;
     65   }
     66 
     67   /** Returns a new SSL context for this host's current localhost address. */
     68   public static synchronized SSLContext localhost() {
     69     if (localhost == null) {
     70       try {
     71         localhost = new SslContextBuilder(InetAddress.getByName("localhost").getHostName()).build();
     72       } catch (GeneralSecurityException e) {
     73         throw new RuntimeException(e);
     74       } catch (UnknownHostException e) {
     75         throw new RuntimeException(e);
     76       }
     77     }
     78     return localhost;
     79   }
     80 
     81   public SSLContext build() throws GeneralSecurityException {
     82     char[] password = "password".toCharArray();
     83 
     84     // Generate public and private keys and use them to make a self-signed certificate.
     85     KeyPair keyPair = generateKeyPair();
     86     X509Certificate certificate = selfSignedCertificate(keyPair, "1");
     87 
     88     // Put 'em in a key store.
     89     KeyStore keyStore = newEmptyKeyStore(password);
     90     Certificate[] certificateChain = { certificate };
     91     keyStore.setKeyEntry("private", keyPair.getPrivate(), password, certificateChain);
     92     keyStore.setCertificateEntry("cert", certificate);
     93 
     94     // Wrap it up in an SSL context.
     95     KeyManagerFactory keyManagerFactory =
     96         KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
     97     keyManagerFactory.init(keyStore, password);
     98     TrustManagerFactory trustManagerFactory =
     99         TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    100     trustManagerFactory.init(keyStore);
    101     SSLContext sslContext = SSLContext.getInstance("TLS");
    102     sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(),
    103         new SecureRandom());
    104     return sslContext;
    105   }
    106 
    107   public KeyPair generateKeyPair() throws GeneralSecurityException {
    108     KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
    109     keyPairGenerator.initialize(1024, new SecureRandom());
    110     return keyPairGenerator.generateKeyPair();
    111   }
    112 
    113   /**
    114    * Generates a certificate for {@code hostName} containing {@code keyPair}'s
    115    * public key, signed by {@code keyPair}'s private key.
    116    */
    117   @SuppressWarnings("deprecation") // use the old Bouncy Castle APIs to reduce dependencies.
    118   public X509Certificate selfSignedCertificate(KeyPair keyPair, String serialNumber)
    119       throws GeneralSecurityException {
    120     X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
    121     X500Principal issuer = new X500Principal("CN=" + hostName);
    122     X500Principal subject = new X500Principal("CN=" + hostName);
    123     generator.setSerialNumber(new BigInteger(serialNumber));
    124     generator.setIssuerDN(issuer);
    125     generator.setNotBefore(new Date(notBefore));
    126     generator.setNotAfter(new Date(notAfter));
    127     generator.setSubjectDN(subject);
    128     generator.setPublicKey(keyPair.getPublic());
    129     generator.setSignatureAlgorithm("SHA256WithRSAEncryption");
    130     return generator.generateX509Certificate(keyPair.getPrivate(), "BC");
    131   }
    132 
    133   private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
    134     try {
    135       KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    136       InputStream in = null; // By convention, 'null' creates an empty key store.
    137       keyStore.load(in, password);
    138       return keyStore;
    139     } catch (IOException e) {
    140       throw new AssertionError(e);
    141     }
    142   }
    143 }
    144