• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 com.android.server.wifi.util;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Process;
22 import android.security.keystore.AndroidKeyStoreProvider;
23 import android.security.keystore.KeyGenParameterSpec;
24 import android.security.keystore.KeyProperties;
25 import android.text.TextUtils;
26 import android.util.Log;
27 
28 import java.security.InvalidAlgorithmParameterException;
29 import java.security.InvalidKeyException;
30 import java.security.KeyStore;
31 import java.security.KeyStoreException;
32 import java.security.NoSuchAlgorithmException;
33 import java.security.NoSuchProviderException;
34 import java.security.ProviderException;
35 import java.security.UnrecoverableEntryException;
36 
37 import javax.crypto.BadPaddingException;
38 import javax.crypto.Cipher;
39 import javax.crypto.IllegalBlockSizeException;
40 import javax.crypto.KeyGenerator;
41 import javax.crypto.NoSuchPaddingException;
42 import javax.crypto.SecretKey;
43 import javax.crypto.spec.GCMParameterSpec;
44 
45 /**
46  * Tools to help encrypt/decrypt
47  */
48 public class WifiConfigStoreEncryptionUtil {
49     private static final String TAG = "WifiConfigStoreEncryptionUtil";
50 
51     private static final String ALIAS_SUFFIX = ".data-encryption-key";
52     private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding";
53     private static final int GCM_TAG_LENGTH = 128;
54     private static final int KEY_LENGTH = 256;
55     private static final String KEY_STORE = "AndroidKeyStore";
56     private final SecretKey mSecretKeyReference;
57     private Cipher mEncryptCipher;
58 
59     private final String mDataFileName;
60 
61     /**
62      * Construct a new util to help {@link com.android.server.wifi.WifiConfigStore.StoreData}
63      * modules to encrypt/decrypt credential data written/read from this config store file.
64      *
65      * @param dataFileName The full path of the data file.
66      * @throws NullPointerException When data file is empty string.
67      */
WifiConfigStoreEncryptionUtil(@onNull String dataFileName)68     public WifiConfigStoreEncryptionUtil(@NonNull String dataFileName) {
69         if (TextUtils.isEmpty(dataFileName)) {
70             throw new NullPointerException("dataFileName must not be null or the empty "
71                     + "string");
72         }
73         mDataFileName = dataFileName;
74         mSecretKeyReference = getOrCreateSecretKey(getKeyAlias());
75         try {
76             mEncryptCipher = Cipher.getInstance(CIPHER_ALGORITHM);
77         } catch (NoSuchAlgorithmException e) {
78             reportException(e, "encrypt could not find the algorithm: " + CIPHER_ALGORITHM);
79         } catch (NoSuchPaddingException e) {
80             reportException(e, "encrypt had a padding exception");
81         } catch (Exception e) {
82             reportException(e, "exception caught");
83         }
84     }
85 
getKeyAlias()86     private String getKeyAlias() {
87         return mDataFileName + ALIAS_SUFFIX;
88     }
89 
90     /**
91      * Encrypt the provided data blob.
92      *
93      * @param data Data blob to be encrypted.
94      * @return Instance of {@link EncryptedData} containing the encrypted info.
95      */
encrypt(byte[] data)96     public @Nullable EncryptedData encrypt(byte[] data) {
97         if (data == null || data.length == 0) {
98             return null;
99         }
100         EncryptedData encryptedData = null;
101         try {
102             if (mSecretKeyReference != null) {
103                 mEncryptCipher.init(Cipher.ENCRYPT_MODE, mSecretKeyReference);
104                 encryptedData = new EncryptedData(mEncryptCipher.doFinal(data),
105                         mEncryptCipher.getIV());
106             } else {
107                 reportException(new Exception("secretKeyReference is null."),
108                         "secretKeyReference is null.");
109             }
110         } catch (BadPaddingException e) {
111             reportException(e, "encrypt had a padding problem");
112         } catch (IllegalBlockSizeException e) {
113             reportException(e, "encrypt had an illegal block size");
114         } catch (InvalidKeyException e) {
115             reportException(e, "encrypt received an invalid key");
116         } catch (Exception e) {
117             reportException(e, "exception caught");
118         }
119         return encryptedData;
120     }
121 
122     /**
123      * Decrypt the original data blob from the provided {@link EncryptedData}.
124      *
125      * @param encryptedData Instance of {@link EncryptedData} containing the encrypted info.
126      * @return Original data blob that was encrypted.
127      */
decrypt(@onNull EncryptedData encryptedData)128     public @Nullable byte[] decrypt(@NonNull EncryptedData encryptedData) {
129         byte[] decryptedData = null;
130         try {
131             Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
132             GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, encryptedData.getIv());
133             if (mSecretKeyReference != null) {
134                 cipher.init(Cipher.DECRYPT_MODE, mSecretKeyReference, spec);
135                 decryptedData = cipher.doFinal(encryptedData.getEncryptedData());
136             }
137         } catch (NoSuchAlgorithmException e) {
138             reportException(e, "decrypt could not find cipher algorithm " + CIPHER_ALGORITHM);
139         } catch (NoSuchPaddingException e) {
140             reportException(e, "decrypt could not find padding algorithm");
141         } catch (IllegalBlockSizeException e) {
142             reportException(e, "decrypt had a illegal block size");
143         } catch (BadPaddingException e) {
144             reportException(e, "decrypt had bad padding");
145         } catch (InvalidKeyException e) {
146             reportException(e, "decrypt had an invalid key");
147         } catch (InvalidAlgorithmParameterException e) {
148             reportException(e, "decrypt had an invalid algorithm parameter");
149         } catch (Exception e) {
150             reportException(e, "exception caught");
151         }
152         return decryptedData;
153     }
154 
getOrCreateSecretKey(String keyAlias)155     private SecretKey getOrCreateSecretKey(String keyAlias) {
156         SecretKey secretKey = null;
157         try {
158             KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(Process.WIFI_UID);
159             if (keyStore.containsAlias(keyAlias)) { // The key exists in key store. Get the key.
160                 KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore
161                         .getEntry(keyAlias, null);
162                 if (secretKeyEntry != null) {
163                     secretKey = secretKeyEntry.getSecretKey();
164                 } else {
165                     reportException(new Exception("keystore contains the alias and the secret key "
166                             + "entry was null"),
167                             "keystore contains the alias and the secret key entry was null");
168                 }
169             } else { // The key does not exist in key store. Create the key and store it.
170                 KeyGenerator keyGenerator = KeyGenerator
171                         .getInstance(KeyProperties.KEY_ALGORITHM_AES, KEY_STORE);
172 
173                 KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(keyAlias,
174                         KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
175                         .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
176                         .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
177                         .setKeySize(KEY_LENGTH)
178                         .setUid(Process.WIFI_UID)
179                         .build();
180 
181                 keyGenerator.init(keyGenParameterSpec);
182                 secretKey = keyGenerator.generateKey();
183             }
184         } catch (InvalidAlgorithmParameterException e) {
185             reportException(e, "getOrCreateSecretKey had an invalid algorithm parameter");
186         } catch (KeyStoreException e) {
187             reportException(e, "getOrCreateSecretKey cannot find the keystore: " + KEY_STORE);
188         } catch (NoSuchAlgorithmException e) {
189             reportException(e, "getOrCreateSecretKey cannot find algorithm");
190         } catch (NoSuchProviderException e) {
191             reportException(e, "getOrCreateSecretKey cannot find crypto provider");
192         } catch (UnrecoverableEntryException e) {
193             reportException(e, "getOrCreateSecretKey had an unrecoverable entry exception.");
194         } catch (ProviderException e) {
195             reportException(e, "getOrCreateSecretKey had a provider exception.");
196         } catch (Exception e) {
197             reportException(e, "exception caught");
198         }
199         return secretKey;
200     }
201 
reportException(Exception exception, String error)202     private void reportException(Exception exception, String error) {
203         Log.wtf(TAG, "An irrecoverable key store error was encountered: " + error, exception);
204     }
205 }
206