Skip to content
Commits on Source (8)
......@@ -3,7 +3,7 @@
# Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo.
#
# Adapted from https://coderwall.com/p/9b_lfq and
# http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/
# https://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/
SLUG="square/okhttp"
JDK="oraclejdk8"
......
Change Log
==========
## Version 3.11.0
_2018-07-12_
* **OkHttp's new okhttp-tls submodule tames HTTPS and TLS.**
`HeldCertificate` is a TLS certificate and its private key. Generate a certificate with its
builder then use it to sign another certificate or perform a TLS handshake. The
`certificatePem()` method encodes the certificate in the familiar PEM format
(`--- BEGIN CERTIFICATE ---`); the `privateKeyPkcs8Pem()` does likewise for the private key.
`HandshakeCertificates` holds the TLS certificates required for a TLS handshake. On the server
it keeps your `HeldCertificate` and its chain. On the client it keeps the root certificates
that are trusted to sign a server's certificate chain. `HandshakeCertificates` also works with
mutual TLS where these roles are reversed.
These classes make it possible to enable HTTPS in MockWebServer in [just a few lines of
code][https_server_sample].
* **OkHttp now supports prior knowledge cleartext HTTP/2.** Enable this by setting
`Protocol.H2_PRIOR_KNOWLEDGE` as the lone protocol on an `OkHttpClient.Builder`. This mode
only supports `http:` URLs and is best suited in closed environments where HTTPS is
inappropriate.
* New: `HttpUrl.get(String)` is an alternative to `HttpUrl.parse(String)` that throws an exception
when the URL is malformed instead of returning null. Use this to avoid checking for null in
situations where the input is known to be well-formed. We've also added `MediaType.get(String)`
which is an exception-throwing alternative to `MediaType.parse(String)`.
* New: The `EventListener` API previewed in OkHttp 3.9 has graduated to a stable API. Use this
interface to track metrics and monitor HTTP requests' size and duration.
* New: `okhttp-dnsoverhttps` is an experimental API for doing DNS queries over HTTPS. Using HTTPS
for DNS offers better security and potentially better performance. This feature is a preview:
the API is subject to change.
* New: `okhttp-sse` is an early preview of Server-Sent Events (SSE). This feature is incomplete
and is only suitable for experimental use.
* New: MockWebServer now supports client authentication (mutual TLS). Call `requestClientAuth()`
to permit an optional client certificate or `requireClientAuth()` to require one.
* New: `RecordedRequest.getHandshake()` returns the HTTPS handshake of a request sent to
`MockWebServer`.
* Fix: Honor the `MockResponse` header delay in MockWebServer.
* Fix: Don't release HTTP/2 connections that have multiple canceled calls. We had a bug where
canceling calls would cause the shared HTTP/2 connection to be unnecessarily released. This
harmed connection reuse.
* Fix: Ensure canceled and discarded HTTP/2 data is not permanently counted against the limited
flow control window. We had a few bugs where window size accounting was broken when streams
were canceled or reset.
* Fix: Recover gracefully if the TLS session returns an unexpected version (`NONE`) or cipher
suite (`SSL_NULL_WITH_NULL_NULL`).
* Fix: Don't change Conscrypt configuration globally. We migrated from a process-wide setting to
configuring only OkHttp's TLS sockets.
* Fix: Prefer TLSv1.2 where it is available. On certain older platforms it is necessary to opt-in
to TLSv1.2.
* New: `Request.tag()` permits multiple tags. Use a `Class<?>` as a key to identify tags. Note
that `tag()` now returns null if the request has no tag. Previously this would return the
request itself.
* New: `Headers.Builder.addAll(Headers)`.
* New: `ResponseBody.create(MediaType, ByteString)`.
* New: Embed R8/ProGuard rules in the jar. These will be applied automatically by R8.
* Fix: Release the connection if `Authenticator` throws an exception.
* Fix: Change the declaration of `OkHttpClient.cache()` to return a `@Nullable Cache`. The return
value has always been nullable but it wasn't declared properly.
* Fix: Reverse suppression of connect exceptions. When both a call and its retry fail, we now
throw the initial exception which is most likely to be actionable.
* Fix: Retain interrupted state when throwing `InterruptedIOException`. A single interrupt should
now be sufficient to break out an in-flight OkHttp call.
* Fix: Don't drop a call to `EventListener.callEnd()` when the response body is consumed inside an
interceptor.
## Version 3.10.0
_2018-02-24_
......@@ -994,7 +1063,7 @@ _2014-12-30_
for OkHttp 2.0 and 2.1 will continue to work with this update.
* **`COMPATIBLE_TLS` no longer supports SSLv3.** In response to the
[POODLE](http://googleonlinesecurity.blogspot.ca/2014/10/this-poodle-bites-exploiting-ssl-30.html)
[POODLE](https://googleonlinesecurity.blogspot.ca/2014/10/this-poodle-bites-exploiting-ssl-30.html)
vulnerability, OkHttp no longer offers SSLv3 when negotiation an
HTTPS connection. If you continue to need to connect to webservers
running SSLv3, you must manually configure your own `ConnectionSpec`.
......@@ -1474,9 +1543,10 @@ Initial release.
[nginx_959]: https://trac.nginx.org/nginx/ticket/959
[okhttp_idling_resource]: https://github.com/JakeWharton/okhttp-idling-resource
[bom]: https://en.wikipedia.org/wiki/Byte_order_mark
[junit_5_rules]: http://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4-rulesupport
[junit_5_rules]: https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4-rulesupport
[public_suffix]: https://publicsuffix.org/
[maven_provided]: https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
[remove_cbc_ecdsa]: https://developers.google.com/web/updates/2016/12/chrome-56-deprecations#remove_cbc-mode_ecdsa_ciphers_in_tls
[conscrypt]: https://github.com/google/conscrypt/
[conscrypt_dependency]: https://github.com/google/conscrypt/#download
\ No newline at end of file
[conscrypt_dependency]: https://github.com/google/conscrypt/#download
[https_server_sample]: https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/HttpsServer.java
\ No newline at end of file
......@@ -11,12 +11,12 @@ Download [the latest JAR][3] or grab via Maven:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.9.1</version>
<version>3.10.0</version>
</dependency>
```
or Gradle:
```groovy
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
```
Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap].
......@@ -36,27 +36,23 @@ Download [the latest JAR][4] or grab via Maven:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>3.9.1</version>
<version>3.10.0</version>
<scope>test</scope>
</dependency>
```
or Gradle:
```groovy
testImplementation 'com.squareup.okhttp3:mockwebserver:3.9.1'
testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'
```
ProGuard
--------
R8 / ProGuard
-------------
If you are using ProGuard you might need to add the following options:
If you are using R8 or ProGuard add the options from
[this file](https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro).
You might also need rules for Okio which is a dependency of this library.
```
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
```
License
-------
......@@ -74,7 +70,7 @@ License
limitations under the License.
[1]: http://square.github.io/okhttp
[1]: https://square.github.io/okhttp
[2]: https://github.com/square/okhttp/wiki
[3]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=okhttp&v=LATEST
[4]: https://search.maven.org/remote_content?g=com.squareup.okhttp3&a=mockwebserver&v=LATEST
......
......@@ -6,7 +6,7 @@
<parent>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>parent</artifactId>
<version>3.10.0</version>
<version>3.11.0</version>
</parent>
<artifactId>benchmarks</artifactId>
......@@ -36,12 +36,13 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>mockwebserver</artifactId>
<artifactId>okhttp-tls</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<groupId>${project.groupId}</groupId>
<artifactId>mockwebserver</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
......
......@@ -20,7 +20,7 @@ import java.io.InputStream;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.internal.tls.SslClient;
import okhttp3.tls.HandshakeCertificates;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
......@@ -31,6 +31,8 @@ import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import static okhttp3.tls.internal.TlsUtil.localhost;
/** Benchmark Apache HTTP client. */
class ApacheHttpClient extends SynchronousHttpClient {
private static final boolean VERBOSE = false;
......@@ -41,9 +43,9 @@ class ApacheHttpClient extends SynchronousHttpClient {
super.prepare(benchmark);
ClientConnectionManager connectionManager = new PoolingClientConnectionManager();
if (benchmark.tls) {
SslClient sslClient = SslClient.localhost();
HandshakeCertificates handshakeCertificates = localhost();
connectionManager.getSchemeRegistry().register(
new Scheme("https", 443, new SSLSocketFactory(sslClient.sslContext)));
new Scheme("https", 443, new SSLSocketFactory(handshakeCertificates.sslContext())));
}
client = new DefaultHttpClient(connectionManager);
}
......
......@@ -28,14 +28,16 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import okhttp3.HttpUrl;
import okhttp3.Protocol;
import okhttp3.mockwebserver.internal.tls.SslClient;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okhttp3.tls.HandshakeCertificates;
import okio.Buffer;
import okio.GzipSink;
import static okhttp3.tls.internal.TlsUtil.localhost;
/**
* This benchmark is fake, but may be useful for certain relative comparisons. It uses a local
* connection to a MockWebServer to measure how many identical requests per second can be carried
......@@ -160,8 +162,8 @@ public class Benchmark extends com.google.caliper.Benchmark {
MockWebServer server = new MockWebServer();
if (tls) {
SslClient sslClient = SslClient.localhost();
server.useHttps(sslClient.socketFactory, false);
HandshakeCertificates handshakeCertificates = localhost();
server.useHttps(handshakeCertificates.sslSocketFactory(), false);
server.setProtocols(protocols);
}
......
......@@ -44,7 +44,9 @@ import java.util.Deque;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLEngine;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.internal.tls.SslClient;
import okhttp3.tls.HandshakeCertificates;
import static okhttp3.tls.internal.TlsUtil.localhost;
/** Netty isn't an HTTP client, but it's almost one. */
class NettyHttpClient implements HttpClient {
......@@ -64,12 +66,12 @@ class NettyHttpClient implements HttpClient {
this.targetBacklog = benchmark.targetBacklog;
ChannelInitializer<SocketChannel> channelInitializer = new ChannelInitializer<SocketChannel>() {
@Override public void initChannel(SocketChannel channel) throws Exception {
@Override public void initChannel(SocketChannel channel) {
ChannelPipeline pipeline = channel.pipeline();
if (benchmark.tls) {
SslClient sslClient = SslClient.localhost();
SSLEngine engine = sslClient.sslContext.createSSLEngine();
HandshakeCertificates handshakeCertificates = localhost();
SSLEngine engine = handshakeCertificates.sslContext().createSSLEngine();
engine.setUseClientMode(true);
pipeline.addLast("ssl", new SslHandler(engine));
}
......@@ -152,7 +154,7 @@ class NettyHttpClient implements HttpClient {
}
@Override protected void channelRead0(
ChannelHandlerContext context, HttpObject message) throws Exception {
ChannelHandlerContext context, HttpObject message) {
if (message instanceof HttpResponse) {
receive((HttpResponse) message);
}
......
......@@ -25,7 +25,9 @@ import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import okhttp3.mockwebserver.internal.tls.SslClient;
import okhttp3.tls.HandshakeCertificates;
import static okhttp3.tls.internal.TlsUtil.localhost;
class OkHttp extends SynchronousHttpClient {
private static final boolean VERBOSE = false;
......@@ -39,15 +41,15 @@ class OkHttp extends SynchronousHttpClient {
.build();
if (benchmark.tls) {
SslClient sslClient = SslClient.localhost();
SSLSocketFactory socketFactory = sslClient.socketFactory;
HandshakeCertificates handshakeCertificates = localhost();
SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory();
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override public boolean verify(String s, SSLSession session) {
return true;
}
};
client = new OkHttpClient.Builder()
.sslSocketFactory(socketFactory, sslClient.trustManager)
.sslSocketFactory(socketFactory, handshakeCertificates.trustManager())
.hostnameVerifier(hostnameVerifier)
.build();
}
......
......@@ -31,7 +31,9 @@ import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.mockwebserver.internal.tls.SslClient;
import okhttp3.tls.HandshakeCertificates;
import static okhttp3.tls.internal.TlsUtil.localhost;
class OkHttpAsync implements HttpClient {
private static final boolean VERBOSE = false;
......@@ -54,15 +56,15 @@ class OkHttpAsync implements HttpClient {
.build();
if (benchmark.tls) {
SslClient sslClient = SslClient.localhost();
SSLSocketFactory socketFactory = sslClient.socketFactory;
HandshakeCertificates handshakeCertificates = localhost();
SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory();
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override public boolean verify(String s, SSLSession session) {
return true;
}
};
client = client.newBuilder()
.sslSocketFactory(socketFactory, sslClient.trustManager)
.sslSocketFactory(socketFactory, handshakeCertificates.trustManager())
.hostnameVerifier(hostnameVerifier)
.build();
}
......
......@@ -25,7 +25,9 @@ import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.internal.tls.SslClient;
import okhttp3.tls.HandshakeCertificates;
import static okhttp3.tls.internal.TlsUtil.localhost;
class UrlConnection extends SynchronousHttpClient {
private static final boolean VERBOSE = false;
......@@ -33,8 +35,8 @@ class UrlConnection extends SynchronousHttpClient {
@Override public void prepare(Benchmark benchmark) {
super.prepare(benchmark);
if (benchmark.tls) {
SslClient sslClient = SslClient.localhost();
SSLSocketFactory socketFactory = sslClient.socketFactory;
HandshakeCertificates handshakeCertificates = localhost();
SSLSocketFactory socketFactory = handshakeCertificates.sslSocketFactory();
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override public boolean verify(String s, SSLSession session) {
return true;
......
libokhttp-java (3.11.0-1) unstable; urgency=medium
* New upstream version 3.11.0.
* Ignore okhttp-lts, okhttp-sse and okhttp-dnsoverhttps modules.
* Declare compliance with Debian Policy 4.1.5.
-- Markus Koschany <apo@debian.org> Sat, 28 Jul 2018 05:37:48 +0200
libokhttp-java (3.10.0-1) unstable; urgency=medium
* New upstream version 3.10.0.
......
......@@ -12,7 +12,7 @@ Build-Depends:
libandroid-23-java,
libokio-java (>= 1.14.0),
maven-debian-helper (>= 2.1)
Standards-Version: 4.1.3
Standards-Version: 4.1.5
Vcs-Git: https://anonscm.debian.org/git/pkg-java/libokhttp-java.git
Vcs-Browser: https://anonscm.debian.org/cgit/pkg-java/libokhttp-java.git
Homepage: https://github.com/square/okhttp
......
......@@ -33,6 +33,9 @@ okhttp-apache/pom.xml --ignore
okhttp-testing-support/pom.xml --ignore
okhttp-urlconnection/pom.xml --ignore
okhttp-logging-interceptor/pom.xml --ignore
okhttp-tls/pom.xml --ignore
okhttp-dnsoverhttps/pom.xml --ignore
okhttp-sse/pom.xml --ignore
okcurl/pom.xml --ignore
mockwebserver/pom.xml --ignore
samples/pom.xml --ignore
......
From: Markus Koschany <apo@debian.org>
Date: Sat, 3 Mar 2018 18:20:00 +0100
Date: Fri, 27 Jul 2018 19:22:11 +0200
Subject: no Conscrypt
The conscrypt package is not available in Debian.
---
.../internal/platform/ConscryptPlatform.java | 112 ---------------------
.../internal/platform/ConscryptPlatform.java | 118 ---------------------
.../java/okhttp3/internal/platform/Platform.java | 2 +-
2 files changed, 1 insertion(+), 113 deletions(-)
2 files changed, 1 insertion(+), 119 deletions(-)
delete mode 100644 okhttp/src/main/java/okhttp3/internal/platform/ConscryptPlatform.java
diff --git a/okhttp/src/main/java/okhttp3/internal/platform/ConscryptPlatform.java b/okhttp/src/main/java/okhttp3/internal/platform/ConscryptPlatform.java
deleted file mode 100644
index a1d77a1..0000000
index 9ed9b1d..0000000
--- a/okhttp/src/main/java/okhttp3/internal/platform/ConscryptPlatform.java
+++ /dev/null
@@ -1,112 +0,0 @@
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2014 Square, Inc.
- *
......@@ -120,18 +119,24 @@ index a1d77a1..0000000
- return null;
- }
-
- Conscrypt.setUseEngineSocketByDefault(true);
- return new ConscryptPlatform();
- } catch (ClassNotFoundException e) {
- return null;
- }
- }
-
- @Override
- public void configureSslSocketFactory(SSLSocketFactory socketFactory) {
- if (Conscrypt.isConscrypt(socketFactory)) {
- Conscrypt.setUseEngineSocket(socketFactory, true);
- }
- }
-}
diff --git a/okhttp/src/main/java/okhttp3/internal/platform/Platform.java b/okhttp/src/main/java/okhttp3/internal/platform/Platform.java
index 04072f4..0aa3ad3 100644
index 4803359..8a63b47 100644
--- a/okhttp/src/main/java/okhttp3/internal/platform/Platform.java
+++ b/okhttp/src/main/java/okhttp3/internal/platform/Platform.java
@@ -203,7 +203,7 @@ public class Platform {
@@ -205,7 +205,7 @@ public class Platform {
}
if (isConscryptPreferred()) {
......
From: Markus Koschany <apo@debian.org>
Date: Fri, 13 Oct 2017 12:45:40 +0200
Date: Fri, 27 Jul 2018 19:18:23 +0200
Subject: no javac with errorprone
Not yet packaged for Debian?
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 8b87c20..8580d56 100644
index 956fb3b..0907917 100644
--- a/pom.xml
+++ b/pom.xml
@@ -137,7 +137,7 @@
@@ -153,7 +153,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<version>3.7.0</version>
<configuration>
- <compilerId>javac-with-errorprone</compilerId>
+ <compilerId>javac</compilerId>
......
......@@ -6,7 +6,7 @@
<parent>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>parent</artifactId>
<version>3.10.0</version>
<version>3.11.0</version>
</parent>
<artifactId>mockwebserver</artifactId>
......@@ -25,8 +25,10 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<groupId>${project.groupId}</groupId>
<artifactId>okhttp-tls</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
......
......@@ -19,7 +19,6 @@ package okhttp3.mockwebserver;
import java.io.Closeable;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
......@@ -103,6 +102,10 @@ public final class MockWebServer extends ExternalResource implements Closeable {
Internal.initializeInstanceForTests();
}
private static final int CLIENT_AUTH_NONE = 0;
private static final int CLIENT_AUTH_REQUESTED = 1;
private static final int CLIENT_AUTH_REQUIRED = 2;
private static final X509TrustManager UNTRUSTED_TRUST_MANAGER = new X509TrustManager() {
@Override public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
......@@ -133,6 +136,7 @@ public final class MockWebServer extends ExternalResource implements Closeable {
private SSLSocketFactory sslSocketFactory;
private ExecutorService executor;
private boolean tunnelProxy;
private int clientAuth = CLIENT_AUTH_NONE;
private Dispatcher dispatcher = new QueueDispatcher();
private int port = -1;
......@@ -214,7 +218,12 @@ public final class MockWebServer extends ExternalResource implements Closeable {
*/
public void setProtocols(List<Protocol> protocols) {
protocols = Util.immutableList(protocols);
if (!protocols.contains(Protocol.HTTP_1_1)) {
if (protocols.contains(Protocol.H2_PRIOR_KNOWLEDGE) && protocols.size() > 1) {
// when using h2_prior_knowledge, no other protocol should be supported.
throw new IllegalArgumentException(
"protocols containing h2_prior_knowledge cannot use other protocols: " + protocols);
} else if (!protocols.contains(Protocol.H2_PRIOR_KNOWLEDGE)
&& !protocols.contains(Protocol.HTTP_1_1)) {
throw new IllegalArgumentException("protocols doesn't contain http/1.1: " + protocols);
}
if (protocols.contains(null)) {
......@@ -223,6 +232,10 @@ public final class MockWebServer extends ExternalResource implements Closeable {
this.protocols = protocols;
}
public List<Protocol> protocols() {
return protocols;
}
/**
* Serve requests with HTTPS rather than otherwise.
*
......@@ -233,6 +246,36 @@ public final class MockWebServer extends ExternalResource implements Closeable {
this.tunnelProxy = tunnelProxy;
}
/**
* Configure the server to not perform SSL authentication of the client. This leaves
* authentication to another layer such as in an HTTP cookie or header. This is the default and
* most common configuration.
*/
public void noClientAuth() {
this.clientAuth = CLIENT_AUTH_NONE;
}
/**
* Configure the server to {@linkplain SSLSocket#setWantClientAuth want client auth}. If the
* client presents a certificate that is {@linkplain TrustManager trusted} the handshake will
* proceed normally. The connection will also proceed normally if the client presents no
* certificate at all! But if the client presents an untrusted certificate the handshake will fail
* and no connection will be established.
*/
public void requestClientAuth() {
this.clientAuth = CLIENT_AUTH_REQUESTED;
}
/**
* Configure the server to {@linkplain SSLSocket#setNeedClientAuth need client auth}. If the
* client presents a certificate that is {@linkplain TrustManager trusted} the handshake will
* proceed normally. If the client presents an untrusted certificate or no certificate at all the
* handshake will fail and no connection will be established.
*/
public void requireClientAuth() {
this.clientAuth = CLIENT_AUTH_REQUIRED;
}
/**
* Awaits the next HTTP request, removes it, and returns it. Callers should use this to verify the
* request was sent as intended. This method will block until the request is available, possibly
......@@ -423,6 +466,11 @@ public final class MockWebServer extends ExternalResource implements Closeable {
raw.getPort(), true);
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.setUseClientMode(false);
if (clientAuth == CLIENT_AUTH_REQUIRED) {
sslSocket.setNeedClientAuth(true);
} else if (clientAuth == CLIENT_AUTH_REQUESTED) {
sslSocket.setWantClientAuth(true);
}
openClientSockets.add(socket);
if (protocolNegotiationEnabled) {
......@@ -436,6 +484,9 @@ public final class MockWebServer extends ExternalResource implements Closeable {
protocol = protocolString != null ? Protocol.get(protocolString) : Protocol.HTTP_1_1;
}
openClientSockets.remove(raw);
} else if (protocols.contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
socket = raw;
protocol = Protocol.H2_PRIOR_KNOWLEDGE;
} else {
socket = raw;
}
......@@ -444,7 +495,7 @@ public final class MockWebServer extends ExternalResource implements Closeable {
return; // Ignore the socket until the server is shut down!
}
if (protocol == Protocol.HTTP_2) {
if (protocol == Protocol.HTTP_2 || protocol == Protocol.H2_PRIOR_KNOWLEDGE) {
Http2SocketHandler http2SocketHandler = new Http2SocketHandler(socket, protocol);
Http2Connection connection = new Http2Connection.Builder(false)
.socket(socket)
......@@ -680,7 +731,8 @@ public final class MockWebServer extends ExternalResource implements Closeable {
// Even if messages are no longer being read we need to wait for the connection close signal.
try {
connectionClose.await();
} catch (InterruptedException ignored) {
} catch (InterruptedException e) {
throw new AssertionError(e);
}
} catch (IOException e) {
......@@ -692,7 +744,7 @@ public final class MockWebServer extends ExternalResource implements Closeable {
private void writeHttpResponse(Socket socket, BufferedSink sink, MockResponse response)
throws IOException {
sleepIfDelayed(response.getBodyDelay(TimeUnit.MILLISECONDS));
sleepIfDelayed(response.getHeadersDelay(TimeUnit.MILLISECONDS));
sink.writeUtf8(response.getStatus());
sink.writeUtf8("\r\n");
......@@ -769,7 +821,7 @@ public final class MockWebServer extends ExternalResource implements Closeable {
try {
Thread.sleep(periodDelayMs);
} catch (InterruptedException e) {
throw new AssertionError();
throw new AssertionError(e);
}
}
}
......@@ -851,7 +903,7 @@ public final class MockWebServer extends ExternalResource implements Closeable {
stream.close(ErrorCode.fromHttp2(peekedResponse.getHttp2ErrorCode()));
return;
} catch (InterruptedException e) {
throw new InterruptedIOException();
throw new AssertionError(e);
}
}
......@@ -894,7 +946,7 @@ public final class MockWebServer extends ExternalResource implements Closeable {
method = value;
} else if (name.equals(Header.TARGET_PATH)) {
path = value;
} else if (protocol == Protocol.HTTP_2) {
} else if (protocol == Protocol.HTTP_2 || protocol == Protocol.H2_PRIOR_KNOWLEDGE) {
httpHeaders.add(name.utf8(), value);
} else {
throw new IllegalStateException();
......
......@@ -16,9 +16,11 @@
package okhttp3.mockwebserver;
import java.io.IOException;
import java.net.Socket;
import java.util.List;
import javax.net.ssl.SSLSocket;
import okhttp3.Handshake;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.TlsVersion;
......@@ -30,11 +32,11 @@ public final class RecordedRequest {
private final String method;
private final String path;
private final Headers headers;
private final Handshake handshake;
private final List<Integer> chunkSizes;
private final long bodySize;
private final Buffer body;
private final int sequenceNumber;
private final TlsVersion tlsVersion;
private final HttpUrl requestUrl;
public RecordedRequest(String requestLine, Headers headers, List<Integer> chunkSizes,
......@@ -45,9 +47,15 @@ public final class RecordedRequest {
this.bodySize = bodySize;
this.body = body;
this.sequenceNumber = sequenceNumber;
this.tlsVersion = socket instanceof SSLSocket
? TlsVersion.forJavaName(((SSLSocket) socket).getSession().getProtocol())
: null;
if (socket instanceof SSLSocket) {
try {
this.handshake = Handshake.get(((SSLSocket) socket).getSession());
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
} else {
this.handshake = null;
}
if (requestLine != null) {
int methodEnd = requestLine.indexOf(' ');
......@@ -128,7 +136,15 @@ public final class RecordedRequest {
/** Returns the connection's TLS version or null if the connection doesn't use SSL. */
public TlsVersion getTlsVersion() {
return tlsVersion;
return handshake != null ? handshake.tlsVersion() : null;
}
/**
* Returns the TLS handshake of the connection that carried this request, or null if the request
* was received without TLS.
*/
public Handshake getHandshake() {
return handshake;
}
@Override public String toString() {
......
/*
* Copyright (C) 2016 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 okhttp3.mockwebserver.internal.tls;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import static okhttp3.internal.Util.verifyAsIpAddress;
/**
* A certificate and its private key. This can be used on the server side by HTTPS servers, or on
* the client side to verify those HTTPS servers. A held certificate can also be used to sign other
* held certificates, as done in practice by certificate authorities.
*/
public final class HeldCertificate {
public final X509Certificate certificate;
public final KeyPair keyPair;
public HeldCertificate(X509Certificate certificate, KeyPair keyPair) {
this.certificate = certificate;
this.keyPair = keyPair;
}
public static final class Builder {
static {
Security.addProvider(new BouncyCastleProvider());
}
private final long duration = 1000L * 60 * 60 * 24; // One day.
private String hostname;
private List<String> altNames = new ArrayList<>();
private String serialNumber = "1";
private KeyPair keyPair;
private HeldCertificate issuedBy;
private int maxIntermediateCas;
public Builder serialNumber(String serialNumber) {
this.serialNumber = serialNumber;
return this;
}
/**
* Set this certificate's name. Typically this is the URL hostname for TLS certificates. This is
* the CN (common name) in the certificate. Will be a random string if no value is provided.
*/
public Builder commonName(String hostname) {
this.hostname = hostname;
return this;
}
public Builder keyPair(KeyPair keyPair) {
this.keyPair = keyPair;
return this;
}
/**
* Set the certificate that signs this certificate. If unset, a self-signed certificate will be
* generated.
*/
public Builder issuedBy(HeldCertificate signedBy) {
this.issuedBy = signedBy;
return this;
}
/**
* Set this certificate to be a certificate authority, with up to {@code maxIntermediateCas}
* intermediate certificate authorities beneath it.
*/
public Builder ca(int maxIntermediateCas) {
this.maxIntermediateCas = maxIntermediateCas;
return this;
}
/**
* Adds a subject alternative name to the certificate. This is usually a hostname or IP address.
* If no subject alternative names are added that extension will not be used.
*/
public Builder subjectAlternativeName(String altName) {
altNames.add(altName);
return this;
}
public HeldCertificate build() throws GeneralSecurityException {
// Subject, public & private keys for this certificate.
KeyPair heldKeyPair = keyPair != null
? keyPair
: generateKeyPair();
X500Principal subject = hostname != null
? new X500Principal("CN=" + hostname)
: new X500Principal("CN=" + UUID.randomUUID());
// Subject, public & private keys for this certificate's signer. It may be self signed!
KeyPair signedByKeyPair;
X500Principal signedByPrincipal;
if (issuedBy != null) {
signedByKeyPair = issuedBy.keyPair;
signedByPrincipal = issuedBy.certificate.getSubjectX500Principal();
} else {
signedByKeyPair = heldKeyPair;
signedByPrincipal = subject;
}
// Generate & sign the certificate.
long now = System.currentTimeMillis();
X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
generator.setSerialNumber(new BigInteger(serialNumber));
generator.setIssuerDN(signedByPrincipal);
generator.setNotBefore(new Date(now));
generator.setNotAfter(new Date(now + duration));
generator.setSubjectDN(subject);
generator.setPublicKey(heldKeyPair.getPublic());
generator.setSignatureAlgorithm("SHA256WithRSAEncryption");
if (maxIntermediateCas > 0) {
generator.addExtension(X509Extensions.BasicConstraints, true,
new BasicConstraints(maxIntermediateCas));
}
if (!altNames.isEmpty()) {
ASN1Encodable[] encodableAltNames = new ASN1Encodable[altNames.size()];
for (int i = 0, size = altNames.size(); i < size; i++) {
String altName = altNames.get(i);
int tag = verifyAsIpAddress(altName)
? GeneralName.iPAddress
: GeneralName.dNSName;
encodableAltNames[i] = new GeneralName(tag, altName);
}
generator.addExtension(X509Extensions.SubjectAlternativeName, true,
new DERSequence(encodableAltNames));
}
X509Certificate certificate = generator.generateX509Certificate(
signedByKeyPair.getPrivate(), "BC");
return new HeldCertificate(certificate, heldKeyPair);
}
public KeyPair generateKeyPair() throws GeneralSecurityException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
keyPairGenerator.initialize(1024, new SecureRandom());
return keyPairGenerator.generateKeyPair();
}
}
}
/*
* 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 okhttp3.mockwebserver.internal.tls;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.internal.platform.Platform;
/**
* Combines an SSL socket factory and trust manager, a pairing enough for OkHttp or MockWebServer to
* create a secure connection.
*/
public final class SslClient {
private static SslClient localhost; // Lazily initialized.
public final SSLContext sslContext;
public final SSLSocketFactory socketFactory;
public final X509TrustManager trustManager;
private SslClient(SSLContext sslContext, X509TrustManager trustManager) {
this.sslContext = sslContext;
this.socketFactory = sslContext.getSocketFactory();
this.trustManager = trustManager;
}
/** Returns an SSL client for this host's localhost address. */
public static synchronized SslClient localhost() {
if (localhost != null) return localhost;
try {
// Generate a self-signed cert for the server to serve and the client to trust.
HeldCertificate heldCertificate = new HeldCertificate.Builder()
.serialNumber("1")
.commonName(InetAddress.getByName("localhost").getCanonicalHostName())
.build();
localhost = new Builder()
.certificateChain(heldCertificate.keyPair, heldCertificate.certificate)
.addTrustedCertificate(heldCertificate.certificate)
.build();
return localhost;
} catch (GeneralSecurityException | UnknownHostException e) {
throw new RuntimeException(e);
}
}
public static class Builder {
private final List<X509Certificate> chainCertificates = new ArrayList<>();
private final List<X509Certificate> certificates = new ArrayList<>();
private KeyPair keyPair;
private String keyStoreType = KeyStore.getDefaultType();
private SSLContext sslContext;
/**
* Configure the certificate chain to use when serving HTTPS responses. The first certificate is
* the server's certificate, further certificates are included in the handshake so the client
* can build a trusted path to a CA certificate.
*/
public Builder certificateChain(HeldCertificate localCert, HeldCertificate... chain) {
X509Certificate[] certificates = new X509Certificate[chain.length];
for (int i = 0; i < chain.length; i++) {
certificates[i] = chain[i].certificate;
}
return certificateChain(localCert.keyPair, localCert.certificate, certificates);
}
public Builder certificateChain(KeyPair keyPair, X509Certificate keyCert,
X509Certificate... certificates) {
this.keyPair = keyPair;
this.chainCertificates.add(keyCert);
this.chainCertificates.addAll(Arrays.asList(certificates));
this.certificates.addAll(Arrays.asList(certificates));
return this;
}
/**
* Add a certificate authority that this client trusts. Servers that provide certificate chains
* signed by these roots (or their intermediates) will be accepted.
*/
public Builder addTrustedCertificate(X509Certificate certificate) {
this.certificates.add(certificate);
return this;
}
public Builder keyStoreType(String keyStoreType) {
this.keyStoreType = keyStoreType;
return this;
}
public Builder sslContext(SSLContext sslContext) {
this.sslContext = sslContext;
return this;
}
public SslClient build() {
try {
// Put the certificate in a key store.
char[] password = "password".toCharArray();
KeyStore keyStore = newEmptyKeyStore(password);
if (keyPair != null) {
Certificate[] certificates = chainCertificates.toArray(
new Certificate[chainCertificates.size()]);
keyStore.setKeyEntry("private", keyPair.getPrivate(), password, certificates);
}
for (int i = 0; i < certificates.size(); i++) {
keyStore.setCertificateEntry("cert_" + i, certificates.get(i));
}
// 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);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
SSLContext activeSslContext =
this.sslContext != null ? this.sslContext : Platform.get().getSSLContext();
activeSslContext.init(keyManagerFactory.getKeyManagers(), trustManagers,
new SecureRandom());
return new SslClient(activeSslContext, (X509TrustManager) trustManagers[0]);
} catch (GeneralSecurityException gse) {
throw new AssertionError(gse);
}
}
private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
try {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
InputStream in = null; // By convention, 'null' creates an empty key store.
keyStore.load(in, password);
return keyStore;
} catch (IOException e) {
throw new AssertionError(e);
}
}
}
}