1 /*
2  * Copyright 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 androidx.security.crypto;
18 
19 import static android.security.keystore.KeyProperties.AUTH_BIOMETRIC_STRONG;
20 import static android.security.keystore.KeyProperties.AUTH_DEVICE_CREDENTIAL;
21 
22 import android.annotation.SuppressLint;
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.os.Build;
26 import android.security.keystore.KeyGenParameterSpec;
27 import android.security.keystore.KeyProperties;
28 
29 import androidx.annotation.IntRange;
30 import androidx.annotation.RequiresApi;
31 
32 import org.jspecify.annotations.NonNull;
33 import org.jspecify.annotations.Nullable;
34 
35 import java.io.IOException;
36 import java.security.GeneralSecurityException;
37 import java.security.KeyStore;
38 import java.security.KeyStoreException;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.cert.CertificateException;
41 
42 /**
43  * Wrapper for a master key used in the library.
44  *
45  * <p>On Android M (API 23) and above, this is class references a key that's stored in the
46  * Android Keystore. On Android L (API 21, 22), there isn't a master key.
47  * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
48  */
49 @Deprecated
50 public final class MasterKey {
51     static final String KEYSTORE_PATH_URI = "android-keystore://";
52 
53     /**
54      * The default master key alias.
55      * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
56      */
57     @Deprecated
58     public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
59 
60     /**
61      * The default and recommended size for the master key.
62      * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
63      */
64     @Deprecated
65     public static final int DEFAULT_AES_GCM_MASTER_KEY_SIZE = 256;
66 
67     private static final int DEFAULT_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 5 * 60;
68 
69     private final @NonNull String mKeyAlias;
70     private final @Nullable KeyGenParameterSpec mKeyGenParameterSpec;
71 
72     /**
73      * Algorithm/Cipher choices used for the master key.
74      * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
75      */
76     @Deprecated
77     public enum KeyScheme {
78         AES256_GCM
79     }
80 
81     /**
82      * The default validity period for authentication in seconds.
83      */
84     @SuppressLint("MethodNameUnits")
getDefaultAuthenticationValidityDurationSeconds()85     public static int getDefaultAuthenticationValidityDurationSeconds() {
86         return DEFAULT_AUTHENTICATION_VALIDITY_DURATION_SECONDS;
87     }
88 
MasterKey(@onNull String keyAlias, @Nullable Object keyGenParameterSpec)89     /* package */ MasterKey(@NonNull String keyAlias, @Nullable Object keyGenParameterSpec) {
90         mKeyAlias = keyAlias;
91         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
92             mKeyGenParameterSpec = (KeyGenParameterSpec) keyGenParameterSpec;
93         } else {
94             mKeyGenParameterSpec = null;
95         }
96     }
97 
98     /**
99      * Checks if this key is backed by the Android Keystore.
100      *
101      * @return {@code true} if the key is in Android Keystore, {@code false} otherwise. This
102      * method always returns false when called on Android Lollipop (API 21 and 22).
103      */
isKeyStoreBacked()104     public boolean isKeyStoreBacked() {
105         // Keystore is not used prior to Android M (API 23)
106         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
107             return false;
108         }
109 
110         try {
111             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
112             keyStore.load(null);
113             return keyStore.containsAlias(mKeyAlias);
114         } catch (KeyStoreException | CertificateException
115                 | NoSuchAlgorithmException | IOException ignored) {
116             return false;
117         }
118     }
119 
120     /**
121      * Gets whether user authentication is required to use this key.
122      *
123      * <p>This method always returns {@code false} on Android L (API 21 + 22).
124      */
isUserAuthenticationRequired()125     public boolean isUserAuthenticationRequired() {
126         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
127             return false;
128         }
129         return mKeyGenParameterSpec != null
130                 && Api23Impl.isUserAuthenticationRequired(mKeyGenParameterSpec);
131     }
132 
133     /**
134      * Gets the duration in seconds that the key is unlocked for following user authentication.
135      *
136      * <p>The value returned for this method is only meaningful on Android M+ (API 23) when
137      * {@link #isUserAuthenticationRequired()} returns {@code true}.
138      *
139      * @return The duration the key is unlocked for in seconds.
140      */
141     @SuppressLint("MethodNameUnits")
getUserAuthenticationValidityDurationSeconds()142     public int getUserAuthenticationValidityDurationSeconds() {
143         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
144             return 0;
145         }
146         return mKeyGenParameterSpec == null ? 0 :
147                 Api23Impl.getUserAuthenticationValidityDurationSeconds(mKeyGenParameterSpec);
148     }
149 
150     /**
151      * Gets whether the key is backed by strong box.
152      */
isStrongBoxBacked()153     public boolean isStrongBoxBacked() {
154         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || mKeyGenParameterSpec == null) {
155             return false;
156         }
157         return Api28Impl.isStrongBoxBacked(mKeyGenParameterSpec);
158     }
159 
160     @Override
toString()161     public @NonNull String toString() {
162         return "MasterKey{keyAlias=" + mKeyAlias
163                 + ", isKeyStoreBacked=" + isKeyStoreBacked()
164                 + "}";
165     }
166 
getKeyAlias()167     /* package */ @NonNull String getKeyAlias() {
168         return mKeyAlias;
169     }
170 
171     /**
172      * Builder for generating a {@link MasterKey}.
173      * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead.
174      */
175     @Deprecated
176     public static final class Builder {
177         final @NonNull String mKeyAlias;
178 
179         @Nullable KeyGenParameterSpec mKeyGenParameterSpec;
180         @Nullable KeyScheme mKeyScheme;
181 
182         boolean mAuthenticationRequired;
183         int mUserAuthenticationValidityDurationSeconds;
184         boolean mRequestStrongBoxBacked;
185 
186         final Context mContext;
187 
188         /**
189          * Creates a builder for a {@link MasterKey} using the default alias of
190          * {@link #DEFAULT_MASTER_KEY_ALIAS}.
191          *
192          * @param context The context to use with this master key.
193          */
Builder(@onNull Context context)194         public Builder(@NonNull Context context) {
195             this(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS);
196         }
197 
198         /**
199          * Creates a builder for a {@link MasterKey}.
200          *
201          * @param context The context to use with this master key.
202          */
Builder(@onNull Context context, @NonNull String keyAlias)203         public Builder(@NonNull Context context, @NonNull String keyAlias) {
204             mContext = context.getApplicationContext();
205             mKeyAlias = keyAlias;
206         }
207 
208         /**
209          * Sets a {@link KeyScheme} to be used for the master key.
210          * <p>This uses a default {@link KeyGenParameterSpec} associated with the provided
211          * {@code KeyScheme}.
212          * <p>NOTE: Either this method OR {@link #setKeyGenParameterSpec} should be used to set
213          * the parameters to use for building the master key. Calling either function after
214          * the other will throw an {@link IllegalArgumentException}.
215          *
216          * @param keyScheme The KeyScheme to use.
217          * @return This builder.
218          */
setKeyScheme(@onNull KeyScheme keyScheme)219         public @NonNull Builder setKeyScheme(@NonNull KeyScheme keyScheme) {
220             switch (keyScheme) {
221                 case AES256_GCM:
222                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
223                         if (mKeyGenParameterSpec != null) {
224                             throw new IllegalArgumentException("KeyScheme set after setting a "
225                                     + "KeyGenParamSpec");
226                         }
227                     }
228                     break;
229                 default:
230                     throw new IllegalArgumentException("Unsupported scheme: " + keyScheme);
231             }
232             mKeyScheme = keyScheme;
233             return this;
234         }
235 
236         /**
237          * When used with {@link #setKeyScheme(KeyScheme)}, sets that the built master key should
238          * require the user to authenticate before it's unlocked, probably using the
239          * androidx.biometric library.
240          *
241          * <p>This method sets the validity duration of the key to
242          * {@link #getDefaultAuthenticationValidityDurationSeconds()}.
243          *
244          * @param authenticationRequired Whether user authentication should be required to use
245          *                               the key.
246          * @return This builder.
247          */
setUserAuthenticationRequired(boolean authenticationRequired)248         public @NonNull Builder setUserAuthenticationRequired(boolean authenticationRequired) {
249             return setUserAuthenticationRequired(authenticationRequired,
250                     getDefaultAuthenticationValidityDurationSeconds());
251         }
252 
253         /**
254          * When used with {@link #setKeyScheme(KeyScheme)}, sets that the built master key should
255          * require the user to authenticate before it's unlocked, probably using the
256          * androidx.biometric library, and that the key should remain unlocked for the provided
257          * duration.
258          *
259          * @param authenticationRequired                    Whether user authentication should be
260          *                                                  required to use the key.
261          * @param userAuthenticationValidityDurationSeconds Duration in seconds that the key
262          *                                                  should remain unlocked following user
263          *                                                  authentication.
264          * @return This builder.
265          */
setUserAuthenticationRequired(boolean authenticationRequired, @IntRange(from = 1) int userAuthenticationValidityDurationSeconds)266         public @NonNull Builder setUserAuthenticationRequired(boolean authenticationRequired,
267                 @IntRange(from = 1) int userAuthenticationValidityDurationSeconds) {
268             mAuthenticationRequired = authenticationRequired;
269             mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
270             return this;
271         }
272 
273         /**
274          * Sets whether or not to request this key is strong box backed. This setting is only
275          * applicable on {@link Build.VERSION_CODES#P} and above, and only on devices that
276          * support Strongbox.
277          *
278          * @param requestStrongBoxBacked Whether to request to use strongbox
279          * @return This builder.
280          */
setRequestStrongBoxBacked(boolean requestStrongBoxBacked)281         public @NonNull Builder setRequestStrongBoxBacked(boolean requestStrongBoxBacked) {
282             mRequestStrongBoxBacked = requestStrongBoxBacked;
283             return this;
284         }
285 
286         /**
287          * Sets a custom {@link KeyGenParameterSpec} to use as the basis of the master key.
288          * NOTE: Either this method OR {@link #setKeyScheme(KeyScheme)} should be used to set
289          * the parameters to use for building the master key. Calling either function after
290          * the other will throw an {@link IllegalArgumentException}.
291          *
292          * @param keyGenParameterSpec The key spec to use.
293          * @return This builder.
294          */
295         @RequiresApi(Build.VERSION_CODES.M)
setKeyGenParameterSpec( @onNull KeyGenParameterSpec keyGenParameterSpec)296         public @NonNull Builder setKeyGenParameterSpec(
297                 @NonNull KeyGenParameterSpec keyGenParameterSpec) {
298             if (mKeyScheme != null) {
299                 throw new IllegalArgumentException("KeyGenParamSpec set after setting a "
300                         + "KeyScheme");
301             }
302             if (!mKeyAlias.equals(Api23Impl.getKeystoreAlias(keyGenParameterSpec))) {
303                 throw new IllegalArgumentException("KeyGenParamSpec's key alias does not match "
304                         + "provided alias (" + mKeyAlias + " vs "
305                         + Api23Impl.getKeystoreAlias(keyGenParameterSpec));
306             }
307             mKeyGenParameterSpec = keyGenParameterSpec;
308             return this;
309         }
310 
311         /**
312          * Builds a {@link MasterKey} from this builder.
313          *
314          * @return The master key.
315          */
build()316         public @NonNull MasterKey build() throws GeneralSecurityException, IOException {
317             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
318                 return Api23Impl.build(this);
319             } else {
320                 return new MasterKey(mKeyAlias, null);
321             }
322         }
323 
324         @RequiresApi(23)
325         static class Api23Impl {
Api23Impl()326             private Api23Impl() {
327                 // This class is not instantiable.
328             }
329 
getKeystoreAlias(KeyGenParameterSpec keyGenParameterSpec)330             static String getKeystoreAlias(KeyGenParameterSpec keyGenParameterSpec) {
331                 return keyGenParameterSpec.getKeystoreAlias();
332             }
333 
334             @SuppressWarnings("deprecation")
build(Builder builder)335             static MasterKey build(Builder builder) throws GeneralSecurityException, IOException {
336                 if (builder.mKeyScheme == null && builder.mKeyGenParameterSpec == null) {
337                     throw new IllegalArgumentException("build() called before "
338                             + "setKeyGenParameterSpec or setKeyScheme.");
339                 }
340 
341                 if (builder.mKeyScheme == KeyScheme.AES256_GCM) {
342                     KeyGenParameterSpec.Builder keyGenBuilder = new KeyGenParameterSpec.Builder(
343                             builder.mKeyAlias,
344                             KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
345                             .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
346                             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
347                             .setKeySize(DEFAULT_AES_GCM_MASTER_KEY_SIZE);
348 
349                     if (builder.mAuthenticationRequired) {
350                         keyGenBuilder.setUserAuthenticationRequired(true);
351                         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
352                             Api30Impl.setUserAuthenticationParameters(keyGenBuilder,
353                                     builder.mUserAuthenticationValidityDurationSeconds,
354                                     AUTH_DEVICE_CREDENTIAL | AUTH_BIOMETRIC_STRONG);
355                         } else {
356                             keyGenBuilder.setUserAuthenticationValidityDurationSeconds(
357                                     builder.mUserAuthenticationValidityDurationSeconds);
358                         }
359                     }
360 
361                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
362                             && builder.mRequestStrongBoxBacked) {
363                         if (builder.mContext.getPackageManager().hasSystemFeature(
364                                 PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
365                             Api28Impl.setIsStrongBoxBacked(keyGenBuilder);
366                         }
367                     }
368 
369                     builder.mKeyGenParameterSpec = keyGenBuilder.build();
370                 }
371                 if (builder.mKeyGenParameterSpec == null) {
372                     // This really should not happen.
373                     throw new NullPointerException(
374                             "KeyGenParameterSpec was null after build() check");
375                 }
376 
377                 String keyAlias = MasterKeys.getOrCreate(builder.mKeyGenParameterSpec);
378                 return new MasterKey(keyAlias, builder.mKeyGenParameterSpec);
379             }
380 
381             @RequiresApi(28)
382             static class Api28Impl {
Api28Impl()383                 private Api28Impl() {
384                     // This class is not instantiable.
385                 }
386 
setIsStrongBoxBacked(KeyGenParameterSpec.Builder builder)387                 static void setIsStrongBoxBacked(KeyGenParameterSpec.Builder builder) {
388                     builder.setIsStrongBoxBacked(true);
389                 }
390             }
391 
392             @RequiresApi(30)
393             static class Api30Impl {
Api30Impl()394                 private Api30Impl() {
395                     // This class is not instantiable.
396                 }
397 
setUserAuthenticationParameters(KeyGenParameterSpec.Builder builder, int timeout, int type)398                 static void setUserAuthenticationParameters(KeyGenParameterSpec.Builder builder,
399                         int timeout,
400                         int type) {
401                     builder.setUserAuthenticationParameters(timeout, type);
402                 }
403 
404             }
405         }
406     }
407 
408     @RequiresApi(23)
409     static class Api23Impl {
Api23Impl()410         private Api23Impl() {
411             // This class is not instantiable.
412         }
413 
isUserAuthenticationRequired(KeyGenParameterSpec keyGenParameterSpec)414         static boolean isUserAuthenticationRequired(KeyGenParameterSpec keyGenParameterSpec) {
415             return keyGenParameterSpec.isUserAuthenticationRequired();
416         }
417 
getUserAuthenticationValidityDurationSeconds( KeyGenParameterSpec keyGenParameterSpec)418         static int getUserAuthenticationValidityDurationSeconds(
419                 KeyGenParameterSpec keyGenParameterSpec) {
420             return keyGenParameterSpec.getUserAuthenticationValidityDurationSeconds();
421         }
422     }
423 
424     @RequiresApi(28)
425     static class Api28Impl {
Api28Impl()426         private Api28Impl() {
427             // This class is not instantiable.
428         }
429 
isStrongBoxBacked(KeyGenParameterSpec keyGenParameterSpec)430         static boolean isStrongBoxBacked(KeyGenParameterSpec keyGenParameterSpec) {
431             return keyGenParameterSpec.isStrongBoxBacked();
432         }
433 
434     }
435 }
436