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 17 package com.android.server.locksettings.recoverablekeystore; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.util.Log; 22 23 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb; 24 25 import java.security.InvalidKeyException; 26 import java.security.KeyStoreException; 27 import java.security.NoSuchAlgorithmException; 28 import java.util.Locale; 29 30 import javax.crypto.KeyGenerator; 31 import javax.crypto.SecretKey; 32 import javax.crypto.spec.SecretKeySpec; 33 34 /** 35 * Generates/imports keys and stores them both in AndroidKeyStore and on disk, in wrapped form. 36 * 37 * <p>Generates/imports 256-bit AES keys, which can be used for encrypt and decrypt with AES-GCM. 38 * They are synced to disk wrapped by a platform key. This allows them to be exported to a remote 39 * service. 40 * 41 * @hide 42 */ 43 public class RecoverableKeyGenerator { 44 45 private static final String TAG = "PlatformKeyGen"; 46 private static final int RESULT_CANNOT_INSERT_ROW = -1; 47 private static final String SECRET_KEY_ALGORITHM = "AES"; 48 49 static final int KEY_SIZE_BITS = 256; 50 51 /** 52 * A new {@link RecoverableKeyGenerator} instance. 53 * 54 * @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is 55 * unavailable. Should never happen. 56 * 57 * @hide 58 */ newInstance(RecoverableKeyStoreDb database)59 public static RecoverableKeyGenerator newInstance(RecoverableKeyStoreDb database) 60 throws NoSuchAlgorithmException { 61 // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key 62 // material, so that it can be synced to disk in encrypted form. 63 KeyGenerator keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGORITHM); 64 return new RecoverableKeyGenerator(keyGenerator, database); 65 } 66 67 private final KeyGenerator mKeyGenerator; 68 private final RecoverableKeyStoreDb mDatabase; 69 RecoverableKeyGenerator( KeyGenerator keyGenerator, RecoverableKeyStoreDb recoverableKeyStoreDb)70 private RecoverableKeyGenerator( 71 KeyGenerator keyGenerator, 72 RecoverableKeyStoreDb recoverableKeyStoreDb) { 73 mKeyGenerator = keyGenerator; 74 mDatabase = recoverableKeyStoreDb; 75 } 76 77 /** 78 * Generates a 256-bit AES key with the given alias. 79 * 80 * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is 81 * persisted to disk so that it can be synced remotely, and then recovered on another device. 82 * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding. 83 * 84 * @param platformKey The user's platform key, with which to wrap the generated key. 85 * @param userId The user ID of the profile to which the calling app belongs. 86 * @param uid The uid of the application that will own the key. 87 * @param alias The alias by which the key will be known in the recoverable key store. 88 * @param metadata The optional metadata that will be authenticated (but unencrypted) together 89 * with the key material when the key is uploaded to cloud. 90 * @throws RecoverableKeyStorageException if there is some error persisting the key either to 91 * the database. 92 * @throws KeyStoreException if there is a KeyStore error wrapping the generated key. 93 * @throws InvalidKeyException if the platform key cannot be used to wrap keys. 94 * 95 * @hide 96 */ generateAndStoreKey( PlatformEncryptionKey platformKey, int userId, int uid, String alias, @Nullable byte[] metadata)97 public byte[] generateAndStoreKey( 98 PlatformEncryptionKey platformKey, int userId, int uid, String alias, 99 @Nullable byte[] metadata) 100 throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException { 101 mKeyGenerator.init(KEY_SIZE_BITS); 102 SecretKey key = mKeyGenerator.generateKey(); 103 104 WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key, metadata); 105 long result = mDatabase.insertKey(userId, uid, alias, wrappedKey); 106 107 if (result == RESULT_CANNOT_INSERT_ROW) { 108 throw new RecoverableKeyStorageException( 109 String.format( 110 Locale.US, "Failed writing (%d, %s) to database.", uid, alias)); 111 } 112 113 long updatedRows = mDatabase.setShouldCreateSnapshot(userId, uid, true); 114 if (updatedRows < 0) { 115 Log.e(TAG, "Failed to set the shoudCreateSnapshot flag in the local DB."); 116 } 117 118 return key.getEncoded(); 119 } 120 121 /** 122 * Imports an AES key with the given alias. 123 * 124 * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is 125 * persisted to disk so that it can be synced remotely, and then recovered on another device. 126 * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding. 127 * 128 * @param platformKey The user's platform key, with which to wrap the generated key. 129 * @param userId The user ID of the profile to which the calling app belongs. 130 * @param uid The uid of the application that will own the key. 131 * @param alias The alias by which the key will be known in the recoverable key store. 132 * @param keyBytes The raw bytes of the AES key to be imported. 133 * @param metadata The optional metadata that will be authenticated (but unencrypted) together 134 * with the key material when the key is uploaded to cloud. 135 * @throws RecoverableKeyStorageException if there is some error persisting the key either to 136 * the database. 137 * @throws KeyStoreException if there is a KeyStore error wrapping the generated key. 138 * @throws InvalidKeyException if the platform key cannot be used to wrap keys. 139 * 140 * @hide 141 */ importKey( @onNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias, @NonNull byte[] keyBytes, @Nullable byte[] metadata)142 public void importKey( 143 @NonNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias, 144 @NonNull byte[] keyBytes, @Nullable byte[] metadata) 145 throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException { 146 SecretKey key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM); 147 148 WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key, metadata); 149 long result = mDatabase.insertKey(userId, uid, alias, wrappedKey); 150 151 if (result == RESULT_CANNOT_INSERT_ROW) { 152 throw new RecoverableKeyStorageException( 153 String.format( 154 Locale.US, "Failed writing (%d, %s) to database.", uid, alias)); 155 } 156 157 mDatabase.setShouldCreateSnapshot(userId, uid, true); 158 } 159 } 160