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.app.ActivityManager; 22 import android.content.pm.Signature; 23 import android.text.TextUtils; 24 25 import libcore.util.HexEncoding; 26 27 import java.io.ByteArrayOutputStream; 28 import java.io.File; 29 import java.io.FileInputStream; 30 import java.io.IOException; 31 import java.security.DigestInputStream; 32 import java.security.MessageDigest; 33 import java.security.NoSuchAlgorithmException; 34 import java.util.Arrays; 35 36 /** 37 * Helper functions applicable to packages. 38 * @hide 39 */ 40 public final class PackageUtils { 41 42 private static final int LOW_RAM_BUFFER_SIZE_BYTES = 1 * 1000; // 1 kB 43 private static final int HIGH_RAM_BUFFER_SIZE_BYTES = 1 * 1000 * 1000; // 1 MB 44 PackageUtils()45 private PackageUtils() { 46 /* hide constructor */ 47 } 48 49 /** 50 * @see #computeSignaturesSha256Digests(Signature[], String) 51 */ computeSignaturesSha256Digests( @onNull Signature[] signatures)52 public static @NonNull String[] computeSignaturesSha256Digests( 53 @NonNull Signature[] signatures) { 54 return computeSignaturesSha256Digests(signatures, null); 55 } 56 57 /** 58 * Computes the SHA256 digests of a list of signatures. Items in the 59 * resulting array of hashes correspond to the signatures in the 60 * input array. 61 * @param signatures The signatures. 62 * @param separator Separator between each pair of characters, such as a colon, or null to omit. 63 * @return The digest array. 64 */ computeSignaturesSha256Digests( @onNull Signature[] signatures, @Nullable String separator)65 public static @NonNull String[] computeSignaturesSha256Digests( 66 @NonNull Signature[] signatures, @Nullable String separator) { 67 final int signatureCount = signatures.length; 68 final String[] digests = new String[signatureCount]; 69 for (int i = 0; i < signatureCount; i++) { 70 digests[i] = computeSha256Digest(signatures[i].toByteArray(), separator); 71 } 72 return digests; 73 } 74 /** 75 * Computes a SHA256 digest of the signatures' SHA256 digests. First, 76 * individual hashes for each signature is derived in a hexademical 77 * form, then these strings are sorted based the natural ordering, and 78 * finally a hash is derived from these strings' bytes. 79 * @param signatures The signatures. 80 * @return The digest. 81 */ computeSignaturesSha256Digest( @onNull Signature[] signatures)82 public static @NonNull String computeSignaturesSha256Digest( 83 @NonNull Signature[] signatures) { 84 // Shortcut for optimization - most apps singed by a single cert 85 if (signatures.length == 1) { 86 return computeSha256Digest(signatures[0].toByteArray(), null); 87 } 88 89 // Make sure these are sorted to handle reversed certificates 90 final String[] sha256Digests = computeSignaturesSha256Digests(signatures, null); 91 return computeSignaturesSha256Digest(sha256Digests); 92 } 93 94 /** 95 * Computes a SHA256 digest in of the signatures SHA256 digests. First, 96 * the strings are sorted based the natural ordering, and then a hash is 97 * derived from these strings' bytes. 98 * @param sha256Digests Signature SHA256 hashes in hexademical form. 99 * @return The digest. 100 */ computeSignaturesSha256Digest( @onNull String[] sha256Digests)101 public static @NonNull String computeSignaturesSha256Digest( 102 @NonNull String[] sha256Digests) { 103 // Shortcut for optimization - most apps singed by a single cert 104 if (sha256Digests.length == 1) { 105 return sha256Digests[0]; 106 } 107 108 // Make sure these are sorted to handle reversed certificates 109 Arrays.sort(sha256Digests); 110 111 final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 112 for (String sha256Digest : sha256Digests) { 113 try { 114 bytes.write(sha256Digest.getBytes()); 115 } catch (IOException e) { 116 /* ignore - can't happen */ 117 } 118 } 119 return computeSha256Digest(bytes.toByteArray(), null); 120 } 121 122 /** 123 * Computes the SHA256 digest of some data. 124 * @param data The data. 125 * @return The digest or null if an error occurs. 126 */ computeSha256DigestBytes(@onNull byte[] data)127 public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) { 128 MessageDigest messageDigest; 129 try { 130 messageDigest = MessageDigest.getInstance("SHA256"); 131 } catch (NoSuchAlgorithmException e) { 132 /* can't happen */ 133 return null; 134 } 135 136 messageDigest.update(data); 137 138 return messageDigest.digest(); 139 } 140 141 /** 142 * @see #computeSha256Digest(byte[], String) 143 */ computeSha256Digest(@onNull byte[] data)144 public static @Nullable String computeSha256Digest(@NonNull byte[] data) { 145 return computeSha256Digest(data, null); 146 } 147 /** 148 * Computes the SHA256 digest of some data. 149 * @param data The data. 150 * @param separator Separator between each pair of characters, such as a colon, or null to omit. 151 * @return The digest or null if an error occurs. 152 */ computeSha256Digest(@onNull byte[] data, @Nullable String separator)153 public static @Nullable String computeSha256Digest(@NonNull byte[] data, 154 @Nullable String separator) { 155 byte[] sha256DigestBytes = computeSha256DigestBytes(data); 156 if (sha256DigestBytes == null) { 157 return null; 158 } 159 160 if (separator == null) { 161 return HexEncoding.encodeToString(sha256DigestBytes, true /* uppercase */); 162 } 163 164 int length = sha256DigestBytes.length; 165 String[] pieces = new String[length]; 166 for (int index = 0; index < length; index++) { 167 pieces[index] = HexEncoding.encodeToString(sha256DigestBytes[index], true); 168 } 169 170 return TextUtils.join(separator, pieces); 171 } 172 173 /** 174 * Creates a fixed size buffer based on whether the device is low ram or not. This is to be used 175 * with the {@link #computeSha256DigestForLargeFile(String, byte[])} and 176 * {@link #computeSha256DigestForLargeFile(String, byte[], String)} methods. 177 * @return a byte array of size {@link #LOW_RAM_BUFFER_SIZE_BYTES} if the device is a low RAM 178 * device, otherwise a byte array of size {@link #HIGH_RAM_BUFFER_SIZE_BYTES} 179 */ createLargeFileBuffer()180 public static @NonNull byte[] createLargeFileBuffer() { 181 int bufferSize = ActivityManager.isLowRamDeviceStatic() 182 ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES; 183 return new byte[bufferSize]; 184 } 185 186 /** 187 * Computes the SHA256 digest of a large file. 188 * @param filePath The path to which the file's content is to be hashed. 189 * @param fileBuffer A buffer to read file's content into memory. It is strongly recommended to 190 * make use of the {@link #createLargeFileBuffer()} method to create this 191 * buffer. 192 * @return The byte array of SHA256 digest or null if an error occurs. 193 */ computeSha256DigestForLargeFileAsBytes(@onNull String filePath, @NonNull byte[] fileBuffer)194 public static @Nullable byte[] computeSha256DigestForLargeFileAsBytes(@NonNull String filePath, 195 @NonNull byte[] fileBuffer) { 196 MessageDigest messageDigest; 197 try { 198 messageDigest = MessageDigest.getInstance("SHA256"); 199 messageDigest.reset(); 200 } catch (NoSuchAlgorithmException e) { 201 // this really shouldn't happen! 202 return null; 203 } 204 205 File f = new File(filePath); 206 try { 207 DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f), 208 messageDigest); 209 while (digestInputStream.read(fileBuffer) != -1); 210 } catch (IOException e) { 211 e.printStackTrace(); 212 return null; 213 } 214 215 return messageDigest.digest(); 216 } 217 218 /** 219 * @see #computeSha256DigestForLargeFile(String, byte[], String) 220 */ computeSha256DigestForLargeFile(@onNull String filePath, @NonNull byte[] fileBuffer)221 public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, 222 @NonNull byte[] fileBuffer) { 223 return computeSha256DigestForLargeFile(filePath, fileBuffer, null); 224 } 225 226 /** 227 * Computes the SHA256 digest of a large file. 228 * @param filePath The path to which the file's content is to be hashed. 229 * @param fileBuffer A buffer to read file's content into memory. It is strongly recommended to 230 * make use of the {@link #createLargeFileBuffer()} method to create this 231 * buffer. 232 * @param separator Separator between each pair of characters, such as colon, or null to omit. 233 * @see #computeSha256DigestForLargeFile(String, byte[]) 234 * @return The encoded string of SHA256 digest or null if an error occurs. 235 */ computeSha256DigestForLargeFile(@onNull String filePath, @NonNull byte[] fileBuffer, @Nullable String separator)236 public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, 237 @NonNull byte[] fileBuffer, @Nullable String separator) { 238 byte[] resultBytes = computeSha256DigestForLargeFileAsBytes(filePath, fileBuffer); 239 if (separator == null) { 240 return HexEncoding.encodeToString(resultBytes, false); 241 } 242 243 int length = resultBytes.length; 244 String[] pieces = new String[length]; 245 for (int index = 0; index < length; index++) { 246 pieces[index] = HexEncoding.encodeToString(resultBytes[index], true); 247 } 248 return TextUtils.join(separator, pieces); 249 } 250 } 251