• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.trust;
18 
19 import android.annotation.Nullable;
20 import android.app.ActivityManager;
21 import android.bluetooth.BluetoothDevice;
22 import android.car.trust.TrustedDeviceInfo;
23 import android.content.Context;
24 import android.content.SharedPreferences;
25 import android.security.keystore.KeyGenParameterSpec;
26 import android.security.keystore.KeyProperties;
27 import android.sysprop.CarProperties;
28 import android.util.Base64;
29 import android.util.Log;
30 
31 import com.android.car.CarServiceBase;
32 import com.android.car.R;
33 import com.android.car.Utils;
34 
35 import java.io.IOException;
36 import java.io.PrintWriter;
37 import java.security.InvalidAlgorithmParameterException;
38 import java.security.InvalidKeyException;
39 import java.security.Key;
40 import java.security.KeyStore;
41 import java.security.KeyStoreException;
42 import java.security.NoSuchAlgorithmException;
43 import java.security.NoSuchProviderException;
44 import java.security.UnrecoverableKeyException;
45 import java.security.cert.CertificateException;
46 import java.util.List;
47 import java.util.UUID;
48 
49 import javax.crypto.BadPaddingException;
50 import javax.crypto.Cipher;
51 import javax.crypto.IllegalBlockSizeException;
52 import javax.crypto.KeyGenerator;
53 import javax.crypto.NoSuchPaddingException;
54 import javax.crypto.spec.GCMParameterSpec;
55 
56 /**
57  * The part of the Car service that enables the Trusted device feature.  Trusted Device is a feature
58  * where a remote device is enrolled as a trusted device that can authorize an Android user in lieu
59  * of the user entering a password or PIN.
60  * <p>
61  * It is comprised of the {@link CarTrustAgentEnrollmentService} for handling enrollment and
62  * {@link CarTrustAgentUnlockService} for handling unlock/auth.
63  *
64  */
65 public class CarTrustedDeviceService implements CarServiceBase {
66     private static final String TAG = CarTrustedDeviceService.class.getSimpleName();
67 
68     private static final String UNIQUE_ID_KEY = "CTABM_unique_id";
69     private static final String PREF_ENCRYPTION_KEY_PREFIX = "CTABM_encryption_key";
70     private static final String KEY_ALIAS = "Ukey2Key";
71     private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding";
72     private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
73     private static final String IV_SPEC_SEPARATOR = ";";
74 
75     // Device name length is limited by available bytes in BLE advertisement data packet.
76     //
77     // BLE advertisement limits data packet length to 31
78     // Currently we send:
79     // - 18 bytes for 16 chars UUID: 16 bytes + 2 bytes for header;
80     // - 3 bytes for advertisement being connectable;
81     // which leaves 10 bytes.
82     // Subtracting 2 bytes used by header, we have 8 bytes for device name.
83     private static final int DEVICE_NAME_LENGTH_LIMIT = 8;
84     // Limit prefix to 4 chars and fill the rest with randomly generated name. Use random name
85     // to improve uniqueness in paired device name.
86     private static final int DEVICE_NAME_PREFIX_LIMIT = 4;
87 
88     // The length of the authentication tag for a cipher in GCM mode. The GCM specification states
89     // that this length can only have the values {128, 120, 112, 104, 96}. Using the highest
90     // possible value.
91     private static final int GCM_AUTHENTICATION_TAG_LENGTH = 128;
92 
93     private final Context mContext;
94     private CarTrustAgentEnrollmentService mCarTrustAgentEnrollmentService;
95     private CarTrustAgentUnlockService mCarTrustAgentUnlockService;
96     private CarTrustAgentBleManager mCarTrustAgentBleManager;
97     private SharedPreferences mTrustAgentTokenPreferences;
98     private UUID mUniqueId;
99     private String mEnrollmentDeviceName;
100 
CarTrustedDeviceService(Context context)101     public CarTrustedDeviceService(Context context) {
102         mContext = context;
103 
104         // TODO(b/134695083): Decouple these classes. The services should instead register as
105         // listeners on CarTrustAgentBleManager. CarTrustAgentBleManager should not know about
106         // the services and just dispatch BLE events.
107         mCarTrustAgentBleManager = new CarTrustAgentBleManager(context);
108         mCarTrustAgentEnrollmentService = new CarTrustAgentEnrollmentService(mContext, this,
109                 mCarTrustAgentBleManager);
110         mCarTrustAgentUnlockService = new CarTrustAgentUnlockService(this,
111                 mCarTrustAgentBleManager);
112     }
113 
114     @Override
init()115     public synchronized void init() {
116         mCarTrustAgentEnrollmentService.init();
117         mCarTrustAgentUnlockService.init();
118     }
119 
120     @Override
release()121     public synchronized void release() {
122         mCarTrustAgentBleManager.cleanup();
123         mCarTrustAgentEnrollmentService.release();
124         mCarTrustAgentUnlockService.release();
125     }
126 
127     /**
128      * Returns the internal {@link CarTrustAgentEnrollmentService} instance.
129      */
getCarTrustAgentEnrollmentService()130     public CarTrustAgentEnrollmentService getCarTrustAgentEnrollmentService() {
131         return mCarTrustAgentEnrollmentService;
132     }
133 
134     /**
135      * Returns the internal {@link CarTrustAgentUnlockService} instance.
136      */
getCarTrustAgentUnlockService()137     public CarTrustAgentUnlockService getCarTrustAgentUnlockService() {
138         return mCarTrustAgentUnlockService;
139     }
140 
141     /**
142      * Returns User Id for the given token handle
143      *
144      * @param handle The handle corresponding to the escrow token
145      * @return User id corresponding to the handle
146      */
getUserHandleByTokenHandle(long handle)147     int getUserHandleByTokenHandle(long handle) {
148         return getSharedPrefs().getInt(String.valueOf(handle), -1);
149     }
150 
onRemoteDeviceConnected(BluetoothDevice device)151     void onRemoteDeviceConnected(BluetoothDevice device) {
152         mCarTrustAgentEnrollmentService.onRemoteDeviceConnected(device);
153         mCarTrustAgentUnlockService.onRemoteDeviceConnected(device);
154     }
155 
onRemoteDeviceDisconnected(BluetoothDevice device)156     void onRemoteDeviceDisconnected(BluetoothDevice device) {
157         mCarTrustAgentEnrollmentService.onRemoteDeviceDisconnected(device);
158         mCarTrustAgentUnlockService.onRemoteDeviceDisconnected(device);
159     }
160 
onDeviceNameRetrieved(String deviceName)161     void onDeviceNameRetrieved(String deviceName) {
162         mCarTrustAgentEnrollmentService.onDeviceNameRetrieved(deviceName);
163     }
164 
cleanupBleService()165     void cleanupBleService() {
166         if (Log.isLoggable(TAG, Log.DEBUG)) {
167             Log.d(TAG, "cleanupBleService");
168         }
169         mCarTrustAgentBleManager.stopGattServer();
170         mCarTrustAgentBleManager.stopEnrollmentAdvertising();
171         mCarTrustAgentBleManager.stopUnlockAdvertising();
172     }
173 
getSharedPrefs()174     SharedPreferences getSharedPrefs() {
175         if (mTrustAgentTokenPreferences != null) {
176             return mTrustAgentTokenPreferences;
177         }
178         mTrustAgentTokenPreferences = mContext.getSharedPreferences(
179                 mContext.getString(R.string.token_handle_shared_preferences), Context.MODE_PRIVATE);
180         return mTrustAgentTokenPreferences;
181     }
182 
183     @Override
dump(PrintWriter writer)184     public void dump(PrintWriter writer) {
185         writer.println("*CarTrustedDeviceService*");
186         int uid = ActivityManager.getCurrentUser();
187         writer.println("current user id: " + uid);
188         List<TrustedDeviceInfo> deviceInfos = mCarTrustAgentEnrollmentService
189                 .getEnrolledDeviceInfosForUser(uid);
190         writer.println(getDeviceInfoListString(uid, deviceInfos));
191         mCarTrustAgentEnrollmentService.dump(writer);
192         mCarTrustAgentUnlockService.dump(writer);
193     }
194 
getDeviceInfoListString(int uid, List<TrustedDeviceInfo> deviceInfos)195     private static String getDeviceInfoListString(int uid, List<TrustedDeviceInfo> deviceInfos) {
196         StringBuilder sb = new StringBuilder();
197         sb.append("device list of (user : ").append(uid).append("):");
198         if (deviceInfos != null && deviceInfos.size() > 0) {
199             for (int i = 0; i < deviceInfos.size(); i++) {
200                 sb.append("\n\tdevice# ").append(i + 1).append(" : ")
201                     .append(deviceInfos.get(i).toString());
202             }
203         } else {
204             sb.append("\n\tno device listed");
205         }
206         return sb.toString();
207     }
208 
209     /**
210      * Get the unique id for head unit. Persists on device until factory reset.
211      *
212      * @return unique id, or null if unable to retrieve generated id (this should never happen)
213      */
214     @Nullable
getUniqueId()215     UUID getUniqueId() {
216         if (mUniqueId != null) {
217             return mUniqueId;
218         }
219 
220         SharedPreferences prefs = getSharedPrefs();
221         if (prefs.contains(UNIQUE_ID_KEY)) {
222             mUniqueId = UUID.fromString(
223                     prefs.getString(UNIQUE_ID_KEY, null));
224             if (Log.isLoggable(TAG, Log.DEBUG)) {
225                 Log.d(TAG, "Found existing trusted unique id: "
226                         + prefs.getString(UNIQUE_ID_KEY, ""));
227             }
228         } else {
229             mUniqueId = UUID.randomUUID();
230             if (!prefs.edit().putString(UNIQUE_ID_KEY, mUniqueId.toString()).commit()) {
231                 mUniqueId = null;
232             } else if (Log.isLoggable(TAG, Log.DEBUG)) {
233                 Log.d(TAG, "Generated new trusted unique id: "
234                         + prefs.getString(UNIQUE_ID_KEY, ""));
235             }
236         }
237 
238         return mUniqueId;
239     }
240 
241     /**
242      * Get communication encryption key for the given device
243      *
244      * @param deviceId id of trusted device
245      * @return encryption key, null if device id is not recognized
246      */
247     @Nullable
getEncryptionKey(String deviceId)248     byte[] getEncryptionKey(String deviceId) {
249         SharedPreferences prefs = getSharedPrefs();
250         String key = PREF_ENCRYPTION_KEY_PREFIX + deviceId;
251         if (!prefs.contains(key)) {
252             return null;
253         }
254 
255         // This value will not be "null" because we already checked via a call to contains().
256         String[] values = prefs.getString(key, null).split(IV_SPEC_SEPARATOR);
257 
258         if (values.length != 2) {
259             return null;
260         }
261 
262         byte[] encryptedKey = Base64.decode(values[0], Base64.DEFAULT);
263         byte[] ivSpec = Base64.decode(values[1], Base64.DEFAULT);
264         return decryptWithKeyStore(KEY_ALIAS, encryptedKey, ivSpec);
265     }
266 
267     /**
268      * Save encryption key for the given device
269      *
270      * @param deviceId did of trusted device
271      * @param encryptionKey encryption key
272      * @return {@code true} if the operation succeeded
273      */
saveEncryptionKey(@ullable String deviceId, @Nullable byte[] encryptionKey)274     boolean saveEncryptionKey(@Nullable String deviceId, @Nullable byte[] encryptionKey) {
275         if (encryptionKey == null || deviceId == null) {
276             return false;
277         }
278         String encryptedKey = encryptWithKeyStore(KEY_ALIAS, encryptionKey);
279         if (encryptedKey == null) {
280             return false;
281         }
282         if (getSharedPrefs().contains(deviceId)) {
283             clearEncryptionKey(deviceId);
284         }
285 
286         return getSharedPrefs()
287                 .edit()
288                 .putString(PREF_ENCRYPTION_KEY_PREFIX + deviceId, encryptedKey)
289                 .commit();
290     }
291 
292     /**
293      * Clear the encryption key for the given device
294      *
295      * @param deviceId id of the peer device
296      */
clearEncryptionKey(@ullable String deviceId)297     void clearEncryptionKey(@Nullable String deviceId) {
298         if (deviceId == null) {
299             return;
300         }
301         getSharedPrefs().edit().remove(deviceId);
302     }
303 
304     /**
305      * Returns the name that should be used for the device during enrollment of a trusted device.
306      *
307      * <p>The returned name will be a combination of a prefix sysprop and randomized digits.
308      */
getEnrollmentDeviceName()309     String getEnrollmentDeviceName() {
310         if (mEnrollmentDeviceName == null) {
311             String deviceNamePrefix =
312                     CarProperties.trusted_device_device_name_prefix().orElse("");
313             deviceNamePrefix = deviceNamePrefix.substring(
314                     0, Math.min(deviceNamePrefix.length(), DEVICE_NAME_PREFIX_LIMIT));
315 
316             int randomNameLength = DEVICE_NAME_LENGTH_LIMIT - deviceNamePrefix.length();
317             String randomName = Utils.generateRandomNumberString(randomNameLength);
318             mEnrollmentDeviceName = deviceNamePrefix + randomName;
319         }
320         return mEnrollmentDeviceName;
321     }
322 
323     /**
324      * Encrypt value with designated key
325      *
326      * <p>The encrypted value is of the form:
327      *
328      * <p>key + IV_SPEC_SEPARATOR + ivSpec
329      *
330      * <p>The {@code ivSpec} is needed to decrypt this key later on.
331      *
332      * @param keyAlias KeyStore alias for key to use
333      * @param value a value to encrypt
334      * @return encrypted value, null if unable to encrypt
335      */
336     @Nullable
encryptWithKeyStore(String keyAlias, byte[] value)337     String encryptWithKeyStore(String keyAlias, byte[] value) {
338         if (value == null) {
339             return null;
340         }
341 
342         Key key = getKeyStoreKey(keyAlias);
343         try {
344             Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
345             cipher.init(Cipher.ENCRYPT_MODE, key);
346             return new StringBuffer(Base64.encodeToString(cipher.doFinal(value), Base64.DEFAULT))
347                 .append(IV_SPEC_SEPARATOR)
348                 .append(Base64.encodeToString(cipher.getIV(), Base64.DEFAULT))
349                 .toString();
350         } catch (IllegalBlockSizeException
351                 | BadPaddingException
352                 | NoSuchAlgorithmException
353                 | NoSuchPaddingException
354                 | IllegalStateException
355                 | InvalidKeyException e) {
356             Log.e(TAG, "Unable to encrypt value with key " + keyAlias, e);
357             return null;
358         }
359     }
360 
361     /**
362      * Decrypt value with designated key
363      *
364      * @param keyAlias KeyStore alias for key to use
365      * @param value encrypted value
366      * @return decrypted value, null if unable to decrypt
367      */
368     @Nullable
decryptWithKeyStore(String keyAlias, byte[] value, byte[] ivSpec)369     byte[] decryptWithKeyStore(String keyAlias, byte[] value, byte[] ivSpec) {
370         if (value == null) {
371             return null;
372         }
373 
374         try {
375             Key key = getKeyStoreKey(keyAlias);
376             Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
377             cipher.init(Cipher.DECRYPT_MODE, key,
378                     new GCMParameterSpec(GCM_AUTHENTICATION_TAG_LENGTH, ivSpec));
379             return cipher.doFinal(value);
380         } catch (IllegalBlockSizeException
381                 | BadPaddingException
382                 | NoSuchAlgorithmException
383                 | NoSuchPaddingException
384                 | IllegalStateException
385                 | InvalidKeyException
386                 | InvalidAlgorithmParameterException e) {
387             Log.e(TAG, "Unable to decrypt value with key " + keyAlias, e);
388             return null;
389         }
390     }
391 
getKeyStoreKey(String keyAlias)392     private Key getKeyStoreKey(String keyAlias) {
393         KeyStore keyStore;
394         try {
395             keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
396             keyStore.load(null);
397             if (!keyStore.containsAlias(keyAlias)) {
398                 KeyGenerator keyGenerator = KeyGenerator.getInstance(
399                         KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER);
400                 keyGenerator.init(
401                         new KeyGenParameterSpec.Builder(keyAlias,
402                                 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
403                                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
404                                 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
405                                 .build());
406                 keyGenerator.generateKey();
407             }
408             return keyStore.getKey(keyAlias, null);
409 
410         } catch (KeyStoreException
411                 | NoSuchAlgorithmException
412                 | UnrecoverableKeyException
413                 | NoSuchProviderException
414                 | CertificateException
415                 | IOException
416                 | InvalidAlgorithmParameterException e) {
417             Log.e(TAG, "Unable to retrieve key " + keyAlias + " from KeyStore.", e);
418             throw new IllegalStateException(e);
419         }
420     }
421 }
422