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