1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-24 04:02:07 +03:00

Use bouncy castle to generate certs for TLS.

Because there are no platform APIs to generate certificates,
this needs a third party library (bouncy castle) to do the
heavy lifting.

Each target platform has its own built-in crypto library:
 - The JVM has its own internal crypto library. It uses
   key stores like "JCA".
 - Android has its own internal crypto library that's
   based on bouncy castle. It is repackaged in com.android
   and is not used by this code.

With this change, okhttp brings its own copy of bouncy castle
for cert generation. Once the certificate is generated we're
done with bouncy castle, and use the platform libraries for TLS.
This approach allows us to use one codebase on either platform.
This commit is contained in:
Jesse Wilson
2012-08-01 12:50:04 -04:00
parent 3608deb867
commit 33a0c620e1
3 changed files with 156 additions and 16 deletions

View File

@@ -39,6 +39,7 @@
<java.version>1.6</java.version>
<npn.version>8.1.2.v20120308</npn.version>
<mockwebserver.version>20120401</mockwebserver.version>
<bouncycastle.version>1.47</bouncycastle.version>
<!-- Test Dependencies -->
<junit.version>3.8.2</junit.version>
@@ -80,6 +81,12 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -21,6 +21,7 @@ import com.google.mockwebserver.MockWebServer;
import com.google.mockwebserver.RecordedRequest;
import com.google.mockwebserver.SocketPolicy;
import com.squareup.okhttp.OkHttpConnection;
import com.squareup.okhttp.OkHttpsConnection;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -30,6 +31,7 @@ import java.net.CacheRequest;
import java.net.CacheResponse;
import java.net.ConnectException;
import java.net.HttpRetryException;
import java.net.InetAddress;
import java.net.PasswordAuthentication;
import java.net.ProtocolException;
import java.net.Proxy;
@@ -53,9 +55,12 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;
import junit.framework.TestCase;
import libcore.net.ssl.SslContextBuilder;
import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_END;
import static com.google.mockwebserver.SocketPolicy.DISCONNECT_AT_START;
import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END;
@@ -380,22 +385,24 @@ public final class URLConnectionTest extends TestCase {
}
}
// public void testConnectViaHttps() throws IOException, InterruptedException {
// TestSSLContext testSSLContext = TestSSLContext.create();
//
// server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
// server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
// server.play();
//
// HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
// connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
//
// assertContent("this response comes via HTTPS", connection);
//
// RecordedRequest request = server.takeRequest();
// assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
// }
//
public void testConnectViaHttps() throws Exception {
SSLContext sslContext = new SslContextBuilder(InetAddress.getLocalHost().getHostName())
.build();
server.useHttps(sslContext.getSocketFactory(), false);
server.enqueue(new MockResponse().setBody("this response comes via HTTPS"));
server.play();
OkHttpsConnection connection = OkHttpsConnection.open(server.getUrl("/foo"));
connection.setHostnameVerifier(new RecordingHostnameVerifier());
connection.setSSLSocketFactory(sslContext.getSocketFactory());
assertContent("this response comes via HTTPS", connection);
RecordedRequest request = server.takeRequest();
assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
}
// public void testConnectViaHttpsReusingConnections() throws IOException, InterruptedException {
// TestSSLContext testSSLContext = TestSSLContext.create();
//

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2012 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.net.ssl;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
/**
* Constructs an SSL context for testing. This uses Bouncy Castle to generate a
* self-signed certificate for a single hostname such as "localhost".
*
* <p>The crypto performed by this class is relatively slow. Clients should
* reuse SSL context instances where possible.
*/
public final class SslContextBuilder {
static {
Security.addProvider(new BouncyCastleProvider());
}
private final String hostName;
private long notBefore = System.currentTimeMillis();
private long notAfter = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
/**
* @param hostName the subject of the host. For TLS this should be the
* domain name that the client uses to identify the server.
*/
public SslContextBuilder(String hostName) {
this.hostName = hostName;
}
public SSLContext build() throws GeneralSecurityException {
char[] password = "password".toCharArray();
// Generate public and private keys and use them to make a self-signed certificate.
KeyPair keyPair = generateKeyPair();
X509Certificate certificate = selfSignedCertificate(keyPair);
// Put 'em in a key store.
KeyStore keyStore = newEmptyKeyStore(password);
Certificate[] certificateChain = {
certificate
};
keyStore.setKeyEntry("private", keyPair.getPrivate(), password, certificateChain);
keyStore.setCertificateEntry("cert", certificate);
// Wrap it up in an SSL context.
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(),
new SecureRandom());
return sslContext;
}
private KeyPair generateKeyPair() throws GeneralSecurityException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(1024, new SecureRandom());
return keyPairGenerator.generateKeyPair();
}
/**
* Generates a certificate for {@code hostName} containing {@code keyPair}'s
* public key, signed by {@code keyPair}'s private key.
*/
@SuppressWarnings("deprecation") // use the old Bouncy Castle APIs to reduce dependencies.
private X509Certificate selfSignedCertificate(KeyPair keyPair) throws GeneralSecurityException {
X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
X500Principal issuer = new X500Principal("CN=" + hostName);
X500Principal subject = new X500Principal("CN=" + hostName);
generator.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
generator.setIssuerDN(issuer);
generator.setNotBefore(new Date(notBefore));
generator.setNotAfter(new Date(notAfter));
generator.setSubjectDN(subject);
generator.setPublicKey(keyPair.getPublic());
generator.setSignatureAlgorithm("SHA256WithRSAEncryption");
return generator.generateX509Certificate(keyPair.getPrivate(), "BC");
}
private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream in = null; // By convention, 'null' creates an empty key store.
keyStore.load(in, password);
return keyStore;
} catch (IOException e) {
throw new AssertionError();
}
}
}