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