Bluetooth: Add selection of the SC authentication method
authorJohan Hedberg <johan.hedberg@intel.com>
Sat, 31 May 2014 15:51:02 +0000 (18:51 +0300)
committerMarcel Holtmann <marcel@holtmann.org>
Wed, 3 Dec 2014 15:51:18 +0000 (16:51 +0100)
This patch adds code to select the authentication method for Secure
Connections based on the local and remote capabilities. A new
DSP_PASSKEY method is also added for displaying the passkey - something
that is not part of legacy SMP pairing.

Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
net/bluetooth/smp.c

index 45e527d..9e11931 100644 (file)
@@ -661,6 +661,7 @@ static void smp_failure(struct l2cap_conn *conn, u8 reason)
 #define REQ_PASSKEY    0x02
 #define CFM_PASSKEY    0x03
 #define REQ_OOB                0x04
+#define DSP_PASSKEY    0x05
 #define OVERLAP                0xFF
 
 static const u8 gen_method[5][5] = {
@@ -671,6 +672,14 @@ static const u8 gen_method[5][5] = {
        { CFM_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, OVERLAP     },
 };
 
+static const u8 sc_method[5][5] = {
+       { JUST_WORKS,  JUST_CFM,    REQ_PASSKEY, JUST_WORKS, REQ_PASSKEY },
+       { JUST_WORKS,  CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY },
+       { DSP_PASSKEY, DSP_PASSKEY, REQ_PASSKEY, JUST_WORKS, DSP_PASSKEY },
+       { JUST_WORKS,  JUST_CFM,    JUST_WORKS,  JUST_WORKS, JUST_CFM    },
+       { DSP_PASSKEY, CFM_PASSKEY, REQ_PASSKEY, JUST_WORKS, CFM_PASSKEY },
+};
+
 static u8 get_auth_method(struct smp_chan *smp, u8 local_io, u8 remote_io)
 {
        /* If either side has unknown io_caps, use JUST_CFM (which gets
@@ -680,6 +689,9 @@ static u8 get_auth_method(struct smp_chan *smp, u8 local_io, u8 remote_io)
            remote_io > SMP_IO_KEYBOARD_DISPLAY)
                return JUST_CFM;
 
+       if (test_bit(SMP_FLAG_SC, &smp->flags))
+               return sc_method[remote_io][local_io];
+
        return gen_method[remote_io][local_io];
 }
 
@@ -1305,6 +1317,11 @@ static u8 smp_cmd_pairing_req(struct l2cap_conn *conn, struct sk_buff *skb)
        memcpy(&smp->preq[1], req, sizeof(*req));
        skb_pull(skb, sizeof(*req));
 
+       build_pairing_cmd(conn, req, &rsp, auth);
+
+       if (rsp.auth_req & SMP_AUTH_SC)
+               set_bit(SMP_FLAG_SC, &smp->flags);
+
        if (conn->hcon->io_capability == HCI_IO_NO_INPUT_OUTPUT)
                sec_level = BT_SECURITY_MEDIUM;
        else
@@ -1323,11 +1340,6 @@ static u8 smp_cmd_pairing_req(struct l2cap_conn *conn, struct sk_buff *skb)
                        return SMP_AUTH_REQUIREMENTS;
        }
 
-       build_pairing_cmd(conn, req, &rsp, auth);
-
-       if (rsp.auth_req & SMP_AUTH_SC)
-               set_bit(SMP_FLAG_SC, &smp->flags);
-
        key_size = min(req->max_key_size, rsp.max_key_size);
        if (check_enc_key_size(conn, key_size))
                return SMP_ENC_KEY_SIZE;
@@ -1901,12 +1913,54 @@ static int smp_cmd_sign_info(struct l2cap_conn *conn, struct sk_buff *skb)
        return 0;
 }
 
+static u8 sc_select_method(struct smp_chan *smp)
+{
+       struct l2cap_conn *conn = smp->conn;
+       struct hci_conn *hcon = conn->hcon;
+       struct smp_cmd_pairing *local, *remote;
+       u8 local_mitm, remote_mitm, local_io, remote_io, method;
+
+       /* The preq/prsp contain the raw Pairing Request/Response PDUs
+        * which are needed as inputs to some crypto functions. To get
+        * the "struct smp_cmd_pairing" from them we need to skip the
+        * first byte which contains the opcode.
+        */
+       if (hcon->out) {
+               local = (void *) &smp->preq[1];
+               remote = (void *) &smp->prsp[1];
+       } else {
+               local = (void *) &smp->prsp[1];
+               remote = (void *) &smp->preq[1];
+       }
+
+       local_io = local->io_capability;
+       remote_io = remote->io_capability;
+
+       local_mitm = (local->auth_req & SMP_AUTH_MITM);
+       remote_mitm = (remote->auth_req & SMP_AUTH_MITM);
+
+       /* If either side wants MITM, look up the method from the table,
+        * otherwise use JUST WORKS.
+        */
+       if (local_mitm || remote_mitm)
+               method = get_auth_method(smp, local_io, remote_io);
+       else
+               method = JUST_WORKS;
+
+       /* Don't confirm locally initiated pairing attempts */
+       if (method == JUST_CFM && test_bit(SMP_FLAG_INITIATOR, &smp->flags))
+               method = JUST_WORKS;
+
+       return method;
+}
+
 static int smp_cmd_public_key(struct l2cap_conn *conn, struct sk_buff *skb)
 {
        struct smp_cmd_public_key *key = (void *) skb->data;
        struct hci_conn *hcon = conn->hcon;
        struct l2cap_chan *chan = conn->smp;
        struct smp_chan *smp = chan->data;
+       struct hci_dev *hdev = hcon->hdev;
        struct smp_cmd_pairing_confirm cfm;
        int err;
 
@@ -1936,6 +1990,16 @@ static int smp_cmd_public_key(struct l2cap_conn *conn, struct sk_buff *skb)
 
        set_bit(SMP_FLAG_REMOTE_PK, &smp->flags);
 
+       smp->method = sc_select_method(smp);
+
+       BT_DBG("%s selected method 0x%02x", hdev->name, smp->method);
+
+       /* JUST_WORKS and JUST_CFM result in an unauthenticated key */
+       if (smp->method == JUST_WORKS || smp->method == JUST_CFM)
+               hcon->pending_sec_level = BT_SECURITY_MEDIUM;
+       else
+               hcon->pending_sec_level = BT_SECURITY_FIPS;
+
        /* The Initiating device waits for the non-initiating device to
         * send the confirm value.
         */