How can BlueZ be configured to require a connecting device to enter a matching pass key?
Asked Answered
P

1

6

I'm trying to setup Bluetooth on an embedded Linux device such that SSP mode is used, with my device generating and displaying a passcode on it's display, and the remote end (e.g. user's phone) being required to enter the matching passcode to be able to successfully pair.

I'm written/registered an agent that sets it's mode to DisplayOnly; I expected that when a device connects the "RequestPasskey" callback would be called and that my agent would then be able to generate and return a new passcode, and that the remote end would be required to enter this same code. The behaviour I actually see is that the remote end is generating it's own passcode.

I can achieve something like the required behaviour if I disable SSP and use legacy pairing, but I'd rather not have to do this.

Is it possible to achieve this behaviour with BlueZ?

Parmesan answered 3/9, 2018 at 3:29 Comment(1)
I am still working on this example in my end, but still a quick hint is, when you the device supports "DisplayOnly" mode when registering using "RegisterAgent", bluetoothd will automatically choose "RequestConfirmation" at device end and in phone end, it varies based on its own agent. Typically in iOS and Android it also chooses "RequestConfirmation" method. So the passkey will be automatically generated by the kernel/bluez itself and you see only the confirmation of the key at both the end. You need to specify "KeyboardOnly" for "RequestPasskey", but still no examples with me :-(Dene
D
1

To simply the discussion here, please consider the explanation from Bluetooth Pairing blog from Bluetooth SIG.

To simplify, the below image is copied from the above mentioned blog.

enter image description here

So input can be three forms "No Input", "Yes/No" and "Keyboard". Output can be "No Output" and "Numeric Output".

In your case, you want to generate the passkey in Embedded Device (we can name this as responder) and enter the key in the device which initiates the connection (we can name this as initiator).

To achieve such case you need to specify,

Responder (Embedded Device): As "KeyboardOnly" Initiator: Also as "KeyboardOnly"

To experiment this, use the below example agent,

/*
 * gcc `pkg-config --cflags glib-2.0 gio-2.0` -Wall -Wextra -o ./bin/agent ./agent.c `pkg-config --libs glib-2.0 gio-2.0`
 */
#include <glib.h>
#include <gio/gio.h>
#include <stdio.h>

GMainLoop *loop;
GDBusConnection *con;
#define AGENT_PATH  "/org/bluez/AutoPinAgent"

static void bluez_agent_method_call(GDBusConnection *conn,
                    const gchar *sender,
                    const gchar *path,
                    const gchar *interface,
                    const gchar *method,
                    GVariant *params,
                    GDBusMethodInvocation *invocation,
                    void *userdata)
{
    int pass;
    int entered;
    char *opath;
    GVariant *p= g_dbus_method_invocation_get_parameters(invocation);

    g_print("Agent method call: %s.%s()\n", interface, method);
    if(!strcmp(method, "RequestPinCode")) {
        ;
    }
    else if(!strcmp(method, "DisplayPinCode")) {
        ;
    }
    else if(!strcmp(method, "RequestPasskey")) {
        g_print("Getting the Pin from user: ");
        fscanf(stdin, "%d", &pass);
        g_print("\n");
        g_dbus_method_invocation_return_value(invocation, g_variant_new("(u)", pass));
    }
    else if(!strcmp(method, "DisplayPasskey")) {
        g_variant_get(params, "(ouq)", &opath, &pass, &entered);
        g_print("Path: %s Pass: %d Entered: %d\n", opath, pass, entered);
        g_dbus_method_invocation_return_value(invocation, NULL);
    }
    else if(!strcmp(method, "RequestConfirmation")) {
        g_variant_get(params, "(ou)", &opath, &pass);
        g_print("Path: %s Pass: %d\n", opath, pass);
        g_dbus_method_invocation_return_value(invocation, NULL);
    }
    else if(!strcmp(method, "RequestAuthorization")) {
        ;
    }
    else if(!strcmp(method, "AuthorizeService")) {
        ;
    }
    else if(!strcmp(method, "Cancel")) {
        ;
    }
    else
        g_print("We should not come here, unknown method\n");
}

static const GDBusInterfaceVTable agent_method_table = {
    .method_call = bluez_agent_method_call,
};

int bluez_register_agent(GDBusConnection *con)
{
    GError *error = NULL;
    guint id = 0;
    GDBusNodeInfo *info = NULL;

    static const gchar bluez_agent_introspection_xml[] =
        "<node name='/org/bluez/SampleAgent'>"
        "   <interface name='org.bluez.Agent1'>"
        "       <method name='Release'>"
        "       </method>"
        "       <method name='RequestPinCode'>"
        "           <arg type='o' name='device' direction='in' />"
        "           <arg type='s' name='pincode' direction='out' />"
        "       </method>"
        "       <method name='DisplayPinCode'>"
        "           <arg type='o' name='device' direction='in' />"
        "           <arg type='s' name='pincode' direction='in' />"
        "       </method>"
        "       <method name='RequestPasskey'>"
        "           <arg type='o' name='device' direction='in' />"
        "           <arg type='u' name='passkey' direction='out' />"
        "       </method>"
        "       <method name='DisplayPasskey'>"
        "           <arg type='o' name='device' direction='in' />"
        "           <arg type='u' name='passkey' direction='in' />"
        "           <arg type='q' name='entered' direction='in' />"
        "       </method>"
        "       <method name='RequestConfirmation'>"
        "           <arg type='o' name='device' direction='in' />"
        "           <arg type='u' name='passkey' direction='in' />"
        "       </method>"
        "       <method name='RequestAuthorization'>"
        "           <arg type='o' name='device' direction='in' />"
        "       </method>"
        "       <method name='AuthorizeService'>"
        "           <arg type='o' name='device' direction='in' />"
        "           <arg type='s' name='uuid' direction='in' />"
        "       </method>"
        "       <method name='Cancel'>"
        "       </method>"
        "   </interface>"
        "</node>";

    info = g_dbus_node_info_new_for_xml(bluez_agent_introspection_xml, &error);
    if(error) {
        g_printerr("Unable to create node: %s\n", error->message);
        g_clear_error(&error);
        return 0;
    }

    id = g_dbus_connection_register_object(con, 
            AGENT_PATH,
            info->interfaces[0],
            &agent_method_table,
            NULL, NULL, &error);
    g_dbus_node_info_unref(info);
    //g_dbus_connection_unregister_object(con, id);
    /* call register method in AgentManager1 interface */
    return id;
}

static int bluez_agent_call_method(const gchar *method, GVariant *param)
{
    GVariant *result;
    GError *error = NULL;

    result = g_dbus_connection_call_sync(con,
            "org.bluez",
            "/org/bluez",
            "org.bluez.AgentManager1",
            method,
            param,
            NULL,
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            NULL,
            &error);
    if(error != NULL) {
        g_print("Register %s: %s\n", AGENT_PATH, error->message);
        return 1;
    }

    g_variant_unref(result);
    return 0;
}

static int bluez_register_autopair_agent(const char *cap)
{
    int rc;

    rc = bluez_agent_call_method("RegisterAgent", g_variant_new("(os)", AGENT_PATH, cap));
    if(rc)
        return 1;

    rc = bluez_agent_call_method("RequestDefaultAgent", g_variant_new("(o)", AGENT_PATH));
    if(rc) {
        bluez_agent_call_method("UnregisterAgent", g_variant_new("(o)", AGENT_PATH));
        return 1;
    }

    return 0;
}


static void cleanup_handler(int signo)
{
    if (signo == SIGINT) {
        g_print("received SIGINT\n");
        g_main_loop_quit(loop);
    }
}

int main(int argc, char **argv)
{
    int id;
    int rc;

    if(argc < 2)
        return 1;

    if(signal(SIGINT, cleanup_handler) == SIG_ERR)
        g_print("can't catch SIGINT\n");

    con = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL);
    if(con == NULL) {
        g_print("Not able to get connection to system bus\n");
        return 1;
    }

    loop = g_main_loop_new(NULL, FALSE);

    id = bluez_register_agent(con);
    if(id == 0)
        goto fail;

    rc = bluez_register_autopair_agent(argv[1]);
    if(rc) {
        g_print("Not able to register default autopair agent\n");
        goto fail;
    }

    g_main_loop_run(loop);

fail:
    g_dbus_connection_unregister_object(con, id);
    g_object_unref(con);
    return 0;
}

To experiment this, you must disable agent in bluetoothctl "agent off" and turn on the agent as,

Responder: ./bin/agent "KeyboardOnly"
Initiator: ./bin/agent "KeyboardOnly"

When you try to initiate the connection from initiator end, RequestPasskey is called at both the end. Where you can enter the same key in both the end. If you want to respond Fixed Key PIN, you edit the agent code in "Responder" end for the function "RequestPasskey" by replying the Fixed PIN and entering the same PIN manually or again automatically from "Initiator" end as well.

Dene answered 6/9, 2018 at 11:29 Comment(1)
Thanks for the detailed answer, I'll try this out. I won't have any control over the remote end (initiator) though.Parmesan

© 2022 - 2024 — McMap. All rights reserved.