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
17package com.squareup.okhttp.internal;
18
19import java.io.IOException;
20import java.io.InputStream;
21import java.math.BigInteger;
22import java.net.InetAddress;
23import java.net.UnknownHostException;
24import java.security.GeneralSecurityException;
25import java.security.KeyPair;
26import java.security.KeyPairGenerator;
27import java.security.KeyStore;
28import java.security.SecureRandom;
29import java.security.Security;
30import java.security.cert.Certificate;
31import java.security.cert.X509Certificate;
32import java.util.Date;
33import javax.net.ssl.KeyManagerFactory;
34import javax.net.ssl.SSLContext;
35import javax.net.ssl.TrustManagerFactory;
36import javax.security.auth.x500.X500Principal;
37import org.bouncycastle.jce.provider.BouncyCastleProvider;
38import 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 */
47public 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.getLocalHost().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);
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  private 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  private X509Certificate selfSignedCertificate(KeyPair keyPair) throws GeneralSecurityException {
119    X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
120    X500Principal issuer = new X500Principal("CN=" + hostName);
121    X500Principal subject = new X500Principal("CN=" + hostName);
122    generator.setSerialNumber(BigInteger.ONE);
123    generator.setIssuerDN(issuer);
124    generator.setNotBefore(new Date(notBefore));
125    generator.setNotAfter(new Date(notAfter));
126    generator.setSubjectDN(subject);
127    generator.setPublicKey(keyPair.getPublic());
128    generator.setSignatureAlgorithm("SHA256WithRSAEncryption");
129    return generator.generateX509Certificate(keyPair.getPrivate(), "BC");
130  }
131
132  private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
133    try {
134      KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
135      InputStream in = null; // By convention, 'null' creates an empty key store.
136      keyStore.load(in, password);
137      return keyStore;
138    } catch (IOException e) {
139      throw new AssertionError(e);
140    }
141  }
142}
143