Merge "Throw on attempt to unwrap a WrappedKey with old PlatformKey"
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<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,
|
||||
|
||||
@@ -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<String, WrappedKey> keysByAlias = new HashMap<>();
|
||||
keysByAlias.put(alias, wrappedKey);
|
||||
|
||||
@@ -99,11 +101,29 @@ public class WrappedKeyTest {
|
||||
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 @@ 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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user