Bump okhttp version
This commit is contained in:
1 parent
d7e2adaef4
commit
e80df262a3
13 files changed
+275
-218
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user