| /* |
| * Copyright (C) 2011 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.content.pm; |
| |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.security.SecureRandom; |
| import java.util.Random; |
| |
| /** |
| * An identity that uniquely identifies a particular device. In this |
| * implementation, the identity is represented as a 64-bit integer encoded to a |
| * 13-character string using RFC 4648's Base32 encoding without the trailing |
| * padding. This makes it easy for users to read and write the code without |
| * confusing 'I' (letter) with '1' (one) or 'O' (letter) with '0' (zero). |
| * |
| * @hide |
| */ |
| public class VerifierDeviceIdentity implements Parcelable { |
| /** |
| * Encoded size of a long (64-bit) into Base32. This format will end up |
| * looking like XXXX-XXXX-XXXX-X (length ignores hyphens) when applied with |
| * the GROUP_SIZE below. |
| */ |
| private static final int LONG_SIZE = 13; |
| |
| /** |
| * Size of groupings when outputting as strings. This helps people read it |
| * out and keep track of where they are. |
| */ |
| private static final int GROUP_SIZE = 4; |
| |
| private final long mIdentity; |
| |
| private final String mIdentityString; |
| |
| /** |
| * Create a verifier device identity from a long. |
| * |
| * @param identity device identity in a 64-bit integer. |
| * @throws |
| */ |
| public VerifierDeviceIdentity(long identity) { |
| mIdentity = identity; |
| mIdentityString = encodeBase32(identity); |
| } |
| |
| private VerifierDeviceIdentity(Parcel source) { |
| final long identity = source.readLong(); |
| |
| mIdentity = identity; |
| mIdentityString = encodeBase32(identity); |
| } |
| |
| /** |
| * Generate a new device identity. |
| * |
| * @return random uniformly-distributed device identity |
| */ |
| public static VerifierDeviceIdentity generate() { |
| final SecureRandom sr = new SecureRandom(); |
| return generate(sr); |
| } |
| |
| /** |
| * Generate a new device identity using a provided random number generator |
| * class. This is used for testing. |
| * |
| * @param rng random number generator to retrieve the next long from |
| * @return verifier device identity based on the input from the provided |
| * random number generator |
| */ |
| @VisibleForTesting |
| static VerifierDeviceIdentity generate(Random rng) { |
| long identity = rng.nextLong(); |
| return new VerifierDeviceIdentity(identity); |
| } |
| |
| private static final char ENCODE[] = { |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', |
| 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', |
| 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', |
| 'Y', 'Z', '2', '3', '4', '5', '6', '7', |
| }; |
| |
| private static final char SEPARATOR = '-'; |
| |
| private static final String encodeBase32(long input) { |
| final char[] alphabet = ENCODE; |
| |
| /* |
| * Make a character array with room for the separators between each |
| * group. |
| */ |
| final char encoded[] = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)]; |
| |
| int index = encoded.length; |
| for (int i = 0; i < LONG_SIZE; i++) { |
| /* |
| * Make sure we don't put a separator at the beginning. Since we're |
| * building from the rear of the array, we use (LONG_SIZE % |
| * GROUP_SIZE) to make the odd-size group appear at the end instead |
| * of the beginning. |
| */ |
| if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) { |
| encoded[--index] = SEPARATOR; |
| } |
| |
| /* |
| * Extract 5 bits of data, then shift it out. |
| */ |
| final int group = (int) (input & 0x1F); |
| input >>>= 5; |
| |
| encoded[--index] = alphabet[group]; |
| } |
| |
| return String.valueOf(encoded); |
| } |
| |
| // TODO move this out to its own class (android.util.Base32) |
| private static final long decodeBase32(byte[] input) throws IllegalArgumentException { |
| long output = 0L; |
| int numParsed = 0; |
| |
| final int N = input.length; |
| for (int i = 0; i < N; i++) { |
| final int group = input[i]; |
| |
| /* |
| * This essentially does the reverse of the ENCODED alphabet above |
| * without a table. A..Z are 0..25 and 2..7 are 26..31. |
| */ |
| final int value; |
| if ('A' <= group && group <= 'Z') { |
| value = group - 'A'; |
| } else if ('2' <= group && group <= '7') { |
| value = group - ('2' - 26); |
| } else if (group == SEPARATOR) { |
| continue; |
| } else if ('a' <= group && group <= 'z') { |
| /* Lowercase letters should be the same as uppercase for Base32 */ |
| value = group - 'a'; |
| } else if (group == '0') { |
| /* Be nice to users that mistake O (letter) for 0 (zero) */ |
| value = 'O' - 'A'; |
| } else if (group == '1') { |
| /* Be nice to users that mistake I (letter) for 1 (one) */ |
| value = 'I' - 'A'; |
| } else { |
| throw new IllegalArgumentException("base base-32 character: " + group); |
| } |
| |
| output = (output << 5) | value; |
| numParsed++; |
| |
| if (numParsed == 1) { |
| if ((value & 0xF) != value) { |
| throw new IllegalArgumentException("illegal start character; will overflow"); |
| } |
| } else if (numParsed > 13) { |
| throw new IllegalArgumentException("too long; should have 13 characters"); |
| } |
| } |
| |
| if (numParsed != 13) { |
| throw new IllegalArgumentException("too short; should have 13 characters"); |
| } |
| |
| return output; |
| } |
| |
| @Override |
| public int hashCode() { |
| return (int) mIdentity; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (!(other instanceof VerifierDeviceIdentity)) { |
| return false; |
| } |
| |
| final VerifierDeviceIdentity o = (VerifierDeviceIdentity) other; |
| return mIdentity == o.mIdentity; |
| } |
| |
| @Override |
| public String toString() { |
| return mIdentityString; |
| } |
| |
| public static VerifierDeviceIdentity parse(String deviceIdentity) |
| throws IllegalArgumentException { |
| final byte[] input; |
| try { |
| input = deviceIdentity.getBytes("US-ASCII"); |
| } catch (UnsupportedEncodingException e) { |
| throw new IllegalArgumentException("bad base-32 characters in input"); |
| } |
| |
| return new VerifierDeviceIdentity(decodeBase32(input)); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeLong(mIdentity); |
| } |
| |
| public static final @android.annotation.NonNull Parcelable.Creator<VerifierDeviceIdentity> CREATOR |
| = new Parcelable.Creator<VerifierDeviceIdentity>() { |
| public VerifierDeviceIdentity createFromParcel(Parcel source) { |
| return new VerifierDeviceIdentity(source); |
| } |
| |
| public VerifierDeviceIdentity[] newArray(int size) { |
| return new VerifierDeviceIdentity[size]; |
| } |
| }; |
| } |