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.app.KeyguardManager;
20import android.content.Context;
21import android.security.keystore.AndroidKeyStoreSecretKey;
22import android.security.keystore.KeyProperties;
23import android.security.keystore.KeyProtection;
24import android.util.Log;
25
26import com.android.internal.annotations.VisibleForTesting;
27import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
28
29import java.io.IOException;
30import java.security.KeyStore;
31import java.security.KeyStoreException;
32import java.security.NoSuchAlgorithmException;
33import java.security.UnrecoverableKeyException;
34import java.security.cert.CertificateException;
35import java.util.Locale;
36
37import javax.crypto.KeyGenerator;
38import javax.crypto.SecretKey;
39import javax.security.auth.DestroyFailedException;
40
41/**
42 * Manages creating and checking the validity of the platform key.
43 *
44 * <p>The platform key is used to wrap the material of recoverable keys before persisting them to
45 * disk. It is also used to decrypt the same keys on a screen unlock, before re-wrapping them with
46 * a recovery key and syncing them with remote storage.
47 *
48 * <p>Each platform key has two entries in AndroidKeyStore:
49 *
50 * <ul>
51 *     <li>Encrypt entry - this entry enables the root user to at any time encrypt.
52 *     <li>Decrypt entry - this entry enables the root user to decrypt only after recent user
53 *       authentication, i.e., within 15 seconds after a screen unlock.
54 * </ul>
55 *
56 * <p>Both entries are enabled only for AES/GCM/NoPadding Cipher algorithm.
57 *
58 * @hide
59 */
60public class PlatformKeyManager {
61    private static final String TAG = "PlatformKeyManager";
62
63    private static final String KEY_ALGORITHM = "AES";
64    private static final int KEY_SIZE_BITS = 256;
65    private static final String KEY_ALIAS_PREFIX =
66            "com.android.server.locksettings.recoverablekeystore/platform/";
67    private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
68    private static final String DECRYPT_KEY_ALIAS_SUFFIX = "decrypt";
69    private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
70
71    private final Context mContext;
72    private final KeyStoreProxy mKeyStore;
73    private final RecoverableKeyStoreDb mDatabase;
74
75    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
76
77    /**
78     * A new instance operating on behalf of {@code userId}, storing its prefs in the location
79     * defined by {@code context}.
80     *
81     * @param context This should be the context of the RecoverableKeyStoreLoader service.
82     * @throws KeyStoreException if failed to initialize AndroidKeyStore.
83     * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
84     * @throws SecurityException if the caller does not have permission to write to /data/system.
85     *
86     * @hide
87     */
88    public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database)
89            throws KeyStoreException, NoSuchAlgorithmException {
90        return new PlatformKeyManager(
91                context.getApplicationContext(),
92                new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
93                database);
94    }
95
96    @VisibleForTesting
97    PlatformKeyManager(
98            Context context,
99            KeyStoreProxy keyStore,
100            RecoverableKeyStoreDb database) {
101        mKeyStore = keyStore;
102        mContext = context;
103        mDatabase = database;
104    }
105
106    /**
107     * Returns the current generation ID of the platform key. This increments whenever a platform
108     * key has to be replaced. (e.g., because the user has removed and then re-added their lock
109     * screen). Returns -1 if no key has been generated yet.
110     *
111     * @param userId The ID of the user to whose lock screen the platform key must be bound.
112     *
113     * @hide
114     */
115    public int getGenerationId(int userId) {
116        return mDatabase.getPlatformKeyGenerationId(userId);
117    }
118
119    /**
120     * Returns {@code true} if the platform key is available. A platform key won't be available if
121     * the user has not set up a lock screen.
122     *
123     * @param userId The ID of the user to whose lock screen the platform key must be bound.
124     *
125     * @hide
126     */
127    public boolean isAvailable(int userId) {
128        return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
129    }
130
131    /**
132     * Removes the platform key from Android KeyStore.
133     * It is triggered when user disables lock screen.
134     *
135     * @param userId The ID of the user to whose lock screen the platform key must be bound.
136     * @param generationId Generation id.
137     *
138     * @hide
139     */
140    public void invalidatePlatformKey(int userId, int generationId) {
141        if (generationId != -1) {
142            try {
143                mKeyStore.deleteEntry(getEncryptAlias(userId, generationId));
144                mKeyStore.deleteEntry(getDecryptAlias(userId, generationId));
145            } catch (KeyStoreException e) {
146                // Ignore failed attempt to delete key.
147            }
148        }
149    }
150
151    /**
152     * Generates a new key and increments the generation ID. Should be invoked if the platform key
153     * is corrupted and needs to be rotated.
154     * Updates status of old keys to {@code RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE}.
155     *
156     * @param userId The ID of the user to whose lock screen the platform key must be bound.
157     * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
158     * @throws KeyStoreException if there is an error in AndroidKeyStore.
159     * @throws InsecureUserException if the user does not have a lock screen set.
160     * @throws IOException if there was an issue with local database update.
161     *
162     * @hide
163     */
164    @VisibleForTesting
165    void regenerate(int userId)
166            throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException {
167        if (!isAvailable(userId)) {
168            throw new InsecureUserException(String.format(
169                    Locale.US, "%d does not have a lock screen set.", userId));
170        }
171
172        int generationId = getGenerationId(userId);
173        int nextId;
174        if (generationId == -1) {
175            nextId = 1;
176        } else {
177            invalidatePlatformKey(userId, generationId);
178            nextId = generationId + 1;
179        }
180        generateAndLoadKey(userId, nextId);
181    }
182
183    /**
184     * Returns the platform key used for encryption.
185     * Tries to regenerate key one time if it is permanently invalid.
186     *
187     * @param userId The ID of the user to whose lock screen the platform key must be bound.
188     * @throws KeyStoreException if there was an AndroidKeyStore error.
189     * @throws UnrecoverableKeyException if the key could not be recovered.
190     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
191     * @throws InsecureUserException if the user does not have a lock screen set.
192     * @throws IOException if there was an issue with local database update.
193     *
194     * @hide
195     */
196    public PlatformEncryptionKey getEncryptKey(int userId) throws KeyStoreException,
197           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException {
198        init(userId);
199        try {
200            // Try to see if the decryption key is still accessible before using the encryption key.
201            // The auth-bound decryption will be unrecoverable if the screen lock is disabled.
202            getDecryptKeyInternal(userId);
203            return getEncryptKeyInternal(userId);
204        } catch (UnrecoverableKeyException e) {
205            Log.i(TAG, String.format(Locale.US,
206                    "Regenerating permanently invalid Platform key for user %d.",
207                    userId));
208            regenerate(userId);
209            return getEncryptKeyInternal(userId);
210        }
211    }
212
213    /**
214     * Returns the platform key used for encryption.
215     *
216     * @param userId The ID of the user to whose lock screen the platform key must be bound.
217     * @throws KeyStoreException if there was an AndroidKeyStore error.
218     * @throws UnrecoverableKeyException if the key could not be recovered.
219     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
220     * @throws InsecureUserException if the user does not have a lock screen set.
221     *
222     * @hide
223     */
224    private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
225           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
226        int generationId = getGenerationId(userId);
227        String alias = getEncryptAlias(userId, generationId);
228        if (!isKeyLoaded(userId, generationId)) {
229            throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias);
230        }
231        AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
232                alias, /*password=*/ null);
233        return new PlatformEncryptionKey(generationId, key);
234    }
235
236    /**
237     * Returns the platform key used for decryption. Only works after a recent screen unlock.
238     * Tries to regenerate key one time if it is permanently invalid.
239     *
240     * @param userId The ID of the user to whose lock screen the platform key must be bound.
241     * @throws KeyStoreException if there was an AndroidKeyStore error.
242     * @throws UnrecoverableKeyException if the key could not be recovered.
243     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
244     * @throws InsecureUserException if the user does not have a lock screen set.
245     * @throws IOException if there was an issue with local database update.
246     *
247     * @hide
248     */
249    public PlatformDecryptionKey getDecryptKey(int userId) throws KeyStoreException,
250           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException, IOException {
251        init(userId);
252        try {
253            return getDecryptKeyInternal(userId);
254        } catch (UnrecoverableKeyException e) {
255            Log.i(TAG, String.format(Locale.US,
256                    "Regenerating permanently invalid Platform key for user %d.",
257                    userId));
258            regenerate(userId);
259            return getDecryptKeyInternal(userId);
260        }
261    }
262
263    /**
264     * Returns the platform key used for decryption. Only works after a recent screen unlock.
265     *
266     * @param userId The ID of the user to whose lock screen the platform key must be bound.
267     * @throws KeyStoreException if there was an AndroidKeyStore error.
268     * @throws UnrecoverableKeyException if the key could not be recovered.
269     * @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
270     * @throws InsecureUserException if the user does not have a lock screen set.
271     *
272     * @hide
273     */
274    private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
275           UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
276        int generationId = getGenerationId(userId);
277        String alias = getDecryptAlias(userId, generationId);
278        if (!isKeyLoaded(userId, generationId)) {
279            throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias);
280        }
281        AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
282                alias, /*password=*/ null);
283        return new PlatformDecryptionKey(generationId, key);
284    }
285
286    /**
287     * Initializes the class. If there is no current platform key, and the user has a lock screen
288     * set, will create the platform key and set the generation ID.
289     *
290     * @param userId The ID of the user to whose lock screen the platform key must be bound.
291     * @throws KeyStoreException if there was an error in AndroidKeyStore.
292     * @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
293     * @throws IOException if there was an issue with local database update.
294     *
295     * @hide
296     */
297    void init(int userId)
298            throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException {
299        if (!isAvailable(userId)) {
300            throw new InsecureUserException(String.format(
301                    Locale.US, "%d does not have a lock screen set.", userId));
302        }
303
304        int generationId = getGenerationId(userId);
305        if (isKeyLoaded(userId, generationId)) {
306            Log.i(TAG, String.format(
307                    Locale.US, "Platform key generation %d exists already.", generationId));
308            return;
309        }
310        if (generationId == -1) {
311            Log.i(TAG, "Generating initial platform key generation ID.");
312            generationId = 1;
313        } else {
314            Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
315                    + "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
316            // Have to generate a fresh key, so bump the generation id
317            generationId++;
318        }
319
320        generateAndLoadKey(userId, generationId);
321    }
322
323    /**
324     * Returns the alias of the encryption key with the specific {@code generationId} in the
325     * AndroidKeyStore.
326     *
327     * <p>These IDs look as follows:
328     * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/encrypt}
329     *
330     * @param userId The ID of the user to whose lock screen the platform key must be bound.
331     * @param generationId The generation ID.
332     * @return The alias.
333     */
334    private String getEncryptAlias(int userId, int generationId) {
335        return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + ENCRYPT_KEY_ALIAS_SUFFIX;
336    }
337
338    /**
339     * Returns the alias of the decryption key with the specific {@code generationId} in the
340     * AndroidKeyStore.
341     *
342     * <p>These IDs look as follows:
343     * {@code com.security.recoverablekeystore/platform/<user id>/<generation id>/decrypt}
344     *
345     * @param userId The ID of the user to whose lock screen the platform key must be bound.
346     * @param generationId The generation ID.
347     * @return The alias.
348     */
349    private String getDecryptAlias(int userId, int generationId) {
350        return KEY_ALIAS_PREFIX + userId + "/" + generationId + "/" + DECRYPT_KEY_ALIAS_SUFFIX;
351    }
352
353    /**
354     * Sets the current generation ID to {@code generationId}.
355     * @throws IOException if there was an issue with local database update.
356     */
357    private void setGenerationId(int userId, int generationId) throws IOException {
358        long updatedRows = mDatabase.setPlatformKeyGenerationId(userId, generationId);
359        if (updatedRows < 0) {
360            throw new IOException("Failed to set the platform key in the local DB.");
361        }
362    }
363
364    /**
365     * Returns {@code true} if a key has been loaded with the given {@code generationId} into
366     * AndroidKeyStore.
367     *
368     * @throws KeyStoreException if there was an error checking AndroidKeyStore.
369     */
370    private boolean isKeyLoaded(int userId, int generationId) throws KeyStoreException {
371        return mKeyStore.containsAlias(getEncryptAlias(userId, generationId))
372                && mKeyStore.containsAlias(getDecryptAlias(userId, generationId));
373    }
374
375    /**
376     * Generates a new 256-bit AES key, and loads it into AndroidKeyStore with the given
377     * {@code generationId} determining its aliases.
378     *
379     * @throws NoSuchAlgorithmException if AES is unavailable. This should never happen, as it is
380     *     available since API version 1.
381     * @throws KeyStoreException if there was an issue loading the keys into AndroidKeyStore.
382     * @throws IOException if there was an issue with local database update.
383     */
384    private void generateAndLoadKey(int userId, int generationId)
385            throws NoSuchAlgorithmException, KeyStoreException, IOException {
386        String encryptAlias = getEncryptAlias(userId, generationId);
387        String decryptAlias = getDecryptAlias(userId, generationId);
388        // SecretKey implementation doesn't provide reliable way to destroy the secret
389        // so it may live in memory for some time.
390        SecretKey secretKey = generateAesKey();
391
392        // Store decryption key first since it is more likely to fail.
393        mKeyStore.setEntry(
394                decryptAlias,
395                new KeyStore.SecretKeyEntry(secretKey),
396                new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
397                    .setUserAuthenticationRequired(true)
398                    .setUserAuthenticationValidityDurationSeconds(
399                            USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
400                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
401                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
402                    .setBoundToSpecificSecureUserId(userId)
403                    .build());
404        mKeyStore.setEntry(
405                encryptAlias,
406                new KeyStore.SecretKeyEntry(secretKey),
407                new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
408                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
409                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
410                    .build());
411
412        setGenerationId(userId, generationId);
413    }
414
415    /**
416     * Generates a new 256-bit AES key, in software.
417     *
418     * @return The software-generated AES key.
419     * @throws NoSuchAlgorithmException if AES key generation is not available. This should never
420     *     happen, as AES has been supported since API level 1.
421     */
422    private static SecretKey generateAesKey() throws NoSuchAlgorithmException {
423        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
424        keyGenerator.init(KEY_SIZE_BITS);
425        return keyGenerator.generateKey();
426    }
427
428    /**
429     * Returns AndroidKeyStore-provided {@link KeyStore}, having already invoked
430     * {@link KeyStore#load(KeyStore.LoadStoreParameter)}.
431     *
432     * @throws KeyStoreException if there was a problem getting or initializing the key store.
433     */
434    private static KeyStore getAndLoadAndroidKeyStore() throws KeyStoreException {
435        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
436        try {
437            keyStore.load(/*param=*/ null);
438        } catch (CertificateException | IOException | NoSuchAlgorithmException e) {
439            // Should never happen.
440            throw new KeyStoreException("Unable to load keystore.", e);
441        }
442        return keyStore;
443    }
444
445}
446