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