Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 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; |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 21 | import android.content.pm.Signature; |
| 22 | |
Neil Fuller | b39d83e | 2019-04-08 21:57:53 +0100 | [diff] [blame] | 23 | import libcore.util.HexEncoding; |
| 24 | |
Svetoslav Ganov | cdd685c | 2017-08-22 14:35:04 -0700 | [diff] [blame] | 25 | import java.io.ByteArrayOutputStream; |
| 26 | import java.io.IOException; |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 27 | import java.security.MessageDigest; |
| 28 | import java.security.NoSuchAlgorithmException; |
Svetoslav Ganov | cdd685c | 2017-08-22 14:35:04 -0700 | [diff] [blame] | 29 | import java.util.Arrays; |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 30 | |
| 31 | /** |
| 32 | * Helper functions applicable to packages. |
| 33 | * @hide |
| 34 | */ |
| 35 | public final class PackageUtils { |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 36 | |
| 37 | private PackageUtils() { |
| 38 | /* hide constructor */ |
| 39 | } |
| 40 | |
| 41 | /** |
Svetoslav Ganov | cdd685c | 2017-08-22 14:35:04 -0700 | [diff] [blame] | 42 | * Computes the SHA256 digests of a list of signatures. Items in the |
| 43 | * resulting array of hashes correspond to the signatures in the |
| 44 | * input array. |
| 45 | * @param signatures The signatures. |
| 46 | * @return The digest array. |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 47 | */ |
Svetoslav Ganov | cdd685c | 2017-08-22 14:35:04 -0700 | [diff] [blame] | 48 | public static @NonNull String[] computeSignaturesSha256Digests( |
| 49 | @NonNull Signature[] signatures) { |
| 50 | final int signatureCount = signatures.length; |
| 51 | final String[] digests = new String[signatureCount]; |
| 52 | for (int i = 0; i < signatureCount; i++) { |
| 53 | digests[i] = computeSha256Digest(signatures[i].toByteArray()); |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 54 | } |
Svetoslav Ganov | cdd685c | 2017-08-22 14:35:04 -0700 | [diff] [blame] | 55 | return digests; |
| 56 | } |
| 57 | /** |
| 58 | * Computes a SHA256 digest of the signatures' SHA256 digests. First, |
| 59 | * individual hashes for each signature is derived in a hexademical |
| 60 | * form, then these strings are sorted based the natural ordering, and |
| 61 | * finally a hash is derived from these strings' bytes. |
| 62 | * @param signatures The signatures. |
| 63 | * @return The digest. |
| 64 | */ |
| 65 | public static @NonNull String computeSignaturesSha256Digest( |
| 66 | @NonNull Signature[] signatures) { |
| 67 | // Shortcut for optimization - most apps singed by a single cert |
| 68 | if (signatures.length == 1) { |
| 69 | return computeSha256Digest(signatures[0].toByteArray()); |
| 70 | } |
| 71 | |
| 72 | // Make sure these are sorted to handle reversed certificates |
| 73 | final String[] sha256Digests = computeSignaturesSha256Digests(signatures); |
| 74 | return computeSignaturesSha256Digest(sha256Digests); |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 75 | } |
| 76 | |
| 77 | /** |
Svetoslav Ganov | cdd685c | 2017-08-22 14:35:04 -0700 | [diff] [blame] | 78 | * Computes a SHA256 digest in of the signatures SHA256 digests. First, |
| 79 | * the strings are sorted based the natural ordering, and then a hash is |
| 80 | * derived from these strings' bytes. |
| 81 | * @param sha256Digests Signature SHA256 hashes in hexademical form. |
| 82 | * @return The digest. |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 83 | */ |
Svetoslav Ganov | cdd685c | 2017-08-22 14:35:04 -0700 | [diff] [blame] | 84 | public static @NonNull String computeSignaturesSha256Digest( |
| 85 | @NonNull String[] sha256Digests) { |
| 86 | // Shortcut for optimization - most apps singed by a single cert |
| 87 | if (sha256Digests.length == 1) { |
| 88 | return sha256Digests[0]; |
| 89 | } |
| 90 | |
| 91 | // Make sure these are sorted to handle reversed certificates |
| 92 | Arrays.sort(sha256Digests); |
| 93 | |
| 94 | final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
| 95 | for (String sha256Digest : sha256Digests) { |
| 96 | try { |
| 97 | bytes.write(sha256Digest.getBytes()); |
| 98 | } catch (IOException e) { |
| 99 | /* ignore - can't happen */ |
| 100 | } |
| 101 | } |
| 102 | return computeSha256Digest(bytes.toByteArray()); |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 103 | } |
| 104 | |
| 105 | /** |
| 106 | * Computes the SHA256 digest of some data. |
| 107 | * @param data The data. |
| 108 | * @return The digest or null if an error occurs. |
| 109 | */ |
Daniel Cashman | 5cdda34 | 2018-01-19 07:22:52 -0800 | [diff] [blame] | 110 | public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) { |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 111 | MessageDigest messageDigest; |
| 112 | try { |
| 113 | messageDigest = MessageDigest.getInstance("SHA256"); |
| 114 | } catch (NoSuchAlgorithmException e) { |
| 115 | /* can't happen */ |
| 116 | return null; |
| 117 | } |
| 118 | |
| 119 | messageDigest.update(data); |
| 120 | |
Daniel Cashman | 5cdda34 | 2018-01-19 07:22:52 -0800 | [diff] [blame] | 121 | return messageDigest.digest(); |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * Computes the SHA256 digest of some data. |
| 126 | * @param data The data. |
| 127 | * @return The digest or null if an error occurs. |
| 128 | */ |
| 129 | public static @Nullable String computeSha256Digest(@NonNull byte[] data) { |
Neil Fuller | b39d83e | 2019-04-08 21:57:53 +0100 | [diff] [blame] | 130 | byte[] sha256DigestBytes = computeSha256DigestBytes(data); |
| 131 | if (sha256DigestBytes == null) { |
| 132 | return null; |
| 133 | } |
| 134 | return HexEncoding.encodeToString(sha256DigestBytes, true /* uppercase */); |
Svet Ganov | 5d09c99 | 2016-09-07 09:57:41 -0700 | [diff] [blame] | 135 | } |
| 136 | } |