| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.util; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.ActivityManager; |
| import android.content.pm.Signature; |
| import android.text.TextUtils; |
| |
| import libcore.util.HexEncoding; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.security.DigestInputStream; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Arrays; |
| |
| /** |
| * Helper functions applicable to packages. |
| * @hide |
| */ |
| public final class PackageUtils { |
| |
| private static final int LOW_RAM_BUFFER_SIZE_BYTES = 1 * 1000; // 1 kB |
| private static final int HIGH_RAM_BUFFER_SIZE_BYTES = 1 * 1000 * 1000; // 1 MB |
| |
| private PackageUtils() { |
| /* hide constructor */ |
| } |
| |
| /** |
| * @see #computeSignaturesSha256Digests(Signature[], String) |
| */ |
| public static @NonNull String[] computeSignaturesSha256Digests( |
| @NonNull Signature[] signatures) { |
| return computeSignaturesSha256Digests(signatures, null); |
| } |
| |
| /** |
| * Computes the SHA256 digests of a list of signatures. Items in the |
| * resulting array of hashes correspond to the signatures in the |
| * input array. |
| * @param signatures The signatures. |
| * @param separator Separator between each pair of characters, such as a colon, or null to omit. |
| * @return The digest array. |
| */ |
| public static @NonNull String[] computeSignaturesSha256Digests( |
| @NonNull Signature[] signatures, @Nullable String separator) { |
| final int signatureCount = signatures.length; |
| final String[] digests = new String[signatureCount]; |
| for (int i = 0; i < signatureCount; i++) { |
| digests[i] = computeSha256Digest(signatures[i].toByteArray(), separator); |
| } |
| return digests; |
| } |
| /** |
| * Computes a SHA256 digest of the signatures' SHA256 digests. First, |
| * individual hashes for each signature is derived in a hexademical |
| * form, then these strings are sorted based the natural ordering, and |
| * finally a hash is derived from these strings' bytes. |
| * @param signatures The signatures. |
| * @return The digest. |
| */ |
| public static @NonNull String computeSignaturesSha256Digest( |
| @NonNull Signature[] signatures) { |
| // Shortcut for optimization - most apps singed by a single cert |
| if (signatures.length == 1) { |
| return computeSha256Digest(signatures[0].toByteArray(), null); |
| } |
| |
| // Make sure these are sorted to handle reversed certificates |
| final String[] sha256Digests = computeSignaturesSha256Digests(signatures, null); |
| return computeSignaturesSha256Digest(sha256Digests); |
| } |
| |
| /** |
| * Computes a SHA256 digest in of the signatures SHA256 digests. First, |
| * the strings are sorted based the natural ordering, and then a hash is |
| * derived from these strings' bytes. |
| * @param sha256Digests Signature SHA256 hashes in hexademical form. |
| * @return The digest. |
| */ |
| public static @NonNull String computeSignaturesSha256Digest( |
| @NonNull String[] sha256Digests) { |
| // Shortcut for optimization - most apps singed by a single cert |
| if (sha256Digests.length == 1) { |
| return sha256Digests[0]; |
| } |
| |
| // Make sure these are sorted to handle reversed certificates |
| Arrays.sort(sha256Digests); |
| |
| final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
| for (String sha256Digest : sha256Digests) { |
| try { |
| bytes.write(sha256Digest.getBytes()); |
| } catch (IOException e) { |
| /* ignore - can't happen */ |
| } |
| } |
| return computeSha256Digest(bytes.toByteArray(), null); |
| } |
| |
| /** |
| * Computes the SHA256 digest of some data. |
| * @param data The data. |
| * @return The digest or null if an error occurs. |
| */ |
| public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) { |
| MessageDigest messageDigest; |
| try { |
| messageDigest = MessageDigest.getInstance("SHA256"); |
| } catch (NoSuchAlgorithmException e) { |
| /* can't happen */ |
| return null; |
| } |
| |
| messageDigest.update(data); |
| |
| return messageDigest.digest(); |
| } |
| |
| /** |
| * @see #computeSha256Digest(byte[], String) |
| */ |
| public static @Nullable String computeSha256Digest(@NonNull byte[] data) { |
| return computeSha256Digest(data, null); |
| } |
| /** |
| * Computes the SHA256 digest of some data. |
| * @param data The data. |
| * @param separator Separator between each pair of characters, such as a colon, or null to omit. |
| * @return The digest or null if an error occurs. |
| */ |
| public static @Nullable String computeSha256Digest(@NonNull byte[] data, |
| @Nullable String separator) { |
| byte[] sha256DigestBytes = computeSha256DigestBytes(data); |
| if (sha256DigestBytes == null) { |
| return null; |
| } |
| |
| if (separator == null) { |
| return HexEncoding.encodeToString(sha256DigestBytes, true /* uppercase */); |
| } |
| |
| int length = sha256DigestBytes.length; |
| String[] pieces = new String[length]; |
| for (int index = 0; index < length; index++) { |
| pieces[index] = HexEncoding.encodeToString(sha256DigestBytes[index], true); |
| } |
| |
| return TextUtils.join(separator, pieces); |
| } |
| |
| /** |
| * Creates a fixed size buffer based on whether the device is low ram or not. This is to be used |
| * with the {@link #computeSha256DigestForLargeFile(String, byte[])} and |
| * {@link #computeSha256DigestForLargeFile(String, byte[], String)} methods. |
| * @return a byte array of size {@link #LOW_RAM_BUFFER_SIZE_BYTES} if the device is a low RAM |
| * device, otherwise a byte array of size {@link #HIGH_RAM_BUFFER_SIZE_BYTES} |
| */ |
| public static @NonNull byte[] createLargeFileBuffer() { |
| int bufferSize = ActivityManager.isLowRamDeviceStatic() |
| ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES; |
| return new byte[bufferSize]; |
| } |
| |
| /** |
| * Computes the SHA256 digest of a large file. |
| * @param filePath The path to which the file's content is to be hashed. |
| * @param fileBuffer A buffer to read file's content into memory. It is strongly recommended to |
| * make use of the {@link #createLargeFileBuffer()} method to create this |
| * buffer. |
| * @return The byte array of SHA256 digest or null if an error occurs. |
| */ |
| public static @Nullable byte[] computeSha256DigestForLargeFileAsBytes(@NonNull String filePath, |
| @NonNull byte[] fileBuffer) { |
| MessageDigest messageDigest; |
| try { |
| messageDigest = MessageDigest.getInstance("SHA256"); |
| messageDigest.reset(); |
| } catch (NoSuchAlgorithmException e) { |
| // this really shouldn't happen! |
| return null; |
| } |
| |
| File f = new File(filePath); |
| try (DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f), |
| messageDigest)) { |
| while (digestInputStream.read(fileBuffer) != -1); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| return null; |
| } |
| |
| return messageDigest.digest(); |
| } |
| |
| /** |
| * @see #computeSha256DigestForLargeFile(String, byte[], String) |
| */ |
| public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, |
| @NonNull byte[] fileBuffer) { |
| return computeSha256DigestForLargeFile(filePath, fileBuffer, null); |
| } |
| |
| /** |
| * Computes the SHA256 digest of a large file. |
| * @param filePath The path to which the file's content is to be hashed. |
| * @param fileBuffer A buffer to read file's content into memory. It is strongly recommended to |
| * make use of the {@link #createLargeFileBuffer()} method to create this |
| * buffer. |
| * @param separator Separator between each pair of characters, such as colon, or null to omit. |
| * @see #computeSha256DigestForLargeFile(String, byte[]) |
| * @return The encoded string of SHA256 digest or null if an error occurs. |
| */ |
| public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, |
| @NonNull byte[] fileBuffer, @Nullable String separator) { |
| byte[] resultBytes = computeSha256DigestForLargeFileAsBytes(filePath, fileBuffer); |
| if (separator == null) { |
| return HexEncoding.encodeToString(resultBytes, false); |
| } |
| |
| int length = resultBytes.length; |
| String[] pieces = new String[length]; |
| for (int index = 0; index < length; index++) { |
| pieces[index] = HexEncoding.encodeToString(resultBytes[index], true); |
| } |
| return TextUtils.join(separator, pieces); |
| } |
| } |