package com.android.server.accounts; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; import android.os.Parcel; import android.util.Log; import com.android.internal.util.Preconditions; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; /** * A crypto helper for encrypting and decrypting bundle with in-memory symmetric * key for {@link AccountManagerService}. */ /* default */ class CryptoHelper { private static final String TAG = "Account"; private static final String KEY_CIPHER = "cipher"; private static final String KEY_MAC = "mac"; private static final String KEY_ALGORITHM = "AES"; private static final String KEY_IV = "iv"; private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; private static final String MAC_ALGORITHM = "HMACSHA256"; private static final int IV_LENGTH = 16; private static CryptoHelper sInstance; // Keys used for encrypting and decrypting data returned in a Bundle. private final SecretKey mEncryptionKey; private final SecretKey mMacKey; /* default */ synchronized static CryptoHelper getInstance() throws NoSuchAlgorithmException { if (sInstance == null) { sInstance = new CryptoHelper(); } return sInstance; } private CryptoHelper() throws NoSuchAlgorithmException { KeyGenerator kgen = KeyGenerator.getInstance(KEY_ALGORITHM); mEncryptionKey = kgen.generateKey(); // Use a different key for mac-ing than encryption/decryption. kgen = KeyGenerator.getInstance(MAC_ALGORITHM); mMacKey = kgen.generateKey(); } @NonNull /* default */ Bundle encryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException { Preconditions.checkNotNull(bundle, "Cannot encrypt null bundle."); Parcel parcel = Parcel.obtain(); bundle.writeToParcel(parcel, 0); byte[] clearBytes = parcel.marshall(); parcel.recycle(); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, mEncryptionKey); byte[] encryptedBytes = cipher.doFinal(clearBytes); byte[] iv = cipher.getIV(); byte[] mac = createMac(encryptedBytes, iv); Bundle encryptedBundle = new Bundle(); encryptedBundle.putByteArray(KEY_CIPHER, encryptedBytes); encryptedBundle.putByteArray(KEY_MAC, mac); encryptedBundle.putByteArray(KEY_IV, iv); return encryptedBundle; } @Nullable /* default */ Bundle decryptBundle(@NonNull Bundle bundle) throws GeneralSecurityException { Preconditions.checkNotNull(bundle, "Cannot decrypt null bundle."); byte[] iv = bundle.getByteArray(KEY_IV); byte[] encryptedBytes = bundle.getByteArray(KEY_CIPHER); byte[] mac = bundle.getByteArray(KEY_MAC); if (!verifyMac(encryptedBytes, iv, mac)) { Log.w(TAG, "Escrow mac mismatched!"); return null; } IvParameterSpec ivSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, mEncryptionKey, ivSpec); byte[] decryptedBytes = cipher.doFinal(encryptedBytes); Parcel decryptedParcel = Parcel.obtain(); decryptedParcel.unmarshall(decryptedBytes, 0, decryptedBytes.length); decryptedParcel.setDataPosition(0); Bundle decryptedBundle = new Bundle(); decryptedBundle.readFromParcel(decryptedParcel); decryptedParcel.recycle(); return decryptedBundle; } private boolean verifyMac(@Nullable byte[] cipherArray, @Nullable byte[] iv, @Nullable byte[] macArray) throws GeneralSecurityException { if (cipherArray == null || cipherArray.length == 0 || macArray == null || macArray.length == 0) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Cipher or MAC is empty!"); } return false; } return constantTimeArrayEquals(macArray, createMac(cipherArray, iv)); } @NonNull private byte[] createMac(@NonNull byte[] cipher, @NonNull byte[] iv) throws GeneralSecurityException { Mac mac = Mac.getInstance(MAC_ALGORITHM); mac.init(mMacKey); mac.update(cipher); mac.update(iv); return mac.doFinal(); } private static boolean constantTimeArrayEquals(byte[] a, byte[] b) { if (a == null || b == null) { return a == b; } if (a.length != b.length) { return false; } boolean isEqual = true; for (int i = 0; i < b.length; i++) { isEqual &= (a[i] == b[i]); } return isEqual; } }