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