blob: 2ade673523acf25376cd25cfa0994e395e994e37 [file] [log] [blame]
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;
}
}