• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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;
18 
19 import android.annotation.Nullable;
20 import android.content.pm.UserInfo;
21 import android.os.UserHandle;
22 import android.os.UserManager;
23 import android.security.keystore.KeyGenParameterSpec;
24 import android.security.keystore.KeyProperties;
25 import android.security.keystore.UserNotAuthenticatedException;
26 import android.util.Slog;
27 import android.util.SparseArray;
28 
29 import com.android.internal.widget.LockscreenCredential;
30 
31 import java.security.GeneralSecurityException;
32 import java.security.Key;
33 import java.security.KeyStore;
34 import java.security.KeyStoreException;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.UnrecoverableKeyException;
37 import java.util.Arrays;
38 import java.util.concurrent.TimeUnit;
39 
40 import javax.crypto.Cipher;
41 import javax.crypto.KeyGenerator;
42 import javax.crypto.SecretKey;
43 import javax.crypto.spec.GCMParameterSpec;
44 
45 /**
46  * Caches *unified* work challenge for user 0's managed profiles. Only user 0's profile is supported
47  * at the moment because the cached credential is encrypted using a keystore key auth-bound to
48  * user 0: this is to match how unified work challenge is similarly auth-bound to its parent user's
49  * lockscreen credential normally. It's possible to extend this class to support managed profiles
50  * for secondary users, that will require generating auth-bound keys to their corresponding parent
51  * user though (which {@link KeyGenParameterSpec} does not support right now).
52  *
53  * <p> The cache is filled whenever the managed profile's unified challenge is created or derived
54  * (as part of the parent user's credential verification flow). It's removed when the profile is
55  * deleted or a (separate) lockscreen credential is explicitly set on the profile. There is also
56  * an ADB command to evict the cache "cmd lock_settings remove-cache --user X", to assist
57  * development and testing.
58 
59  * <p> The encrypted credential is stored in-memory only so the cache does not persist across
60  * reboots.
61  */
62 public class ManagedProfilePasswordCache {
63 
64     private static final String TAG = "ManagedProfilePasswordCache";
65     private static final int KEY_LENGTH = 256;
66     private static final int CACHE_TIMEOUT_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
67 
68     private final SparseArray<byte[]> mEncryptedPasswords = new SparseArray<>();
69     private final KeyStore mKeyStore;
70     private final UserManager mUserManager;
71 
ManagedProfilePasswordCache(KeyStore keyStore, UserManager userManager)72     public ManagedProfilePasswordCache(KeyStore keyStore, UserManager userManager) {
73         mKeyStore = keyStore;
74         mUserManager = userManager;
75     }
76 
77     /**
78      * Encrypt and store the password in the cache. Does NOT overwrite existing password cache
79      * if one for the given user already exists.
80      */
storePassword(int userId, LockscreenCredential password)81     public void storePassword(int userId, LockscreenCredential password) {
82         synchronized (mEncryptedPasswords) {
83             if (mEncryptedPasswords.contains(userId)) {
84                 return;
85             }
86             UserInfo parent = mUserManager.getProfileParent(userId);
87             if (parent == null || parent.id != UserHandle.USER_SYSTEM) {
88                 // Since the cached password is encrypted using a keystore key auth-bound to user 0,
89                 // only support caching password for user 0's profile.
90                 return;
91             }
92             String keyName = getEncryptionKeyName(userId);
93             KeyGenerator generator;
94             SecretKey key;
95             try {
96                 generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,
97                         mKeyStore.getProvider());
98                 generator.init(new KeyGenParameterSpec.Builder(
99                         keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
100                         .setKeySize(KEY_LENGTH)
101                         .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
102                         .setNamespace(SyntheticPasswordCrypto.keyNamespace())
103                         .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
104                         // Generate auth-bound key to user 0 (since we the caller is user 0)
105                         .setUserAuthenticationRequired(true)
106                         .setUserAuthenticationValidityDurationSeconds(CACHE_TIMEOUT_SECONDS)
107                         .build());
108                 key = generator.generateKey();
109             } catch (GeneralSecurityException e) {
110                 Slog.e(TAG, "Cannot generate key", e);
111                 return;
112             }
113 
114             Cipher cipher;
115             try {
116                 cipher = Cipher.getInstance("AES/GCM/NoPadding");
117                 cipher.init(Cipher.ENCRYPT_MODE, key);
118                 byte[] ciphertext = cipher.doFinal(password.getCredential());
119                 byte[] iv = cipher.getIV();
120                 byte[] block = Arrays.copyOf(iv, ciphertext.length + iv.length);
121                 System.arraycopy(ciphertext, 0, block, iv.length, ciphertext.length);
122                 mEncryptedPasswords.put(userId, block);
123             } catch (GeneralSecurityException e) {
124                 Slog.d(TAG, "Cannot encrypt", e);
125             }
126         }
127     }
128 
129     /** Attempt to retrieve the password for the given user. Returns {@code null} if it's not in the
130      * cache or if decryption fails.
131      */
retrievePassword(int userId)132     public @Nullable LockscreenCredential retrievePassword(int userId) {
133         synchronized (mEncryptedPasswords) {
134             byte[] block = mEncryptedPasswords.get(userId);
135             if (block == null) {
136                 return null;
137             }
138             Key key;
139             try {
140                 key = mKeyStore.getKey(getEncryptionKeyName(userId), null);
141             } catch (UnrecoverableKeyException | KeyStoreException | NoSuchAlgorithmException e) {
142                 Slog.d(TAG, "Cannot get key", e);
143                 return null;
144             }
145             if (key == null) {
146                 return null;
147             }
148             byte[] iv = Arrays.copyOf(block, 12);
149             byte[] ciphertext = Arrays.copyOfRange(block, 12, block.length);
150             byte[] credential;
151             try {
152                 Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
153                 cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
154                 credential = cipher.doFinal(ciphertext);
155             } catch (UserNotAuthenticatedException e) {
156                 Slog.i(TAG, "Device not unlocked for more than 7 days");
157                 return null;
158             } catch (GeneralSecurityException e) {
159                 Slog.d(TAG, "Cannot decrypt", e);
160                 return null;
161             }
162             LockscreenCredential result = LockscreenCredential.createManagedPassword(credential);
163             Arrays.fill(credential, (byte) 0);
164             return result;
165         }
166     }
167 
168     /** Remove the given user's password from cache, if one exists. */
removePassword(int userId)169     public void removePassword(int userId) {
170         synchronized (mEncryptedPasswords) {
171             String keyName = getEncryptionKeyName(userId);
172             String legacyKeyName = getLegacyEncryptionKeyName(userId);
173             try {
174                 if (mKeyStore.containsAlias(keyName)) {
175                     mKeyStore.deleteEntry(keyName);
176                 }
177                 if (mKeyStore.containsAlias(legacyKeyName)) {
178                     mKeyStore.deleteEntry(legacyKeyName);
179                 }
180             } catch (KeyStoreException e) {
181                 Slog.d(TAG, "Cannot delete key", e);
182             }
183             if (mEncryptedPasswords.contains(userId)) {
184                 Arrays.fill(mEncryptedPasswords.get(userId), (byte) 0);
185                 mEncryptedPasswords.remove(userId);
186             }
187         }
188     }
189 
getEncryptionKeyName(int userId)190     private static String getEncryptionKeyName(int userId) {
191         return "com.android.server.locksettings.unified_profile_cache_v2_" + userId;
192     }
193 
194     /**
195      * Returns the legacy keystore key name when setUnlockedDeviceRequired() was set explicitly.
196      * Only existed during Android 11 internal testing period.
197      */
getLegacyEncryptionKeyName(int userId)198     private static String getLegacyEncryptionKeyName(int userId) {
199         return "com.android.server.locksettings.unified_profile_cache_" + userId;
200     }
201 }
202