Index: components/cronet/android/api/src/org/chromium/net/CronetEngine.java |
diff --git a/components/cronet/android/api/src/org/chromium/net/CronetEngine.java b/components/cronet/android/api/src/org/chromium/net/CronetEngine.java |
index 89c4c892e973e1d25c9bdbc22d79965fa33428db..abf01bfd589740273c08c369f4c935592e581ede 100644 |
--- a/components/cronet/android/api/src/org/chromium/net/CronetEngine.java |
+++ b/components/cronet/android/api/src/org/chromium/net/CronetEngine.java |
@@ -6,6 +6,7 @@ package org.chromium.net; |
import android.content.Context; |
import android.support.annotation.IntDef; |
+import android.util.Base64; |
import android.util.Log; |
import org.json.JSONArray; |
@@ -20,8 +21,12 @@ import java.net.Proxy; |
import java.net.URL; |
import java.net.URLConnection; |
import java.net.URLStreamHandlerFactory; |
+import java.util.Collection; |
+import java.util.Date; |
+import java.util.HashSet; |
import java.util.List; |
import java.util.Map; |
+import java.util.Set; |
import java.util.concurrent.Executor; |
/** |
@@ -303,6 +308,96 @@ public abstract class CronetEngine { |
} |
/** |
+ * Adds public key pins for a given host. |
+ * |
+ * @param hostName name of the host to which public keys should be pinned. |
+ * @param pinsSha256 a collection of pins. Each pin is the SHA-256 cryptographic |
+ * hash of DER-encoded ASN.1 representation of Subject Public |
+ * Key Info (SPKI) of the host X.509 certificate. Use |
+ * {@link java.security.cert.Certificate#getPublicKey() |
+ * Certificate.getPublicKey} and |
+ * {@link java.security.Key#getEncoded() Key.getEncoded} |
+ * to obtain DER-encoded ASN.1 representation of SPKI. |
+ * @param includeSubdomains indicates whether the pinning policy should be applied to |
+ * subdomains of {@code hostName}. |
+ * @param expirationDate specifies the expiration date for the pins. |
+ * @return the builder to facilitate chaining. |
+ * @throws NullPointerException if one of the input parameters is null. |
+ * @throws IllegalArgumentException if the given host name is invalid or the |
+ * {@code pinsSha256} collection contains a byte array |
+ * that does not represent a valid SHA-256 hash. |
+ */ |
+ public Builder addPublicKeyPins(String hostName, Collection<byte[]> pinsSha256, |
+ boolean includeSubdomains, Date expirationDate) { |
+ if (hostName == null) { |
+ throw new NullPointerException("The hostname cannot be null"); |
+ } |
+ if (!isValidHostNameForPinning(hostName)) { |
+ throw new IllegalArgumentException("Invalid host name: " + hostName); |
+ } |
+ if (pinsSha256 == null) { |
+ throw new NullPointerException("The collection of SHA256 pins cannot be null"); |
+ } |
+ try { |
+ // Add HPKP_LIST JSON array element if it is not present. |
+ JSONArray hpkpList = mConfig.optJSONArray(CronetEngineBuilderList.HPKP_LIST); |
estark
2015/11/18 23:10:06
totally optional nit: it's a little confusing to u
kapishnikov
2015/11/19 00:06:56
Done. Renamed HPKP to PKP everywhere in the new co
|
+ if (hpkpList == null) { |
+ hpkpList = new JSONArray(); |
+ mConfig.put(CronetEngineBuilderList.HPKP_LIST, hpkpList); |
+ } |
+ |
+ // Convert the pin to BASE64 encoding. |
+ Set<String> hashes = new HashSet<>(pinsSha256.size()); |
+ for (byte[] pinSha256 : pinsSha256) { |
+ hashes.add(convertSha256ToBase64WithPrefix(pinSha256)); |
+ } |
+ |
+ // Add new element to HPKP_LIST JSON array. |
+ JSONObject hpkp = new JSONObject(); |
+ hpkp.put(CronetEngineBuilderList.HPKP_HOST, hostName); |
+ hpkp.put(CronetEngineBuilderList.HPKP_PIN_HASHES, new JSONArray(hashes)); |
+ hpkp.put(CronetEngineBuilderList.HPKP_INCLUDE_SUBDOMAINS, includeSubdomains); |
+ // The expiration time is passed as a double, in seconds since January 1, 1970. |
+ hpkp.put(CronetEngineBuilderList.HPKP_EXPIRATION_DATE, |
+ (double) expirationDate.getTime() / 1000); |
+ hpkpList.put(hpkp); |
+ } catch (JSONException e) { |
+ // This exception should never happen. |
+ throw new RuntimeException( |
+ "Failed to add pubic key pins with the given arguments", e); |
+ } |
+ return this; |
+ } |
+ |
+ /** |
+ * Converts a given SHA256 array of bytes to BASE64 encoded string and prepends |
+ * {@code sha256/} prefix to it. The format corresponds to the format that is expected by |
+ * {@code net::HashValue} class. |
+ * |
+ * @param sha256 SHA256 bytes to convert to BASE64. |
+ * @return the BASE64 encoded SHA256 with the prefix. |
+ * @throws IllegalArgumentException if the provided pin is invalid. |
+ */ |
+ private static String convertSha256ToBase64WithPrefix(byte[] sha256) { |
+ if (sha256 == null || sha256.length != 32) { |
+ throw new IllegalArgumentException("Public key pin is invalid"); |
+ } |
+ return "sha256/" + Base64.encodeToString(sha256, Base64.NO_WRAP); |
+ } |
+ |
+ /** |
+ * Checks whether the given string that represents a host name is valid for HPKP. |
+ * A valid host name should not match IPv4 address. |
+ * |
+ * @see <a href="https://tools.ietf.org/html/rfc7469#section-2.3.3>RFC 7469</a> |
+ * @param hostName host name to check. |
+ * @return true if the string is a valid host name. |
+ */ |
+ private static boolean isValidHostNameForPinning(String hostName) { |
+ return CronetUtil.isValidHostName(hostName) && !CronetUtil.isValidIPv4(hostName); |
+ } |
+ |
+ /** |
* Sets experimental QUIC connection options, overwriting any pre-existing |
* options. List of options is subject to change. |
* |