Hi,
This patch series is the userspace side of the changes that introduced
the specific messages for communicating SMP keys.
--
Vinicius Costa Gomes (12):
Add messages to the mgmt interface to handle SMP key storage
Add storage functions to handle SMP keys
Add macros for the possible key types (including SMP keys)
Fix using "magic" values for the key types
Add support for storing the SMP Long Term Key
Add handlers for the new mgmt SMP messages
Add support for creating devices from the stored SMP keys
Fix memory leak when loading keys
Remove the SMP Long Term Key when the device is removed
Add support for getting the Encryption Key Size via btio
Add support for passing the CID to btiotest
Add support for btiotest to returning the key size
btio/btio.c | 21 +++++++++
btio/btio.h | 1 +
doc/mgmt-api.txt | 41 ++++++++++++++++++
lib/bluetooth.h | 1 +
lib/hci.h | 14 ++++++
lib/mgmt.h | 35 +++++++++++++++
plugins/hciops.c | 31 +++++++++++---
plugins/mgmtops.c | 97 ++++++++++++++++++++++++++++++++++++++++++-
src/adapter.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++-
src/adapter.h | 10 ++++
src/device.c | 1 +
src/event.c | 64 ++++++++++++++++++++++++++--
src/event.h | 3 +-
src/storage.c | 34 +++++++++++++++
src/storage.h | 2 +
test/btiotest.c | 24 +++++++++-
16 files changed, 482 insertions(+), 18 deletions(-)
--
1.7.6
Hi Johan,
On 11:07 Wed 31 Aug, Johan Hedberg wrote:
> Hi Vinicius,
>
> On Fri, Aug 19, 2011, Vinicius Costa Gomes wrote:
> > +Load SMP Keys Command
> > +=====================
> > +
> > + Command Code: 0x001F
> > + Controller Index: <controller id>
> > + Command Parameters: Debug Keys (1 Octet)
> > + Key Count (2 Octets)
> > + Key1 {
> > + Address (6 Octets)
> > + Type (1 Octet)
> > + PIN_Length (1 Octet)
>
> Isn't the PIN Length completely irrelevant here since the "Passkey
> Entry" pairing method of SSP always requires a 6-digit passkey?
_If_ the Passkey Entry method is used. If Just Works is used, PIN Length
will be zero, and as Carles pointed out, if OOB is used it should be
something else.
>
> > + Value (16 Octets)
> > + [
> > + Encryption Size (1 Octet)
> > + Enc. Diversifier (2 Octets)
> > + Random Number (8 Octets)
> > + ] || [
> > + Address Type (1 Octet)
> > + ]
>
> Having unions within mgmt messages starts to push their complexity over
> the level that I feel comfortable with. Would it make sense to instead
> have separate "Load LTKs" and "Load IRKs" commands (though you'd then
> need to have two separate "New LTK" and "New IRK" events too).
Glad that this discussion is happening, as it was not an easy decision
and I made a compromise in favor of adding less messages to the mgmt
protocol (less API) and less lines of code in exchange of adding
(debatably[1]) more complexity.
One point in favor of this union is that for most the time (if not all)
userspace doesn't need to know what is inside that union, userspace
will only be storing those keys.
And there's one more key type, the Connection Signature Resolving Key,
CSRK, used to sign commands, this key type doesn't have any associated
data with it. So it will mean six more messages.
[1] IMHO using new messages will mean just about moving the switch statement
that selects what to do in each key type case nearer to the protocol
level, the complexity will be almost the same, with just more repeated
code.
>
> Btw, what exactly do you need the Address Type for in the case of IRKs
> and why don't you need it in the case of LTKs?
I am just exporting all the information that I receive from the SMP
messages. That address type is the address type of the _resolved_
address not the address type that was used in the connection that
was used for exchange the keys.
Having the address type along with the LTK would be useful if I could
use it for reconnections, but that's another problem.
>
> Johan
Cheers,
--
Vinicius
> Isn't the PIN Length completely irrelevant here since the "Passkey
> Entry" pairing method of SSP always requires a 6-digit passkey?
If OOB keys are supported using the same API, then those are 16-byte long. For passkey, then length is fixed to 6 as you mention.
Hi Vinicius,
On Fri, Aug 19, 2011, Vinicius Costa Gomes wrote:
> +Load SMP Keys Command
> +=====================
> +
> + Command Code: 0x001F
> + Controller Index: <controller id>
> + Command Parameters: Debug Keys (1 Octet)
> + Key Count (2 Octets)
> + Key1 {
> + Address (6 Octets)
> + Type (1 Octet)
> + PIN_Length (1 Octet)
Isn't the PIN Length completely irrelevant here since the "Passkey
Entry" pairing method of SSP always requires a 6-digit passkey?
> + Value (16 Octets)
> + [
> + Encryption Size (1 Octet)
> + Enc. Diversifier (2 Octets)
> + Random Number (8 Octets)
> + ] || [
> + Address Type (1 Octet)
> + ]
Having unions within mgmt messages starts to push their complexity over
the level that I feel comfortable with. Would it make sense to instead
have separate "Load LTKs" and "Load IRKs" commands (though you'd then
need to have two separate "New LTK" and "New IRK" events too).
Btw, what exactly do you need the Address Type for in the case of IRKs
and why don't you need it in the case of LTKs?
Johan
Hi,
On 21:11 Fri 19 Aug, Vinicius Costa Gomes wrote:
> Hi,
>
> This patch series is the userspace side of the changes that introduced
> the specific messages for communicating SMP keys.
>
As the Blacklisting events patches were applied first to the kernel,
there are a few conflicting types between those series. So if this
series is to be applied I need to send a new version that solve those
conflicts (already done, but I don't want to spam the list even more ;-)
Cheers,
--
Vinicius
---
test/btiotest.c | 13 +++++++++++++
1 files changed, 13 insertions(+), 0 deletions(-)
diff --git a/test/btiotest.c b/test/btiotest.c
index c4d8f4f..fdf2234 100644
--- a/test/btiotest.c
+++ b/test/btiotest.c
@@ -135,6 +135,19 @@ static void connect_cb(GIOChannel *io, GError *err, gpointer user_data)
printf("imtu=%u, omtu=%u\n", imtu, omtu);
}
+ if (data->type == BT_IO_L2CAP) {
+ uint8_t key_size;
+
+ if (!bt_io_get(io, data->type, &err,
+ BT_IO_OPT_KEY_SIZE, &key_size,
+ BT_IO_OPT_INVALID)) {
+ printf("Unable to get L2CAP Key size: %s\n",
+ err->message);
+ g_clear_error(&err);
+ } else
+ printf("key_size=%u\n", key_size);
+ }
+
if (data->disconn == 0) {
g_io_channel_shutdown(io, TRUE, NULL);
printf("Disconnected\n");
--
1.7.6
---
test/btiotest.c | 11 ++++++++---
1 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/test/btiotest.c b/test/btiotest.c
index c02a25a..c4d8f4f 100644
--- a/test/btiotest.c
+++ b/test/btiotest.c
@@ -225,7 +225,7 @@ static void confirm_cb(GIOChannel *io, gpointer user_data)
}
static void l2cap_connect(const char *src, const char *dst, uint16_t psm,
- gint disconn, gint sec)
+ uint16_t cid, gint disconn, gint sec)
{
struct io_data *data;
GError *err = NULL;
@@ -241,6 +241,7 @@ static void l2cap_connect(const char *src, const char *dst, uint16_t psm,
BT_IO_OPT_SOURCE, src,
BT_IO_OPT_DEST, dst,
BT_IO_OPT_PSM, psm,
+ BT_IO_OPT_CID, cid,
BT_IO_OPT_SEC_LEVEL, sec,
BT_IO_OPT_INVALID);
else
@@ -249,6 +250,7 @@ static void l2cap_connect(const char *src, const char *dst, uint16_t psm,
&err,
BT_IO_OPT_DEST, dst,
BT_IO_OPT_PSM, psm,
+ BT_IO_OPT_CID, cid,
BT_IO_OPT_SEC_LEVEL, sec,
BT_IO_OPT_INVALID);
@@ -466,6 +468,7 @@ static gint opt_disconn = -1;
static gint opt_accept = DEFAULT_ACCEPT_TIMEOUT;
static gint opt_sec = 0;
static gboolean opt_master = FALSE;
+static gint opt_cid = 0;
static GMainLoop *main_loop;
@@ -474,6 +477,8 @@ static GOptionEntry options[] = {
"RFCOMM channel" },
{ "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm,
"L2CAP PSM" },
+ { "cid", 'j', 0, G_OPTION_ARG_INT, &opt_cid,
+ "L2CAP CID" },
{ "sco", 's', 0, G_OPTION_ARG_NONE, &opt_sco,
"Use SCO" },
{ "defer", 'd', 0, G_OPTION_ARG_NONE, &opt_defer,
@@ -513,9 +518,9 @@ int main(int argc, char *argv[])
printf("accept=%d, reject=%d, discon=%d, defer=%d, sec=%d\n",
opt_accept, opt_reject, opt_disconn, opt_defer, opt_sec);
- if (opt_psm) {
+ if (opt_psm || opt_cid) {
if (argc > 1)
- l2cap_connect(opt_dev, argv[1], opt_psm,
+ l2cap_connect(opt_dev, argv[1], opt_psm, opt_cid,
opt_disconn, opt_sec);
else
l2cap_listen(opt_dev, opt_psm, opt_defer, opt_reject,
--
1.7.6
Some profiles specify some restriction depending on the length
of the key used to encrypt the link, this adds an way to retrieve
that value from the kernel.
Current kernels don't provide this information so the size is
always zero.
---
btio/btio.c | 21 +++++++++++++++++++++
btio/btio.h | 1 +
lib/bluetooth.h | 1 +
3 files changed, 23 insertions(+), 0 deletions(-)
diff --git a/btio/btio.c b/btio/btio.c
index 2cc9082..337dcb9 100644
--- a/btio/btio.c
+++ b/btio/btio.c
@@ -502,6 +502,22 @@ static int l2cap_set_flushable(int sock, gboolean flushable)
return 0;
}
+static gboolean get_key_size(int sock, BtIOType type, int *size,
+ GError **err)
+{
+ struct bt_security sec;
+ socklen_t len;
+
+ memset(&sec, 0, sizeof(sec));
+ len = sizeof(sec);
+ if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) == 0) {
+ *size = sec.key_size;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static gboolean l2cap_set(int sock, int sec_level, uint16_t imtu,
uint16_t omtu, uint8_t mode, int master,
int flushable, GError **err)
@@ -848,6 +864,11 @@ static gboolean l2cap_get(int sock, GError **err, BtIOOption opt1,
va_arg(args, int *), err))
return FALSE;
break;
+ case BT_IO_OPT_KEY_SIZE:
+ if (!get_key_size(sock, BT_IO_L2CAP,
+ va_arg(args, int *), err))
+ return FALSE;
+ break;
case BT_IO_OPT_PSM:
*(va_arg(args, uint16_t *)) = src.l2_psm ?
src.l2_psm : dst.l2_psm;
diff --git a/btio/btio.h b/btio/btio.h
index c6b736f..eaa9874 100644
--- a/btio/btio.h
+++ b/btio/btio.h
@@ -52,6 +52,7 @@ typedef enum {
BT_IO_OPT_DEST_BDADDR,
BT_IO_OPT_DEFER_TIMEOUT,
BT_IO_OPT_SEC_LEVEL,
+ BT_IO_OPT_KEY_SIZE,
BT_IO_OPT_CHANNEL,
BT_IO_OPT_SOURCE_CHANNEL,
BT_IO_OPT_DEST_CHANNEL,
diff --git a/lib/bluetooth.h b/lib/bluetooth.h
index 738e07a..1492139 100644
--- a/lib/bluetooth.h
+++ b/lib/bluetooth.h
@@ -63,6 +63,7 @@ extern "C" {
#define BT_SECURITY 4
struct bt_security {
uint8_t level;
+ uint8_t key_size;
};
#define BT_SECURITY_SDP 0
#define BT_SECURITY_LOW 1
--
1.7.6
---
src/device.c | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/src/device.c b/src/device.c
index 9dd657c..1a333de 100644
--- a/src/device.c
+++ b/src/device.c
@@ -991,6 +991,7 @@ static void device_remove_stored(struct btd_device *device)
if (device_is_bonded(device)) {
delete_entry(&src, "linkkeys", addr);
delete_entry(&src, "aliases", addr);
+ delete_entry(&src, "longtermkeys", addr);
device_set_bonded(device, FALSE);
device_set_paired(device, FALSE);
btd_adapter_remove_bonding(device->adapter, &device->bdaddr);
--
1.7.6
If we need a copy of those keys we should copy them.
---
plugins/hciops.c | 13 ++++++++++++-
1 files changed, 12 insertions(+), 1 deletions(-)
diff --git a/plugins/hciops.c b/plugins/hciops.c
index cf8dd3a..03eec82 100644
--- a/plugins/hciops.c
+++ b/plugins/hciops.c
@@ -3450,6 +3450,7 @@ static int hciops_restore_powered(int index)
static int hciops_load_keys(int index, GSList *keys, gboolean debug_keys)
{
struct dev_info *dev = &devs[index];
+ GSList *l, *new;
DBG("hci%d keys %d debug_keys %d", index, g_slist_length(keys),
debug_keys);
@@ -3457,7 +3458,17 @@ static int hciops_load_keys(int index, GSList *keys, gboolean debug_keys)
if (dev->keys != NULL)
return -EEXIST;
- dev->keys = keys;
+ for (new = NULL, l = keys; l; l = l->next) {
+ struct link_key_info *orig, *dup;
+
+ orig = l->data;
+
+ dup = g_memdup(orig, sizeof(*orig));
+
+ new = g_slist_prepend(new, dup);
+ }
+
+ dev->keys = new;
dev->debug_keys = debug_keys;
return 0;
--
1.7.6
---
src/adapter.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 118 insertions(+), 3 deletions(-)
diff --git a/src/adapter.c b/src/adapter.c
index 4c88a0e..cfa4ddc 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -1926,6 +1926,71 @@ static struct link_key_info *get_key_info(const char *addr, const char *value)
return info;
}
+static uint8_t *str2buf(const char *str, int *len)
+{
+ uint8_t *buf;
+ int i, dlen;
+
+ DBG("");
+
+ if (str == NULL)
+ return NULL;
+
+ dlen = (strlen(str) / 2);
+
+ buf = g_try_malloc0(sizeof(uint8_t) * dlen);
+ if (buf == NULL)
+ return NULL;
+
+ for (i = 0; i < dlen; i++)
+ sscanf(str + (i * 2), "%02hhX", &buf[i]);
+
+ if (len)
+ *len = dlen;
+
+ return buf;
+}
+
+static struct smp_key_info *get_ltk_info(const char *addr, const char *value)
+{
+ struct smp_key_info *ltk;
+ char tmp[3], *ptr;
+ int i, ret, total;
+
+ total = strlen(value);
+ /* FIXME */
+ if (total < 99) {
+ error("Unexpectedly short (%zu) LTK", strlen(value));
+ return NULL;
+ }
+
+ ltk = g_new0(struct smp_key_info, 1);
+
+ str2ba(addr, <k->bdaddr);
+
+ memset(tmp, 0, sizeof(tmp));
+
+ for (i = 0; i < 16; i++) {
+ memcpy(tmp, value + (i * 2), 2);
+ ltk->val[i] = (uint8_t) strtol(tmp, NULL, 16);
+ }
+
+ ptr = (char *) value + 33;
+ total -= 33;
+
+ ret = sscanf(ptr, "%hhd %hhd %n", <k->type, <k->pin_len, &i);
+ if (ret < 2) {
+ g_free(ltk);
+ return NULL;
+ }
+ ptr += i;
+ total -= i;
+
+ ltk->data = str2buf(ptr, <k->dlen);
+
+ return ltk;
+}
+
static void create_stored_device_from_linkkeys(char *key, char *value,
void *user_data)
{
@@ -1949,6 +2014,37 @@ static void create_stored_device_from_linkkeys(char *key, char *value,
}
}
+static void create_stored_device_from_ltks(char *key, char *value,
+ void *user_data)
+{
+ struct adapter_keys *keys = user_data;
+ struct btd_adapter *adapter = keys->adapter;
+ struct btd_device *device;
+ struct smp_key_info *info;
+ char srcaddr[18];
+ bdaddr_t src;
+
+ info = get_ltk_info(key, value);
+ if (info)
+ keys->keys = g_slist_append(keys->keys, info);
+
+ if (g_slist_find_custom(adapter->devices, key,
+ (GCompareFunc) device_address_cmp))
+ return;
+
+ adapter_get_address(adapter, &src);
+ ba2str(&src, srcaddr);
+
+ if (g_strcmp0(srcaddr, key) == 0)
+ return;
+
+ device = device_create(connection, adapter, key, DEVICE_TYPE_LE);
+ if (device) {
+ device_set_temporary(device, FALSE);
+ adapter->devices = g_slist_append(adapter->devices, device);
+ }
+}
+
static void create_stored_device_from_blocked(char *key, char *value,
void *user_data)
{
@@ -2060,6 +2156,14 @@ static void create_stored_device_from_primary(char *key, char *value,
g_slist_free(uuids);
}
+static void smp_key_free(void *data)
+{
+ struct smp_key_info *info = data;
+
+ g_free(info->data);
+ g_free(info);
+}
+
static void load_devices(struct btd_adapter *adapter)
{
char filename[PATH_MAX + 1];
@@ -2082,11 +2186,22 @@ static void load_devices(struct btd_adapter *adapter)
err = adapter_ops->load_keys(adapter->dev_id, keys.keys,
main_opts.debug_keys);
- if (err < 0) {
+ if (err < 0)
error("Unable to load keys to adapter_ops: %s (%d)",
strerror(-err), -err);
- g_slist_free_full(keys.keys, g_free);
- }
+
+ g_slist_free_full(keys.keys, g_free);
+ keys.keys = NULL;
+
+ create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "longtermkeys");
+ textfile_foreach(filename, create_stored_device_from_ltks, &keys);
+
+ err = adapter_ops->load_smp_keys(adapter->dev_id, keys.keys);
+ if (err < 0)
+ error("Unable to load keys to adapter_ops: %s (%d)",
+ strerror(-err), -err);
+ g_slist_free_full(keys.keys, smp_key_free);
+ keys.keys = NULL;
create_name(filename, PATH_MAX, STORAGEDIR, srcaddr, "blocked");
textfile_foreach(filename, create_stored_device_from_blocked, adapter);
--
1.7.6
---
plugins/hciops.c | 6 +++
plugins/mgmtops.c | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/adapter.h | 10 ++++++
3 files changed, 110 insertions(+), 0 deletions(-)
diff --git a/plugins/hciops.c b/plugins/hciops.c
index 869a814..cf8dd3a 100644
--- a/plugins/hciops.c
+++ b/plugins/hciops.c
@@ -3636,6 +3636,11 @@ static int hciops_remove_remote_oob_data(int index, bdaddr_t *bdaddr)
return 0;
}
+static int hciops_load_smp_keys(int index, GSList *keys)
+{
+ return -ENOSYS;
+}
+
static struct btd_adapter_ops hci_ops = {
.setup = hciops_setup,
.cleanup = hciops_cleanup,
@@ -3674,6 +3679,7 @@ static struct btd_adapter_ops hci_ops = {
.read_local_oob_data = hciops_read_local_oob_data,
.add_remote_oob_data = hciops_add_remote_oob_data,
.remove_remote_oob_data = hciops_remove_remote_oob_data,
+ .load_smp_keys = hciops_load_smp_keys,
};
static int hciops_init(void)
diff --git a/plugins/mgmtops.c b/plugins/mgmtops.c
index bf9e9af..3fd1b3f 100644
--- a/plugins/mgmtops.c
+++ b/plugins/mgmtops.c
@@ -1386,6 +1386,45 @@ static void mgmt_discovering(int sk, uint16_t index, void *buf, size_t len)
adapter_set_state(adapter, state);
}
+static void mgmt_new_smp_key(int sk, uint16_t index, void *buf, size_t len)
+{
+ struct mgmt_ev_new_smp_key *ev = buf;
+ struct controller_info *info;
+
+ if (len != sizeof(*ev)) {
+ error("new_smp_key event size mismatch (%zu != %zu)",
+ len, sizeof(*ev));
+ return;
+ }
+
+ DBG("Controller %u new key of type %u pin_len %u", index,
+ ev->key.type, ev->key.pin_len);
+
+ if (index > max_index) {
+ error("Unexpected index %u in new_key event", index);
+ return;
+ }
+
+ if (ev->key.pin_len > 16) {
+ error("Invalid PIN length (%u) in new_key event",
+ ev->key.pin_len);
+ return;
+ }
+
+ info = &controllers[index];
+
+ if (ev->store_hint) {
+ struct smp_ltk_info *ltk = &ev->key.ltk;
+
+ btd_event_link_key_notify(&info->bdaddr, &ev->key.bdaddr,
+ ev->key.val, ev->key.type,
+ ev->key.pin_len, ev->key.data,
+ sizeof(*ltk));
+ }
+
+ btd_event_bonding_complete(&info->bdaddr, &ev->key.bdaddr, 0);
+}
+
static gboolean mgmt_event(GIOChannel *io, GIOCondition cond, gpointer user_data)
{
char buf[MGMT_BUF_SIZE];
@@ -1490,6 +1529,9 @@ static gboolean mgmt_event(GIOChannel *io, GIOCondition cond, gpointer user_data
case MGMT_EV_DISCOVERING:
mgmt_discovering(sk, index, buf + MGMT_HDR_SIZE, len);
break;
+ case MGMT_EV_NEW_SMP_KEY:
+ mgmt_new_smp_key(sk, index, buf + MGMT_HDR_SIZE, len);
+ break;
default:
error("Unknown Management opcode %u (index %u)", opcode, index);
break;
@@ -2030,6 +2072,57 @@ static int mgmt_remove_remote_oob_data(int index, bdaddr_t *bdaddr)
return 0;
}
+static int mgmtops_load_smp_keys(int index, GSList *keys)
+{
+ char *buf;
+ struct mgmt_hdr *hdr;
+ struct mgmt_cp_load_smp_keys *cp;
+ struct mgmt_smp_key_info *key;
+ size_t key_count, cp_size;
+ GSList *l;
+ int err;
+
+ key_count = g_slist_length(keys);
+
+ DBG("index %d keys %zu", index, key_count);
+
+ cp_size = sizeof(*cp) + (key_count * sizeof(*key));
+
+ buf = g_try_malloc0(sizeof(*hdr) + cp_size);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ memset(buf, 0, sizeof(buf));
+
+ hdr = (void *) buf;
+ hdr->opcode = htobs(MGMT_OP_LOAD_SMP_KEYS);
+ hdr->len = htobs(cp_size);
+ hdr->index = htobs(index);
+
+ cp = (void *) (buf + sizeof(*hdr));
+ cp->key_count = htobs(key_count);
+
+ for (l = keys, key = cp->keys; l != NULL; l = g_slist_next(l), key++) {
+ struct smp_key_info *info = l->data;
+
+ bacpy(&key->bdaddr, &info->bdaddr);
+ key->type = info->type;
+ memcpy(key->val, info->val, 16);
+ key->pin_len = info->pin_len;
+
+ memcpy(key->data, info->data, info->dlen);
+ }
+
+ if (write(mgmt_sock, buf, sizeof(*hdr) + cp_size) < 0)
+ err = -errno;
+ else
+ err = 0;
+
+ g_free(buf);
+
+ return err;
+}
+
static struct btd_adapter_ops mgmt_ops = {
.setup = mgmt_setup,
.cleanup = mgmt_cleanup,
@@ -2068,6 +2161,7 @@ static struct btd_adapter_ops mgmt_ops = {
.read_local_oob_data = mgmt_read_local_oob_data,
.add_remote_oob_data = mgmt_add_remote_oob_data,
.remove_remote_oob_data = mgmt_remove_remote_oob_data,
+ .load_smp_keys = mgmtops_load_smp_keys,
};
static int mgmt_init(void)
diff --git a/src/adapter.h b/src/adapter.h
index 687275a..a835eed 100644
--- a/src/adapter.h
+++ b/src/adapter.h
@@ -63,6 +63,15 @@ struct link_key_info {
uint8_t pin_len;
};
+struct smp_key_info {
+ bdaddr_t bdaddr;
+ uint8_t type;
+ uint8_t pin_len;
+ uint8_t val[16];
+ int dlen;
+ uint8_t *data;
+};
+
struct remote_dev_info {
bdaddr_t bdaddr;
int8_t rssi;
@@ -220,6 +229,7 @@ struct btd_adapter_ops {
int (*add_remote_oob_data) (int index, bdaddr_t *bdaddr, uint8_t *hash,
uint8_t *randomizer);
int (*remove_remote_oob_data) (int index, bdaddr_t *bdaddr);
+ int (*load_smp_keys) (int index, GSList *keys);
};
int btd_register_adapter_ops(struct btd_adapter_ops *ops, gboolean priority);
--
1.7.6
This adds support for receiving keys with arbitrary data following
it, this is because for SMP we may have different types of keys that
have different kind of data associated with them.
And we keep that associated data opaque, as only the communication
functions need to be aware of its contents, for storage there's no
need to export that information.
---
plugins/hciops.c | 2 +-
plugins/mgmtops.c | 3 +-
src/event.c | 64 +++++++++++++++++++++++++++++++++++++++++++++++++---
src/event.h | 3 +-
4 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/plugins/hciops.c b/plugins/hciops.c
index 43f4f02..869a814 100644
--- a/plugins/hciops.c
+++ b/plugins/hciops.c
@@ -996,7 +996,7 @@ static void link_key_notify(int index, void *ptr)
err = btd_event_link_key_notify(&dev->bdaddr, dba,
evt->link_key, key_type,
- dev->pin_length);
+ dev->pin_length, NULL, 0);
if (err == -ENODEV)
status = HCI_OE_LOW_RESOURCES;
diff --git a/plugins/mgmtops.c b/plugins/mgmtops.c
index 3cdb97e..bf9e9af 100644
--- a/plugins/mgmtops.c
+++ b/plugins/mgmtops.c
@@ -408,7 +408,8 @@ static void mgmt_new_key(int sk, uint16_t index, void *buf, size_t len)
if (ev->store_hint)
btd_event_link_key_notify(&info->bdaddr, &ev->key.bdaddr,
ev->key.val, ev->key.type,
- ev->key.pin_len);
+ ev->key.pin_len, NULL, 0);
+
btd_event_bonding_complete(&info->bdaddr, &ev->key.bdaddr, 0);
}
diff --git a/src/event.c b/src/event.c
index dc45bd3..ade882e 100644
--- a/src/event.c
+++ b/src/event.c
@@ -385,9 +385,48 @@ proceed:
adapter_set_state(adapter, STATE_IDLE);
}
-int btd_event_link_key_notify(bdaddr_t *local, bdaddr_t *peer,
- uint8_t *key, uint8_t key_type,
- uint8_t pin_length)
+static gchar *buf2str(uint8_t *data, int datalen)
+{
+ char *buf;
+ int i;
+
+ buf = g_try_new0(gchar, (datalen * 2) + 1);
+ if (buf == NULL)
+ return NULL;
+
+ for (i = 0; i < datalen; i++)
+ sprintf(buf + (i * 2), "%2.2x", data[i]);
+
+ return buf;
+}
+
+static int store_longtermkey(bdaddr_t *local, bdaddr_t *peer, unsigned char *key,
+ uint8_t type, int length, int dlen, uint8_t *data)
+{
+ GString *newkey;
+ char *val, *str;
+ int err;
+
+ val = buf2str(key, 16);
+ newkey = g_string_new(val);
+ g_free(val);
+
+ g_string_append_printf(newkey, " %d %d %d ", type, length, dlen);
+
+ str = buf2str(data, dlen);
+ newkey = g_string_append(newkey, str);
+ g_free(str);
+
+ err = write_longtermkeys(local, peer, newkey->str);
+
+ g_string_free(newkey, TRUE);
+
+ return err;
+}
+
+int btd_event_link_key_notify(bdaddr_t *local, bdaddr_t *peer, uint8_t *key,
+ uint8_t key_type, uint8_t pin_length,
+ uint8_t *data, int dlen)
{
struct btd_adapter *adapter;
struct btd_device *device;
@@ -398,7 +437,24 @@ int btd_event_link_key_notify(bdaddr_t *local, bdaddr_t *peer,
DBG("storing link key of type 0x%02x", key_type);
- ret = write_link_key(local, peer, key, key_type, pin_length);
+ switch (key_type) {
+ case HCI_LK_COMBINATION:
+ case HCI_LK_LOCAL_UNIT:
+ case HCI_LK_REMOTE_UNIT:
+ case HCI_LK_DEBUG_COMBINATION:
+ case HCI_LK_UNAUTH_COMBINATION:
+ case HCI_LK_AUTH_COMBINATION:
+ case HCI_LK_CHANGED_COMBINATION:
+ ret = write_link_key(local, peer, key, key_type, pin_length);
+ break;
+ case HCI_LK_SMP_LTK:
+ ret = store_longtermkey(local, peer, key, key_type, pin_length,
+ dlen, data);
+ break;
+ default:
+ ret = -ENOTSUP;
+ break;
+ }
if (ret == 0) {
device_set_bonded(device, TRUE);
diff --git a/src/event.h b/src/event.h
index 1268edf..5047db1 100644
--- a/src/event.h
+++ b/src/event.h
@@ -39,4 +39,5 @@ int btd_event_user_confirm(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey);
int btd_event_user_passkey(bdaddr_t *sba, bdaddr_t *dba);
int btd_event_user_notify(bdaddr_t *sba, bdaddr_t *dba, uint32_t passkey);
int btd_event_link_key_notify(bdaddr_t *local, bdaddr_t *peer, uint8_t *key,
- uint8_t key_type, uint8_t pin_length);
+ uint8_t key_type, uint8_t pin_length,
+ uint8_t *data, int dlen);
--
1.7.6
---
plugins/hciops.c | 10 ++++++----
1 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/plugins/hciops.c b/plugins/hciops.c
index ecc0e86..43f4f02 100644
--- a/plugins/hciops.c
+++ b/plugins/hciops.c
@@ -955,12 +955,12 @@ static void link_key_notify(int index, void *ptr)
DBG("local auth 0x%02x and remote auth 0x%02x",
conn->loc_auth, conn->rem_auth);
- if (key_type == 0x06) {
+ if (key_type == HCI_LK_CHANGED_COMBINATION) {
/* Some buggy controller combinations generate a changed
* combination key for legacy pairing even when there's no
* previous key */
if (conn->rem_auth == 0xff && old_key_type == 0xff)
- key_type = 0x00;
+ key_type = HCI_LK_COMBINATION;
else if (old_key_type != 0xff)
key_type = old_key_type;
else
@@ -972,7 +972,7 @@ static void link_key_notify(int index, void *ptr)
key_info->type = key_type;
/* Skip the storage check if this is a debug key */
- if (key_type == 0x03)
+ if (key_type == HCI_LK_DEBUG_COMBINATION)
goto done;
/* Store the link key persistently if one of the following is true:
@@ -986,7 +986,9 @@ static void link_key_notify(int index, void *ptr)
* If none of the above match only keep the link key around for
* this connection and set the temporary flag for the device.
*/
- if (key_type < 0x03 || (key_type == 0x06 && old_key_type != 0xff) ||
+ if (key_type < HCI_LK_DEBUG_COMBINATION ||
+ (key_type == HCI_LK_CHANGED_COMBINATION
+ && old_key_type != HCI_LK_INVALID) ||
(conn->loc_auth > 0x01 && conn->rem_auth > 0x01) ||
(conn->loc_auth == 0x02 || conn->loc_auth == 0x03) ||
(conn->rem_auth == 0x02 || conn->rem_auth == 0x03)) {
--
1.7.6
---
lib/hci.h | 14 ++++++++++++++
1 files changed, 14 insertions(+), 0 deletions(-)
diff --git a/lib/hci.h b/lib/hci.h
index 51184ee..d6f79f2 100644
--- a/lib/hci.h
+++ b/lib/hci.h
@@ -293,6 +293,20 @@ enum {
#define HCI_LM_RELIABLE 0x0010
#define HCI_LM_SECURE 0x0020
+/* Link Key types */
+#define HCI_LK_COMBINATION 0x00
+#define HCI_LK_LOCAL_UNIT 0x01
+#define HCI_LK_REMOTE_UNIT 0x02
+#define HCI_LK_DEBUG_COMBINATION 0x03
+#define HCI_LK_UNAUTH_COMBINATION 0x04
+#define HCI_LK_AUTH_COMBINATION 0x05
+#define HCI_LK_CHANGED_COMBINATION 0x06
+#define HCI_LK_INVALID 0xFF
+/* The spec doesn't define types for SMP keys */
+#define HCI_LK_SMP_LTK 0x81
+#define HCI_LK_SMP_IRK 0x82
+#define HCI_LK_SMP_CSRK 0x83
+
/* ----- HCI Commands ----- */
/* Link Control */
--
1.7.6
For now, only the Long Term Key (LTK) is stored.
---
src/storage.c | 34 ++++++++++++++++++++++++++++++++++
src/storage.h | 2 ++
2 files changed, 36 insertions(+), 0 deletions(-)
diff --git a/src/storage.c b/src/storage.c
index 1f3da6e..3414e34 100644
--- a/src/storage.c
+++ b/src/storage.c
@@ -1341,3 +1341,37 @@ device_type_t read_device_type(const bdaddr_t *sba, const bdaddr_t *dba)
return type;
}
+
+int write_longtermkeys(bdaddr_t *local, bdaddr_t *peer, const char *key)
+{
+ char filename[PATH_MAX + 1], addr[18];
+
+ if (!key)
+ return -EINVAL;
+
+ create_filename(filename, PATH_MAX, local, "longtermkeys");
+
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ ba2str(peer, addr);
+ return textfile_put(filename, addr, key);
+}
+
+gboolean has_longtermkeys(bdaddr_t *local, bdaddr_t *peer)
+{
+ char filename[PATH_MAX + 1], addr[18], *str;
+
+ create_filename(filename, PATH_MAX, local, "longtermkeys");
+
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ ba2str(peer, addr);
+
+ str = textfile_caseget(filename, addr);
+ if (str) {
+ free(str);
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/src/storage.h b/src/storage.h
index bb64727..14e072d 100644
--- a/src/storage.h
+++ b/src/storage.h
@@ -87,6 +87,8 @@ int read_device_attributes(const bdaddr_t *sba, textfile_cb func, void *data);
int write_device_type(const bdaddr_t *sba, const bdaddr_t *dba,
device_type_t type);
device_type_t read_device_type(const bdaddr_t *sba, const bdaddr_t *dba);
+int write_longtermkeys(bdaddr_t *local, bdaddr_t *peer, const char *key);
+gboolean has_longtermkeys(bdaddr_t *local, bdaddr_t *peer);
#define PNP_UUID "00001200-0000-1000-8000-00805f9b34fb"
--
1.7.6
The SMP keys are to be communicated to/from userspace using these
messages.
---
doc/mgmt-api.txt | 41 +++++++++++++++++++++++++++++++++++++++++
lib/mgmt.h | 35 +++++++++++++++++++++++++++++++++++
2 files changed, 76 insertions(+), 0 deletions(-)
diff --git a/doc/mgmt-api.txt b/doc/mgmt-api.txt
index e5cf5de..127af94 100644
--- a/doc/mgmt-api.txt
+++ b/doc/mgmt-api.txt
@@ -387,6 +387,28 @@ Write Controller Tracing Filter Command
Protocol_Identifier[i] (16 Octets)
Return Parameters: Status (1 Octet)
+Load SMP Keys Command
+=====================
+
+ Command Code: 0x001F
+ Controller Index: <controller id>
+ Command Parameters: Debug Keys (1 Octet)
+ Key Count (2 Octets)
+ Key1 {
+ Address (6 Octets)
+ Type (1 Octet)
+ PIN_Length (1 Octet)
+ Value (16 Octets)
+ [
+ Encryption Size (1 Octet)
+ Enc. Diversifier (2 Octets)
+ Random Number (8 Octets)
+ ] || [
+ Address Type (1 Octet)
+ ]
+ }
+ Key2 { }
+ ...
Command Complete Event
======================
@@ -552,3 +574,22 @@ Discovering Event
Event Code 0x00014
Controller Index <controller id>
Event Parameters Discovering (1 Octet)
+
+New SMP Key Event
+=================
+
+Event Code 0x0015
+Controller Index <controller id>
+Event Parameters Store Hint (1 Octet)
+ Key {
+ Address (6 Octets)
+ Type (1 Octet)
+ PIN_Length (1 Octet)
+ Value (16 Octets)
+ [ Encryption Size (1 Octet)
+ Enc. Diversifier (2 Octets)
+ Random Number (8 Octets)
+ ] || [
+ Address Type (1 Octet)
+ ]
+ }
diff --git a/lib/mgmt.h b/lib/mgmt.h
index f22434e..0c52850 100644
--- a/lib/mgmt.h
+++ b/lib/mgmt.h
@@ -215,6 +215,35 @@ struct mgmt_cp_unblock_device {
bdaddr_t bdaddr;
} __packed;
+struct smp_ltk_info {
+ uint8_t enc_size;
+ uint16_t ediv;
+ uint8_t rand[8];
+} __packed;
+
+struct smp_irsk_info {
+ uint8_t addr_type;
+} __packed;
+
+struct mgmt_smp_key_info {
+ bdaddr_t bdaddr;
+ uint8_t type;
+ uint8_t pin_len;
+ uint8_t val[16];
+ union {
+ struct smp_ltk_info ltk;
+ struct smp_irsk_info irsk;
+ uint8_t data[0];
+ };
+} __packed;
+
+#define MGMT_OP_LOAD_SMP_KEYS 0x001F
+struct mgmt_cp_load_smp_keys {
+ uint8_t debug_keys;
+ uint16_t key_count;
+ struct mgmt_smp_key_info keys[0];
+} __packed;
+
#define MGMT_EV_CMD_COMPLETE 0x0001
struct mgmt_ev_cmd_complete {
uint16_t opcode;
@@ -305,3 +334,9 @@ struct mgmt_ev_remote_name {
} __packed;
#define MGMT_EV_DISCOVERING 0x0014
+
+#define MGMT_EV_NEW_SMP_KEY 0x0015
+struct mgmt_ev_new_smp_key {
+ uint8_t store_hint;
+ struct mgmt_smp_key_info key;
+} __packed;
--
1.7.6