1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.locksettings.recoverablekeystore; 18 19import android.annotation.NonNull; 20 21import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; 22 23import java.security.InvalidKeyException; 24import java.security.KeyStoreException; 25import java.security.NoSuchAlgorithmException; 26import java.util.Locale; 27import android.util.Log; 28 29import javax.crypto.KeyGenerator; 30import javax.crypto.SecretKey; 31import javax.crypto.spec.SecretKeySpec; 32 33/** 34 * Generates/imports keys and stores them both in AndroidKeyStore and on disk, in wrapped form. 35 * 36 * <p>Generates/imports 256-bit AES keys, which can be used for encrypt and decrypt with AES-GCM. 37 * They are synced to disk wrapped by a platform key. This allows them to be exported to a remote 38 * service. 39 * 40 * @hide 41 */ 42public class RecoverableKeyGenerator { 43 44 private static final String TAG = "PlatformKeyGen"; 45 private static final int RESULT_CANNOT_INSERT_ROW = -1; 46 private static final String SECRET_KEY_ALGORITHM = "AES"; 47 48 static final int KEY_SIZE_BITS = 256; 49 50 /** 51 * A new {@link RecoverableKeyGenerator} instance. 52 * 53 * @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is 54 * unavailable. Should never happen. 55 * 56 * @hide 57 */ 58 public static RecoverableKeyGenerator newInstance(RecoverableKeyStoreDb database) 59 throws NoSuchAlgorithmException { 60 // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key 61 // material, so that it can be synced to disk in encrypted form. 62 KeyGenerator keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGORITHM); 63 return new RecoverableKeyGenerator(keyGenerator, database); 64 } 65 66 private final KeyGenerator mKeyGenerator; 67 private final RecoverableKeyStoreDb mDatabase; 68 69 private RecoverableKeyGenerator( 70 KeyGenerator keyGenerator, 71 RecoverableKeyStoreDb recoverableKeyStoreDb) { 72 mKeyGenerator = keyGenerator; 73 mDatabase = recoverableKeyStoreDb; 74 } 75 76 /** 77 * Generates a 256-bit AES key with the given alias. 78 * 79 * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is 80 * persisted to disk so that it can be synced remotely, and then recovered on another device. 81 * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding. 82 * 83 * @param platformKey The user's platform key, with which to wrap the generated key. 84 * @param userId The user ID of the profile to which the calling app belongs. 85 * @param uid The uid of the application that will own the key. 86 * @param alias The alias by which the key will be known in the recoverable key store. 87 * @throws RecoverableKeyStorageException if there is some error persisting the key either to 88 * the database. 89 * @throws KeyStoreException if there is a KeyStore error wrapping the generated key. 90 * @throws InvalidKeyException if the platform key cannot be used to wrap keys. 91 * 92 * @hide 93 */ 94 public byte[] generateAndStoreKey( 95 PlatformEncryptionKey platformKey, int userId, int uid, String alias) 96 throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException { 97 mKeyGenerator.init(KEY_SIZE_BITS); 98 SecretKey key = mKeyGenerator.generateKey(); 99 100 WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key); 101 long result = mDatabase.insertKey(userId, uid, alias, wrappedKey); 102 103 if (result == RESULT_CANNOT_INSERT_ROW) { 104 throw new RecoverableKeyStorageException( 105 String.format( 106 Locale.US, "Failed writing (%d, %s) to database.", uid, alias)); 107 } 108 109 long updatedRows = mDatabase.setShouldCreateSnapshot(userId, uid, true); 110 if (updatedRows < 0) { 111 Log.e(TAG, "Failed to set the shoudCreateSnapshot flag in the local DB."); 112 } 113 114 return key.getEncoded(); 115 } 116 117 /** 118 * Imports an AES key with the given alias. 119 * 120 * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is 121 * persisted to disk so that it can be synced remotely, and then recovered on another device. 122 * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding. 123 * 124 * @param platformKey The user's platform key, with which to wrap the generated key. 125 * @param userId The user ID of the profile to which the calling app belongs. 126 * @param uid The uid of the application that will own the key. 127 * @param alias The alias by which the key will be known in the recoverable key store. 128 * @param keyBytes The raw bytes of the AES key to be imported. 129 * @throws RecoverableKeyStorageException if there is some error persisting the key either to 130 * the database. 131 * @throws KeyStoreException if there is a KeyStore error wrapping the generated key. 132 * @throws InvalidKeyException if the platform key cannot be used to wrap keys. 133 * 134 * @hide 135 */ 136 public void importKey( 137 @NonNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias, 138 @NonNull byte[] keyBytes) 139 throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException { 140 SecretKey key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM); 141 142 WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key); 143 long result = mDatabase.insertKey(userId, uid, alias, wrappedKey); 144 145 if (result == RESULT_CANNOT_INSERT_ROW) { 146 throw new RecoverableKeyStorageException( 147 String.format( 148 Locale.US, "Failed writing (%d, %s) to database.", uid, alias)); 149 } 150 151 mDatabase.setShouldCreateSnapshot(userId, uid, true); 152 } 153} 154