1 package com.android.server.accounts; 2 3 import android.annotation.NonNull; 4 import android.annotation.Nullable; 5 import android.os.Bundle; 6 import android.os.Parcel; 7 import android.util.Log; 8 9 import com.android.internal.util.Preconditions; 10 11 import java.security.GeneralSecurityException; 12 import java.security.NoSuchAlgorithmException; 13 import java.util.Objects; 14 15 import javax.crypto.Cipher; 16 import javax.crypto.KeyGenerator; 17 import javax.crypto.Mac; 18 import javax.crypto.SecretKey; 19 import javax.crypto.spec.IvParameterSpec; 20 21 /** 22 * A crypto helper for encrypting and decrypting bundle with in-memory symmetric 23 * key for {@link AccountManagerService}. 24 */ 25 /* default */ class CryptoHelper { 26 private static final String TAG = "Account"; 27 28 private static final String KEY_CIPHER = "cipher"; 29 private static final String KEY_MAC = "mac"; 30 private static final String KEY_ALGORITHM = "AES"; 31 private static final String KEY_IV = "iv"; 32 private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; 33 private static final String MAC_ALGORITHM = "HMACSHA256"; 34 private static final int IV_LENGTH = 16; 35 36 private static CryptoHelper sInstance; 37 // Keys used for encrypting and decrypting data returned in a Bundle. 38 private final SecretKey mEncryptionKey; 39 private final SecretKey mMacKey; 40 getInstance()41 /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException { 42 if (sInstance == null) { 43 sInstance = new CryptoHelper(); 44 } 45 return sInstance; 46 } 47 CryptoHelper()48 private CryptoHelper() throws NoSuchAlgorithmException { 49 KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM); 50 mEncryptionKey = kgen.generateKey(); 51 // Use a different key for mac-ing than encryption/decryption. 52 kgen = KeyGenerator.getInstance(MAC_ALGORITHM); 53 mMacKey = kgen.generateKey(); 54 } 55 56 @NonNull encryptBundle(@onNull Bundle bundle)57 /* default */ Bundle encryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException { 58 Objects.requireNonNull(bundle, "Cannot encrypt null bundle."); 59 Parcel parcel = Parcel.obtain(); 60 bundle.writeToParcel(parcel, 0); 61 byte[] clearBytes = parcel.marshall(); 62 parcel.recycle(); 63 64 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 65 cipher.init(Cipher.ENCRYPT_MODE, mEncryptionKey); 66 byte[] encryptedBytes = cipher.doFinal(clearBytes); 67 byte[] iv = cipher.getIV(); 68 byte[] mac = createMac(encryptedBytes, iv); 69 70 Bundle encryptedBundle = new Bundle(); 71 encryptedBundle.putByteArray(KEY_CIPHER, encryptedBytes); 72 encryptedBundle.putByteArray(KEY_MAC, mac); 73 encryptedBundle.putByteArray(KEY_IV, iv); 74 75 return encryptedBundle; 76 } 77 78 @Nullable decryptBundle(@onNull Bundle bundle)79 /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException { 80 Objects.requireNonNull(bundle, "Cannot decrypt null bundle."); 81 byte[] iv = bundle.getByteArray(KEY_IV); 82 byte[] encryptedBytes = bundle.getByteArray(KEY_CIPHER); 83 byte[] mac = bundle.getByteArray(KEY_MAC); 84 if (!verifyMac(encryptedBytes, iv, mac)) { 85 Log.w(TAG, "Escrow mac mismatched!"); 86 return null; 87 } 88 89 IvParameterSpec ivSpec = new IvParameterSpec(iv); 90 Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); 91 cipher.init(Cipher.DECRYPT_MODE, mEncryptionKey, ivSpec); 92 byte[] decryptedBytes = cipher.doFinal(encryptedBytes); 93 94 Parcel decryptedParcel = Parcel.obtain(); 95 decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length); 96 decryptedParcel.setDataPosition(0); 97 Bundle decryptedBundle = new Bundle(); 98 decryptedBundle.readFromParcel(decryptedParcel); 99 decryptedParcel.recycle(); 100 return decryptedBundle; 101 } 102 verifyMac(@ullable byte[] cipherArray, @Nullable byte[] iv, @Nullable byte[] macArray)103 private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] iv, @Nullable byte[] macArray) 104 throws GeneralSecurityException { 105 if (cipherArray == null || cipherArray.length == 0 || macArray == null 106 || macArray.length == 0) { 107 if (Log.isLoggable(TAG, Log.VERBOSE)) { 108 Log.v(TAG, "Cipher or MAC is empty!"); 109 } 110 return false; 111 } 112 return constantTimeArrayEquals(macArray, createMac(cipherArray, iv)); 113 } 114 115 @NonNull createMac(@onNull byte[] cipher, @NonNull byte[] iv)116 private byte[] createMac(@NonNull byte[] cipher, @NonNull byte[] iv) throws GeneralSecurityException { 117 Mac mac = Mac.getInstance(MAC_ALGORITHM); 118 mac.init(mMacKey); 119 mac.update(cipher); 120 mac.update(iv); 121 return mac.doFinal(); 122 } 123 constantTimeArrayEquals(byte[] a, byte[] b)124 private static boolean constantTimeArrayEquals(byte[] a, byte[] b) { 125 if (a == null || b == null) { 126 return a == b; 127 } 128 if (a.length != b.length) { 129 return false; 130 } 131 boolean isEqual = true; 132 for (int i = 0; i < b.length; i++) { 133 isEqual &= (a[i] == b[i]); 134 } 135 return isEqual; 136 } 137 } 138