blob: 7ebe8bf20d62f3bfa3b14a0ee64da636022b7102 [file] [log] [blame]
Robert Berryce50cd32017-12-07 14:33:54 +00001/*
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
Robert Berry5397d4d2017-12-12 13:22:00 +000017package com.android.server.locksettings.recoverablekeystore;
Robert Berryce50cd32017-12-07 14:33:54 +000018
Bo Zhu2c8e5382018-02-26 15:54:25 -080019import android.annotation.NonNull;
20
Robert Berrya244b2e2017-12-19 10:44:56 +000021import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
22
Robert Berryce50cd32017-12-07 14:33:54 +000023import java.security.InvalidKeyException;
24import java.security.KeyStoreException;
25import java.security.NoSuchAlgorithmException;
Robert Berrya244b2e2017-12-19 10:44:56 +000026import java.util.Locale;
Robert Berryce50cd32017-12-07 14:33:54 +000027
28import javax.crypto.KeyGenerator;
29import javax.crypto.SecretKey;
Bo Zhu2c8e5382018-02-26 15:54:25 -080030import javax.crypto.spec.SecretKeySpec;
Robert Berryce50cd32017-12-07 14:33:54 +000031
Bo Zhu2c8e5382018-02-26 15:54:25 -080032// TODO: Rename RecoverableKeyGenerator to RecoverableKeyManager as it can import a key too now
Robert Berryce50cd32017-12-07 14:33:54 +000033/**
Bo Zhu2c8e5382018-02-26 15:54:25 -080034 * Generates/imports keys and stores them both in AndroidKeyStore and on disk, in wrapped form.
Robert Berryce50cd32017-12-07 14:33:54 +000035 *
Bo Zhu2c8e5382018-02-26 15:54:25 -080036 * <p>Generates/imports 256-bit AES keys, which can be used for encrypt and decrypt with AES-GCM.
Robert Berryce50cd32017-12-07 14:33:54 +000037 * 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 {
Bo Zhu2c8e5382018-02-26 15:54:25 -080043
Robert Berrya244b2e2017-12-19 10:44:56 +000044 private static final int RESULT_CANNOT_INSERT_ROW = -1;
Bo Zhu2c8e5382018-02-26 15:54:25 -080045 private static final String SECRET_KEY_ALGORITHM = "AES";
46
47 static final int KEY_SIZE_BITS = 256;
Robert Berryce50cd32017-12-07 14:33:54 +000048
49 /**
50 * A new {@link RecoverableKeyGenerator} instance.
51 *
Robert Berryce50cd32017-12-07 14:33:54 +000052 * @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is
53 * unavailable. Should never happen.
54 *
55 * @hide
56 */
Robert Berrya244b2e2017-12-19 10:44:56 +000057 public static RecoverableKeyGenerator newInstance(RecoverableKeyStoreDb database)
Robert Berryce50cd32017-12-07 14:33:54 +000058 throws NoSuchAlgorithmException {
59 // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
60 // material, so that it can be synced to disk in encrypted form.
Bo Zhu2c8e5382018-02-26 15:54:25 -080061 KeyGenerator keyGenerator = KeyGenerator.getInstance(SECRET_KEY_ALGORITHM);
Robert Berrycfc990a2017-12-22 15:54:30 +000062 return new RecoverableKeyGenerator(keyGenerator, database);
Robert Berryce50cd32017-12-07 14:33:54 +000063 }
64
65 private final KeyGenerator mKeyGenerator;
Robert Berrya244b2e2017-12-19 10:44:56 +000066 private final RecoverableKeyStoreDb mDatabase;
Robert Berryce50cd32017-12-07 14:33:54 +000067
68 private RecoverableKeyGenerator(
69 KeyGenerator keyGenerator,
Robert Berrycfc990a2017-12-22 15:54:30 +000070 RecoverableKeyStoreDb recoverableKeyStoreDb) {
Robert Berryce50cd32017-12-07 14:33:54 +000071 mKeyGenerator = keyGenerator;
Robert Berrya244b2e2017-12-19 10:44:56 +000072 mDatabase = recoverableKeyStoreDb;
Robert Berryce50cd32017-12-07 14:33:54 +000073 }
74
75 /**
76 * Generates a 256-bit AES key with the given alias.
77 *
78 * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is
79 * persisted to disk so that it can be synced remotely, and then recovered on another device.
80 * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
81 *
Robert Berrya244b2e2017-12-19 10:44:56 +000082 * @param platformKey The user's platform key, with which to wrap the generated key.
Robert Berryb7c06ea2017-12-21 13:37:23 +000083 * @param userId The user ID of the profile to which the calling app belongs.
Robert Berrya244b2e2017-12-19 10:44:56 +000084 * @param uid The uid of the application that will own the key.
Robert Berrycfc990a2017-12-22 15:54:30 +000085 * @param alias The alias by which the key will be known in the recoverable key store.
Robert Berrya244b2e2017-12-19 10:44:56 +000086 * @throws RecoverableKeyStorageException if there is some error persisting the key either to
Robert Berrycfc990a2017-12-22 15:54:30 +000087 * the database.
Robert Berrya244b2e2017-12-19 10:44:56 +000088 * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
Robert Berryce50cd32017-12-07 14:33:54 +000089 * @throws InvalidKeyException if the platform key cannot be used to wrap keys.
Robert Berryce50cd32017-12-07 14:33:54 +000090 *
91 * @hide
92 */
Robert Berrycfc990a2017-12-22 15:54:30 +000093 public byte[] generateAndStoreKey(
Robert Berryb7c06ea2017-12-21 13:37:23 +000094 PlatformEncryptionKey platformKey, int userId, int uid, String alias)
Robert Berrya244b2e2017-12-19 10:44:56 +000095 throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
Robert Berryce50cd32017-12-07 14:33:54 +000096 mKeyGenerator.init(KEY_SIZE_BITS);
97 SecretKey key = mKeyGenerator.generateKey();
98
Robert Berrya244b2e2017-12-19 10:44:56 +000099 WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
Robert Berryb7c06ea2017-12-21 13:37:23 +0000100 long result = mDatabase.insertKey(userId, uid, alias, wrappedKey);
Robert Berryce50cd32017-12-07 14:33:54 +0000101
Robert Berrya244b2e2017-12-19 10:44:56 +0000102 if (result == RESULT_CANNOT_INSERT_ROW) {
Robert Berrya244b2e2017-12-19 10:44:56 +0000103 throw new RecoverableKeyStorageException(
104 String.format(
105 Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
Robert Berryce50cd32017-12-07 14:33:54 +0000106 }
Robert Berrycfc990a2017-12-22 15:54:30 +0000107
Dmitry Dementyev77183ef2018-01-05 15:46:00 -0800108 mDatabase.setShouldCreateSnapshot(userId, uid, true);
Robert Berrycfc990a2017-12-22 15:54:30 +0000109 return key.getEncoded();
Robert Berryce50cd32017-12-07 14:33:54 +0000110 }
Bo Zhu2c8e5382018-02-26 15:54:25 -0800111
112 /**
113 * Imports an AES key with the given alias.
114 *
115 * <p>Stores in the AndroidKeyStore, as well as persisting in wrapped form to disk. It is
116 * persisted to disk so that it can be synced remotely, and then recovered on another device.
117 * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
118 *
119 * @param platformKey The user's platform key, with which to wrap the generated key.
120 * @param userId The user ID of the profile to which the calling app belongs.
121 * @param uid The uid of the application that will own the key.
122 * @param alias The alias by which the key will be known in the recoverable key store.
123 * @param keyBytes The raw bytes of the AES key to be imported.
124 * @throws RecoverableKeyStorageException if there is some error persisting the key either to
125 * the database.
126 * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
127 * @throws InvalidKeyException if the platform key cannot be used to wrap keys.
128 *
129 * @hide
130 */
131 public void importKey(
132 @NonNull PlatformEncryptionKey platformKey, int userId, int uid, @NonNull String alias,
133 @NonNull byte[] keyBytes)
134 throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
135 SecretKey key = new SecretKeySpec(keyBytes, SECRET_KEY_ALGORITHM);
136
137 WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
138 long result = mDatabase.insertKey(userId, uid, alias, wrappedKey);
139
140 if (result == RESULT_CANNOT_INSERT_ROW) {
141 throw new RecoverableKeyStorageException(
142 String.format(
143 Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
144 }
145
146 mDatabase.setShouldCreateSnapshot(userId, uid, true);
147 }
Robert Berryce50cd32017-12-07 14:33:54 +0000148}