Bump okhttp version

This commit is contained in:
seven332 committed 2016-03-05 01:32:03 +08:00
1 parent d7e2adaef4
commit e80df262a3
13 files changed
+275 -218

No files matched your search

+1 -2
View File
@@ -51,8 +51,7 @@ public interface Call {
* immediately unless there are several other requests currently being executed.
*
* <p>This client will later call back {@code responseCallback} with either an HTTP response or a
* failure exception. If you {@link #cancel} a request before it completes the callback will not
* be invoked.
* failure exception.
*
* @throws IllegalStateException when the call has already been executed.
*/
@@ -17,30 +17,25 @@ package okhttp3;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.net.ssl.SSLPeerUnverifiedException;
import okhttp3.internal.Util;
import okhttp3.internal.tls.CertificateChainCleaner;
import okhttp3.internal.tls.TrustRootIndex;
import okio.ByteString;
import static java.util.Collections.unmodifiableSet;
/**
* Constrains which certificates are trusted. Pinning certificates defends against attacks on
* certificate authorities. It also prevents connections through man-in-the-middle certificate
* authorities either known or unknown to the application's user.
*
* <p>This class currently pins a certificate's Subject Public Key Info as described on <a
* href="http://goo.gl/AIx3e5">Adam Langley's Weblog</a>. Pins are base-64 SHA-1 hashes, consistent
* with the format Chromium uses for <a href="http://goo.gl/XDh6je">static certificates</a>. See
* Chromium's <a href="http://goo.gl/4CCnGs">pinsets</a> for hostnames that are pinned in that
* browser.
* href="http://goo.gl/AIx3e5">Adam Langley's Weblog</a>. Pins are either base64 SHA-256 hashes as
* in <a href="http://tools.ietf.org/html/rfc7469">HTTP Public Key Pinning (HPKP)</a> or SHA-1
* base64 hashes as in Chromium's <a href="http://goo.gl/XDh6je">static certificates</a>.
*
* <h3>Setting up Certificate Pinning</h3>
*
@@ -54,7 +49,7 @@ import static java.util.Collections.unmodifiableSet;
*
* String hostname = "publicobject.com";
* CertificatePinner certificatePinner = new CertificatePinner.Builder()
* .add(hostname, "sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=")
* .add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
* .build();
* OkHttpClient client = new OkHttpClient();
* client.setCertificatePinner(certificatePinner);
@@ -69,12 +64,12 @@ import static java.util.Collections.unmodifiableSet;
*
* javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
* Peer certificate chain:
* sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=: CN=publicobject.com, OU=PositiveSSL
* sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=: CN=COMODO RSA Domain Validation Secure Server CA
* sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=: CN=COMODO RSA Certification Authority
* sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=: CN=AddTrust External CA Root
* sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=: CN=publicobject.com, OU=PositiveSSL
* sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=: CN=COMODO RSA Secure Server CA
* sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=: CN=COMODO RSA Certification Authority
* sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=: CN=AddTrust External CA Root
* Pinned certificates for publicobject.com:
* sha1/AAAAAAAAAAAAAAAAAAAAAAAAAAA=
* sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
* at okhttp3.CertificatePinner.check(CertificatePinner.java)
* at okhttp3.Connection.upgradeToTls(Connection.java)
* at okhttp3.Connection.connect(Connection.java)
@@ -85,10 +80,10 @@ import static java.util.Collections.unmodifiableSet;
* certificate pinner's configuration: <pre> {@code
*
* CertificatePinner certificatePinner = new CertificatePinner.Builder()
* .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
* .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
* .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
* .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
* .add("publicobject.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=")
* .add("publicobject.com", "sha256/klO23nT2ehFDXCfx3eHTDRESMz3asj1muO+4aIdjiuY=")
* .add("publicobject.com", "sha256/grX4Ta9HpZx6tSHkmCrvpApTQGo67CYDnvprLg5yRME=")
* .add("publicobject.com", "sha256/lCppFqbkrlJ3EcVFAkeip0+44VaoJUymbnOaEUk7tEU=")
* .build();
* }</pre>
*
@@ -129,11 +124,11 @@ import static java.util.Collections.unmodifiableSet;
public final class CertificatePinner {
public static final CertificatePinner DEFAULT = new Builder().build();
private final Map<String, Set<ByteString>> hostnameToPins;
private final List<Pin> pins;
private final TrustRootIndex trustRootIndex;
private CertificatePinner(Builder builder) {
this.hostnameToPins = Util.immutableMap(builder.hostnameToPins);
this.pins = Util.immutableList(builder.pins);
this.trustRootIndex = builder.trustRootIndex;
}
@@ -147,31 +142,47 @@ public final class CertificatePinner {
*/
public void check(String hostname, List<Certificate> peerCertificates)
throws SSLPeerUnverifiedException {
List<Pin> pins = findMatchingPins(hostname);
if (pins.isEmpty()) return;
if (trustRootIndex != null) {
peerCertificates = new CertificateChainCleaner(trustRootIndex).clean(peerCertificates);
}
Set<ByteString> pins = findMatchingPins(hostname);
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
if (pins == null) return;
// Lazily compute the hashes for each certificate.
ByteString sha1 = null;
ByteString sha256 = null;
for (int i = 0, size = peerCertificates.size(); i < size; i++) {
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i);
if (pins.contains(sha1(x509Certificate))) return; // Success!
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
if (pin.hashAlgorithm.equals("sha256/")) {
if (sha256 == null) sha256 = sha256(x509Certificate);
if (pin.hash.equals(sha256)) return; // Success!
} else if (pin.hashAlgorithm.equals("sha1/")) {
if (sha1 == null) sha1 = sha1(x509Certificate);
if (pin.hash.equals(sha1)) return; // Success!
} else {
throw new AssertionError();
}
}
}
// If we couldn't find a matching pin, format a nice exception.
StringBuilder message = new StringBuilder()
.append("Certificate pinning failure!")
.append("\n Peer certificate chain:");
for (int i = 0, size = peerCertificates.size(); i < size; i++) {
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(i);
for (int c = 0, certsSize = peerCertificates.size(); c < certsSize; c++) {
X509Certificate x509Certificate = (X509Certificate) peerCertificates.get(c);
message.append("\n ").append(pin(x509Certificate))
.append(": ").append(x509Certificate.getSubjectDN().getName());
}
message.append("\n Pinned certificates for ").append(hostname).append(":");
for (ByteString pin : pins) {
message.append("\n sha1/").append(pin.base64());
for (int p = 0, pinsSize = pins.size(); p < pinsSize; p++) {
Pin pin = pins.get(p);
message.append("\n ").append(pin);
}
throw new SSLPeerUnverifiedException(message.toString());
}
@@ -183,36 +194,18 @@ public final class CertificatePinner {
}
/**
* Returns list of matching certificates' pins for the hostname or {@code null} if hostname does
* not have pinned certificates.
* Returns list of matching certificates' pins for the hostname. Returns an empty list if the
* hostname does not have pinned certificates.
*/
Set<ByteString> findMatchingPins(String hostname) {
Set<ByteString> directPins = hostnameToPins.get(hostname);
Set<ByteString> wildcardPins = null;
int indexOfFirstDot = hostname.indexOf('.');
int indexOfLastDot = hostname.lastIndexOf('.');
// Skip hostnames with one dot symbol for wildcard pattern search
// example.com will be skipped
// a.example.com won't be skipped
if (indexOfFirstDot != indexOfLastDot) {
// a.example.com -> search for wildcard pattern *.example.com
wildcardPins = hostnameToPins.get("*." + hostname.substring(indexOfFirstDot + 1));
List<Pin> findMatchingPins(String hostname) {
List<Pin> result = Collections.emptyList();
for (Pin pin : pins) {
if (pin.matches(hostname)) {
if (result.isEmpty()) result = new ArrayList<>();
result.add(pin);
}
}
if (directPins == null && wildcardPins == null) return null;
if (directPins != null && wildcardPins != null) {
Set<ByteString> pins = new LinkedHashSet<>();
pins.addAll(directPins);
pins.addAll(wildcardPins);
return pins;
}
if (directPins != null) return directPins;
return wildcardPins;
return result;
}
Builder newBuilder() {
@@ -220,31 +213,89 @@ public final class CertificatePinner {
}
/**
* Returns the SHA-1 of {@code certificate}'s public key. This uses the mechanism Moxie
* Marlinspike describes in <a href="https://github.com/moxie0/AndroidPinning">Android
* Pinning</a>.
* Returns the SHA-256 of {@code certificate}'s public key.
*
* <p>In OkHttp 3.1.2 and earlier, this returned a SHA-1 hash of the public key. Both types are
* supported, but SHA-256 is preferred.
*/
public static String pin(Certificate certificate) {
if (!(certificate instanceof X509Certificate)) {
throw new IllegalArgumentException("Certificate pinning requires X509 certificates");
}
return "sha1/" + sha1((X509Certificate) certificate).base64();
return "sha256/" + sha256((X509Certificate) certificate).base64();
}
private static ByteString sha1(X509Certificate x509Certificate) {
static ByteString sha1(X509Certificate x509Certificate) {
return Util.sha1(ByteString.of(x509Certificate.getPublicKey().getEncoded()));
}
static ByteString sha256(X509Certificate x509Certificate) {
return Util.sha256(ByteString.of(x509Certificate.getPublicKey().getEncoded()));
}
static final class Pin {
/** A hostname like {@code example.com} or a pattern like {@code *.example.com}. */
final String pattern;
/** Either {@code sha1/} or {@code sha256/}. */
final String hashAlgorithm;
/** The hash of the pinned certificate using {@link #hashAlgorithm}. */
final ByteString hash;
Pin(String pattern, String pin) {
this.pattern = pattern;
if (pin.startsWith("sha1/")) {
this.hashAlgorithm = "sha1/";
this.hash = ByteString.decodeBase64(pin.substring("sha1/".length()));
} else if (pin.startsWith("sha256/")) {
this.hashAlgorithm = "sha256/";
this.hash = ByteString.decodeBase64(pin.substring("sha256/".length()));
} else {
throw new IllegalArgumentException("pins must start with 'sha256/' or 'sha1/': " + pin);
}
if (this.hash == null) {
throw new IllegalArgumentException("pins must be base64: " + pin);
}
}
boolean matches(String hostname) {
if (pattern.equals(hostname)) return true;
int firstDot = hostname.indexOf('.');
return pattern.startsWith("*.")
&& hostname.regionMatches(false, firstDot + 1, pattern, 2, pattern.length() - 2);
}
@Override public boolean equals(Object other) {
return other instanceof Pin
&& pattern.equals(((Pin) other).pattern)
&& hashAlgorithm.equals(((Pin) other).hashAlgorithm)
&& hash.equals(((Pin) other).hash);
}
@Override public int hashCode() {
int result = 17;
result = 31 * result + pattern.hashCode();
result = 31 * result + hashAlgorithm.hashCode();
result = 31 * result + hash.hashCode();
return result;
}
@Override public String toString() {
return hashAlgorithm + hash.base64();
}
}
/** Builds a configured certificate pinner. */
public static final class Builder {
private final Map<String, Set<ByteString>> hostnameToPins = new LinkedHashMap<>();
private final List<Pin> pins = new ArrayList<>();
private TrustRootIndex trustRootIndex;
public Builder() {
}
Builder(CertificatePinner certificatePinner) {
this.hostnameToPins.putAll(certificatePinner.hostnameToPins);
this.pins.addAll(certificatePinner.pins);
this.trustRootIndex = certificatePinner.trustRootIndex;
}
@@ -254,30 +305,17 @@ public final class CertificatePinner {
}
/**
* Pins certificates for {@code hostname}.
* Pins certificates for {@code pattern}.
*
* @param hostname lower-case host name or wildcard pattern such as {@code *.example.com}.
* @param pins SHA-1 hashes. Each pin is a SHA-1 hash of a certificate's Subject Public Key
* Info, base64-encoded and prefixed with {@code sha1/}.
* @param pattern lower-case host name or wildcard pattern such as {@code *.example.com}.
* @param pins SHA-256 or SHA-1 hashes. Each pin is a hash of a certificate's Subject Public Key
* Info, base64-encoded and prefixed with either {@code sha256/} or {@code sha1/}.
*/
public Builder add(String hostname, String... pins) {
if (hostname == null) throw new IllegalArgumentException("hostname == null");
Set<ByteString> hostPins = new LinkedHashSet<>();
Set<ByteString> previousPins = hostnameToPins.put(hostname, unmodifiableSet(hostPins));
if (previousPins != null) {
hostPins.addAll(previousPins);
}
public Builder add(String pattern, String... pins) {
if (pattern == null) throw new IllegalArgumentException("pattern == null");
for (String pin : pins) {
if (!pin.startsWith("sha1/")) {
throw new IllegalArgumentException("pins must start with 'sha1/': " + pin);
}
ByteString decodedPin = ByteString.decodeBase64(pin.substring("sha1/".length()));
if (decodedPin == null) {
throw new IllegalArgumentException("pins must be base64: " + pin);
}
hostPins.add(decodedPin);
this.pins.add(new Pin(pattern, pin));
}
return this;
+38 -3
View File
@@ -772,6 +772,15 @@ public final class HttpUrl {
return this;
}
/**
* Adds a set of path segments separated by a slash (either {@code \} or {@code /}). If
* {@code pathSegments} starts with a slash, the resulting URL will have empty path segment.
*/
public Builder addPathSegments(String pathSegments) {
if (pathSegments == null) throw new IllegalArgumentException("pathSegments == null");
return addPathSegments(pathSegments, false);
}
public Builder addEncodedPathSegment(String encodedPathSegment) {
if (encodedPathSegment == null) {
throw new IllegalArgumentException("encodedPathSegment == null");
@@ -780,6 +789,29 @@ public final class HttpUrl {
return this;
}
/**
* Adds a set of encoded path segments separated by a slash (either {@code \} or {@code /}). If
* {@code encodedPathSegments} starts with a slash, the resulting URL will have empty path
* segment.
*/
public Builder addEncodedPathSegments(String encodedPathSegments) {
if (encodedPathSegments == null) {
throw new IllegalArgumentException("encodedPathSegments == null");
}
return addPathSegments(encodedPathSegments, true);
}
private Builder addPathSegments(String pathSegments, boolean alreadyEncoded) {
int offset = 0;
do {
int segmentEnd = delimiterOffset(pathSegments, offset, pathSegments.length(), "/\\");
boolean addTrailingSlash = segmentEnd < pathSegments.length();
push(pathSegments, offset, segmentEnd, addTrailingSlash, alreadyEncoded);
offset = segmentEnd + 1;
} while (offset <= pathSegments.length());
return this;
}
public Builder setPathSegment(int index, String pathSegment) {
if (pathSegment == null) throw new IllegalArgumentException("pathSegment == null");
String canonicalPathSegment = canonicalize(
@@ -1270,9 +1302,12 @@ public final class HttpUrl {
// checked for IPv6 square braces. But Chrome does it first, and that's more lenient.
String percentDecoded = percentDecode(input, pos, limit, false);
// If the input is encased in square braces "[...]", drop 'em. We have an IPv6 address.
if (percentDecoded.startsWith("[") && percentDecoded.endsWith("]")) {
InetAddress inetAddress = decodeIpv6(percentDecoded, 1, percentDecoded.length() - 1);
// If the input contains a :, it’s an IPv6 address.
if (percentDecoded.contains(":")) {
// If the input is encased in square braces "[...]", drop 'em.
InetAddress inetAddress = percentDecoded.startsWith("[") && percentDecoded.endsWith("]")
? decodeIpv6(percentDecoded, 1, percentDecoded.length() - 1)
: decodeIpv6(percentDecoded, 0, percentDecoded.length());
if (inetAddress == null) return null;
byte[] address = inetAddress.getAddress();
if (address.length == 16) return inet6AddressToAscii(address);
@@ -151,6 +151,7 @@ public final class DiskLruCache implements Closeable, Flushable {
// Must be read and written when synchronized on 'this'.
private boolean initialized;
private boolean closed;
private boolean mostRecentTrimFailed;
/**
* To differentiate between old and current snapshots, each entry is given a sequence number each
@@ -167,8 +168,14 @@ public final class DiskLruCache implements Closeable, Flushable {
if (!initialized | closed) {
return; // Nothing to do
}
try {
trimToSize();
} catch (IOException ignored) {
mostRecentTrimFailed = true;
}
try {
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
@@ -453,6 +460,11 @@ public final class DiskLruCache implements Closeable, Flushable {
if (entry != null && entry.currentEditor != null) {
return null; // Another edit is in progress.
}
if (mostRecentTrimFailed) {
// Prevent new writes so the cache doesn't grow any further and retry the clean up operation.
executor.execute(cleanupRunnable);
return null;
}
// Flush the journal before creating files to prevent file leaks.
journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n');
@@ -586,7 +598,9 @@ public final class DiskLruCache implements Closeable, Flushable {
validateKey(key);
Entry entry = lruEntries.get(key);
if (entry == null) return false;
return removeEntry(entry);
boolean removed = removeEntry(entry);
if (removed && size <= maxSize) mostRecentTrimFailed = false;
return removed;
}
private boolean removeEntry(Entry entry) throws IOException {
@@ -654,6 +668,7 @@ public final class DiskLruCache implements Closeable, Flushable {
Entry toEvict = lruEntries.values().iterator().next();
removeEntry(toEvict);
}
mostRecentTrimFailed = false;
}
/**
@@ -675,6 +690,7 @@ public final class DiskLruCache implements Closeable, Flushable {
for (Entry entry : lruEntries.values().toArray(new Entry[lruEntries.size()])) {
removeEntry(entry);
}
mostRecentTrimFailed = false;
}
private void validateKey(String key) {
@@ -25,7 +25,6 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
@@ -83,14 +82,18 @@ public class Platform {
System.out.println(warning);
}
public void tagSocket(Socket socket) throws SocketException {
}
public void untagSocket(Socket socket) throws SocketException {
}
public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
return null;
// Attempt to get the trust manager from an OpenJDK socket factory. We attempt this on all
// platforms in order to support Robolectric, which mixes classes from both Android and the
// Oracle JDK. Note that we don't support HTTP/2 or other nice features on Robolectric.
try {
Class<?> sslContextClass = Class.forName("sun.security.ssl.SSLContextImpl");
Object context = readFieldOrNull(sslSocketFactory, sslContextClass, "context");
if (context == null) return null;
return readFieldOrNull(context, X509TrustManager.class, "trustManager");
} catch (ClassNotFoundException e) {
return null;
}
}
public TrustRootIndex trustRootIndex(X509TrustManager trustManager) {
@@ -140,61 +143,43 @@ public class Platform {
"org.apache.harmony.xnet.provider.jsse.SSLParametersImpl");
}
OptionalMethod<Socket> setUseSessionTickets
= new OptionalMethod<>(null, "setUseSessionTickets", boolean.class);
OptionalMethod<Socket> setHostname
= new OptionalMethod<>(null, "setHostname", String.class);
Method trafficStatsTagSocket = null;
Method trafficStatsUntagSocket = null;
OptionalMethod<Socket> setUseSessionTickets = new OptionalMethod<>(
null, "setUseSessionTickets", boolean.class);
OptionalMethod<Socket> setHostname = new OptionalMethod<>(
null, "setHostname", String.class);
OptionalMethod<Socket> getAlpnSelectedProtocol = null;
OptionalMethod<Socket> setAlpnProtocols = null;
// Attempt to find Android 4.0+ APIs.
// Attempt to find Android 5.0+ APIs.
try {
Class<?> trafficStats = Class.forName("android.net.TrafficStats");
trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class);
trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class);
// Attempt to find Android 5.0+ APIs.
try {
Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.
getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
} catch (ClassNotFoundException ignored) {
}
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
Class.forName("android.net.Network"); // Arbitrary class added in Android 5.0.
getAlpnSelectedProtocol = new OptionalMethod<>(byte[].class, "getAlpnSelectedProtocol");
setAlpnProtocols = new OptionalMethod<>(null, "setAlpnProtocols", byte[].class);
} catch (ClassNotFoundException ignored) {
}
return new Android(sslParametersClass, setUseSessionTickets, setHostname,
trafficStatsTagSocket, trafficStatsUntagSocket, getAlpnSelectedProtocol,
setAlpnProtocols);
getAlpnSelectedProtocol, setAlpnProtocols);
} catch (ClassNotFoundException ignored) {
// This isn't an Android runtime.
}
// Find an Oracle JDK.
// Find Jetty's ALPN extension for OpenJDK.
try {
Class<?> sslContextClass = Class.forName("sun.security.ssl.SSLContextImpl");
// Find Jetty's ALPN extension for OpenJDK.
try {
String negoClassName = "org.eclipse.jetty.alpn.ALPN";
Class<?> negoClass = Class.forName(negoClassName);
Class<?> providerClass = Class.forName(negoClassName + "$Provider");
Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
Method getMethod = negoClass.getMethod("get", SSLSocket.class);
Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
return new JdkWithJettyBootPlatform(sslContextClass,
putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass);
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
}
return new JdkPlatform(sslContextClass);
} catch (ClassNotFoundException ignored) {
String negoClassName = "org.eclipse.jetty.alpn.ALPN";
Class<?> negoClass = Class.forName(negoClassName);
Class<?> providerClass = Class.forName(negoClassName + "$Provider");
Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
Method getMethod = negoClass.getMethod("get", SSLSocket.class);
Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
return new JdkWithJettyBootPlatform(
putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass);
} catch (ClassNotFoundException | NoSuchMethodException ignored) {
}
// Probably an Oracle JDK like OpenJDK.
return new Platform();
}
@@ -206,23 +191,16 @@ public class Platform {
private final OptionalMethod<Socket> setUseSessionTickets;
private final OptionalMethod<Socket> setHostname;
// Non-null on Android 4.0+.
private final Method trafficStatsTagSocket;
private final Method trafficStatsUntagSocket;
// Non-null on Android 5.0+.
private final OptionalMethod<Socket> getAlpnSelectedProtocol;
private final OptionalMethod<Socket> setAlpnProtocols;
public Android(Class<?> sslParametersClass, OptionalMethod<Socket> setUseSessionTickets,
OptionalMethod<Socket> setHostname, Method trafficStatsTagSocket,
Method trafficStatsUntagSocket, OptionalMethod<Socket> getAlpnSelectedProtocol,
OptionalMethod<Socket> setHostname, OptionalMethod<Socket> getAlpnSelectedProtocol,
OptionalMethod<Socket> setAlpnProtocols) {
this.sslParametersClass = sslParametersClass;
this.setUseSessionTickets = setUseSessionTickets;
this.setHostname = setHostname;
this.trafficStatsTagSocket = trafficStatsTagSocket;
this.trafficStatsUntagSocket = trafficStatsUntagSocket;
this.getAlpnSelectedProtocol = getAlpnSelectedProtocol;
this.setAlpnProtocols = setAlpnProtocols;
}
@@ -254,7 +232,7 @@ public class Platform {
sslSocketFactory.getClass().getClassLoader());
context = readFieldOrNull(sslSocketFactory, gmsSslParametersClass, "sslParameters");
} catch (ClassNotFoundException e) {
return null;
return super.trustManager(sslSocketFactory);
}
}
@@ -294,30 +272,6 @@ public class Platform {
return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
}
@Override public void tagSocket(Socket socket) throws SocketException {
if (trafficStatsTagSocket == null) return;
try {
trafficStatsTagSocket.invoke(null, socket);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
@Override public void untagSocket(Socket socket) throws SocketException {
if (trafficStatsUntagSocket == null) return;
try {
trafficStatsUntagSocket.invoke(null, socket);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
@Override public void log(String message) {
// Split by line, then ensure each line can fit into Log's maximum length.
for (int i = 0, length = message.length(); i < length; i++) {
@@ -332,34 +286,18 @@ public class Platform {
}
}
/** JDK 1.7 or better. */
private static class JdkPlatform extends Platform {
private final Class<?> sslContextClass;
public JdkPlatform(Class<?> sslContextClass) {
this.sslContextClass = sslContextClass;
}
@Override public X509TrustManager trustManager(SSLSocketFactory sslSocketFactory) {
Object context = readFieldOrNull(sslSocketFactory, sslContextClass, "context");
if (context == null) return null;
return readFieldOrNull(context, X509TrustManager.class, "trustManager");
}
}
/**
* OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path.
*/
private static class JdkWithJettyBootPlatform extends JdkPlatform {
private static class JdkWithJettyBootPlatform extends Platform {
private final Method putMethod;
private final Method getMethod;
private final Method removeMethod;
private final Class<?> clientProviderClass;
private final Class<?> serverProviderClass;
public JdkWithJettyBootPlatform(Class<?> sslContextClass, Method putMethod, Method getMethod,
Method removeMethod, Class<?> clientProviderClass, Class<?> serverProviderClass) {
super(sslContextClass);
public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod,
Class<?> clientProviderClass, Class<?> serverProviderClass) {
this.putMethod = putMethod;
this.getMethod = getMethod;
this.removeMethod = removeMethod;
@@ -223,6 +223,17 @@ public final class Util {
}
}
/** Returns a SHA-256 hash of {@code s}. */
public static ByteString sha256(ByteString s) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] sha1Bytes = messageDigest.digest(s.toByteArray());
return ByteString.of(sha1Bytes);
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
/** Returns an immutable copy of {@code list}. */
public static <T> List<T> immutableList(List<T> list) {
return Collections.unmodifiableList(new ArrayList<>(list));
@@ -275,11 +286,13 @@ public final class Util {
return result;
}
public static String hostHeader(HttpUrl url) {
// TODO: square braces for IPv6 ?
return url.port() != HttpUrl.defaultPort(url.scheme())
? url.host() + ":" + url.port()
public static String hostHeader(HttpUrl url, boolean includeDefaultPort) {
String host = url.host().contains(":")
? "[" + url.host() + "]"
: url.host();
return includeDefaultPort || url.port() != HttpUrl.defaultPort(url.scheme())
? host + ":" + url.port()
: host;
}
/** Returns {@code s} with control characters and non-ASCII characters replaced with '?'. */
@@ -17,7 +17,7 @@ package okhttp3.internal;
public final class Version {
public static String userAgent() {
return "okhttp/3.1.1";
return "okhttp/3.2.0";
}
private Version() {
@@ -161,7 +161,7 @@ public final class Http2xStream implements HttpStream {
result.add(new Header(TARGET_METHOD, request.method()));
result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.url())));
result.add(new Header(VERSION, "HTTP/1.1"));
result.add(new Header(TARGET_HOST, Util.hostHeader(request.url())));
result.add(new Header(TARGET_HOST, Util.hostHeader(request.url(), false)));
result.add(new Header(TARGET_SCHEME, request.url().scheme()));
Set<ByteString> names = new LinkedHashSet<>();
@@ -200,7 +200,7 @@ public final class Http2xStream implements HttpStream {
List<Header> result = new ArrayList<>(headers.size() + 4);
result.add(new Header(TARGET_METHOD, request.method()));
result.add(new Header(TARGET_PATH, RequestLine.requestPath(request.url())));
result.add(new Header(TARGET_AUTHORITY, Util.hostHeader(request.url()))); // Optional.
result.add(new Header(TARGET_AUTHORITY, Util.hostHeader(request.url(), false))); // Optional.
result.add(new Header(TARGET_SCHEME, request.url().scheme()));
for (int i = 0, size = headers.size(); i < size; i++) {
@@ -502,7 +502,7 @@ public final class HttpEngine {
Request.Builder result = request.newBuilder();
if (request.header("Host") == null) {
result.header("Host", hostHeader(request.url()));
result.header("Host", hostHeader(request.url(), false));
}
if (request.header("Connection") == null) {
@@ -71,7 +71,7 @@ public interface FileSystem {
}
}
@Override public boolean exists(File file) throws IOException {
@Override public boolean exists(File file) {
return file.exists();
}
@@ -121,7 +121,7 @@ public interface FileSystem {
void delete(File file) throws IOException;
/** Returns true if {@code file} exists on the file system. */
boolean exists(File file) throws IOException;
boolean exists(File file);
/** Returns the number of bytes stored in {@code file}, or 0 if it does not exist. */
long size(File file);
@@ -241,7 +241,7 @@ public final class RealConnection extends FramedConnection.Listener implements C
// Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
Request tunnelRequest = createTunnelRequest();
HttpUrl url = tunnelRequest.url();
String requestLine = "CONNECT " + url.host() + ":" + url.port() + " HTTP/1.1";
String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
while (true) {
Http1xStream tunnelConnection = new Http1xStream(null, source, sink);
source.timeout().timeout(readTimeout, MILLISECONDS);
@@ -291,7 +291,7 @@ public final class RealConnection extends FramedConnection.Listener implements C
private Request createTunnelRequest() throws IOException {
return new Request.Builder()
.url(route.address().url())
.header("Host", Util.hostHeader(route.address().url()))
.header("Host", Util.hostHeader(route.address().url(), true))
.header("Proxy-Connection", "Keep-Alive")
.header("User-Agent", Version.userAgent()) // For HTTP/1.0 proxies like Squid.
.build();
@@ -40,7 +40,9 @@ public final class AndroidTrustRootIndex implements TrustRootIndex {
try {
TrustAnchor trustAnchor = (TrustAnchor) findByIssuerAndSignatureMethod.invoke(
trustManager, cert);
return trustAnchor.getTrustedCert();
return trustAnchor != null
? trustAnchor.getTrustedCert()
: null;
} catch (IllegalAccessException e) {
throw new AssertionError();
} catch (InvocationTargetException e) {
@@ -40,6 +40,9 @@ import javax.net.ssl.SSLPeerUnverifiedException;
* TrustManagerImpl} and {@code TrustedCertificateIndex}.
*/
public final class CertificateChainCleaner {
/** The maximum number of signers in a chain. We use 9 for consistency with OpenSSL. */
private static final int MAX_SIGNERS = 9;
private final TrustRootIndex trustRootIndex;
public CertificateChainCleaner(TrustRootIndex trustRootIndex) {
@@ -57,40 +60,53 @@ public final class CertificateChainCleaner {
Deque<Certificate> queue = new ArrayDeque<>(chain);
List<Certificate> result = new ArrayList<>();
result.add(queue.removeFirst());
boolean foundTrustedCertificate = false;
followIssuerChain:
while (true) {
for (int c = 0; c < MAX_SIGNERS; c++) {
X509Certificate toVerify = (X509Certificate) result.get(result.size() - 1);
// If this cert has been signed by a trusted CA cert, we're done. Add the trusted CA
// certificate to the end of the chain, unless it's already present. (That would happen if the
// first certificate in the chain is itself a self-signed and trusted CA certificate.)
X509Certificate caCert = trustRootIndex.findByIssuerAndSignature(toVerify);
if (caCert != null) {
if (result.size() > 1 || !toVerify.equals(caCert)) {
result.add(caCert);
// If this cert has been signed by a trusted cert, use that. Add the trusted certificate to
// the end of the chain unless it's already present. (That would happen if the first
// certificate in the chain is itself a self-signed and trusted CA certificate.)
X509Certificate trustedCert = trustRootIndex.findByIssuerAndSignature(toVerify);
if (trustedCert != null) {
if (result.size() > 1 || !toVerify.equals(trustedCert)) {
result.add(trustedCert);
}
return result;
if (verifySignature(trustedCert, trustedCert)) {
return result; // The self-signed cert is a root CA. We're done.
}
foundTrustedCertificate = true;
continue;
}
// Search for the certificate in the chain that signed this certificate. This is typically the
// next element in the chain, but it could be any element.
for (Iterator<Certificate> i = queue.iterator(); i.hasNext(); ) {
X509Certificate signingCert = (X509Certificate) i.next();
if (toVerify.getIssuerDN().equals(signingCert.getSubjectDN())
&& verifySignature(toVerify, signingCert)) {
if (verifySignature(toVerify, signingCert)) {
i.remove();
result.add(signingCert);
continue followIssuerChain;
}
}
throw new SSLPeerUnverifiedException("Failed to find a cert that signed " + toVerify);
// We've reached the end of the chain. If any cert in the chain is trusted, we're done.
if (foundTrustedCertificate) {
return result;
}
// The last link isn't trusted. Fail.
throw new SSLPeerUnverifiedException("Failed to find a trusted cert that signed " + toVerify);
}
throw new SSLPeerUnverifiedException("Certificate chain too long: " + result);
}
/** Returns true if {@code toVerify} was signed by {@code signingCert}'s public key. */
private boolean verifySignature(X509Certificate toVerify, X509Certificate signingCert) {
if (!toVerify.getIssuerDN().equals(signingCert.getSubjectDN())) return false;
try {
toVerify.verify(signingCert.getPublicKey());
return true;