1 /* 2 * Copyright (C) 2021 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 package com.android.car.bluetooth; 17 18 import static com.android.car.bluetooth.FastPairAccountKeyStorage.AccountKey; 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothGatt; 24 import android.bluetooth.BluetoothGattCharacteristic; 25 import android.bluetooth.BluetoothGattDescriptor; 26 import android.bluetooth.BluetoothGattServer; 27 import android.bluetooth.BluetoothGattServerCallback; 28 import android.bluetooth.BluetoothGattService; 29 import android.bluetooth.BluetoothManager; 30 import android.bluetooth.BluetoothProfile; 31 import android.car.builtin.util.Slogf; 32 import android.content.BroadcastReceiver; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.os.Handler; 37 import android.os.ParcelUuid; 38 import android.util.Base64; 39 import android.util.Log; 40 41 import com.android.car.CarLog; 42 import com.android.car.CarServiceUtils; 43 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 44 import com.android.car.internal.util.IndentingPrintWriter; 45 46 import java.math.BigInteger; 47 import java.nio.ByteBuffer; 48 import java.nio.ByteOrder; 49 import java.security.KeyFactory; 50 import java.security.KeyPairGenerator; 51 import java.security.MessageDigest; 52 import java.security.PrivateKey; 53 import java.security.PublicKey; 54 import java.security.interfaces.ECPublicKey; 55 import java.security.spec.ECParameterSpec; 56 import java.security.spec.ECPoint; 57 import java.security.spec.ECPrivateKeySpec; 58 import java.security.spec.ECPublicKeySpec; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.List; 62 import java.util.Objects; 63 import java.util.Random; 64 65 import javax.crypto.Cipher; 66 import javax.crypto.KeyAgreement; 67 import javax.crypto.spec.SecretKeySpec; 68 69 /** 70 * The FastPairGattServer is responsible for all 2 way communications with the Fast Pair Seeker. 71 * It is running in the background over BLE whenever the Fast Pair Service is running, waiting for a 72 * Seeker to connect, after which time it manages the authentication an performs the steps as 73 * required by the Fast Pair Specification. 74 */ 75 public class FastPairGattServer { 76 // Service ID assigned for FastPair. 77 public static final ParcelUuid FAST_PAIR_SERVICE_UUID = ParcelUuid 78 .fromString("0000FE2C-0000-1000-8000-00805f9b34fb"); 79 public static final ParcelUuid FAST_PAIR_MODEL_ID_UUID = ParcelUuid 80 .fromString("FE2C1233-8366-4814-8EB0-01DE32100BEA"); 81 public static final ParcelUuid KEY_BASED_PAIRING_UUID = ParcelUuid 82 .fromString("FE2C1234-8366-4814-8EB0-01DE32100BEA"); 83 public static final ParcelUuid PASSKEY_UUID = ParcelUuid 84 .fromString("FE2C1235-8366-4814-8EB0-01DE32100BEA"); 85 public static final ParcelUuid ACCOUNT_KEY_UUID = ParcelUuid 86 .fromString("FE2C1236-8366-4814-8EB0-01DE32100BEA"); 87 public static final ParcelUuid CLIENT_CHARACTERISTIC_CONFIG = ParcelUuid 88 .fromString("00002902-0000-1000-8000-00805f9b34fb"); 89 public static final ParcelUuid DEVICE_NAME_CHARACTERISTIC_CONFIG = ParcelUuid 90 .fromString("00002A00-0000-1000-8000-00805f9b34fb"); 91 private static final String TAG = CarLog.tagFor(FastPairGattServer.class); 92 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 93 private static final int MAX_KEY_COUNT = 10; 94 private static final int KEY_LIFESPAN_AWAIT_PAIRING = 10_000; 95 // Spec *does* say indefinitely but not having a timeout is risky. This matches the BT stack's 96 // internal pairing timeout 97 private static final int KEY_LIFESPAN_PAIRING = 35_000; 98 private static final int KEY_LIFESPAN_AWAIT_ACCOUNT_KEY = 10_000; 99 private static final int INVALID = -1; 100 101 private final boolean mAutomaticPasskeyConfirmation; 102 private final byte[] mModelId; 103 private final String mPrivateAntiSpoof; 104 private final Context mContext; 105 106 private final FastPairAccountKeyStorage mFastPairAccountKeyStorage; 107 108 private BluetoothGattServer mBluetoothGattServer; 109 private final BluetoothManager mBluetoothManager; 110 private final BluetoothAdapter mBluetoothAdapter; 111 private int mPairingPasskey = INVALID; 112 private final DecryptionFailureCounter mFailureCounter = new DecryptionFailureCounter(); 113 private BluetoothGattService mFastPairService = new BluetoothGattService( 114 FAST_PAIR_SERVICE_UUID.getUuid(), BluetoothGattService.SERVICE_TYPE_PRIMARY); 115 private Callbacks mCallbacks; 116 private SecretKeySpec mSharedSecretKey; 117 private BluetoothDevice mLocalRpaDevice; 118 private BluetoothDevice mRemotePairingDevice; 119 private BluetoothDevice mRemoteGattDevice; 120 121 interface Callbacks { 122 /** 123 * Notify the Provider of completion to a GATT session 124 * @param successful 125 */ onPairingCompleted(boolean successful)126 void onPairingCompleted(boolean successful); 127 } 128 129 private class DecryptionFailureCounter { 130 public static final int FAILURE_LIMIT = 10; 131 private static final int FAILURE_RESET_TIMEOUT = 300_000; // 5 minutes 132 133 private int mCount = 0; 134 135 private Runnable mResetRunnable = new Runnable() { 136 @Override 137 public void run() { 138 Slogf.i(TAG, "Five minutes have expired. Reset failure count to 0"); 139 reset(); 140 } 141 }; 142 increment()143 public void increment() { 144 if (hasExceededLimit()) { 145 Slogf.w(TAG, "Failure count is already at the limit."); 146 return; 147 } 148 149 mCount++; 150 Slogf.i(TAG, "Failure count increased, failures=%d", mCount); 151 if (hasExceededLimit()) { 152 Slogf.w(TAG, "Failure count has reached 10, wait 5 minutes for more tries"); 153 mHandler.postDelayed(mResetRunnable, FAILURE_RESET_TIMEOUT); 154 } 155 } 156 reset()157 public void reset() { 158 Slogf.i(TAG, "Reset failure count"); 159 mHandler.removeCallbacks(mResetRunnable); 160 mCount = 0; 161 } 162 hasExceededLimit()163 public boolean hasExceededLimit() { 164 return mCount >= FAILURE_LIMIT; 165 } 166 167 @Override toString()168 public String toString() { 169 return String.valueOf(mCount); 170 } 171 } 172 173 /** 174 * Notify this FastPairGattServer of a new RPA from the FastPairAdvertiser 175 */ updateLocalRpa(BluetoothDevice device)176 public void updateLocalRpa(BluetoothDevice device) { 177 mLocalRpaDevice = device; 178 } 179 180 private Runnable mClearSharedSecretKey = new Runnable() { 181 @Override 182 public void run() { 183 Slogf.w(TAG, "Shared secret key has expired. Clearing key material."); 184 clearSharedSecretKey(); 185 } 186 }; 187 188 private final Handler mHandler = new Handler( 189 CarServiceUtils.getHandlerThread(FastPairProvider.THREAD_NAME).getLooper()); 190 private BluetoothGattCharacteristic mModelIdCharacteristic; 191 private BluetoothGattCharacteristic mKeyBasedPairingCharacteristic; 192 private BluetoothGattCharacteristic mPasskeyCharacteristic; 193 private BluetoothGattCharacteristic mAccountKeyCharacteristic; 194 private BluetoothGattCharacteristic mDeviceNameCharacteristic; 195 196 /** 197 * GATT server callbacks responsible for servicing read and write calls from the remote device 198 */ 199 private BluetoothGattServerCallback mBluetoothGattServerCallback = 200 new BluetoothGattServerCallback() { 201 @Override 202 public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { 203 super.onConnectionStateChange(device, status, newState); 204 if (DBG) { 205 Slogf.d(TAG, "onConnectionStateChange %d Device: %s", newState, device); 206 } 207 if (newState == BluetoothProfile.STATE_DISCONNECTED) { 208 mPairingPasskey = INVALID; 209 clearSharedSecretKey(); 210 mRemoteGattDevice = null; 211 mRemotePairingDevice = null; 212 mCallbacks.onPairingCompleted(false); 213 } else if (newState == BluetoothProfile.STATE_CONNECTED) { 214 mRemoteGattDevice = device; 215 } 216 } 217 218 @Override 219 public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, 220 BluetoothGattCharacteristic characteristic) { 221 super.onCharacteristicReadRequest(device, requestId, offset, characteristic); 222 if (DBG) { 223 Slogf.d(TAG, "onCharacteristicReadRequest"); 224 } 225 if (characteristic == mModelIdCharacteristic) { 226 if (DBG) { 227 Slogf.d(TAG, "reading model ID"); 228 } 229 } 230 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, 231 characteristic.getValue()); 232 } 233 234 @Override 235 public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, 236 BluetoothGattCharacteristic characteristic, boolean preparedWrite, 237 boolean responseNeeded, 238 int offset, byte[] value) { 239 super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, 240 responseNeeded, offset, value); 241 if (DBG) { 242 Slogf.d(TAG, "onWrite, uuid=%s, length=%d", characteristic.getUuid(), 243 (value != null ? value.length : -1)); 244 } 245 246 if (characteristic == mKeyBasedPairingCharacteristic) { 247 if (DBG) { 248 Slogf.d(TAG, "onWriteKeyBasedPairingCharacteristic"); 249 } 250 byte[] response = processKeyBasedPairing(value); 251 if (response == null) { 252 Slogf.w(TAG, "Could not process key based pairing request. Ignoring."); 253 mBluetoothGattServer 254 .sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, 255 null); 256 return; 257 } 258 mKeyBasedPairingCharacteristic.setValue(response); 259 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 260 offset, response); 261 mBluetoothGattServer 262 .notifyCharacteristicChanged(device, mDeviceNameCharacteristic, false); 263 mBluetoothGattServer 264 .notifyCharacteristicChanged(device, mKeyBasedPairingCharacteristic, false); 265 266 } else if (characteristic == mPasskeyCharacteristic) { 267 if (DBG) { 268 Slogf.d(TAG, "onWritePasskey %s", characteristic.getUuid()); 269 } 270 processPairingKey(value); 271 mBluetoothGattServer 272 .sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null); 273 } else if (characteristic == mAccountKeyCharacteristic) { 274 if (DBG) { 275 Slogf.d(TAG, "onWriteAccountKeyCharacteristic"); 276 } 277 processAccountKey(value); 278 279 mBluetoothGattServer 280 .sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null); 281 } else { 282 Slogf.w(TAG, "onWriteOther %s", characteristic.getUuid()); 283 } 284 } 285 286 @Override 287 public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, 288 BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, 289 int offset, byte[] value) { 290 if (DBG) { 291 Slogf.d(TAG, "onDescriptorWriteRequest"); 292 } 293 mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, 294 descriptor.getValue()); 295 } 296 }; 297 298 /** 299 * Receive incoming pairing requests such that we can confirm Keys match. 300 */ 301 BroadcastReceiver mPairingAttemptsReceiver = new BroadcastReceiver() { 302 @Override 303 public void onReceive(Context context, Intent intent) { 304 String action = intent.getAction(); 305 if (DBG) { 306 Slogf.d(TAG, action); 307 } 308 309 switch (action) { 310 case BluetoothDevice.ACTION_PAIRING_REQUEST: 311 mRemotePairingDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 312 mPairingPasskey = 313 intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, INVALID); 314 if (DBG) { 315 Slogf.d(TAG, "Pairing Request - device=%s, pin_code=%s", 316 mRemotePairingDevice, mPairingPasskey); 317 } 318 sendPairingResponse(mPairingPasskey); 319 // TODO (243578517): Abort the broadcast when everything is valid and we support 320 // automatic acceptance. 321 break; 322 323 case BluetoothDevice.ACTION_BOND_STATE_CHANGED: 324 BluetoothDevice device = 325 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 326 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, INVALID); 327 int previousState = 328 intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, INVALID); 329 330 if (DBG) { 331 Slogf.d(TAG, "Bond State Change - device=%s, old_state=%s, new_state=%s", 332 device, previousState, state); 333 } 334 335 // If the bond state has changed for the device we're current fast pairing with 336 // and it is now bonded, then pairing is complete. Reset the failure count to 0. 337 // Await a potential account key. 338 if (device != null && device.equals(mRemotePairingDevice)) { 339 if (state == BluetoothDevice.BOND_BONDED) { 340 if (DBG) { 341 Slogf.d(TAG, "Pairing complete, device=%s", mRemotePairingDevice); 342 } 343 setSharedSecretKeyLifespan(KEY_LIFESPAN_AWAIT_ACCOUNT_KEY); 344 mRemotePairingDevice = null; 345 mFailureCounter.reset(); 346 } else if (state == BluetoothDevice.BOND_NONE) { 347 if (DBG) { 348 Slogf.d(TAG, "Pairing attempt failed, device=%s", 349 mRemotePairingDevice); 350 } 351 mRemotePairingDevice = null; 352 } 353 } 354 break; 355 356 case BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED: 357 String name = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME); 358 updateLocalName(name); 359 break; 360 361 default: 362 Slogf.w(TAG, "Unknown action. Skipped"); 363 break; 364 } 365 } 366 }; 367 368 /** 369 * FastPairGattServer 370 * @param context user specific context on which to make callse 371 * @param modelId assigned Fast Pair Model ID 372 * @param antiSpoof assigned Fast Pair private Anti Spoof key 373 * @param callbacks callbacks used to report back current pairing status 374 * @param automaticAcceptance automatically accept an incoming pairing request that has been 375 * authenticated through the Fast Pair protocol without further user interaction. 376 */ FastPairGattServer(Context context, int modelId, String antiSpoof, Callbacks callbacks, boolean automaticAcceptance, FastPairAccountKeyStorage fastPairAccountKeyStorage)377 FastPairGattServer(Context context, int modelId, String antiSpoof, 378 Callbacks callbacks, boolean automaticAcceptance, 379 FastPairAccountKeyStorage fastPairAccountKeyStorage) { 380 mContext = Objects.requireNonNull(context); 381 mFastPairAccountKeyStorage = Objects.requireNonNull(fastPairAccountKeyStorage); 382 mCallbacks = Objects.requireNonNull(callbacks); 383 mPrivateAntiSpoof = antiSpoof; 384 mAutomaticPasskeyConfirmation = automaticAcceptance; 385 mBluetoothManager = context.getSystemService(BluetoothManager.class); 386 mBluetoothAdapter = mBluetoothManager.getAdapter(); 387 ByteBuffer modelIdBytes = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt( 388 modelId); 389 mModelId = Arrays.copyOfRange(modelIdBytes.array(), 0, 3); 390 setup(); 391 } 392 393 /** 394 * Initialize all of the GATT characteristics with appropriate default values and the required 395 * configurations. 396 */ setup()397 private void setup() { 398 mModelIdCharacteristic = new BluetoothGattCharacteristic(FAST_PAIR_MODEL_ID_UUID.getUuid(), 399 BluetoothGattCharacteristic.PROPERTY_READ, 400 BluetoothGattCharacteristic.PERMISSION_READ); 401 mModelIdCharacteristic.setValue(mModelId); 402 mFastPairService.addCharacteristic(mModelIdCharacteristic); 403 404 mKeyBasedPairingCharacteristic = 405 new BluetoothGattCharacteristic(KEY_BASED_PAIRING_UUID.getUuid(), 406 BluetoothGattCharacteristic.PROPERTY_WRITE 407 | BluetoothGattCharacteristic.PROPERTY_NOTIFY, 408 BluetoothGattCharacteristic.PERMISSION_WRITE); 409 mKeyBasedPairingCharacteristic.setValue(mModelId); 410 mKeyBasedPairingCharacteristic.addDescriptor(new BluetoothGattDescriptor( 411 CLIENT_CHARACTERISTIC_CONFIG.getUuid(), 412 BluetoothGattDescriptor.PERMISSION_READ 413 | BluetoothGattDescriptor.PERMISSION_WRITE)); 414 mFastPairService.addCharacteristic(mKeyBasedPairingCharacteristic); 415 416 mPasskeyCharacteristic = 417 new BluetoothGattCharacteristic(PASSKEY_UUID.getUuid(), 418 BluetoothGattCharacteristic.PROPERTY_WRITE 419 | BluetoothGattCharacteristic.PROPERTY_NOTIFY, 420 BluetoothGattCharacteristic.PERMISSION_WRITE); 421 mPasskeyCharacteristic.setValue(mModelId); 422 mPasskeyCharacteristic.addDescriptor(new BluetoothGattDescriptor( 423 CLIENT_CHARACTERISTIC_CONFIG.getUuid(), 424 BluetoothGattDescriptor.PERMISSION_READ 425 | BluetoothGattDescriptor.PERMISSION_WRITE)); 426 427 mFastPairService.addCharacteristic(mPasskeyCharacteristic); 428 429 mAccountKeyCharacteristic = 430 new BluetoothGattCharacteristic(ACCOUNT_KEY_UUID.getUuid(), 431 BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE, 432 BluetoothGattCharacteristic.PERMISSION_WRITE); 433 mFastPairService.addCharacteristic(mAccountKeyCharacteristic); 434 435 mDeviceNameCharacteristic = 436 new BluetoothGattCharacteristic(DEVICE_NAME_CHARACTERISTIC_CONFIG.getUuid(), 437 BluetoothGattCharacteristic.PROPERTY_READ, 438 BluetoothGattCharacteristic.PERMISSION_READ); 439 String name = mBluetoothAdapter.getName(); 440 if (name == null) { 441 name = ""; 442 } 443 mDeviceNameCharacteristic.setValue(name); 444 mFastPairService.addCharacteristic(mDeviceNameCharacteristic); 445 } 446 updateLocalName(String name)447 void updateLocalName(String name) { 448 Slogf.d(TAG, "Device name changed to '%s'", name); 449 if (name != null) { 450 mDeviceNameCharacteristic.setValue(name); 451 } 452 } 453 454 /** 455 * Start the FastPairGattServer 456 * 457 * This makes the underlying service and characteristics available and registers us for events. 458 */ start()459 public boolean start() { 460 if (isStarted()) { 461 return false; 462 } 463 464 // Setup filter to receive pairing attempts and passkey. Make this a high priority broadcast 465 // receiver so others can't intercept it before we can handle it. 466 IntentFilter filter = new IntentFilter(); 467 filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST); 468 filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); 469 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 470 filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); 471 mContext.registerReceiver(mPairingAttemptsReceiver, filter); 472 473 mBluetoothGattServer = mBluetoothManager 474 .openGattServer(mContext, mBluetoothGattServerCallback); 475 476 if (mBluetoothGattServer == null) { 477 Slogf.e(TAG, "Start failed, could not get a GATT server."); 478 mContext.unregisterReceiver(mPairingAttemptsReceiver); 479 return false; 480 } 481 482 mBluetoothGattServer.addService(mFastPairService); 483 return true; 484 } 485 486 /** 487 * Stop the FastPairGattServer 488 * 489 * This removes our underlying service and clears our state. 490 */ stop()491 public boolean stop() { 492 if (!isStarted()) { 493 return true; 494 } 495 496 clearSharedSecretKey(); 497 498 if (isConnected()) { 499 mBluetoothGattServer.cancelConnection(mRemoteGattDevice); 500 mRemoteGattDevice = null; 501 mCallbacks.onPairingCompleted(false); 502 } 503 mPairingPasskey = -1; 504 mSharedSecretKey = null; 505 mBluetoothGattServer.removeService(mFastPairService); 506 mContext.unregisterReceiver(mPairingAttemptsReceiver); 507 return true; 508 } 509 510 /** 511 * Check if this service is started 512 */ isStarted()513 public boolean isStarted() { 514 return (mBluetoothGattServer == null) 515 ? false 516 : mBluetoothGattServer.getService(FAST_PAIR_SERVICE_UUID.getUuid()) != null; 517 } 518 519 /** 520 * Check if a client is connected to this GATT server 521 * @return true if connected; 522 */ isConnected()523 public boolean isConnected() { 524 if (DBG) { 525 Slogf.d(TAG, "isConnected() -> %s", (mRemoteGattDevice != null)); 526 } 527 return (mRemoteGattDevice != null); 528 } 529 setSharedSecretKey(SecretKeySpec key, int lifespan)530 private void setSharedSecretKey(SecretKeySpec key, int lifespan) { 531 if (key == null) { 532 Slogf.w(TAG, "Cannot set a null shared secret."); 533 return; 534 } 535 Slogf.i(TAG, "Shared secret key set, key=%s lifespan=%d", key, lifespan); 536 mSharedSecretKey = key; 537 setSharedSecretKeyLifespan(lifespan); 538 } 539 setSharedSecretKeyLifespan(int lifespan)540 private void setSharedSecretKeyLifespan(int lifespan) { 541 if (mSharedSecretKey == null) { 542 Slogf.w(TAG, "Ignoring lifespan on null key"); 543 return; 544 } 545 if (DBG) { 546 Slogf.d(TAG, "Update key lifespan to %d", lifespan); 547 } 548 mHandler.removeCallbacks(mClearSharedSecretKey); 549 if (lifespan > 0) { 550 mHandler.postDelayed(mClearSharedSecretKey, lifespan); 551 } 552 } 553 clearSharedSecretKey()554 private void clearSharedSecretKey() { 555 Slogf.i(TAG, "Shared secret key has been cleared"); 556 mHandler.removeCallbacks(mClearSharedSecretKey); 557 mSharedSecretKey = null; 558 } 559 isFastPairSessionActive()560 public boolean isFastPairSessionActive() { 561 return mSharedSecretKey != null; 562 } 563 564 /** 565 * Attempt to encrypt the provided data with the provided key 566 * 567 * @param data data to be encrypted 568 * @param secretKeySpec key to ecrypt the data with 569 * @return encrypted data upon success; null otherwise 570 */ encrypt(byte[] data, SecretKeySpec secretKeySpec)571 private byte[] encrypt(byte[] data, SecretKeySpec secretKeySpec) { 572 if (secretKeySpec == null) { 573 Slogf.e(TAG, "Encryption failed: no key"); 574 return null; 575 } 576 try { 577 Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); 578 cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); 579 return cipher.doFinal(data); 580 581 } catch (Exception e) { 582 Slogf.e(TAG, "Encryption failed: %s", e); 583 } 584 return null; 585 } 586 /** 587 * Attempt to decrypt the provided data with the provided key 588 * 589 * @param encryptedData data to be decrypted 590 * @param secretKeySpec key to decrypt the data with 591 * @return decrypted data upon success; null otherwise 592 */ decrypt(byte[] encryptedData, SecretKeySpec secretKeySpec)593 private byte[] decrypt(byte[] encryptedData, SecretKeySpec secretKeySpec) { 594 if (secretKeySpec == null) { 595 Slogf.e(TAG, "Decryption failed: no key"); 596 return null; 597 } 598 try { 599 Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); 600 cipher.init(Cipher.DECRYPT_MODE, secretKeySpec); 601 return cipher.doFinal(encryptedData); 602 603 } catch (Exception e) { 604 Slogf.e(TAG, "Decryption Failed: %s", e); 605 } 606 return null; 607 } 608 609 /** 610 * Determine if this pairing request is based on the anti-spoof keys associated with the model 611 * id or stored account keys. 612 * 613 * @param accountKey 614 * @return 615 */ processKeyBasedPairing(byte[] pairingRequest)616 private byte[] processKeyBasedPairing(byte[] pairingRequest) { 617 if (mFailureCounter.hasExceededLimit()) { 618 Slogf.w(TAG, "Failure count has exceeded 10. Ignoring Key-Based Pairing requests"); 619 return null; 620 } 621 622 if (pairingRequest == null) { 623 Slogf.w(TAG, "Received a null pairing request"); 624 mFailureCounter.increment(); 625 clearSharedSecretKey(); 626 return null; 627 } 628 629 List<SecretKeySpec> possibleKeys = new ArrayList<>(); 630 if (pairingRequest.length == 80) { 631 if (DBG) { 632 Slogf.d(TAG, "Use Anti-spoofing key"); 633 } 634 // if the pairingRequest is 80 bytes long try the anit-spoof key 635 final byte[] remotePublicKey = Arrays.copyOfRange(pairingRequest, 16, 80); 636 637 possibleKeys 638 .add(calculateAntiSpoofing(Base64.decode(mPrivateAntiSpoof, 0), remotePublicKey) 639 .getKeySpec()); 640 } else if (pairingRequest.length == 16) { 641 if (DBG) { 642 Slogf.d(TAG, "Use stored account keys"); 643 } 644 // otherwise the pairing request is the encrypted request, try all the stored account 645 // keys 646 List<AccountKey> storedAccountKeys = mFastPairAccountKeyStorage.getAllAccountKeys(); 647 for (AccountKey key : storedAccountKeys) { 648 possibleKeys.add(new SecretKeySpec(key.toBytes(), "AES")); 649 } 650 } else { 651 Slogf.w(TAG, "Received key based pairing request of invalid length %d", 652 pairingRequest.length); 653 mFailureCounter.increment(); 654 clearSharedSecretKey(); 655 return null; 656 } 657 658 byte[] encryptedRequest = Arrays.copyOfRange(pairingRequest, 0, 16); 659 if (DBG) { 660 Slogf.d(TAG, "Checking %d Keys", possibleKeys.size()); 661 } 662 // check all the keys for a valid pairing request 663 for (SecretKeySpec key : possibleKeys) { 664 if (DBG) { 665 Slogf.d(TAG, "Checking possible key"); 666 } 667 if (validateRequestAgainstKey(encryptedRequest, key)) { 668 // If the key was able to decrypt the request and the addresses match then set it as 669 // the shared secret and set a lifespan timeout 670 setSharedSecretKey(key, KEY_LIFESPAN_AWAIT_PAIRING); 671 672 // Use the key to craft encrypted response to the seeker with the local public 673 // address and salt. If encryption goes wrong, move on to the next key 674 String localAddress = mBluetoothAdapter.getAddress(); 675 byte[] localAddressBytes = BluetoothUtils.getBytesFromAddress(localAddress); 676 byte[] rawResponse = new byte[16]; 677 new Random().nextBytes(rawResponse); 678 rawResponse[0] = 0x01; 679 System.arraycopy(localAddressBytes, 0, rawResponse, 1, 6); 680 byte[] response = encrypt(rawResponse, key); 681 if (response == null) { 682 clearSharedSecretKey(); 683 return null; 684 } 685 return response; 686 } 687 } 688 Slogf.w(TAG, "No matching key found"); 689 mFailureCounter.increment(); 690 clearSharedSecretKey(); 691 return null; 692 } 693 694 /** 695 * New pairings based upon model ID requires the Fast Pair provider to authenticate to that the 696 * seeker it is in possession of the private key associated with the model ID advertised. This 697 * is accomplished via Eliptic-curve Diffie-Hellman 698 * 699 * @param localPrivateKey 700 * @param remotePublicKey 701 * @return 702 */ calculateAntiSpoofing(byte[] localPrivateKey, byte[] remotePublicKey)703 private AccountKey calculateAntiSpoofing(byte[] localPrivateKey, byte[] remotePublicKey) { 704 try { 705 if (DBG) { 706 Slogf.d(TAG, "Calculating secret key from remote public key"); 707 } 708 // Initialize the EC key generator 709 KeyFactory keyFactory = KeyFactory.getInstance("EC"); 710 KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); 711 ECParameterSpec ecParameterSpec = ((ECPublicKey) kpg.generateKeyPair().getPublic()) 712 .getParams(); 713 // Use the private anti-spoofing key 714 ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec( 715 new BigInteger(1, localPrivateKey), 716 ecParameterSpec); 717 // Calculate the public point utilizing the data received from the remote device 718 ECPoint publicPoint = new ECPoint(new BigInteger(1, Arrays.copyOf(remotePublicKey, 32)), 719 new BigInteger(1, Arrays.copyOfRange(remotePublicKey, 32, 64))); 720 ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(publicPoint, ecParameterSpec); 721 PrivateKey privateKey = keyFactory.generatePrivate(ecPrivateKeySpec); 722 PublicKey publicKey = keyFactory.generatePublic(ecPublicKeySpec); 723 724 // Generate a shared secret 725 KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH"); 726 keyAgreement.init(privateKey); 727 keyAgreement.doPhase(publicKey, true); 728 byte[] sharedSecret = keyAgreement.generateSecret(); 729 730 // Use the first 16 bytes of a hash of the shared secret as the session key 731 final byte[] digest = MessageDigest.getInstance("SHA-256").digest(sharedSecret); 732 733 byte[] AESAntiSpoofingKey = Arrays.copyOf(digest, 16); 734 if (DBG) { 735 Slogf.d(TAG, "Key calculated"); 736 } 737 return new AccountKey(AESAntiSpoofingKey); 738 } catch (Exception e) { 739 Slogf.w(TAG, "Error calculating anti-spoofing key: %s", e); 740 return null; 741 } 742 } 743 744 /** 745 * Check if the given key can be used to decrypt the pairing request and prove the request is 746 * valid. 747 * 748 * A request is valid if its decrypted value is of type 0x00 or 0x10 and it contains either the 749 * seekers public or current BLE address. If a key successfully decrypts and validates a request 750 * then that is the key we should use as our shared secret key. 751 * 752 * @param encryptedRequest the request to decrypt and validate 753 * @param secretKeySpec the key to use while attempting to decrypt the request 754 * @return true if the key matches, false otherwise 755 */ validateRequestAgainstKey(byte[] encryptedRequest, SecretKeySpec secretKeySpec)756 private boolean validateRequestAgainstKey(byte[] encryptedRequest, 757 SecretKeySpec secretKeySpec) { 758 // Decrypt the request 759 byte[] decryptedRequest = decrypt(encryptedRequest, secretKeySpec); 760 if (decryptedRequest == null) { 761 return false; 762 } 763 764 if (DBG) { 765 StringBuilder sb = new StringBuilder(); 766 for (byte b : decryptedRequest) { 767 sb.append(String.format("%02X ", b)); 768 } 769 Slogf.d(TAG, "Decrypted Request=[ %s]", sb.toString()); 770 } 771 // Check that the request is either a Key-based Pairing Request or an Action Request 772 if (decryptedRequest[0] == 0x00 || decryptedRequest[0] == 0x10) { 773 String localAddress = mBluetoothAdapter.getAddress(); 774 byte[] localAddressBytes = BluetoothUtils.getBytesFromAddress(localAddress); 775 // Extract the remote address bytes from the message 776 byte[] remoteAddressBytes = Arrays.copyOfRange(decryptedRequest, 2, 8); 777 BluetoothDevice localDevice = mBluetoothAdapter.getRemoteDevice(localAddress); 778 BluetoothDevice reportedDevice = mBluetoothAdapter.getRemoteDevice(remoteAddressBytes); 779 if (DBG) { 780 Slogf.d(TAG, "rpa=%s, public=%s, reported=%s", mLocalRpaDevice, localAddress, 781 reportedDevice); 782 } 783 if (mLocalRpaDevice == null) { 784 Slogf.w(TAG, "Cannot get own address"); 785 } 786 // Test that the received device address matches this devices address 787 if (reportedDevice.equals(localDevice) || reportedDevice.equals(mLocalRpaDevice)) { 788 if (DBG) { 789 Slogf.d(TAG, "SecretKey Validated"); 790 } 791 return encryptedRequest != null; 792 } 793 } 794 return false; 795 } 796 797 /** 798 * Extract the 6 digit Bluetooth Simple Secure Passkey from the received message and confirm 799 * it matches the key received through the Bluetooth pairing procedure. 800 * 801 * If the passkeys match and automatic passkey confirmation is enabled, approve of the pairing. 802 * If the passkeys do not match reject the pairing and invalidate our key material. 803 * 804 * @param pairingKey 805 * @return true if the procedure completed, although pairing may not have been approved 806 */ processPairingKey(byte[] pairingKey)807 private boolean processPairingKey(byte[] pairingKey) { 808 if (pairingKey == null || pairingKey.length != 16) { 809 clearSharedSecretKey(); 810 return false; 811 } 812 813 byte[] decryptedRequest = decrypt(pairingKey, mSharedSecretKey); 814 if (decryptedRequest == null) { 815 clearSharedSecretKey(); 816 return false; 817 } 818 int passkey = Byte.toUnsignedInt(decryptedRequest[1]) * 65536 819 + Byte.toUnsignedInt(decryptedRequest[2]) * 256 820 + Byte.toUnsignedInt(decryptedRequest[3]); 821 822 if (DBG) { 823 Slogf.d(TAG, "Received passkey request, type=%s, passkey=%d, our_passkey=%d", 824 decryptedRequest[0], passkey, mPairingPasskey); 825 } 826 // compare the Bluetooth received passkey with the Fast Pair received passkey 827 if (mPairingPasskey == passkey) { 828 if (DBG) { 829 Slogf.d(TAG, "Passkeys match, auto_accept=%s", mAutomaticPasskeyConfirmation); 830 } 831 if (mAutomaticPasskeyConfirmation) { 832 mRemotePairingDevice.setPairingConfirmation(true); 833 } 834 } else if (mPairingPasskey != INVALID) { 835 Slogf.w(TAG, "Passkeys don't match, rejecting"); 836 mRemotePairingDevice.setPairingConfirmation(false); 837 clearSharedSecretKey(); 838 } 839 return true; 840 } 841 842 /** 843 * Send the seeker the pin code we received so they can validate it. Encrypt it with our shared 844 * secret. 845 * 846 * @param passkey the key-based pairing passkey, as described by the core BT specification 847 */ sendPairingResponse(int passkey)848 private void sendPairingResponse(int passkey) { 849 if (!isConnected()) return; 850 if (DBG) { 851 Slogf.d(TAG, "sendPairingResponse %d", passkey); 852 } 853 854 // Once pairing begins, we can hold on to the shared secret key until pairing 855 // completes 856 setSharedSecretKeyLifespan(KEY_LIFESPAN_PAIRING); 857 858 // Send an encrypted response to the seeker with the Bluetooth passkey as required 859 byte[] decryptedResponse = new byte[16]; 860 new Random().nextBytes(decryptedResponse); 861 ByteBuffer pairingPasskeyBytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt( 862 passkey); 863 decryptedResponse[0] = 0x3; 864 decryptedResponse[1] = pairingPasskeyBytes.get(1); 865 decryptedResponse[2] = pairingPasskeyBytes.get(2); 866 decryptedResponse[3] = pairingPasskeyBytes.get(3); 867 868 byte[] response = encrypt(decryptedResponse, mSharedSecretKey); 869 if (response == null) { 870 clearSharedSecretKey(); 871 return; 872 } 873 mPasskeyCharacteristic.setValue(response); 874 mBluetoothGattServer 875 .notifyCharacteristicChanged(mRemoteGattDevice, mPasskeyCharacteristic, false); 876 } 877 878 /** 879 * The final step of the Fast Pair procedure involves receiving an account key from the 880 * Fast Pair seeker, authenticating it, and then storing it for future use. Only one attempt 881 * at writing this key is allowed by the spec. Discard the shared secret after this one attempt. 882 * 883 * @param accountKey the account key, encrypted with our sharded secret 884 */ processAccountKey(byte[] accountKey)885 private void processAccountKey(byte[] accountKey) { 886 if (accountKey == null || accountKey.length != 16) { 887 clearSharedSecretKey(); 888 return; 889 } 890 891 byte[] decodedAccountKey = decrypt(accountKey, mSharedSecretKey); 892 if (decodedAccountKey != null && decodedAccountKey[0] == 0x04) { 893 AccountKey receivedKey = new AccountKey(decodedAccountKey); 894 if (DBG) { 895 Slogf.d(TAG, "Received Account Key, key=%s", receivedKey); 896 } 897 mFastPairAccountKeyStorage.add(receivedKey); 898 } else { 899 if (DBG) { 900 Slogf.d(TAG, "Received invalid Account Key"); 901 } 902 } 903 904 // Always clear the shared secret key following any attempt to write an account key 905 clearSharedSecretKey(); 906 } 907 908 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)909 void dump(IndentingPrintWriter writer) { 910 writer.println("FastPairGattServer:"); 911 writer.increaseIndent(); 912 writer.println("Started : " + isStarted()); 913 writer.println("Active : " + isFastPairSessionActive()); 914 writer.println("Currently connected to : " + mRemoteGattDevice); 915 writer.println("Failsure counter : " + mFailureCounter); 916 writer.decreaseIndent(); 917 } 918 } 919