Merge "Move graphics APIs to the light grey." into pi-dev
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 402d9fdd..4b6ab64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -18,25 +18,21 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
-import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import android.Manifest;
import android.app.AppGlobals;
-import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
+import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.Context;
import android.graphics.drawable.Icon;
-import android.os.AsyncTask;
-import android.os.Bundle;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.SnoozeCriterion;
@@ -46,10 +42,8 @@
import android.view.View;
import android.widget.ImageView;
import android.widget.RemoteViews;
-import android.Manifest;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.Dependency;
@@ -454,47 +448,44 @@
return Ranking.VISIBILITY_NO_OVERRIDE;
}
- public boolean shouldSuppressFullScreenIntent(String key) {
+ public boolean shouldSuppressFullScreenIntent(StatusBarNotification sbn) {
+ return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_FULL_SCREEN_INTENT);
+ }
+
+ public boolean shouldSuppressPeek(StatusBarNotification sbn) {
+ return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_PEEK);
+ }
+
+ public boolean shouldSuppressStatusBar(StatusBarNotification sbn) {
+ return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_STATUS_BAR);
+ }
+
+ public boolean shouldSuppressAmbient(StatusBarNotification sbn) {
+ return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_AMBIENT);
+ }
+
+ public boolean shouldSuppressNotificationList(StatusBarNotification sbn) {
+ return shouldSuppressVisualEffect(sbn, SUPPRESSED_EFFECT_NOTIFICATION_LIST);
+ }
+
+ private boolean shouldSuppressVisualEffect(StatusBarNotification sbn, int effect) {
+ if (isExemptFromDndVisualSuppression(sbn)) {
+ return false;
+ }
+ String key = sbn.getKey();
if (mRankingMap != null) {
getRanking(key, mTmpRanking);
- return (mTmpRanking.getSuppressedVisualEffects()
- & SUPPRESSED_EFFECT_FULL_SCREEN_INTENT) != 0;
+ return (mTmpRanking.getSuppressedVisualEffects() & effect) != 0;
}
return false;
}
- public boolean shouldSuppressPeek(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return (mTmpRanking.getSuppressedVisualEffects()
- & SUPPRESSED_EFFECT_PEEK) != 0;
+ protected boolean isExemptFromDndVisualSuppression(StatusBarNotification sbn) {
+ if ((sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ return true;
}
- return false;
- }
-
- public boolean shouldSuppressStatusBar(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return (mTmpRanking.getSuppressedVisualEffects()
- & SUPPRESSED_EFFECT_STATUS_BAR) != 0;
- }
- return false;
- }
-
- public boolean shouldSuppressAmbient(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return (mTmpRanking.getSuppressedVisualEffects()
- & SUPPRESSED_EFFECT_AMBIENT) != 0;
- }
- return false;
- }
-
- public boolean shouldSuppressNotificationList(String key) {
- if (mRankingMap != null) {
- getRanking(key, mTmpRanking);
- return (mTmpRanking.getSuppressedVisualEffects()
- & SUPPRESSED_EFFECT_NOTIFICATION_LIST) != 0;
+ if (sbn.getNotification().isMediaNotification()) {
+ return true;
}
return false;
}
@@ -620,11 +611,11 @@
return true;
}
- if (mEnvironment.isDozing() && shouldSuppressAmbient(sbn.getKey())) {
+ if (mEnvironment.isDozing() && shouldSuppressAmbient(sbn)) {
return true;
}
- if (!mEnvironment.isDozing() && shouldSuppressNotificationList(sbn.getKey())) {
+ if (!mEnvironment.isDozing() && shouldSuppressNotificationList(sbn)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
index 7a7cc99..45df450 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -299,12 +299,12 @@
updateNotifications();
}
- private boolean shouldSuppressFullScreenIntent(String key) {
+ private boolean shouldSuppressFullScreenIntent(StatusBarNotification sbn) {
if (mPresenter.isDeviceInVrMode()) {
return true;
}
- return mNotificationData.shouldSuppressFullScreenIntent(key);
+ return mNotificationData.shouldSuppressFullScreenIntent(sbn);
}
private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
@@ -690,7 +690,7 @@
NotificationData.Entry shadeEntry = createNotificationViews(notification);
boolean isHeadsUped = shouldPeek(shadeEntry);
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
- if (shouldSuppressFullScreenIntent(key)) {
+ if (shouldSuppressFullScreenIntent(notification)) {
if (DEBUG) {
Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
}
@@ -846,13 +846,13 @@
return false;
}
- if (!mPresenter.isDozing() && mNotificationData.shouldSuppressPeek(sbn.getKey())) {
+ if (!mPresenter.isDozing() && mNotificationData.shouldSuppressPeek(sbn)) {
if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
return false;
}
// Peeking triggers an ambient display pulse, so disable peek is ambient is active
- if (mPresenter.isDozing() && mNotificationData.shouldSuppressAmbient(sbn.getKey())) {
+ if (mPresenter.isDozing() && mNotificationData.shouldSuppressAmbient(sbn)) {
if (DEBUG) Log.d(TAG, "No peeking: suppressed by DND: " + sbn.getKey());
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 9063dea..b6a11f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -150,7 +150,7 @@
// showAmbient == show in shade but not shelf
if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar(
- entry.key)) {
+ entry.notification)) {
return false;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
index c437021..5e27fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
@@ -35,6 +35,7 @@
import android.app.NotificationChannel;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.media.session.MediaSession;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
@@ -61,6 +62,7 @@
private static final int UID_NORMAL = 123;
private static final int UID_ALLOW_DURING_SETUP = 456;
private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey";
+ private static final String TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY = "exempt";
private final StatusBarNotification mMockStatusBarNotification =
mock(StatusBarNotification.class);
@@ -275,6 +277,7 @@
@Test
public void testShouldFilterHiddenNotifications() {
+ initStatusBarNotification(false);
// setup
when(mFsc.isSystemAlertWarningNeeded(anyInt(), anyString())).thenReturn(false);
when(mFsc.isSystemAlertNotification(any())).thenReturn(false);
@@ -289,6 +292,33 @@
assertFalse(mNotificationData.shouldFilterOut(mMockStatusBarNotification));
}
+ @Test
+ public void testIsExemptFromDndVisualSuppression_foreground() {
+ initStatusBarNotification(false);
+ when(mMockStatusBarNotification.getKey()).thenReturn(
+ TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY);
+ Notification n = mMockStatusBarNotification.getNotification();
+ n.flags = Notification.FLAG_FOREGROUND_SERVICE;
+
+ assertTrue(mNotificationData.isExemptFromDndVisualSuppression(mMockStatusBarNotification));
+ assertFalse(mNotificationData.shouldSuppressAmbient(mMockStatusBarNotification));
+ }
+
+ @Test
+ public void testIsExemptFromDndVisualSuppression_media() {
+ initStatusBarNotification(false);
+ when(mMockStatusBarNotification.getKey()).thenReturn(
+ TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY);
+ Notification n = mMockStatusBarNotification.getNotification();
+ Notification.Builder nb = Notification.Builder.recoverBuilder(mContext, n);
+ nb.setStyle(new Notification.MediaStyle().setMediaSession(mock(MediaSession.Token.class)));
+ n = nb.build();
+ when(mMockStatusBarNotification.getNotification()).thenReturn(n);
+
+ assertTrue(mNotificationData.isExemptFromDndVisualSuppression(mMockStatusBarNotification));
+ assertFalse(mNotificationData.shouldSuppressAmbient(mMockStatusBarNotification));
+ }
+
private void initStatusBarNotification(boolean allowDuringSetup) {
Bundle bundle = new Bundle();
bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
@@ -318,6 +348,13 @@
outRanking.getImportance(), outRanking.getImportanceExplanation(),
outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
+ } else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) {
+ outRanking.populate(key, outRanking.getRank(),
+ outRanking.matchesInterruptionFilter(),
+ outRanking.getVisibilityOverride(), 255,
+ outRanking.getImportance(), outRanking.getImportanceExplanation(),
+ outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
} else {
outRanking.populate(key, outRanking.getRank(),
outRanking.matchesInterruptionFilter(),
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java
new file mode 100644
index 0000000..dcaa0b4
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotDeserializer.java
@@ -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() {}
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotParserException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotParserException.java
new file mode 100644
index 0000000..a3208af
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotParserException.java
@@ -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);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java
new file mode 100644
index 0000000..ee8b2cf
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSchema.java
@@ -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() {}
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java
new file mode 100644
index 0000000..f817a8f
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializer.java
@@ -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() {}
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index 1eff2d4..7ee809a 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -70,121 +70,6 @@
}
/**
- * Table holding encrypted snapshots of the recoverable key store.
- */
- static class SnapshotsEntry implements BaseColumns {
- static final String TABLE_NAME = "snapshots";
-
- /**
- * The version number of the snapshot.
- */
- static final String COLUMN_NAME_VERSION = "version";
-
- /**
- * The ID of the user whose keystore was snapshotted.
- */
- static final String COLUMN_NAME_USER_ID = "user_id";
-
- /**
- * The UID of the app that owns the snapshot (i.e., the recovery agent).
- */
- static final String COLUMN_NAME_UID = "uid";
-
- /**
- * The maximum number of attempts allowed to attempt to decrypt the recovery key.
- */
- static final String COLUMN_NAME_MAX_ATTEMPTS = "max_attempts";
-
- /**
- * The ID of the counter in the trusted hardware module.
- */
- static final String COLUMN_NAME_COUNTER_ID = "counter_id";
-
- /**
- * Server parameters used to help identify the device (during recovery).
- */
- static final String SERVER_PARAMS = "server_params";
-
- /**
- * The public key of the trusted hardware module. This key has been used to encrypt the
- * snapshot, to ensure that it can only be read by the trusted module.
- */
- static final String TRUSTED_HARDWARE_PUBLIC_KEY = "thm_public_key";
-
- /**
- * {@link java.security.cert.CertPath} signing the trusted hardware module to whose public
- * key this snapshot is encrypted.
- */
- static final String CERT_PATH = "cert_path";
-
- /**
- * The recovery key, encrypted with the user's lock screen and the trusted hardware module's
- * public key.
- */
- static final String ENCRYPTED_RECOVERY_KEY = "encrypted_recovery_key";
- }
-
- /**
- * Table holding encrypted keys belonging to a particular snapshot.
- */
- static class SnapshotKeysEntry implements BaseColumns {
- static final String TABLE_NAME = "snapshot_keys";
-
- /**
- * ID of the associated snapshot entry in {@link SnapshotsEntry}.
- */
- static final String COLUMN_NAME_SNAPSHOT_ID = "snapshot_id";
-
- /**
- * Alias of the key.
- */
- static final String COLUMN_NAME_ALIAS = "alias";
-
- /**
- * Key material, encrypted with the recovery key from the snapshot.
- */
- static final String COLUMN_NAME_ENCRYPTED_BYTES = "encrypted_key_bytes";
- }
-
- /**
- * A layer of protection associated with a snapshot.
- */
- static class SnapshotProtectionParams implements BaseColumns {
- static final String TABLE_NAME = "snapshot_protection_params";
-
- /**
- * ID of the associated snapshot entry in {@link SnapshotsEntry}.
- */
- static final String COLUMN_NAME_SNAPSHOT_ID = "snapshot_id";
-
- /**
- * Type of secret used to generate recovery key. One of
- * {@link android.security.keystore.recovery.KeyChainProtectionParams#TYPE_LOCKSCREEN} or
- */
- static final String COLUMN_NAME_SECRET_TYPE = "secret_type";
-
- /**
- * If a lock screen, the type of UI used. One of
- * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PATTERN},
- * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PIN}, or
- * {@link android.security.keystore.recovery.KeyChainProtectionParams#UI_FORMAT_PASSWORD}.
- */
- static final String COLUMN_NAME_LOCKSCREEN_UI_TYPE = "lock_screen_ui_type";
-
- /**
- * The algorithm used to derive cryptographic material from the key and salt. One of
- * {@link android.security.keystore.recovery.KeyDerivationParams#ALGORITHM_SHA256} or
- * {@link android.security.keystore.recovery.KeyDerivationParams#ALGORITHM_SCRYPT}.
- */
- static final String COLUMN_NAME_KEY_DERIVATION_ALGORITHM = "key_derivation_algorithm";
-
- /**
- * The salt used along with the secret to generate cryptographic material.
- */
- static final String COLUMN_NAME_KEY_DERIVATION_SALT = "key_derivation_salt";
- }
-
- /**
* Recoverable KeyStore metadata for a specific user profile.
*/
static class UserMetadataEntry implements BaseColumns {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java
new file mode 100644
index 0000000..6c2958e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/serialization/KeyChainSnapshotSerializerTest.java
@@ -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();
+ }
+}