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