Merge "Throw on attempt to unwrap a WrappedKey with old PlatformKey"
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java
new file mode 100644
index 0000000..c7a98b6
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/BadPlatformKeyException.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * Error thrown when the {@link PlatformDecryptionKey} instance has a different generation ID from
+ * the {@link WrappedKey} instance.
+ *
+ * @hide
+ */
+public class BadPlatformKeyException extends Exception {
+
+ /**
+ * A new instance with {@code message}.
+ *
+ * @hide
+ */
+ public BadPlatformKeyException(String message) {
+ super(message);
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java
new file mode 100644
index 0000000..35571f1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformDecryptionKey.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.security.keystore.AndroidKeyStoreSecretKey;
+
+/**
+ * Used to unwrap recoverable keys before syncing them with remote storage.
+ *
+ * <p>This is a private key stored in AndroidKeyStore. Has an associated generation ID, which is
+ * stored with wrapped keys, allowing us to ensure the wrapped key has the same version as the
+ * platform key.
+ *
+ * @hide
+ */
+public class PlatformDecryptionKey {
+
+ private final int mGenerationId;
+ private final AndroidKeyStoreSecretKey mKey;
+
+ /**
+ * A new instance.
+ *
+ * @param generationId The generation ID of the platform key.
+ * @param key The key handle in AndroidKeyStore.
+ *
+ * @hide
+ */
+ public PlatformDecryptionKey(int generationId, AndroidKeyStoreSecretKey key) {
+ mGenerationId = generationId;
+ mKey = key;
+ }
+
+ /**
+ * Returns the generation ID.
+ *
+ * @hide
+ */
+ public int getGenerationId() {
+ return mGenerationId;
+ }
+
+ /**
+ * Returns the actual key, which can be used to decrypt.
+ *
+ * @hide
+ */
+ public AndroidKeyStoreSecretKey getKey() {
+ return mKey;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
index 9002292..f18e796 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/WrappedKey.java
@@ -124,27 +124,51 @@
return mKeyMaterial;
}
+
+ /**
+ * Returns the generation ID of the platform key, with which this key was wrapped.
+ *
+ * @hide
+ */
+ public int getPlatformKeyGenerationId() {
+ // TODO(robertberry) Implement. See ag/3362855.
+ return 1;
+ }
+
/**
* Unwraps the {@code wrappedKeys} with the {@code platformKey}.
*
* @return The unwrapped keys, indexed by alias.
* @throws NoSuchAlgorithmException if AES/GCM/NoPadding Cipher or AES key type is unavailable.
+ * @throws BadPlatformKeyException if the {@code platformKey} has a different generation ID to
+ * any of the {@code wrappedKeys}.
*
* @hide
*/
public static Map<String, SecretKey> unwrapKeys(
- SecretKey platformKey,
+ PlatformDecryptionKey platformKey,
Map<String, WrappedKey> wrappedKeys)
- throws NoSuchAlgorithmException, NoSuchPaddingException {
+ throws NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException {
HashMap<String, SecretKey> unwrappedKeys = new HashMap<>();
Cipher cipher = Cipher.getInstance(KEY_WRAP_CIPHER_ALGORITHM);
+ int platformKeyGenerationId = platformKey.getGenerationId();
for (String alias : wrappedKeys.keySet()) {
WrappedKey wrappedKey = wrappedKeys.get(alias);
+ if (wrappedKey.getPlatformKeyGenerationId() != platformKeyGenerationId) {
+ throw new BadPlatformKeyException(String.format(
+ Locale.US,
+ "WrappedKey with alias '%s' was wrapped with platform key %d, not "
+ + "platform key %d",
+ alias,
+ wrappedKey.getPlatformKeyGenerationId(),
+ platformKey.getGenerationId()));
+ }
+
try {
cipher.init(
Cipher.UNWRAP_MODE,
- platformKey,
+ platformKey.getKey(),
new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.getNonce()));
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
Log.e(TAG,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
index fa73722..8371fe5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/WrappedKeyTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.security.keystore.AndroidKeyStoreSecretKey;
import android.security.keystore.KeyGenParameterSpec;
@@ -46,6 +47,7 @@
private static final String KEY_ALGORITHM = "AES";
private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
private static final String WRAPPING_KEY_ALIAS = "WrappedKeyTestWrappingKeyAlias";
+ private static final int GENERATION_ID = 1;
private static final int GCM_TAG_LENGTH_BYTES = 16;
private static final int BITS_PER_BYTE = 8;
private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
@@ -77,9 +79,9 @@
@Test
public void decryptWrappedKeys_decryptsWrappedKeys() throws Exception {
String alias = "karlin";
- SecretKey platformKey = generateAndroidKeyStoreKey();
+ PlatformDecryptionKey platformKey = generatePlatformDecryptionKey();
SecretKey appKey = generateKey();
- WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, appKey);
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey.getKey(), appKey);
HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
keysByAlias.put(alias, wrappedKey);
@@ -99,11 +101,29 @@
keysByAlias.put(alias, wrappedKey);
Map<String, SecretKey> unwrappedKeys = WrappedKey.unwrapKeys(
- generateAndroidKeyStoreKey(), keysByAlias);
+ generatePlatformDecryptionKey(), keysByAlias);
assertEquals(0, unwrappedKeys.size());
}
+ @Test
+ public void decryptWrappedKeys_throwsIfPlatformKeyGenerationIdDoesNotMatch() throws Exception {
+ WrappedKey wrappedKey = WrappedKey.fromSecretKey(generateKey(), generateKey());
+ HashMap<String, WrappedKey> keysByAlias = new HashMap<>();
+ keysByAlias.put("benji", wrappedKey);
+
+ try {
+ WrappedKey.unwrapKeys(
+ generatePlatformDecryptionKey(/*generationId=*/ 2), keysByAlias);
+ fail("Should have thrown.");
+ } catch (BadPlatformKeyException e) {
+ assertEquals(
+ "WrappedKey with alias 'benji' was wrapped with platform key 1,"
+ + " not platform key 2",
+ e.getMessage());
+ }
+ }
+
private SecretKey generateKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
keyGenerator.init(/*keySize=*/ 256);
@@ -122,4 +142,12 @@
.build());
return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
}
+
+ private PlatformDecryptionKey generatePlatformDecryptionKey() throws Exception {
+ return generatePlatformDecryptionKey(GENERATION_ID);
+ }
+
+ private PlatformDecryptionKey generatePlatformDecryptionKey(int generationId) throws Exception {
+ return new PlatformDecryptionKey(generationId, generateAndroidKeyStoreKey());
+ }
}