diff --git a/src/test/java/libcore/net/http/URLConnectionTest.java b/src/test/java/libcore/net/http/URLConnectionTest.java
index f5faf2457..4c5398623 100644
--- a/src/test/java/libcore/net/http/URLConnectionTest.java
+++ b/src/test/java/libcore/net/http/URLConnectionTest.java
@@ -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();
//
diff --git a/src/test/java/libcore/net/ssl/SslContextBuilder.java b/src/test/java/libcore/net/ssl/SslContextBuilder.java
new file mode 100644
index 000000000..a227a0f83
--- /dev/null
+++ b/src/test/java/libcore/net/ssl/SslContextBuilder.java
@@ -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".
+ *
+ * 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();
+ }
+ }
+}