1 /* 2 * Copyright 2019 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 android.os.Build; 20 import android.security.keystore.KeyGenParameterSpec; 21 import android.security.keystore.KeyProperties; 22 23 import androidx.annotation.RequiresApi; 24 import androidx.annotation.VisibleForTesting; 25 26 import org.jspecify.annotations.NonNull; 27 28 import java.io.IOException; 29 import java.security.GeneralSecurityException; 30 import java.security.KeyStore; 31 import java.security.ProviderException; 32 import java.util.Arrays; 33 34 import javax.crypto.KeyGenerator; 35 36 /** 37 * Convenient methods to create and obtain master keys in Android Keystore. 38 * 39 * <p>The master keys are used to encrypt data encryption keys for encrypting files and preferences. 40 * 41 * @deprecated Use {@link javax.crypto.KeyGenerator} with AndroidKeyStore instance instead. 42 */ 43 @Deprecated 44 @RequiresApi(Build.VERSION_CODES.M) 45 public final class MasterKeys { MasterKeys()46 private MasterKeys() { 47 } 48 49 static final String MASTER_KEY_ALIAS = MasterKey.DEFAULT_MASTER_KEY_ALIAS; 50 static final int KEY_SIZE = MasterKey.DEFAULT_AES_GCM_MASTER_KEY_SIZE; 51 52 private static final String ANDROID_KEYSTORE = "AndroidKeyStore"; 53 54 /** 55 * @deprecated Use {@link android.security.keystore.KeyGenParameterSpec.Builder} instead. 56 */ 57 @Deprecated 58 public static final @NonNull KeyGenParameterSpec AES256_GCM_SPEC = 59 createAES256GCMKeyGenParameterSpec(MASTER_KEY_ALIAS); 60 61 private static final Object sLock = new Object(); 62 63 /** 64 * Provides a safe and easy to use KenGenParameterSpec with the settings. 65 * Algorithm: AES 66 * Block Mode: GCM 67 * Padding: No Padding 68 * Key Size: 256 69 * 70 * @param keyAlias The alias for the master key 71 * @return The spec for the master key with the specified keyAlias 72 */ 73 @SuppressWarnings("SameParameterValue") createAES256GCMKeyGenParameterSpec( @onNull String keyAlias)74 private static @NonNull KeyGenParameterSpec createAES256GCMKeyGenParameterSpec( 75 @NonNull String keyAlias) { 76 KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder( 77 keyAlias, 78 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 79 .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 80 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) 81 .setKeySize(KEY_SIZE); 82 return builder.build(); 83 } 84 85 /** 86 * Creates or gets the master key provided 87 * 88 * <p>The encryption scheme is required fields to ensure that the type of 89 * encryption used is clear to developers. 90 * 91 * @param keyGenParameterSpec The key encryption scheme 92 * @return The key alias for the master key 93 */ getOrCreate( @onNull KeyGenParameterSpec keyGenParameterSpec)94 public static @NonNull String getOrCreate( 95 @NonNull KeyGenParameterSpec keyGenParameterSpec) 96 throws GeneralSecurityException, IOException { 97 validate(keyGenParameterSpec); 98 99 synchronized (sLock) { 100 if (!MasterKeys.keyExists(keyGenParameterSpec.getKeystoreAlias())) { 101 generateKey(keyGenParameterSpec); 102 } 103 } 104 105 return keyGenParameterSpec.getKeystoreAlias(); 106 } 107 108 @VisibleForTesting validate(KeyGenParameterSpec spec)109 static void validate(KeyGenParameterSpec spec) { 110 if (spec.getKeySize() != KEY_SIZE) { 111 throw new IllegalArgumentException( 112 "invalid key size, want " + KEY_SIZE + " bits got " + spec.getKeySize() 113 + " bits"); 114 } 115 if (!Arrays.equals(spec.getBlockModes(), new String[]{KeyProperties.BLOCK_MODE_GCM})) { 116 throw new IllegalArgumentException( 117 "invalid block mode, want " + KeyProperties.BLOCK_MODE_GCM + " got " 118 + Arrays.toString(spec.getBlockModes())); 119 } 120 if (spec.getPurposes() != (KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)) { 121 throw new IllegalArgumentException( 122 "invalid purposes mode, want PURPOSE_ENCRYPT | PURPOSE_DECRYPT got " 123 + spec.getPurposes()); 124 } 125 if (!Arrays.equals(spec.getEncryptionPaddings(), new String[] 126 {KeyProperties.ENCRYPTION_PADDING_NONE})) { 127 throw new IllegalArgumentException( 128 "invalid padding mode, want " + KeyProperties.ENCRYPTION_PADDING_NONE + " got " 129 + Arrays.toString(spec.getEncryptionPaddings())); 130 } 131 if (spec.isUserAuthenticationRequired() 132 && spec.getUserAuthenticationValidityDurationSeconds() < 1) { 133 throw new IllegalArgumentException( 134 "per-operation authentication is not supported " 135 + "(UserAuthenticationValidityDurationSeconds must be >0)"); 136 } 137 } 138 generateKey(@onNull KeyGenParameterSpec keyGenParameterSpec)139 private static void generateKey(@NonNull KeyGenParameterSpec keyGenParameterSpec) 140 throws GeneralSecurityException { 141 try { 142 KeyGenerator keyGenerator = KeyGenerator.getInstance( 143 KeyProperties.KEY_ALGORITHM_AES, 144 ANDROID_KEYSTORE); 145 keyGenerator.init(keyGenParameterSpec); 146 keyGenerator.generateKey(); 147 } catch (ProviderException providerException) { 148 // Android 10 (API 29) throws a ProviderException under certain circumstances. Wrap 149 // that as a GeneralSecurityException so it's more consistent across API levels. 150 throw new GeneralSecurityException(providerException.getMessage(), providerException); 151 } 152 } 153 keyExists(@onNull String keyAlias)154 private static boolean keyExists(@NonNull String keyAlias) 155 throws GeneralSecurityException, IOException { 156 KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE); 157 keyStore.load(null); 158 return keyStore.containsAlias(keyAlias); 159 } 160 161 } 162