blob: 54deec27f42368682b63ec90283f1f4c7978d4a9 [file] [log] [blame]
/*
* 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;
import android.security.keystore.KeyProperties;
import android.security.keystore.KeyProtection;
import android.util.Log;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.security.auth.DestroyFailedException;
/**
* Generates keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
*
* <p>Generates 256-bit AES keys, which can be used for encrypt / decrypt with AES/GCM/NoPadding.
* They are synced to disk wrapped by a platform key. This allows them to be exported to a remote
* service.
*
* @hide
*/
public class RecoverableKeyGenerator {
private static final String TAG = "RecoverableKeyGenerator";
private static final String KEY_GENERATOR_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
/**
* A new {@link RecoverableKeyGenerator} instance.
*
* @param platformKey Secret key used to wrap generated keys before persisting to disk.
* @param recoverableKeyStorage Class that manages persisting wrapped keys to disk.
* @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is
* unavailable. Should never happen.
*
* @hide
*/
public static RecoverableKeyGenerator newInstance(
PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage)
throws NoSuchAlgorithmException {
// NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
// material, so that it can be synced to disk in encrypted form.
KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
return new RecoverableKeyGenerator(keyGenerator, platformKey, recoverableKeyStorage);
}
private final KeyGenerator mKeyGenerator;
private final RecoverableKeyStorage mRecoverableKeyStorage;
private final PlatformEncryptionKey mPlatformKey;
private RecoverableKeyGenerator(
KeyGenerator keyGenerator,
PlatformEncryptionKey platformKey,
RecoverableKeyStorage recoverableKeyStorage) {
mKeyGenerator = keyGenerator;
mRecoverableKeyStorage = recoverableKeyStorage;
mPlatformKey = platformKey;
}
/**
* Generates a 256-bit AES key with the given alias.
*
* <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is
* persisted to disk so that it can be synced remotely, and then recovered on another device.
* The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
*
* <p>The key handle returned to the caller is a reference to the AndroidKeyStore key,
* meaning that the caller is never able to access the raw, unencrypted key.
*
* @param alias The alias by which the key will be known in AndroidKeyStore.
* @throws InvalidKeyException if the platform key cannot be used to wrap keys.
* @throws IOException if there was an issue writing the wrapped key to the wrapped key store.
* @throws UnrecoverableEntryException if could not retrieve key after putting it in
* AndroidKeyStore. This should not happen.
* @return A handle to the AndroidKeyStore key.
*
* @hide
*/
public SecretKey generateAndStoreKey(String alias) throws KeyStoreException,
InvalidKeyException, IOException, UnrecoverableEntryException {
mKeyGenerator.init(KEY_SIZE_BITS);
SecretKey key = mKeyGenerator.generateKey();
mRecoverableKeyStorage.importIntoAndroidKeyStore(
alias,
key,
new KeyProtection.Builder(
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
WrappedKey wrappedKey = WrappedKey.fromSecretKey(mPlatformKey, key);
try {
// Keep raw key material in memory for minimum possible time.
key.destroy();
} catch (DestroyFailedException e) {
Log.w(TAG, "Could not destroy SecretKey.");
}
mRecoverableKeyStorage.persistToDisk(alias, wrappedKey);
try {
// Reload from the keystore, so that the caller is only provided with the handle of the
// key, not the raw key material.
return mRecoverableKeyStorage.loadFromAndroidKeyStore(alias);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(
"Impossible: NoSuchAlgorithmException when attempting to retrieve a key "
+ "that has only just been stored in AndroidKeyStore.", e);
}
}
}