2016-06-27 29 views
22

Qualcuno ha un esempio operativo completo di come associare a livello di codice un dispositivo BLE (non Bluetooth Classic) che utilizza la passkey entry (ovvero un PIN a 6 cifre) o Numeric Confronto su Android 4.4 o successivo? Con "programmaticamente" intendo che dico a Android il PIN: l'utente non viene richiesto.Abbinamento a livello di codice con un dispositivo BLE su Android 4.4+

ci sono molte domande simili su questo su SO, ma sono sia a) su Bluetooth Classic, b) vecchio (prima setPin() e createBond() erano pubblico), oppure c) senza risposta.

La mia comprensione è la seguente.

  1. È possibile connettersi al dispositivo e scoprirne i servizi.
  2. Si tenta di leggere una caratteristica 'protetta'.
  3. Il dispositivo restituisce un errore di autenticazione.
  4. Android avvia in qualche modo l'accoppiamento e gli dici il PIN.
  5. Ora è possibile leggere la caratteristica.

Ho creato un dispositivo utilizzando mBed in esecuzione su nRF51-DK e gli ho assegnato una singola caratteristica.

ho istituito i parametri di sicurezza in questo modo:

ble.securityManager().init(
    true, // Enable bonding (though I don't really need this) 
    true, // Require MitM protection. I assume you don't get a PIN prompt without this, though I'm not 100% sure. 
    SecurityManager::IO_CAPS_DISPLAY_ONLY, // This makes it us the Passkey Entry (PIN) pairing method. 
    "123456"); // Static PIN 

E poi nel caratteristico ho usato

requireSecurity(SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM); 

Ora, quando provo a leggere con il Nordic Master Control Panel, ottengo una richiesta di accoppiamento notifica in questo modo:

pairing request

passkey entry

E posso inserire questo PIN, e poi MCP dice che sono incollato e posso leggere la caratteristica.

Tuttavia, nella mia app vorrei evitare che l'utente inserisca il PIN, poiché lo conosco già. Qualcuno ha un esempio recente completo di come farlo?

Modifica: A proposito, this è la domanda più pertinente che ho trovato su SO, ma la risposta non sembra funzionare.

+0

L'SDK di Android consente l'accoppiamento senza che all'utente venga notificato/chiesto conferma? Sembra che tu non sia in grado di ... –

+1

Sì, sì. Ha un metodo 'setPin()' appositamente per questo, e ho avuto modo di lavorare, tranne la notifica "Pairing request" è ancora mostrata. – Timmmm

+0

Non sono sicuro se questo aiuto è andato, ma vale la pena leggerlo http://stackoverflow.com/questions/17971834/android-prevent-bluetooth-pairing-dialog –

risposta

17

I quasi farlo funzionare. Si associa a livello di codice ma non riesco a sbarazzarmi della notifica "Richiesta di abbinamento". Alcune risposte a questa domanda sostengono di essere in grado di nasconderle subito dopo la sua visualizzazione usando il metodo nascosto cancelPairingUserInput() ma non sembra funzionare per me.

Modifica: Successo!

Alla fine ho fatto ricorso alla lettura del codice sorgente di BluetoothPairingRequest e the code that sends the pairing request broadcast e ho realizzato che dovevo intercettare lo ACTION_PAIRING_REQUEST. Fortunatamente è un broadcast ordinato per cui è possibile intercettarlo prima che il sistema lo faccia.

Ecco la procedura.

  1. Registrazione per ricevere BluetoothDevice.ACTION_PAIRING_REQUEST modifiche agli intenti di trasmissione. Utilizzare un'alta priorità!
  2. Connessione al dispositivo.
  3. Scopri servizi.
  4. Se ci si è disconnessi, è probabilmente perché le informazioni di legame non sono corrette (ad esempio, la periferica è stata eliminata). In tal caso, elimina le informazioni sulle obbligazioni utilizzando un metodo nascosto (sul serio Google) e riconnettiti.
  5. Provare a leggere una caratteristica che richiede la protezione MitM di crittografia.
  6. Nel ricevitore di trasmissione ACTION_PAIRING_REQUEST, verificare che il tipo di associazione sia BluetoothDevice.PAIRING_VARIANT_PIN e, in tal caso, chiamare setPin() e abortBroadcast(). Altrimenti puoi semplicemente lasciare che sia il sistema a gestirlo, o mostrare un errore o altro.

Questo è il codice.

/* This implements the BLE connection logic. Things to watch out for: 

1. If the bond information is wrong (e.g. it has been deleted on the peripheral) then 
    discoverServices() will cause a disconnect. You need to delete the bonding information and reconnect. 

2. If the user ignores the PIN request, you get the undocumented GATT_AUTH_FAILED code. 

*/ 
public class ConnectActivityLogic extends Fragment 
{ 
    // The connection to the device, if we are connected. 
    private BluetoothGatt mGatt; 

