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