blob: b486834235dc84d44c723b16d1bf2b4c168e9633 [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:
Dmitry Dementyevebdd19c2018-04-10 16:03:52 -0700135 // Unused
Robert Berry56588372018-03-29 12:07:17 +0100136 break;
137
Robert Berry25f51352018-03-28 20:26:57 +0100138 case TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST:
139 builder.setKeyChainProtectionParams(readKeyChainProtectionParamsList(parser));
140 break;
141
142 case TAG_APPLICATION_KEYS:
143 builder.setWrappedApplicationKeys(readWrappedApplicationKeys(parser));
144 break;
145
146 default:
147 throw new KeyChainSnapshotParserException(String.format(
148 Locale.US, "Unexpected tag %s in keyChainSnapshot", name));
149 }
150 }
151
152 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT);
153 try {
154 return builder.build();
155 } catch (NullPointerException e) {
156 throw new KeyChainSnapshotParserException("Failed to build KeyChainSnapshot", e);
157 }
158 }
159
160 private static List<WrappedApplicationKey> readWrappedApplicationKeys(XmlPullParser parser)
161 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
162 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_APPLICATION_KEYS);
163 ArrayList<WrappedApplicationKey> keys = new ArrayList<>();
164 while (parser.next() != XmlPullParser.END_TAG) {
165 if (parser.getEventType() != XmlPullParser.START_TAG) {
166 continue;
167 }
168 keys.add(readWrappedApplicationKey(parser));
169 }
170 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_APPLICATION_KEYS);
171 return keys;
172 }
173
174 private static WrappedApplicationKey readWrappedApplicationKey(XmlPullParser parser)
175 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
176 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_APPLICATION_KEY);
177 WrappedApplicationKey.Builder builder = new WrappedApplicationKey.Builder();
178 while (parser.next() != XmlPullParser.END_TAG) {
179 if (parser.getEventType() != XmlPullParser.START_TAG) {
180 continue;
181 }
182
183 String name = parser.getName();
184
185 switch (name) {
186 case TAG_ALIAS:
187 builder.setAlias(readStringTag(parser, TAG_ALIAS));
188 break;
189
190 case TAG_KEY_MATERIAL:
191 builder.setEncryptedKeyMaterial(readBlobTag(parser, TAG_KEY_MATERIAL));
192 break;
193
194 default:
195 throw new KeyChainSnapshotParserException(String.format(
196 Locale.US, "Unexpected tag %s in wrappedApplicationKey", name));
197 }
198 }
199 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_APPLICATION_KEY);
200
201 try {
202 return builder.build();
203 } catch (NullPointerException e) {
204 throw new KeyChainSnapshotParserException("Failed to build WrappedApplicationKey", e);
205 }
206 }
207
208 private static List<KeyChainProtectionParams> readKeyChainProtectionParamsList(
209 XmlPullParser parser) throws IOException, XmlPullParserException,
210 KeyChainSnapshotParserException {
211 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST);
212
213 ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList = new ArrayList<>();
214 while (parser.next() != XmlPullParser.END_TAG) {
215 if (parser.getEventType() != XmlPullParser.START_TAG) {
216 continue;
217 }
218 keyChainProtectionParamsList.add(readKeyChainProtectionParams(parser));
219 }
220
221 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST);
222 return keyChainProtectionParamsList;
223 }
224
225 private static KeyChainProtectionParams readKeyChainProtectionParams(XmlPullParser parser)
226 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
227 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS);
228
229 KeyChainProtectionParams.Builder builder = new KeyChainProtectionParams.Builder();
230 while (parser.next() != XmlPullParser.END_TAG) {
231 if (parser.getEventType() != XmlPullParser.START_TAG) {
232 continue;
233 }
234
235 String name = parser.getName();
236
237 switch (name) {
238 case TAG_LOCK_SCREEN_UI_TYPE:
239 builder.setLockScreenUiFormat(readIntTag(parser, TAG_LOCK_SCREEN_UI_TYPE));
240 break;
241
242 case TAG_USER_SECRET_TYPE:
243 builder.setUserSecretType(readIntTag(parser, TAG_USER_SECRET_TYPE));
244 break;
245
246 case TAG_KEY_DERIVATION_PARAMS:
247 builder.setKeyDerivationParams(readKeyDerivationParams(parser));
248 break;
249
250 default:
251 throw new KeyChainSnapshotParserException(String.format(
252 Locale.US,
253 "Unexpected tag %s in keyChainProtectionParams",
254 name));
255
256 }
257 }
258
259 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS);
260
261 try {
262 return builder.build();
263 } catch (NullPointerException e) {
264 throw new KeyChainSnapshotParserException(
265 "Failed to build KeyChainProtectionParams", e);
266 }
267 }
268
269 private static KeyDerivationParams readKeyDerivationParams(XmlPullParser parser)
270 throws XmlPullParserException, IOException, KeyChainSnapshotParserException {
271 parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_DERIVATION_PARAMS);
272
273 int memoryDifficulty = -1;
274 int algorithm = -1;
275 byte[] salt = null;
276
277 while (parser.next() != XmlPullParser.END_TAG) {
278 if (parser.getEventType() != XmlPullParser.START_TAG) {
279 continue;
280 }
281
282 String name = parser.getName();
283
284 switch (name) {
285 case TAG_MEMORY_DIFFICULTY:
286 memoryDifficulty = readIntTag(parser, TAG_MEMORY_DIFFICULTY);
287 break;
288
289 case TAG_ALGORITHM:
290 algorithm = readIntTag(parser, TAG_ALGORITHM);
291 break;
292
293 case TAG_SALT:
294 salt = readBlobTag(parser, TAG_SALT);
295 break;
296
297 default:
298 throw new KeyChainSnapshotParserException(
299 String.format(
300 Locale.US,
301 "Unexpected tag %s in keyDerivationParams",
302 name));
303 }
304 }
305
306 if (salt == null) {
307 throw new KeyChainSnapshotParserException("salt was not set in keyDerivationParams");
308 }
309
310 KeyDerivationParams keyDerivationParams = null;
311
312 switch (algorithm) {
313 case KeyDerivationParams.ALGORITHM_SHA256:
314 keyDerivationParams = KeyDerivationParams.createSha256Params(salt);
315 break;
316
317 case KeyDerivationParams.ALGORITHM_SCRYPT:
318 keyDerivationParams = KeyDerivationParams.createScryptParams(
319 salt, memoryDifficulty);
320 break;
321
322 default:
323 throw new KeyChainSnapshotParserException(
324 "Unknown algorithm in keyDerivationParams");
325 }
326
327 parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_DERIVATION_PARAMS);
328 return keyDerivationParams;
329 }
330
331 private static int readIntTag(XmlPullParser parser, String tagName)
332 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
333 parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
334 String text = readText(parser);
335 parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
336 try {
337 return Integer.valueOf(text);
338 } catch (NumberFormatException e) {
339 throw new KeyChainSnapshotParserException(
340 String.format(
341 Locale.US, "%s expected int but got '%s'", tagName, text), e);
342 }
343 }
344
345 private static long readLongTag(XmlPullParser parser, String tagName)
346 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
347 parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
348 String text = readText(parser);
349 parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
350 try {
351 return Long.valueOf(text);
352 } catch (NumberFormatException e) {
353 throw new KeyChainSnapshotParserException(
354 String.format(
355 Locale.US, "%s expected long but got '%s'", tagName, text), e);
356 }
357 }
358
359 private static String readStringTag(XmlPullParser parser, String tagName)
360 throws IOException, XmlPullParserException {
361 parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
362 String text = readText(parser);
363 parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
364 return text;
365 }
366
367 private static byte[] readBlobTag(XmlPullParser parser, String tagName)
368 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
369 parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
370 String text = readText(parser);
371 parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
372
373 try {
374 return Base64.decode(text, /*flags=*/ Base64.DEFAULT);
375 } catch (IllegalArgumentException e) {
376 throw new KeyChainSnapshotParserException(
377 String.format(
378 Locale.US,
379 "%s expected base64 encoded bytes but got '%s'",
380 tagName, text), e);
381 }
382 }
383
384 private static CertPath readCertPathTag(XmlPullParser parser, String tagName)
385 throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
386 byte[] bytes = readBlobTag(parser, tagName);
387 try {
388 return CertificateFactory.getInstance(CERTIFICATE_FACTORY_TYPE)
389 .generateCertPath(new ByteArrayInputStream(bytes));
390 } catch (CertificateException e) {
391 throw new KeyChainSnapshotParserException("Could not parse CertPath in tag " + tagName,
392 e);
393 }
394 }
395
396 private static String readText(XmlPullParser parser)
397 throws IOException, XmlPullParserException {
398 String result = "";
399 if (parser.next() == XmlPullParser.TEXT) {
400 result = parser.getText();
401 parser.nextTag();
402 }
403 return result;
404 }
405
406 // Statics only
407 private KeyChainSnapshotDeserializer() {}
408}