Search code examples
pythonandroidbluetoothbluez

Trying to run a linux system as a headless bluetooth server that asks the connecting phone or tablet for a pin


I'm working with a linux computer that will have no way to interface with it when it is deployed.

The only (end user friendly) access point it will have to start off is a bluetooth access point.

Bluetooth communication has been succesfully established, and a phone can connect with the controller without having to do any input on the controller.

The problem is that it isn't safe in this situation, anything can now freely pair and connect.

I would like the controller to request a controller unique pin from the connecting device so only people with access to that pin can connect with the controller.

I'm using python to manage the agent that handles the pairing process, and I'm currently using this code:

import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib

BUS_NAME = 'org.bluez'
ADAPTER_IFACE = 'org.bluez.Adapter1'
ADAPTER_ROOT = '/org/bluez/hci'
AGENT_IFACE = 'org.bluez.Agent1'
AGNT_MNGR_IFACE = 'org.bluez.AgentManager1'
AGENT_PATH = '/my/app/agent'
AGNT_MNGR_PATH = '/org/bluez'
CAPABILITY = 'KeyboardDisplay'
DEVICE_IFACE = 'org.bluez.Device1'
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()

def set_trusted(path):
    props = dbus.Interface(bus.get_object(BUS_NAME, path), dbus.PROPERTIES_IFACE)
    props.Set(DEVICE_IFACE, "Trusted", True)

class Agent(dbus.service.Object):

    @dbus.service.method(AGENT_IFACE,
                         in_signature="", out_signature="")
    def Release(self):
        print("Release")

    @dbus.service.method(AGENT_IFACE,
                         in_signature='o', out_signature='s')
    def RequestPinCode(self, device):
        print(f'RequestPinCode {device}')
        return '0000'

    @dbus.service.method(AGENT_IFACE,
                         in_signature="ou", out_signature="")
    def RequestConfirmation(self, device, passkey):
        print("RequestConfirmation (%s, %06d)" % (device, passkey))
        set_trusted(device)
        return

    @dbus.service.method(AGENT_IFACE,
                         in_signature="o", out_signature="")
    def RequestAuthorization(self, device):
        print("RequestAuthorization (%s)" % (device))
        auth = input("Authorize? (yes/no): ")
        if (auth == "yes"):
            return
        raise Rejected("Pairing rejected")

    @dbus.service.method(AGENT_IFACE,
                         in_signature="o", out_signature="u")
    def RequestPasskey(self, device):
        print("RequestPasskey (%s)" % (device))
        set_trusted(device)
        passkey = input("Enter passkey: ")
        return dbus.UInt32(passkey)

    @dbus.service.method(AGENT_IFACE,
                         in_signature="ouq", out_signature="")
    def DisplayPasskey(self, device, passkey, entered):
        print("DisplayPasskey (%s, %06u entered %u)" %
              (device, passkey, entered))

    @dbus.service.method(AGENT_IFACE,
                         in_signature="os", out_signature="")
    def DisplayPinCode(self, device, pincode):
        print("DisplayPinCode (%s, %s)" % (device, pincode))


class Adapter:
    def __init__(self, idx=0):
        bus = dbus.SystemBus()
        self.path = f'{ADAPTER_ROOT}{idx}'
        self.adapter_object = bus.get_object(BUS_NAME, self.path)
        self.adapter_props = dbus.Interface(self.adapter_object,
                                            dbus.PROPERTIES_IFACE)
        self.adapter_props.Set(ADAPTER_IFACE,
                               'DiscoverableTimeout', dbus.UInt32(0))
        self.adapter_props.Set(ADAPTER_IFACE,
                               'Discoverable', True)
        self.adapter_props.Set(ADAPTER_IFACE,
                               'PairableTimeout', dbus.UInt32(0))
        self.adapter_props.Set(ADAPTER_IFACE,
                               'Pairable', True)


if __name__ == '__main__':
    agent = Agent(bus, AGENT_PATH)
    agnt_mngr = dbus.Interface(bus.get_object(BUS_NAME, AGNT_MNGR_PATH),
                               AGNT_MNGR_IFACE)
    agnt_mngr.RegisterAgent(AGENT_PATH, CAPABILITY)
    agnt_mngr.RequestDefaultAgent(AGENT_PATH)

    adapter = Adapter()
    mainloop = GLib.MainLoop()
    try:
        mainloop.run()
    except KeyboardInterrupt:
        agnt_mngr.UnregisterAgent(AGENT_PATH)
        mainloop.quit()

I got this code from here, but to be honest, I don't understand it.

It defines a bunch of dbus.service.method's and I think it somehow chooses one or something.

As someone who is used to less abstract languages I just dont know where its putting all this stuff or where its pulling.

I believe it just uses the RequestConfirmation() method in this case. so all the others are just junk.

But I would like to know if/how I can modify this to make it ask for a pin or passkey on my phone when I try to pair with the controller.


Solution

  • To try to explain what the code is doing...

    There is a Bluetooth daemon (bluetoothd) running on a Linux system. bluetoothd handles the Bluetooth functionality on the system. Below is a sequence diagram of the interactions with bluetoothd for pairing.

    enter image description here

    RegisterAgent() is used to tell bluetoothd where the D-Bus org.bluez.Agent1 interface has been created and which capability to use this registered agent for.

    When a pairing request comes in to bluetoothd it calls RequestConfirmation() at the registered location. If the returned value back is empty then the confirmation is confirmed. If there is either org.bluez.Error.Rejected or org.bluez.Error.Canceled thrown then the pairing is rejected.

    Which of the org.bluez.Agent1 methods gets called by bluetoothd will depend on exactly what type of pairing request has been received.

    The capability setting is probably best summarized by this table:

    enter image description here

    If it is the RequestConfirmation() method that is getting called, then the device D-Bus path and a uint32 passkey are the parameters passed in. It is up to the method in the agent to decide, on that information, if it should accept the request or not.

    The org.bluez.Agent1 interface is documented at:

    https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/agent-api.txt

    The official BlueZ example for a simple agent is at:

    https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/test/simple-agent