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