blob: f789155cee52507517678dcdb006f89c2c0bd870 [file] [log] [blame]
Robert Berry25f51352018-03-28 20:26:57 +01001/*
2 * Copyright (C) 2018 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 com.android.server.locksettings.recoverablekeystore.serialization;
18
19import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.CERTIFICATE_FACTORY_TYPE;
20import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.NAMESPACE;
21import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.OUTPUT_ENCODING;
22import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALGORITHM;
23import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS;
24import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY;
25import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS;
Robert Berry56588372018-03-29 12:07:17 +010026
27import static com.android.server.locksettings.recoverablekeystore.serialization
28 .KeyChainSnapshotSchema.TAG_BACKEND_PUBLIC_KEY;
Robert Berry25f51352018-03-28 20:26:57 +010029import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID;
30import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
31import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS;
32import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST;
33import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT;
34import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS;
35import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL;
36import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE;
37import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS;
38import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY;
39import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT;
40import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS;
41import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION;
42import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_TRUSTED_HARDWARE_CERT_PATH;
43import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_USER_SECRET_TYPE;
44
45import android.security.keystore.recovery.KeyChainProtectionParams;
46import android.security.keystore.recovery.KeyChainSnapshot;
47import android.security.keystore.recovery.KeyDerivationParams;
48import android.security.keystore.recovery.WrappedApplicationKey;
49import android.util.Base64;
50import android.util.Xml;
51
52import java.io.ByteArrayInputStream;
53import java.io.IOException;
54import java.io.InputStream;
55import java.security.cert.CertPath;
56import java.security.cert.CertificateException;
57import java.security.cert.CertificateFactory;
58import java.util.ArrayList;
59import java.util.List;
60import java.util.Locale;
61
62import org.xmlpull.v1.XmlPullParser;
63import org.xmlpull.v1.XmlPullParserException;
64
65/**
66 * Deserializes a {@link android.security.keystore.recovery.KeyChainSnapshot} instance from XML.
67 */
68public class KeyChainSnapshotDeserializer {
69
70 /**
71 * Deserializes a {@link KeyChainSnapshot} instance from the XML in the {@code inputStream}.
72 *
73 * @throws IOException if there is an IO error reading from the stream.
74 * @throws KeyChainSnapshotParserException if the XML does not conform to the expected XML for
75 * a snapshot.
76 */
77 public static KeyChainSnapshot deserialize(InputStream inputStream)
78 throws KeyChainSnapshotParserException, IOException {
79 try {
80 return deserializeInternal(inputStream);
81 } catch (XmlPullParserException e) {
82 throw new KeyChainSnapshotParserException("Malformed KeyChainSnapshot XML", e);
83 }
84 }
85
86 private static KeyChainSnapshot deserializeInternal(InputStream inputStream) throws IOException,
87 XmlPullParserException, KeyChainSnapshotParserException {
88 XmlPullParser parser = Xml.newPullParser();
89 parser.setInput(inputStream, OUTPUT_ENCODING);
90
91 parser.nextTag();
92 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT);
93
94 KeyChainSnapshot.Builder builder = new KeyChainSnapshot.Builder();
95 while (parser.next() != XmlPullParser.END_TAG) {
96 if (parser.getEventType() != XmlPullParser.START_TAG) {
97 continue;
98 }
99
100 String name = parser.getName();
101
102 switch (name) {
103 case TAG_SNAPSHOT_VERSION:
104 builder.setSnapshotVersion(readIntTag(parser, TAG_SNAPSHOT_VERSION));
105 break;
106
107 case TAG_RECOVERY_KEY_MATERIAL:
108 builder.setEncryptedRecoveryKeyBlob(
109 readBlobTag(parser, TAG_RECOVERY_KEY_MATERIAL));
110 break;
111
112 case TAG_COUNTER_ID:
113 builder.setCounterId(readLongTag(parser, TAG_COUNTER_ID));
114 break;
115
116 case TAG_SERVER_PARAMS:
117 builder.setServerParams(readBlobTag(parser, TAG_SERVER_PARAMS));
118 break;
119
120 case TAG_MAX_ATTEMPTS:
121 builder.setMaxAttempts(readIntTag(parser, TAG_MAX_ATTEMPTS));
122 break;
123
124 case TAG_TRUSTED_HARDWARE_CERT_PATH:
125 try {
126 builder.setTrustedHardwareCertPath(
127 readCertPathTag(parser, TAG_TRUSTED_HARDWARE_CERT_PATH));
128 } catch (CertificateException e) {
129 throw new KeyChainSnapshotParserException(
130 "Could not set trustedHardwareCertPath", e);
131 }
132 break;
133
Robert Berry56588372018-03-29 12:07:17 +0100134 case TAG_BACKEND_PUBLIC_KEY:
135 builder.setTrustedHardwarePublicKey(
136 readBlobTag(parser, TAG_BACKEND_PUBLIC_KEY));
137 break;
138
Robert Berry25f51352018-03-28 20:26:57 +0100139 case TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST:
140 builder.setKeyChainProtectionParams(readKeyChainProtectionParamsList(parser));
141 break;
142
143 case TAG_APPLICATION_KEYS:
144 builder.setWrappedApplicationKeys(readWrappedApplicationKeys(parser));
145 break;
146
147 default:
148 throw new KeyChainSnapshotParserException(String.format(
149 Locale.US, "Unexpected tag %s in keyChainSnapshot", name));
150 }
151 }
152
153 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT);
154 try {
155 return builder.build();
156 } catch (NullPointerException e) {
157 throw new KeyChainSnapshotParserException("Failed to build KeyChainSnapshot", e);
158 }
159 }
160
161 private static List<WrappedApplicationKey> readWrappedApplicationKeys(XmlPullParser parser)
162 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
163 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_APPLICATION_KEYS);
164 ArrayList<WrappedApplicationKey> keys = new ArrayList<>();
165 while (parser.next() != XmlPullParser.END_TAG) {
166 if (parser.getEventType() != XmlPullParser.START_TAG) {
167 continue;
168 }
169 keys.add(readWrappedApplicationKey(parser));
170 }
171 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_APPLICATION_KEYS);
172 return keys;
173 }
174
175 private static WrappedApplicationKey readWrappedApplicationKey(XmlPullParser parser)
176 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
177 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_APPLICATION_KEY);
178 WrappedApplicationKey.Builder builder = new WrappedApplicationKey.Builder();
179 while (parser.next() != XmlPullParser.END_TAG) {
180 if (parser.getEventType() != XmlPullParser.START_TAG) {
181 continue;
182 }
183
184 String name = parser.getName();
185
186 switch (name) {
187 case TAG_ALIAS:
188 builder.setAlias(readStringTag(parser, TAG_ALIAS));
189 break;
190
191 case TAG_KEY_MATERIAL:
192 builder.setEncryptedKeyMaterial(readBlobTag(parser, TAG_KEY_MATERIAL));
193 break;
194
195 default:
196 throw new KeyChainSnapshotParserException(String.format(
197 Locale.US, "Unexpected tag %s in wrappedApplicationKey", name));
198 }
199 }
200 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_APPLICATION_KEY);
201
202 try {
203 return builder.build();
204 } catch (NullPointerException e) {
205 throw new KeyChainSnapshotParserException("Failed to build WrappedApplicationKey", e);
206 }
207 }
208
209 private static List<KeyChainProtectionParams> readKeyChainProtectionParamsList(
210 XmlPullParser parser) throws IOException, XmlPullParserException,
211 KeyChainSnapshotParserException {
212 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST);
213
214 ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList = new ArrayList<>();
215 while (parser.next() != XmlPullParser.END_TAG) {
216 if (parser.getEventType() != XmlPullParser.START_TAG) {
217 continue;
218 }
219 keyChainProtectionParamsList.add(readKeyChainProtectionParams(parser));
220 }
221
222 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST);
223 return keyChainProtectionParamsList;
224 }
225
226 private static KeyChainProtectionParams readKeyChainProtectionParams(XmlPullParser parser)
227 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
228 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS);
229
230 KeyChainProtectionParams.Builder builder = new KeyChainProtectionParams.Builder();
231 while (parser.next() != XmlPullParser.END_TAG) {
232 if (parser.getEventType() != XmlPullParser.START_TAG) {
233 continue;
234 }
235
236 String name = parser.getName();
237
238 switch (name) {
239 case TAG_LOCK_SCREEN_UI_TYPE:
240 builder.setLockScreenUiFormat(readIntTag(parser, TAG_LOCK_SCREEN_UI_TYPE));
241 break;
242
243 case TAG_USER_SECRET_TYPE:
244 builder.setUserSecretType(readIntTag(parser, TAG_USER_SECRET_TYPE));
245 break;
246
247 case TAG_KEY_DERIVATION_PARAMS:
248 builder.setKeyDerivationParams(readKeyDerivationParams(parser));
249 break;
250
251 default:
252 throw new KeyChainSnapshotParserException(String.format(
253 Locale.US,
254 "Unexpected tag %s in keyChainProtectionParams",
255 name));
256
257 }
258 }
259
260 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS);
261
262 try {
263 return builder.build();
264 } catch (NullPointerException e) {
265 throw new KeyChainSnapshotParserException(
266 "Failed to build KeyChainProtectionParams", e);
267 }
268 }
269
270 private static KeyDerivationParams readKeyDerivationParams(XmlPullParser parser)
271 throws XmlPullParserException, IOException, KeyChainSnapshotParserException {
272 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_DERIVATION_PARAMS);
273
274 int memoryDifficulty = -1;
275 int algorithm = -1;
276 byte[] salt = null;
277
278 while (parser.next() != XmlPullParser.END_TAG) {
279 if (parser.getEventType() != XmlPullParser.START_TAG) {
280 continue;
281 }
282
283 String name = parser.getName();
284
285 switch (name) {
286 case TAG_MEMORY_DIFFICULTY:
287 memoryDifficulty = readIntTag(parser, TAG_MEMORY_DIFFICULTY);
288 break;
289
290 case TAG_ALGORITHM:
291 algorithm = readIntTag(parser, TAG_ALGORITHM);
292 break;
293
294 case TAG_SALT:
295 salt = readBlobTag(parser, TAG_SALT);
296 break;
297
298 default:
299 throw new KeyChainSnapshotParserException(
300 String.format(
301 Locale.US,
302 "Unexpected tag %s in keyDerivationParams",
303 name));
304 }
305 }
306
307 if (salt == null) {
308 throw new KeyChainSnapshotParserException("salt was not set in keyDerivationParams");
309 }
310
311 KeyDerivationParams keyDerivationParams = null;
312
313 switch (algorithm) {
314 case KeyDerivationParams.ALGORITHM_SHA256:
315 keyDerivationParams = KeyDerivationParams.createSha256Params(salt);
316 break;
317
318 case KeyDerivationParams.ALGORITHM_SCRYPT:
319 keyDerivationParams = KeyDerivationParams.createScryptParams(
320 salt, memoryDifficulty);
321 break;
322
323 default:
324 throw new KeyChainSnapshotParserException(
325 "Unknown algorithm in keyDerivationParams");
326 }
327
328 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_DERIVATION_PARAMS);
329 return keyDerivationParams;
330 }
331
332 private static int readIntTag(XmlPullParser parser, String tagName)
333 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
334 parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
335 String text = readText(parser);
336 parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
337 try {
338 return Integer.valueOf(text);
339 } catch (NumberFormatException e) {
340 throw new KeyChainSnapshotParserException(
341 String.format(
342 Locale.US, "%s expected int but got '%s'", tagName, text), e);
343 }
344 }
345
346 private static long readLongTag(XmlPullParser parser, String tagName)
347 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
348 parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
349 String text = readText(parser);
350 parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
351 try {
352 return Long.valueOf(text);
353 } catch (NumberFormatException e) {
354 throw new KeyChainSnapshotParserException(
355 String.format(
356 Locale.US, "%s expected long but got '%s'", tagName, text), e);
357 }
358 }
359
360 private static String readStringTag(XmlPullParser parser, String tagName)
361 throws IOException, XmlPullParserException {
362 parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
363 String text = readText(parser);
364 parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
365 return text;
366 }
367
368 private static byte[] readBlobTag(XmlPullParser parser, String tagName)
369 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
370 parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
371 String text = readText(parser);
372 parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
373
374 try {
375 return Base64.decode(text, /*flags=*/ Base64.DEFAULT);
376 } catch (IllegalArgumentException e) {
377 throw new KeyChainSnapshotParserException(
378 String.format(
379 Locale.US,
380 "%s expected base64 encoded bytes but got '%s'",
381 tagName, text), e);
382 }
383 }
384
385 private static CertPath readCertPathTag(XmlPullParser parser, String tagName)
386 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
387 byte[] bytes = readBlobTag(parser, tagName);
388 try {
389 return CertificateFactory.getInstance(CERTIFICATE_FACTORY_TYPE)
390 .generateCertPath(new ByteArrayInputStream(bytes));
391 } catch (CertificateException e) {
392 throw new KeyChainSnapshotParserException("Could not parse CertPath in tag " + tagName,
393 e);
394 }
395 }
396
397 private static String readText(XmlPullParser parser)
398 throws IOException, XmlPullParserException {
399 String result = "";
400 if (parser.next() == XmlPullParser.TEXT) {
401 result = parser.getText();
402 parser.nextTag();
403 }
404 return result;
405 }
406
407 // Statics only
408 private KeyChainSnapshotDeserializer() {}
409}