Home | History | Annotate | Download | only in recoverablekeystore
      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.app.KeyguardManager;
     20 import android.content.Context;
     21 import android.security.keystore.AndroidKeyStoreSecretKey;
     22 import android.security.keystore.KeyProperties;
     23 import android.security.keystore.KeyProtection;
     24 import android.util.Log;
     25 
     26 import com.android.internal.annotations.VisibleForTesting;
     27 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
     28 
     29 import java.io.IOException;
     30 import java.security.KeyStore;
     31 import java.security.KeyStoreException;
     32 import java.security.NoSuchAlgorithmException;
     33 import java.security.UnrecoverableKeyException;
     34 import java.security.cert.CertificateException;
     35 import java.util.Locale;
     36 
     37 import javax.crypto.KeyGenerator;
     38 import javax.crypto.SecretKey;
     39 import 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  */
     60 public 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