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