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 0000000000000..c7a98b6672ec4 --- /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 0000000000000..35571f1f27280 --- /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. + * + *

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 9002292174ba8..f18e7961de5f1 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 @@ public class WrappedKey { 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 unwrapKeys( - SecretKey platformKey, + PlatformDecryptionKey platformKey, Map wrappedKeys) - throws NoSuchAlgorithmException, NoSuchPaddingException { + throws NoSuchAlgorithmException, NoSuchPaddingException, BadPlatformKeyException { HashMap 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 fa73722ea060e..8371fe5e376ca 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 @@ package com.android.server.locksettings.recoverablekeystore; 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 @@ public class WrappedKeyTest { 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 @@ public class WrappedKeyTest { @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 keysByAlias = new HashMap<>(); keysByAlias.put(alias, wrappedKey); @@ -99,11 +101,29 @@ public class WrappedKeyTest { keysByAlias.put(alias, wrappedKey); Map 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 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 @@ public class WrappedKeyTest { .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()); + } }