• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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. */
localhost()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    */
certificateChain(HeldCertificate... chain)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    */
addTrustedCertificate(X509Certificate certificate)85   public SslContextBuilder addTrustedCertificate(X509Certificate certificate) {
86     trustedCertificates.add(certificate);
87     return this;
88   }
89 
build()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 
newEmptyKeyStore(char[] password)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