• 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 = 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