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