| Index: components/installedapp/android/java/src/org/chromium/device/installedapp/AssociationVerifier.java
|
| diff --git a/components/installedapp/android/java/src/org/chromium/device/installedapp/AssociationVerifier.java b/components/installedapp/android/java/src/org/chromium/device/installedapp/AssociationVerifier.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..71c76d6a7ea8aa6f99a7d79b8f89076422bd8daf
|
| --- /dev/null
|
| +++ b/components/installedapp/android/java/src/org/chromium/device/installedapp/AssociationVerifier.java
|
| @@ -0,0 +1,138 @@
|
| +package org.chromium.components.installedapp;
|
| +
|
| +import android.content.pm.PackageManager;
|
| +import android.content.pm.PackageManager.NameNotFoundException;
|
| +import android.content.pm.PackageInfo;
|
| +import android.content.pm.Signature;
|
| +
|
| +import android.content.res.Resources.NotFoundException;
|
| +import android.content.res.Resources;
|
| +
|
| +import java.security.MessageDigest;
|
| +import java.security.NoSuchAlgorithmException;
|
| +import java.util.HashSet;
|
| +import java.util.List;
|
| +import java.util.Arrays;
|
| +import java.util.Collections;
|
| +
|
| +import org.json.JSONArray;
|
| +import org.json.JSONException;
|
| +import org.json.JSONObject;
|
| +
|
| +public class AssociationVerifier {
|
| + private static final String ASSOCIATED_ASSETS_KEY = "associated_assets";
|
| + private static final String ASSET_DESCRIPTOR_FIELD_TARGET = "target";
|
| + private static final String ASSET_DESCRIPTOR_WEB = "web";
|
| + private static final String ASSET_DESCRIPTOR_FIELD_SITE = "site";
|
| + private static final String ASSET_DESCRIPTOR_FIELD_NAMESPACE = "namespace";
|
| +
|
| + public static boolean verifyCertFingerprints(
|
| + String packageName, List<String> sha, PackageManager pm) {
|
| + Signature[] signatures;
|
| + try {
|
| + signatures = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures;
|
| + } catch (NameNotFoundException e) {
|
| + return false;
|
| + }
|
| +
|
| + // TODO: Convert this to an array.
|
| + HashSet<String> signatureStrings = new HashSet<String>(signatures.length);
|
| + for (Signature sig : signatures) {
|
| + signatureStrings.add(computeNormalizedSha256Fingerprint(sig.toByteArray()));
|
| + }
|
| +
|
| + return signatureStrings.containsAll(sha) && sha.containsAll(signatureStrings);
|
| + }
|
| +
|
| + private static List<String> getAssociations(String packageName, PackageManager pm) {
|
| + Resources resources;
|
| +
|
| + try {
|
| + resources = pm.getResourcesForApplication(packageName);
|
| + } catch (NameNotFoundException e) {
|
| + return Collections.<String>emptyList();
|
| + }
|
| +
|
| + if (resources == null) {
|
| + return Collections.<String>emptyList();
|
| + }
|
| +
|
| + int identifier = resources.getIdentifier(ASSOCIATED_ASSETS_KEY, "array", packageName);
|
| + System.out.println("Identifier was " + identifier);
|
| + if (identifier == 0) {
|
| + return Collections.<String>emptyList();
|
| + }
|
| + return Arrays.asList(resources.getStringArray(identifier));
|
| + }
|
| +
|
| + public static boolean isOriginAssociatedWithPackage(
|
| + String packageName, String origin, PackageManager pm) {
|
| + List<String> associations = getAssociations(packageName, pm);
|
| + // HashSet<String> associationSet = (new HashSet<>(associations));
|
| + // System.out.println(associationSet);
|
| +
|
| + for (String statementString : associations) {
|
| + try {
|
| + JSONObject statement = new JSONObject(statementString);
|
| + String jsonOrigin = statement.getString(ASSET_DESCRIPTOR_FIELD_TARGET);
|
| + JSONObject webAsset = new JSONObject(jsonOrigin);
|
| + System.out.println(jsonOrigin);
|
| +
|
| + // // TODO: Handle this.
|
| + // JSONArray relations = statement.getJSONArray(ASSET_DESCRIPTOR_FIELD_RELATION);
|
| + // for (int i = 0; i < relations.length(); i++) {
|
| + // // statements.add(Statement
|
| + // // .create(source, target, Relation.create()));
|
| + // }
|
| +
|
| + if (!webAsset.getString(ASSET_DESCRIPTOR_FIELD_NAMESPACE)
|
| + .equals(ASSET_DESCRIPTOR_WEB)) {
|
| + System.out.println("ASSET WAS NOT WEB");
|
| + continue;
|
| + }
|
| +
|
| + if (webAsset.getString(ASSET_DESCRIPTOR_FIELD_SITE).equals(origin)) {
|
| + return true;
|
| + }
|
| + System.out.println(webAsset.getString(ASSET_DESCRIPTOR_FIELD_SITE)
|
| + + " failed to match against " + origin);
|
| + } catch (JSONException e) {
|
| + }
|
| + }
|
| +
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * Computes the hash of the byte array using the specified algorithm, returning a hex string
|
| + * with a colon between each byte.
|
| + */
|
| + public static String computeNormalizedSha256Fingerprint(byte[] cert) {
|
| + MessageDigest messageDigest;
|
| + try {
|
| + messageDigest = MessageDigest.getInstance("SHA-256");
|
| + } catch (NoSuchAlgorithmException e) {
|
| + throw new AssertionError("No SHA-256 implementation found.");
|
| + }
|
| + messageDigest.update(cert);
|
| + return byteArrayToHexString(messageDigest.digest());
|
| + }
|
| +
|
| + /**
|
| + * Converts the byte array to an lowercase hexadecimal digits String with a colon character (:)
|
| + * between each byte.
|
| + */
|
| + private static final char[] HEX_DIGITS = {
|
| + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
| + private static String byteArrayToHexString(byte[] array) {
|
| + char[] hexString = new char[array.length * 3];
|
| + int index = 0;
|
| + for (byte b : array) {
|
| + hexString[index++] = HEX_DIGITS[(b >>> 4) & 0x0F];
|
| + hexString[index++] = HEX_DIGITS[b & 0x0F];
|
| + hexString[index++] = ':';
|
| + }
|
| +
|
| + return new String(Arrays.copyOf(hexString, hexString.length - 1));
|
| + }
|
| +}
|
|
|