Alan Viverette | 3da604b | 2020-06-10 18:34:39 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2011 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.content.pm; |
| 18 | |
| 19 | import android.os.Parcel; |
| 20 | import android.os.Parcelable; |
| 21 | |
| 22 | import com.android.internal.annotations.VisibleForTesting; |
| 23 | |
| 24 | import java.io.UnsupportedEncodingException; |
| 25 | import java.security.SecureRandom; |
| 26 | import java.util.Random; |
| 27 | |
| 28 | /** |
| 29 | * An identity that uniquely identifies a particular device. In this |
| 30 | * implementation, the identity is represented as a 64-bit integer encoded to a |
| 31 | * 13-character string using RFC 4648's Base32 encoding without the trailing |
| 32 | * padding. This makes it easy for users to read and write the code without |
| 33 | * confusing 'I' (letter) with '1' (one) or 'O' (letter) with '0' (zero). |
| 34 | * |
| 35 | * @hide |
| 36 | */ |
| 37 | public class VerifierDeviceIdentity implements Parcelable { |
| 38 | /** |
| 39 | * Encoded size of a long (64-bit) into Base32. This format will end up |
| 40 | * looking like XXXX-XXXX-XXXX-X (length ignores hyphens) when applied with |
| 41 | * the GROUP_SIZE below. |
| 42 | */ |
| 43 | private static final int LONG_SIZE = 13; |
| 44 | |
| 45 | /** |
| 46 | * Size of groupings when outputting as strings. This helps people read it |
| 47 | * out and keep track of where they are. |
| 48 | */ |
| 49 | private static final int GROUP_SIZE = 4; |
| 50 | |
| 51 | private final long mIdentity; |
| 52 | |
| 53 | private final String mIdentityString; |
| 54 | |
| 55 | /** |
| 56 | * Create a verifier device identity from a long. |
| 57 | * |
| 58 | * @param identity device identity in a 64-bit integer. |
| 59 | * @throws |
| 60 | */ |
| 61 | public VerifierDeviceIdentity(long identity) { |
| 62 | mIdentity = identity; |
| 63 | mIdentityString = encodeBase32(identity); |
| 64 | } |
| 65 | |
| 66 | private VerifierDeviceIdentity(Parcel source) { |
| 67 | final long identity = source.readLong(); |
| 68 | |
| 69 | mIdentity = identity; |
| 70 | mIdentityString = encodeBase32(identity); |
| 71 | } |
| 72 | |
| 73 | /** |
| 74 | * Generate a new device identity. |
| 75 | * |
| 76 | * @return random uniformly-distributed device identity |
| 77 | */ |
| 78 | public static VerifierDeviceIdentity generate() { |
| 79 | final SecureRandom sr = new SecureRandom(); |
| 80 | return generate(sr); |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * Generate a new device identity using a provided random number generator |
| 85 | * class. This is used for testing. |
| 86 | * |
| 87 | * @param rng random number generator to retrieve the next long from |
| 88 | * @return verifier device identity based on the input from the provided |
| 89 | * random number generator |
| 90 | */ |
| 91 | @VisibleForTesting |
| 92 | static VerifierDeviceIdentity generate(Random rng) { |
| 93 | long identity = rng.nextLong(); |
| 94 | return new VerifierDeviceIdentity(identity); |
| 95 | } |
| 96 | |
| 97 | private static final char ENCODE[] = { |
| 98 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', |
| 99 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', |
| 100 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', |
| 101 | 'Y', 'Z', '2', '3', '4', '5', '6', '7', |
| 102 | }; |
| 103 | |
| 104 | private static final char SEPARATOR = '-'; |
| 105 | |
| 106 | private static final String encodeBase32(long input) { |
| 107 | final char[] alphabet = ENCODE; |
| 108 | |
| 109 | /* |
| 110 | * Make a character array with room for the separators between each |
| 111 | * group. |
| 112 | */ |
| 113 | final char encoded[] = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)]; |
| 114 | |
| 115 | int index = encoded.length; |
| 116 | for (int i = 0; i < LONG_SIZE; i++) { |
| 117 | /* |
| 118 | * Make sure we don't put a separator at the beginning. Since we're |
| 119 | * building from the rear of the array, we use (LONG_SIZE % |
| 120 | * GROUP_SIZE) to make the odd-size group appear at the end instead |
| 121 | * of the beginning. |
| 122 | */ |
| 123 | if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) { |
| 124 | encoded[--index] = SEPARATOR; |
| 125 | } |
| 126 | |
| 127 | /* |
| 128 | * Extract 5 bits of data, then shift it out. |
| 129 | */ |
| 130 | final int group = (int) (input & 0x1F); |
| 131 | input >>>= 5; |
| 132 | |
| 133 | encoded[--index] = alphabet[group]; |
| 134 | } |
| 135 | |
| 136 | return String.valueOf(encoded); |
| 137 | } |
| 138 | |
| 139 | // TODO move this out to its own class (android.util.Base32) |
| 140 | private static final long decodeBase32(byte[] input) throws IllegalArgumentException { |
| 141 | long output = 0L; |
| 142 | int numParsed = 0; |
| 143 | |
| 144 | final int N = input.length; |
| 145 | for (int i = 0; i < N; i++) { |
| 146 | final int group = input[i]; |
| 147 | |
| 148 | /* |
| 149 | * This essentially does the reverse of the ENCODED alphabet above |
| 150 | * without a table. A..Z are 0..25 and 2..7 are 26..31. |
| 151 | */ |
| 152 | final int value; |
| 153 | if ('A' <= group && group <= 'Z') { |
| 154 | value = group - 'A'; |
| 155 | } else if ('2' <= group && group <= '7') { |
| 156 | value = group - ('2' - 26); |
| 157 | } else if (group == SEPARATOR) { |
| 158 | continue; |
| 159 | } else if ('a' <= group && group <= 'z') { |
| 160 | /* Lowercase letters should be the same as uppercase for Base32 */ |
| 161 | value = group - 'a'; |
| 162 | } else if (group == '0') { |
| 163 | /* Be nice to users that mistake O (letter) for 0 (zero) */ |
| 164 | value = 'O' - 'A'; |
| 165 | } else if (group == '1') { |
| 166 | /* Be nice to users that mistake I (letter) for 1 (one) */ |
| 167 | value = 'I' - 'A'; |
| 168 | } else { |
| 169 | throw new IllegalArgumentException("base base-32 character: " + group); |
| 170 | } |
| 171 | |
| 172 | output = (output << 5) | value; |
| 173 | numParsed++; |
| 174 | |
| 175 | if (numParsed == 1) { |
| 176 | if ((value & 0xF) != value) { |
| 177 | throw new IllegalArgumentException("illegal start character; will overflow"); |
| 178 | } |
| 179 | } else if (numParsed > 13) { |
| 180 | throw new IllegalArgumentException("too long; should have 13 characters"); |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | if (numParsed != 13) { |
| 185 | throw new IllegalArgumentException("too short; should have 13 characters"); |
| 186 | } |
| 187 | |
| 188 | return output; |
| 189 | } |
| 190 | |
| 191 | @Override |
| 192 | public int hashCode() { |
| 193 | return (int) mIdentity; |
| 194 | } |
| 195 | |
| 196 | @Override |
| 197 | public boolean equals(Object other) { |
| 198 | if (!(other instanceof VerifierDeviceIdentity)) { |
| 199 | return false; |
| 200 | } |
| 201 | |
| 202 | final VerifierDeviceIdentity o = (VerifierDeviceIdentity) other; |
| 203 | return mIdentity == o.mIdentity; |
| 204 | } |
| 205 | |
| 206 | @Override |
| 207 | public String toString() { |
| 208 | return mIdentityString; |
| 209 | } |
| 210 | |
| 211 | public static VerifierDeviceIdentity parse(String deviceIdentity) |
| 212 | throws IllegalArgumentException { |
| 213 | final byte[] input; |
| 214 | try { |
| 215 | input = deviceIdentity.getBytes("US-ASCII"); |
| 216 | } catch (UnsupportedEncodingException e) { |
| 217 | throw new IllegalArgumentException("bad base-32 characters in input"); |
| 218 | } |
| 219 | |
| 220 | return new VerifierDeviceIdentity(decodeBase32(input)); |
| 221 | } |
| 222 | |
| 223 | @Override |
| 224 | public int describeContents() { |
| 225 | return 0; |
| 226 | } |
| 227 | |
| 228 | @Override |
| 229 | public void writeToParcel(Parcel dest, int flags) { |
| 230 | dest.writeLong(mIdentity); |
| 231 | } |
| 232 | |
| 233 | public static final @android.annotation.NonNull Parcelable.Creator<VerifierDeviceIdentity> CREATOR |
| 234 | = new Parcelable.Creator<VerifierDeviceIdentity>() { |
| 235 | public VerifierDeviceIdentity createFromParcel(Parcel source) { |
| 236 | return new VerifierDeviceIdentity(source); |
| 237 | } |
| 238 | |
| 239 | public VerifierDeviceIdentity[] newArray(int size) { |
| 240 | return new VerifierDeviceIdentity[size]; |
| 241 | } |
| 242 | }; |
| 243 | } |