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