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