blob: f29aaece6c469db95b92c591e17cf5a8ef612e7c [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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
17package android.content.pm;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21
22import com.android.internal.annotations.VisibleForTesting;
23
24import java.io.UnsupportedEncodingException;
25import java.security.SecureRandom;
26import 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 */
37public 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}