1 /*
2  * Copyright (C) 2016 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  */
16 
17 package android.util;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.pm.Signature;
22 import android.text.TextUtils;
23 
24 import libcore.util.HexEncoding;
25 
26 import java.io.ByteArrayOutputStream;
27 import java.io.IOException;
28 import java.security.MessageDigest;
29 import java.security.NoSuchAlgorithmException;
30 import java.util.Arrays;
31 
32 /**
33  * Helper functions applicable to packages.
34  * @hide
35  */
36 public final class PackageUtils {
37 
PackageUtils()38     private PackageUtils() {
39         /* hide constructor */
40     }
41 
42     /**
43      * @see #computeSignaturesSha256Digests(Signature[], String)
44      */
computeSignaturesSha256Digests( @onNull Signature[] signatures)45     public static @NonNull String[] computeSignaturesSha256Digests(
46             @NonNull Signature[] signatures) {
47         return computeSignaturesSha256Digests(signatures, null);
48     }
49 
50     /**
51      * Computes the SHA256 digests of a list of signatures. Items in the
52      * resulting array of hashes correspond to the signatures in the
53      * input array.
54      * @param signatures The signatures.
55      * @param separator Separator between each pair of characters, such as a colon, or null to omit.
56      * @return The digest array.
57      */
computeSignaturesSha256Digests( @onNull Signature[] signatures, @Nullable String separator)58     public static @NonNull String[] computeSignaturesSha256Digests(
59             @NonNull Signature[] signatures, @Nullable String separator) {
60         final int signatureCount = signatures.length;
61         final String[] digests = new String[signatureCount];
62         for (int i = 0; i < signatureCount; i++) {
63             digests[i] = computeSha256Digest(signatures[i].toByteArray(), separator);
64         }
65         return digests;
66     }
67     /**
68      * Computes a SHA256 digest of the signatures' SHA256 digests. First,
69      * individual hashes for each signature is derived in a hexademical
70      * form, then these strings are sorted based the natural ordering, and
71      * finally a hash is derived from these strings' bytes.
72      * @param signatures The signatures.
73      * @return The digest.
74      */
computeSignaturesSha256Digest( @onNull Signature[] signatures)75     public static @NonNull String computeSignaturesSha256Digest(
76             @NonNull Signature[] signatures) {
77         // Shortcut for optimization - most apps singed by a single cert
78         if (signatures.length == 1) {
79             return computeSha256Digest(signatures[0].toByteArray(), null);
80         }
81 
82         // Make sure these are sorted to handle reversed certificates
83         final String[] sha256Digests = computeSignaturesSha256Digests(signatures, null);
84         return computeSignaturesSha256Digest(sha256Digests);
85     }
86 
87     /**
88      * Computes a SHA256 digest in of the signatures SHA256 digests. First,
89      * the strings are sorted based the natural ordering, and then a hash is
90      * derived from these strings' bytes.
91      * @param sha256Digests Signature SHA256 hashes in hexademical form.
92      * @return The digest.
93      */
computeSignaturesSha256Digest( @onNull String[] sha256Digests)94     public static @NonNull String computeSignaturesSha256Digest(
95             @NonNull String[] sha256Digests) {
96         // Shortcut for optimization - most apps singed by a single cert
97         if (sha256Digests.length == 1) {
98             return sha256Digests[0];
99         }
100 
101         // Make sure these are sorted to handle reversed certificates
102         Arrays.sort(sha256Digests);
103 
104         final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
105         for (String sha256Digest : sha256Digests) {
106             try {
107                 bytes.write(sha256Digest.getBytes());
108             } catch (IOException e) {
109                 /* ignore - can't happen */
110             }
111         }
112         return computeSha256Digest(bytes.toByteArray(), null);
113     }
114 
115     /**
116      * Computes the SHA256 digest of some data.
117      * @param data The data.
118      * @return The digest or null if an error occurs.
119      */
computeSha256DigestBytes(@onNull byte[] data)120     public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) {
121         MessageDigest messageDigest;
122         try {
123             messageDigest = MessageDigest.getInstance("SHA256");
124         } catch (NoSuchAlgorithmException e) {
125             /* can't happen */
126             return null;
127         }
128 
129         messageDigest.update(data);
130 
131         return messageDigest.digest();
132     }
133 
134     /**
135      * @see #computeSha256Digest(byte[], String)
136      */
computeSha256Digest(@onNull byte[] data)137     public static @Nullable String computeSha256Digest(@NonNull byte[] data) {
138         return computeSha256Digest(data, null);
139     }
140     /**
141      * Computes the SHA256 digest of some data.
142      * @param data The data.
143      * @param separator Separator between each pair of characters, such as a colon, or null to omit.
144      * @return The digest or null if an error occurs.
145      */
computeSha256Digest(@onNull byte[] data, @Nullable String separator)146     public static @Nullable String computeSha256Digest(@NonNull byte[] data,
147             @Nullable String separator) {
148         byte[] sha256DigestBytes = computeSha256DigestBytes(data);
149         if (sha256DigestBytes == null) {
150             return null;
151         }
152 
153         if (separator == null) {
154             return HexEncoding.encodeToString(sha256DigestBytes, true /* uppercase */);
155         }
156 
157         int length = sha256DigestBytes.length;
158         String[] pieces = new String[length];
159         for (int index = 0; index < length; index++) {
160             pieces[index] = HexEncoding.encodeToString(sha256DigestBytes[index], true);
161         }
162 
163         return TextUtils.join(separator, pieces);
164     }
165 }
166