    // This is used to allow GUI fragments to subscribe to state change notifications. 
    public static class StateObservable extends Observable 
    { 
     private void notifyChanged() { 
      setChanged(); 
      notifyObservers(); 
     } 
    }; 

    // When the logic state changes, State.notifyObservers(this) is called. 
    public final StateObservable State = new StateObservable(); 

    public ConnectActivityLogic() 
    { 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState) 
    { 
     super.onCreate(savedInstanceState); 

     // Tell the framework to try to keep this fragment around 
     // during a configuration change. 
     setRetainInstance(true); 

     // Actually set it in response to ACTION_PAIRING_REQUEST. 
     final IntentFilter pairingRequestFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); 
     pairingRequestFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 1); 
     getActivity().getApplicationContext().registerReceiver(mPairingRequestRecevier, pairingRequestFilter); 

     // Update the UI. 
     State.notifyChanged(); 

     // Note that we don't actually need to request permission - all apps get BLUETOOTH and BLUETOOTH_ADMIN permissions. 
     // LOCATION_COARSE is only used for scanning which I don't need (MAC is hard-coded). 

     // Connect to the device. 
     connectGatt(); 
    } 

    @Override 
    public void onDestroy() 
    { 
     super.onDestroy(); 

     // Disconnect from the device if we're still connected. 
     disconnectGatt(); 

     // Unregister the broadcast receiver. 
     getActivity().getApplicationContext().unregisterReceiver(mPairingRequestRecevier); 
    } 

    // The state used by the UI to show connection progress. 
    public ConnectionState getConnectionState() 
    { 
     return mState; 
    } 

    // Internal state machine. 
    public enum ConnectionState 
    { 
     IDLE, 
     CONNECT_GATT, 
     DISCOVER_SERVICES, 
     READ_CHARACTERISTIC, 
     FAILED, 
     SUCCEEDED, 
    } 
    private ConnectionState mState = ConnectionState.IDLE; 

    // When this fragment is created it is given the MAC address and PIN to connect to. 
    public byte[] macAddress() 
    { 
     return getArguments().getByteArray("mac"); 
    } 
    public int pinCode() 
    { 
     return getArguments().getInt("pin", -1); 
    } 

    // Start the connection process. 
    private void connectGatt() 
    { 
     // Disconnect if we are already connected. 
     disconnectGatt(); 

     // Update state. 
     mState = ConnectionState.CONNECT_GATT; 
     State.notifyChanged(); 

     BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress()); 

     // Connect! 
     mGatt = device.connectGatt(getActivity(), false, mBleCallback); 
    } 

    private void disconnectGatt() 
    { 
     if (mGatt != null) 
     { 
      mGatt.disconnect(); 
      mGatt.close(); 
      mGatt = null; 
     } 
    } 

    // See https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/master/stack/include/gatt_api.h 
    private static final int GATT_ERROR = 0x85; 
    private static final int GATT_AUTH_FAIL = 0x89; 

    private android.bluetooth.BluetoothGattCallback mBleCallback = new BluetoothGattCallback() 
    { 
     @Override 
     public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) 
     { 
      super.onConnectionStateChange(gatt, status, newState); 
      switch (newState) 
      { 
      case BluetoothProfile.STATE_CONNECTED: 
       // Connected to the device. Try to discover services. 
       if (gatt.discoverServices()) 
       { 
        // Update state. 
        mState = ConnectionState.DISCOVER_SERVICES; 
        State.notifyChanged(); 
       } 
       else 
       { 
        // Couldn't discover services for some reason. Fail. 
        disconnectGatt(); 
        mState = ConnectionState.FAILED; 
        State.notifyChanged(); 
       } 
       break; 
      case BluetoothProfile.STATE_DISCONNECTED: 
       // If we try to discover services while bonded it seems to disconnect. 
       // We need to debond and rebond... 

       switch (mState) 
       { 
        case IDLE: 
         // Do nothing in this case. 
         break; 
        case CONNECT_GATT: 
         // This can happen if the bond information is incorrect. Delete it and reconnect. 
         deleteBondInformation(gatt.getDevice()); 
         connectGatt(); 
         break; 
        case DISCOVER_SERVICES: 
         // This can also happen if the bond information is incorrect. Delete it and reconnect. 
         deleteBondInformation(gatt.getDevice()); 
         connectGatt(); 
         break; 
        case READ_CHARACTERISTIC: 
         // Disconnected while reading the characteristic. Probably just a link failure. 
         gatt.close(); 
         mState = ConnectionState.FAILED; 
         State.notifyChanged(); 
         break; 
        case FAILED: 
        case SUCCEEDED: 
         // Normal disconnection. 
         break; 
       } 
       break; 
      } 
     } 

     @Override 
     public void onServicesDiscovered(BluetoothGatt gatt, int status) 
     { 
      super.onServicesDiscovered(gatt, status); 

      // Services have been discovered. Now I try to read a characteristic that requires MitM protection. 
      // This triggers pairing and bonding. 

      BluetoothGattService nameService = gatt.getService(UUIDs.NAME_SERVICE); 
      if (nameService == null) 
      { 
       // Service not found. 
       disconnectGatt(); 
       mState = ConnectionState.FAILED; 
       State.notifyChanged(); 
       return; 
      } 
      BluetoothGattCharacteristic characteristic = nameService.getCharacteristic(UUIDs.NAME_CHARACTERISTIC); 
      if (characteristic == null) 
      { 
       // Characteristic not found. 
       disconnectGatt(); 
       mState = ConnectionState.FAILED; 
       State.notifyChanged(); 
       return; 
      } 

      // Read the characteristic. 
      gatt.readCharacteristic(characteristic); 
      mState = ConnectionState.READ_CHARACTERISTIC; 
      State.notifyChanged(); 
     } 

     @Override 
     public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 
     { 
      super.onCharacteristicRead(gatt, characteristic, status); 

      if (status == BluetoothGatt.GATT_SUCCESS) 
      { 
       // Characteristic read. Check it is the right one. 
       if (!UUIDs.NAME_CHARACTERISTIC.equals(characteristic.getUuid())) 
       { 
        // Read the wrong characteristic. This shouldn't happen. 
        disconnectGatt(); 
        mState = ConnectionState.FAILED; 
        State.notifyChanged(); 
        return; 
       } 

       // Get the name (the characteristic I am reading just contains the device name). 
       byte[] value = characteristic.getValue(); 
       if (value == null) 
       { 
        // Hmm... 
       } 

       disconnectGatt(); 
       mState = ConnectionState.SUCCEEDED; 
       State.notifyChanged(); 

       // Success! Save it to the database or whatever... 
      } 
      else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) 
      { 
       // This is where the tricky part comes 
       if (gatt.getDevice().getBondState() == BluetoothDevice.BOND_NONE) 
       { 
        // Bonding required. 
        // The broadcast receiver should be called. 
       } 
       else 
       { 
        // ? 
       } 
      } 
      else if (status == GATT_AUTH_FAIL) 
      { 
       // This can happen because the user ignored the pairing request notification for too long. 
       // Or presumably if they put the wrong PIN in. 
       disconnectGatt(); 
       mState = ConnectionState.FAILED; 
       State.notifyChanged(); 
      } 
      else if (status == GATT_ERROR) 
      { 
       // I thought this happened if the bond information was wrong, but now I'm not sure. 
       disconnectGatt(); 
       mState = ConnectionState.FAILED; 
       State.notifyChanged(); 
      } 
      else 
      { 
       // That's weird. 
       disconnectGatt(); 
       mState = ConnectionState.FAILED; 
       State.notifyChanged(); 
      } 
     } 
    }; 


    private final BroadcastReceiver mPairingRequestRecevier = new BroadcastReceiver() 
    { 
     @Override 
     public void onReceive(Context context, Intent intent) 
     { 
      if (BluetoothDevice.ACTION_PAIRING_REQUEST.equals(intent.getAction())) 
      { 
       final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 
       int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR); 

       if (type == BluetoothDevice.PAIRING_VARIANT_PIN) 
       { 
        device.setPin(Util.IntToPasskey(pinCode())); 
        abortBroadcast(); 
       } 
       else 
       { 
        L.w("Unexpected pairing type: " + type); 
       } 
      } 
     } 
    }; 

    public static void deleteBondInformation(BluetoothDevice device) 
    { 
     try 
     { 
      // FFS Google, just unhide the method. 
      Method m = device.getClass().getMethod("removeBond", (Class[]) null); 
      m.invoke(device, (Object[]) null); 
     } 
     catch (Exception e) 
     { 
      L.e(e.getMessage()); 
     } 
    } 
} 
+0

Ciao! Puoi dire da dove provengono i requisiti di protezione MITM: "Prova a leggere una caratteristica che richiede la protezione MitM di crittografia" È una specifica del profilo GATT, il telefono, il tuo prodotto? –

+0

È impostato sul prodotto. Ogni caratteristica GATT può essere configurata come uno di [questi valori] (https://github.com/ARMmbed/ble/blob/master/ble/SecurityManager.h#L27). Non sono esattamente sicuro di come ciò corrisponda a [Bluetooth Core Spec] (https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=286439&_ga=1.199106534.853042725.1464016791) ma ci sono alcune informazioni in Vol 3 Parte G Sezione 8 e Vol 3 Parte C Sezione 5.2.2. – Timmmm

+0

OK, lo vedo dal codice ora. Hai davvero bisogno della protezione MITM? Se il PIN è hardcoded, la protezione non è molto forte. Disabilitare la protezione MITM dovrebbe comportare l'accoppiamento Just Works, e quindi non dovrebbe esserci alcuna finestra di dialogo PIN. –