1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.statementservice.retriever;
17
18import android.content.Context;
19import android.content.pm.PackageManager;
20import android.content.pm.PackageManager.NameNotFoundException;
21import android.content.pm.Signature;
22
23import java.security.MessageDigest;
24import java.security.NoSuchAlgorithmException;
25import java.util.ArrayList;
26import java.util.HashSet;
27import java.util.List;
28
29/**
30 * Utility library for computing certificate fingerprints. Also includes fields name used by
31 * Statement JSON string.
32 */
33public final class Utils {
34
35    private Utils() {}
36
37    /**
38     * Field name for namespace.
39     */
40    public static final String NAMESPACE_FIELD = "namespace";
41
42    /**
43     * Supported asset namespaces.
44     */
45    public static final String NAMESPACE_WEB = "web";
46    public static final String NAMESPACE_ANDROID_APP = "android_app";
47
48    /**
49     * Field names in a web asset descriptor.
50     */
51    public static final String WEB_ASSET_FIELD_SITE = "site";
52
53    /**
54     * Field names in a Android app asset descriptor.
55     */
56    public static final String ANDROID_APP_ASSET_FIELD_PACKAGE_NAME = "package_name";
57    public static final String ANDROID_APP_ASSET_FIELD_CERT_FPS = "sha256_cert_fingerprints";
58
59    /**
60     * Field names in a statement.
61     */
62    public static final String ASSET_DESCRIPTOR_FIELD_RELATION = "relation";
63    public static final String ASSET_DESCRIPTOR_FIELD_TARGET = "target";
64    public static final String DELEGATE_FIELD_DELEGATE = "include";
65
66    private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
67            'A', 'B', 'C', 'D', 'E', 'F' };
68
69    /**
70     * Joins a list of strings, by placing separator between each string. For example,
71     * {@code joinStrings("; ", Arrays.asList(new String[]{"a", "b", "c"}))} returns
72     * "{@code a; b; c}".
73     */
74    public static String joinStrings(String separator, List<String> strings) {
75        switch(strings.size()) {
76            case 0:
77                return "";
78            case 1:
79                return strings.get(0);
80            default:
81                StringBuilder joiner = new StringBuilder();
82                boolean first = true;
83                for (String field : strings) {
84                    if (first) {
85                        first = false;
86                    } else {
87                        joiner.append(separator);
88                    }
89                    joiner.append(field);
90                }
91                return joiner.toString();
92        }
93    }
94
95    /**
96     * Returns the normalized sha-256 fingerprints of a given package according to the Android
97     * package manager.
98     */
99    public static List<String> getCertFingerprintsFromPackageManager(String packageName,
100            Context context) throws NameNotFoundException {
101        Signature[] signatures = context.getPackageManager().getPackageInfo(packageName,
102                PackageManager.GET_SIGNATURES).signatures;
103        ArrayList<String> result = new ArrayList<String>(signatures.length);
104        for (Signature sig : signatures) {
105            result.add(computeNormalizedSha256Fingerprint(sig.toByteArray()));
106        }
107        return result;
108    }
109
110    /**
111     * Computes the hash of the byte array using the specified algorithm, returning a hex string
112     * with a colon between each byte.
113     */
114    public static String computeNormalizedSha256Fingerprint(byte[] signature) {
115        MessageDigest digester;
116        try {
117            digester = MessageDigest.getInstance("SHA-256");
118        } catch (NoSuchAlgorithmException e) {
119            throw new AssertionError("No SHA-256 implementation found.");
120        }
121        digester.update(signature);
122        return byteArrayToHexString(digester.digest());
123    }
124
125    /**
126     * Returns true if there is at least one common string between the two lists of string.
127     */
128    public static boolean hasCommonString(List<String> list1, List<String> list2) {
129        HashSet<String> set2 = new HashSet<>(list2);
130        for (String string : list1) {
131            if (set2.contains(string)) {
132                return true;
133            }
134        }
135        return false;
136    }
137
138    /**
139     * Converts the byte array to an lowercase hexadecimal digits String with a colon character (:)
140     * between each byte.
141     */
142    private static String byteArrayToHexString(byte[] array) {
143        if (array.length == 0) {
144          return "";
145        }
146        char[] buf = new char[array.length * 3 - 1];
147
148        int bufIndex = 0;
149        for (int i = 0; i < array.length; i++) {
150            byte b = array[i];
151            if (i > 0) {
152                buf[bufIndex++] = ':';
153            }
154            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
155            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
156        }
157        return new String(buf);
158    }
159}
160