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