Merge "Persist KeyChainSnapshot to XML" into pi-dev

This commit is contained in:
Robert Berry
2018-03-29 10:59:17 +00:00
committed by Android (Google) Code Review
5 changed files with 904 additions and 0 deletions

View File

@@ -0,0 +1,401 @@
/*
* Copyright (C) 2018 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 com.android.server.locksettings.recoverablekeystore.serialization;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.CERTIFICATE_FACTORY_TYPE;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.NAMESPACE;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.OUTPUT_ENCODING;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALGORITHM;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_TRUSTED_HARDWARE_CERT_PATH;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_USER_SECRET_TYPE;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Base64;
import android.util.Xml;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.cert.CertPath;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Deserializes a {@link android.security.keystore.recovery.KeyChainSnapshot} instance from XML.
*/
public class KeyChainSnapshotDeserializer {
/**
* Deserializes a {@link KeyChainSnapshot} instance from the XML in the {@code inputStream}.
*
* @throws IOException if there is an IO error reading from the stream.
* @throws KeyChainSnapshotParserException if the XML does not conform to the expected XML for
* a snapshot.
*/
public static KeyChainSnapshot deserialize(InputStream inputStream)
throws KeyChainSnapshotParserException, IOException {
try {
return deserializeInternal(inputStream);
} catch (XmlPullParserException e) {
throw new KeyChainSnapshotParserException("Malformed KeyChainSnapshot XML", e);
}
}
private static KeyChainSnapshot deserializeInternal(InputStream inputStream) throws IOException,
XmlPullParserException, KeyChainSnapshotParserException {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(inputStream, OUTPUT_ENCODING);
parser.nextTag();
parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT);
KeyChainSnapshot.Builder builder = new KeyChainSnapshot.Builder();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
switch (name) {
case TAG_SNAPSHOT_VERSION:
builder.setSnapshotVersion(readIntTag(parser, TAG_SNAPSHOT_VERSION));
break;
case TAG_RECOVERY_KEY_MATERIAL:
builder.setEncryptedRecoveryKeyBlob(
readBlobTag(parser, TAG_RECOVERY_KEY_MATERIAL));
break;
case TAG_COUNTER_ID:
builder.setCounterId(readLongTag(parser, TAG_COUNTER_ID));
break;
case TAG_SERVER_PARAMS:
builder.setServerParams(readBlobTag(parser, TAG_SERVER_PARAMS));
break;
case TAG_MAX_ATTEMPTS:
builder.setMaxAttempts(readIntTag(parser, TAG_MAX_ATTEMPTS));
break;
case TAG_TRUSTED_HARDWARE_CERT_PATH:
try {
builder.setTrustedHardwareCertPath(
readCertPathTag(parser, TAG_TRUSTED_HARDWARE_CERT_PATH));
} catch (CertificateException e) {
throw new KeyChainSnapshotParserException(
"Could not set trustedHardwareCertPath", e);
}
break;
case TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST:
builder.setKeyChainProtectionParams(readKeyChainProtectionParamsList(parser));
break;
case TAG_APPLICATION_KEYS:
builder.setWrappedApplicationKeys(readWrappedApplicationKeys(parser));
break;
default:
throw new KeyChainSnapshotParserException(String.format(
Locale.US, "Unexpected tag %s in keyChainSnapshot", name));
}
}
parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT);
try {
return builder.build();
} catch (NullPointerException e) {
throw new KeyChainSnapshotParserException("Failed to build KeyChainSnapshot", e);
}
}
private static List<WrappedApplicationKey> readWrappedApplicationKeys(XmlPullParser parser)
throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_APPLICATION_KEYS);
ArrayList<WrappedApplicationKey> keys = new ArrayList<>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
keys.add(readWrappedApplicationKey(parser));
}
parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_APPLICATION_KEYS);
return keys;
}
private static WrappedApplicationKey readWrappedApplicationKey(XmlPullParser parser)
throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_APPLICATION_KEY);
WrappedApplicationKey.Builder builder = new WrappedApplicationKey.Builder();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
switch (name) {
case TAG_ALIAS:
builder.setAlias(readStringTag(parser, TAG_ALIAS));
break;
case TAG_KEY_MATERIAL:
builder.setEncryptedKeyMaterial(readBlobTag(parser, TAG_KEY_MATERIAL));
break;
default:
throw new KeyChainSnapshotParserException(String.format(
Locale.US, "Unexpected tag %s in wrappedApplicationKey", name));
}
}
parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_APPLICATION_KEY);
try {
return builder.build();
} catch (NullPointerException e) {
throw new KeyChainSnapshotParserException("Failed to build WrappedApplicationKey", e);
}
}
private static List<KeyChainProtectionParams> readKeyChainProtectionParamsList(
XmlPullParser parser) throws IOException, XmlPullParserException,
KeyChainSnapshotParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST);
ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList = new ArrayList<>();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
keyChainProtectionParamsList.add(readKeyChainProtectionParams(parser));
}
parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST);
return keyChainProtectionParamsList;
}
private static KeyChainProtectionParams readKeyChainProtectionParams(XmlPullParser parser)
throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS);
KeyChainProtectionParams.Builder builder = new KeyChainProtectionParams.Builder();
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
switch (name) {
case TAG_LOCK_SCREEN_UI_TYPE:
builder.setLockScreenUiFormat(readIntTag(parser, TAG_LOCK_SCREEN_UI_TYPE));
break;
case TAG_USER_SECRET_TYPE:
builder.setUserSecretType(readIntTag(parser, TAG_USER_SECRET_TYPE));
break;
case TAG_KEY_DERIVATION_PARAMS:
builder.setKeyDerivationParams(readKeyDerivationParams(parser));
break;
default:
throw new KeyChainSnapshotParserException(String.format(
Locale.US,
"Unexpected tag %s in keyChainProtectionParams",
name));
}
}
parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS);
try {
return builder.build();
} catch (NullPointerException e) {
throw new KeyChainSnapshotParserException(
"Failed to build KeyChainProtectionParams", e);
}
}
private static KeyDerivationParams readKeyDerivationParams(XmlPullParser parser)
throws XmlPullParserException, IOException, KeyChainSnapshotParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_KEY_DERIVATION_PARAMS);
int memoryDifficulty = -1;
int algorithm = -1;
byte[] salt = null;
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) {
continue;
}
String name = parser.getName();
switch (name) {
case TAG_MEMORY_DIFFICULTY:
memoryDifficulty = readIntTag(parser, TAG_MEMORY_DIFFICULTY);
break;
case TAG_ALGORITHM:
algorithm = readIntTag(parser, TAG_ALGORITHM);
break;
case TAG_SALT:
salt = readBlobTag(parser, TAG_SALT);
break;
default:
throw new KeyChainSnapshotParserException(
String.format(
Locale.US,
"Unexpected tag %s in keyDerivationParams",
name));
}
}
if (salt == null) {
throw new KeyChainSnapshotParserException("salt was not set in keyDerivationParams");
}
KeyDerivationParams keyDerivationParams = null;
switch (algorithm) {
case KeyDerivationParams.ALGORITHM_SHA256:
keyDerivationParams = KeyDerivationParams.createSha256Params(salt);
break;
case KeyDerivationParams.ALGORITHM_SCRYPT:
keyDerivationParams = KeyDerivationParams.createScryptParams(
salt, memoryDifficulty);
break;
default:
throw new KeyChainSnapshotParserException(
"Unknown algorithm in keyDerivationParams");
}
parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_KEY_DERIVATION_PARAMS);
return keyDerivationParams;
}
private static int readIntTag(XmlPullParser parser, String tagName)
throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
String text = readText(parser);
parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
try {
return Integer.valueOf(text);
} catch (NumberFormatException e) {
throw new KeyChainSnapshotParserException(
String.format(
Locale.US, "%s expected int but got '%s'", tagName, text), e);
}
}
private static long readLongTag(XmlPullParser parser, String tagName)
throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
String text = readText(parser);
parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
try {
return Long.valueOf(text);
} catch (NumberFormatException e) {
throw new KeyChainSnapshotParserException(
String.format(
Locale.US, "%s expected long but got '%s'", tagName, text), e);
}
}
private static String readStringTag(XmlPullParser parser, String tagName)
throws IOException, XmlPullParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
String text = readText(parser);
parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
return text;
}
private static byte[] readBlobTag(XmlPullParser parser, String tagName)
throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
parser.require(XmlPullParser.START_TAG, NAMESPACE, tagName);
String text = readText(parser);
parser.require(XmlPullParser.END_TAG, NAMESPACE, tagName);
try {
return Base64.decode(text, /*flags=*/ Base64.DEFAULT);
} catch (IllegalArgumentException e) {
throw new KeyChainSnapshotParserException(
String.format(
Locale.US,
"%s expected base64 encoded bytes but got '%s'",
tagName, text), e);
}
}
private static CertPath readCertPathTag(XmlPullParser parser, String tagName)
throws IOException, XmlPullParserException, KeyChainSnapshotParserException {
byte[] bytes = readBlobTag(parser, tagName);
try {
return CertificateFactory.getInstance(CERTIFICATE_FACTORY_TYPE)
.generateCertPath(new ByteArrayInputStream(bytes));
} catch (CertificateException e) {
throw new KeyChainSnapshotParserException("Could not parse CertPath in tag " + tagName,
e);
}
}
private static String readText(XmlPullParser parser)
throws IOException, XmlPullParserException {
String result = "";
if (parser.next() == XmlPullParser.TEXT) {
result = parser.getText();
parser.nextTag();
}
return result;
}
// Statics only
private KeyChainSnapshotDeserializer() {}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2018 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 com.android.server.locksettings.recoverablekeystore.serialization;
/**
* Error thrown when parsing invalid XML, while trying to read a
* {@link android.security.keystore.recovery.KeyChainSnapshot}.
*/
public class KeyChainSnapshotParserException extends Exception {
public KeyChainSnapshotParserException(String message, Throwable cause) {
super(message, cause);
}
public KeyChainSnapshotParserException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2018 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 com.android.server.locksettings.recoverablekeystore.serialization;
/**
* Describes the XML schema of the {@link android.security.keystore.recovery.KeyChainSnapshot} file.
*/
class KeyChainSnapshotSchema {
static final String NAMESPACE = null;
static final String OUTPUT_ENCODING = "UTF-8";
static final String CERTIFICATE_FACTORY_TYPE = "X.509";
static final String CERT_PATH_ENCODING = "PkiPath";
static final String TAG_KEY_CHAIN_SNAPSHOT = "keyChainSnapshot";
static final String TAG_SNAPSHOT_VERSION = "snapshotVersion";
static final String TAG_COUNTER_ID = "counterId";
static final String TAG_MAX_ATTEMPTS = "maxAttempts";
static final String TAG_RECOVERY_KEY_MATERIAL = "recoveryKeyMaterial";
static final String TAG_SERVER_PARAMS = "serverParams";
static final String TAG_TRUSTED_HARDWARE_CERT_PATH = "thmCertPath";
static final String TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST =
"keyChainProtectionParamsList";
static final String TAG_KEY_CHAIN_PROTECTION_PARAMS = "keyChainProtectionParams";
static final String TAG_USER_SECRET_TYPE = "userSecretType";
static final String TAG_LOCK_SCREEN_UI_TYPE = "lockScreenUiType";
static final String TAG_KEY_DERIVATION_PARAMS = "keyDerivationParams";
static final String TAG_ALGORITHM = "algorithm";
static final String TAG_MEMORY_DIFFICULTY = "memoryDifficulty";
static final String TAG_SALT = "salt";
static final String TAG_APPLICATION_KEYS = "applicationKeysList";
static final String TAG_APPLICATION_KEY = "applicationKey";
static final String TAG_ALIAS = "alias";
static final String TAG_KEY_MATERIAL = "keyMaterial";
// Statics only
private KeyChainSnapshotSchema() {}
}

View File

@@ -0,0 +1,196 @@
/*
* Copyright (C) 2018 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 com.android.server.locksettings.recoverablekeystore.serialization;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.CERT_PATH_ENCODING;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.NAMESPACE;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.OUTPUT_ENCODING;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALGORITHM;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_ALIAS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_APPLICATION_KEYS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_COUNTER_ID;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_RECOVERY_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_CHAIN_SNAPSHOT;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_DERIVATION_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_KEY_MATERIAL;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_LOCK_SCREEN_UI_TYPE;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MAX_ATTEMPTS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_MEMORY_DIFFICULTY;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SALT;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SERVER_PARAMS;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_SNAPSHOT_VERSION;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_TRUSTED_HARDWARE_CERT_PATH;
import static com.android.server.locksettings.recoverablekeystore.serialization.KeyChainSnapshotSchema.TAG_USER_SECRET_TYPE;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.util.Base64;
import android.util.Xml;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.OutputStream;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.util.List;
/**
* Serializes a {@link KeyChainSnapshot} instance to XML.
*/
public class KeyChainSnapshotSerializer {
/**
* Serializes {@code keyChainSnapshot} to XML, writing to {@code outputStream}.
*
* @throws IOException if there was an IO error writing to the stream.
* @throws CertificateEncodingException if the {@link CertPath} from
* {@link KeyChainSnapshot#getTrustedHardwareCertPath()} is not encoded correctly.
*/
public static void serialize(KeyChainSnapshot keyChainSnapshot, OutputStream outputStream)
throws IOException, CertificateEncodingException {
XmlSerializer xmlSerializer = Xml.newSerializer();
xmlSerializer.setOutput(outputStream, OUTPUT_ENCODING);
xmlSerializer.startDocument(
/*encoding=*/ null,
/*standalone=*/ null);
xmlSerializer.startTag(NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT);
writeKeyChainSnapshotProperties(xmlSerializer, keyChainSnapshot);
writeKeyChainProtectionParams(xmlSerializer,
keyChainSnapshot.getKeyChainProtectionParams());
writeApplicationKeys(xmlSerializer,
keyChainSnapshot.getWrappedApplicationKeys());
xmlSerializer.endTag(NAMESPACE, TAG_KEY_CHAIN_SNAPSHOT);
xmlSerializer.endDocument();
}
private static void writeApplicationKeys(
XmlSerializer xmlSerializer, List<WrappedApplicationKey> wrappedApplicationKeys)
throws IOException {
xmlSerializer.startTag(NAMESPACE, TAG_APPLICATION_KEYS);
for (WrappedApplicationKey key : wrappedApplicationKeys) {
xmlSerializer.startTag(NAMESPACE, TAG_APPLICATION_KEY);
writeApplicationKeyProperties(xmlSerializer, key);
xmlSerializer.endTag(NAMESPACE, TAG_APPLICATION_KEY);
}
xmlSerializer.endTag(NAMESPACE, TAG_APPLICATION_KEYS);
}
private static void writeApplicationKeyProperties(
XmlSerializer xmlSerializer, WrappedApplicationKey applicationKey) throws IOException {
writePropertyTag(xmlSerializer, TAG_ALIAS, applicationKey.getAlias());
writePropertyTag(xmlSerializer, TAG_KEY_MATERIAL, applicationKey.getEncryptedKeyMaterial());
}
private static void writeKeyChainProtectionParams(
XmlSerializer xmlSerializer,
List<KeyChainProtectionParams> keyChainProtectionParamsList) throws IOException {
xmlSerializer.startTag(NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST);
for (KeyChainProtectionParams keyChainProtectionParams : keyChainProtectionParamsList) {
xmlSerializer.startTag(NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS);
writeKeyChainProtectionParamsProperties(xmlSerializer, keyChainProtectionParams);
xmlSerializer.endTag(NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS);
}
xmlSerializer.endTag(NAMESPACE, TAG_KEY_CHAIN_PROTECTION_PARAMS_LIST);
}
private static void writeKeyChainProtectionParamsProperties(
XmlSerializer xmlSerializer, KeyChainProtectionParams keyChainProtectionParams)
throws IOException {
writePropertyTag(xmlSerializer, TAG_USER_SECRET_TYPE,
keyChainProtectionParams.getUserSecretType());
writePropertyTag(xmlSerializer, TAG_LOCK_SCREEN_UI_TYPE,
keyChainProtectionParams.getLockScreenUiFormat());
// NOTE: Do not serialize the 'secret' field. It should never be set anyway for snapshots
// we generate.
writeKeyDerivationParams(xmlSerializer, keyChainProtectionParams.getKeyDerivationParams());
}
private static void writeKeyDerivationParams(
XmlSerializer xmlSerializer, KeyDerivationParams keyDerivationParams)
throws IOException {
xmlSerializer.startTag(NAMESPACE, TAG_KEY_DERIVATION_PARAMS);
writeKeyDerivationParamsProperties(
xmlSerializer, keyDerivationParams);
xmlSerializer.endTag(NAMESPACE, TAG_KEY_DERIVATION_PARAMS);
}
private static void writeKeyDerivationParamsProperties(
XmlSerializer xmlSerializer, KeyDerivationParams keyDerivationParams)
throws IOException {
writePropertyTag(xmlSerializer, TAG_ALGORITHM, keyDerivationParams.getAlgorithm());
writePropertyTag(xmlSerializer, TAG_SALT, keyDerivationParams.getSalt());
writePropertyTag(xmlSerializer, TAG_MEMORY_DIFFICULTY,
keyDerivationParams.getMemoryDifficulty());
}
private static void writeKeyChainSnapshotProperties(
XmlSerializer xmlSerializer, KeyChainSnapshot keyChainSnapshot)
throws IOException, CertificateEncodingException {
writePropertyTag(xmlSerializer, TAG_SNAPSHOT_VERSION,
keyChainSnapshot.getSnapshotVersion());
writePropertyTag(xmlSerializer, TAG_MAX_ATTEMPTS, keyChainSnapshot.getMaxAttempts());
writePropertyTag(xmlSerializer, TAG_COUNTER_ID, keyChainSnapshot.getCounterId());
writePropertyTag(xmlSerializer, TAG_RECOVERY_KEY_MATERIAL,
keyChainSnapshot.getEncryptedRecoveryKeyBlob());
writePropertyTag(xmlSerializer, TAG_SERVER_PARAMS, keyChainSnapshot.getServerParams());
writePropertyTag(xmlSerializer, TAG_TRUSTED_HARDWARE_CERT_PATH,
keyChainSnapshot.getTrustedHardwareCertPath());
}
private static void writePropertyTag(
XmlSerializer xmlSerializer, String propertyName, long propertyValue)
throws IOException {
xmlSerializer.startTag(NAMESPACE, propertyName);
xmlSerializer.text(Long.toString(propertyValue));
xmlSerializer.endTag(NAMESPACE, propertyName);
}
private static void writePropertyTag(
XmlSerializer xmlSerializer, String propertyName, String propertyValue)
throws IOException {
xmlSerializer.startTag(NAMESPACE, propertyName);
xmlSerializer.text(propertyValue);
xmlSerializer.endTag(NAMESPACE, propertyName);
}
private static void writePropertyTag(
XmlSerializer xmlSerializer, String propertyName, byte[] propertyValue)
throws IOException {
xmlSerializer.startTag(NAMESPACE, propertyName);
xmlSerializer.text(Base64.encodeToString(propertyValue, /*flags=*/ Base64.DEFAULT));
xmlSerializer.endTag(NAMESPACE, propertyName);
}
private static void writePropertyTag(
XmlSerializer xmlSerializer, String propertyName, CertPath certPath)
throws IOException, CertificateEncodingException {
writePropertyTag(xmlSerializer, propertyName, certPath.getEncoded(CERT_PATH_ENCODING));
}
// Statics only
private KeyChainSnapshotSerializer() {}
}

View File

@@ -0,0 +1,218 @@
/*
* Copyright (C) 2018 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 com.android.server.locksettings.recoverablekeystore.serialization;
import static com.google.common.truth.Truth.assertThat;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
import android.security.keystore.recovery.KeyDerivationParams;
import android.security.keystore.recovery.WrappedApplicationKey;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.server.locksettings.recoverablekeystore.TestData;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.security.cert.CertPath;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KeyChainSnapshotSerializerTest {
private static final int COUNTER_ID = 2134;
private static final int SNAPSHOT_VERSION = 125;
private static final int MAX_ATTEMPTS = 21;
private static final byte[] SERVER_PARAMS = new byte[] { 8, 2, 4 };
private static final byte[] KEY_BLOB = new byte[] { 124, 53, 53, 53 };
private static final CertPath CERT_PATH = TestData.CERT_PATH_1;
private static final int SECRET_TYPE = KeyChainProtectionParams.TYPE_LOCKSCREEN;
private static final int LOCK_SCREEN_UI = KeyChainProtectionParams.UI_FORMAT_PASSWORD;
private static final byte[] SALT = new byte[] { 5, 4, 3, 2, 1 };
private static final int MEMORY_DIFFICULTY = 45;
private static final int ALGORITHM = KeyDerivationParams.ALGORITHM_SCRYPT;
private static final byte[] SECRET = new byte[] { 1, 2, 3, 4 };
private static final String TEST_KEY_1_ALIAS = "key1";
private static final byte[] TEST_KEY_1_BYTES = new byte[] { 66, 77, 88 };
private static final String TEST_KEY_2_ALIAS = "key2";
private static final byte[] TEST_KEY_2_BYTES = new byte[] { 99, 33, 11 };
private static final String TEST_KEY_3_ALIAS = "key3";
private static final byte[] TEST_KEY_3_BYTES = new byte[] { 2, 8, 100 };
@Test
public void roundTrip_persistsCounterId() throws Exception {
assertThat(roundTrip().getCounterId()).isEqualTo(COUNTER_ID);
}
@Test
public void roundTrip_persistsSnapshotVersion() throws Exception {
assertThat(roundTrip().getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION);
}
@Test
public void roundTrip_persistsMaxAttempts() throws Exception {
assertThat(roundTrip().getMaxAttempts()).isEqualTo(MAX_ATTEMPTS);
}
@Test
public void roundTrip_persistsRecoveryKey() throws Exception {
assertThat(roundTrip().getEncryptedRecoveryKeyBlob()).isEqualTo(KEY_BLOB);
}
@Test
public void roundTrip_persistsServerParams() throws Exception {
assertThat(roundTrip().getServerParams()).isEqualTo(SERVER_PARAMS);
}
@Test
public void roundTrip_persistsCertPath() throws Exception {
assertThat(roundTrip().getTrustedHardwareCertPath()).isEqualTo(CERT_PATH);
}
@Test
public void roundTrip_persistsParamsList() throws Exception {
assertThat(roundTrip().getKeyChainProtectionParams()).hasSize(1);
}
@Test
public void roundTripParams_persistsUserSecretType() throws Exception {
assertThat(roundTripParams().getUserSecretType()).isEqualTo(SECRET_TYPE);
}
@Test
public void roundTripParams_persistsLockScreenUi() throws Exception {
assertThat(roundTripParams().getLockScreenUiFormat()).isEqualTo(LOCK_SCREEN_UI);
}
@Test
public void roundTripParams_persistsSalt() throws Exception {
assertThat(roundTripParams().getKeyDerivationParams().getSalt()).isEqualTo(SALT);
}
@Test
public void roundTripParams_persistsAlgorithm() throws Exception {
assertThat(roundTripParams().getKeyDerivationParams().getAlgorithm()).isEqualTo(ALGORITHM);
}
@Test
public void roundTripParams_persistsMemoryDifficulty() throws Exception {
assertThat(roundTripParams().getKeyDerivationParams().getMemoryDifficulty())
.isEqualTo(MEMORY_DIFFICULTY);
}
@Test
public void roundTripParams_doesNotPersistSecret() throws Exception {
assertThat(roundTripParams().getSecret()).isEmpty();
}
@Test
public void roundTripKeys_hasCorrectLength() throws Exception {
assertThat(roundTripKeys()).hasSize(3);
}
@Test
public void roundTripKeys_0_persistsAlias() throws Exception {
assertThat(roundTripKeys().get(0).getAlias()).isEqualTo(TEST_KEY_1_ALIAS);
}
@Test
public void roundTripKeys_0_persistsKeyBytes() throws Exception {
assertThat(roundTripKeys().get(0).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_1_BYTES);
}
@Test
public void roundTripKeys_1_persistsAlias() throws Exception {
assertThat(roundTripKeys().get(1).getAlias()).isEqualTo(TEST_KEY_2_ALIAS);
}
@Test
public void roundTripKeys_1_persistsKeyBytes() throws Exception {
assertThat(roundTripKeys().get(1).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_2_BYTES);
}
@Test
public void roundTripKeys_2_persistsAlias() throws Exception {
assertThat(roundTripKeys().get(2).getAlias()).isEqualTo(TEST_KEY_3_ALIAS);
}
@Test
public void roundTripKeys_2_persistsKeyBytes() throws Exception {
assertThat(roundTripKeys().get(2).getEncryptedKeyMaterial()).isEqualTo(TEST_KEY_3_BYTES);
}
private static List<WrappedApplicationKey> roundTripKeys() throws Exception {
return roundTrip().getWrappedApplicationKeys();
}
private static KeyChainProtectionParams roundTripParams() throws Exception {
return roundTrip().getKeyChainProtectionParams().get(0);
}
public static KeyChainSnapshot roundTrip() throws Exception {
KeyChainSnapshot snapshot = createTestKeyChainSnapshot();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
KeyChainSnapshotSerializer.serialize(snapshot, byteArrayOutputStream);
return KeyChainSnapshotDeserializer.deserialize(
new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
}
private static KeyChainSnapshot createTestKeyChainSnapshot() throws Exception {
KeyDerivationParams keyDerivationParams =
KeyDerivationParams.createScryptParams(SALT, MEMORY_DIFFICULTY);
KeyChainProtectionParams keyChainProtectionParams = new KeyChainProtectionParams.Builder()
.setKeyDerivationParams(keyDerivationParams)
.setUserSecretType(SECRET_TYPE)
.setLockScreenUiFormat(LOCK_SCREEN_UI)
.setSecret(SECRET)
.build();
ArrayList<KeyChainProtectionParams> keyChainProtectionParamsList =
new ArrayList<>(1);
keyChainProtectionParamsList.add(keyChainProtectionParams);
ArrayList<WrappedApplicationKey> keyList = new ArrayList<>();
keyList.add(createKey(TEST_KEY_1_ALIAS, TEST_KEY_1_BYTES));
keyList.add(createKey(TEST_KEY_2_ALIAS, TEST_KEY_2_BYTES));
keyList.add(createKey(TEST_KEY_3_ALIAS, TEST_KEY_3_BYTES));
return new KeyChainSnapshot.Builder()
.setCounterId(COUNTER_ID)
.setSnapshotVersion(SNAPSHOT_VERSION)
.setServerParams(SERVER_PARAMS)
.setMaxAttempts(MAX_ATTEMPTS)
.setEncryptedRecoveryKeyBlob(KEY_BLOB)
.setKeyChainProtectionParams(keyChainProtectionParamsList)
.setWrappedApplicationKeys(keyList)
.setTrustedHardwareCertPath(CERT_PATH)
.build();
}
private static WrappedApplicationKey createKey(String alias, byte[] bytes) {
return new WrappedApplicationKey.Builder()
.setAlias(alias)
.setEncryptedKeyMaterial(bytes)
.build();
}
}