Store debug keys in runtime memory

This patch adds the possibility of using debug keys for consequtive
connections by storing them in runtime memory instead of discarding them
after the connection. This functionality can be enabled using a new
DebugKeys boolean parameter in main.conf.
diff --git a/src/dbus-hci.c b/src/dbus-hci.c
index a1f51ea..19748a7 100644
--- a/src/dbus-hci.c
+++ b/src/dbus-hci.c
@@ -61,7 +61,7 @@
 
 static DBusConnection *connection = NULL;
 
-static gboolean get_adapter_and_device(bdaddr_t *src, bdaddr_t *dst,
+gboolean get_adapter_and_device(bdaddr_t *src, bdaddr_t *dst,
 					struct btd_adapter **adapter,
 					struct btd_device **device,
 					gboolean create)
@@ -669,15 +669,19 @@
 	struct btd_device *device;
 	struct btd_adapter *adapter;
 	uint8_t local_auth = 0xff, remote_auth, new_key_type;
-	gboolean bonding, stored;
+	gboolean bonding, temporary = FALSE;
 
 	if (!get_adapter_and_device(local, peer, &adapter, &device, TRUE))
 		return -ENODEV;
 
-	if (key_type == 0x06 && old_key_type != 0xff)
-		new_key_type = old_key_type;
-	else
-		new_key_type = key_type;
+	new_key_type = key_type;
+
+	if (key_type == 0x06) {
+		if (device_get_debug_key(device, NULL))
+			old_key_type = 0x03;
+		if (old_key_type != 0xff)
+			new_key_type = old_key_type;
+	}
 
 	get_auth_requirements(local, peer, &local_auth);
 	remote_auth = device_get_auth(device);
@@ -686,7 +690,12 @@
 	DBG("local auth 0x%02x and remote auth 0x%02x",
 					local_auth, remote_auth);
 
-	/* Only store the link key if one of the following is true:
+	/* Clear any previous debug key */
+	device_set_debug_key(device, NULL);
+
+	/* Store the link key only in runtime memory if it's a debug
+	 * key, else store the link key persistently if one of the
+	 * following is true:
 	 * 1. this is a legacy link key
 	 * 2. this is a changed combination key and there was a previously
 	 *    stored one
@@ -694,8 +703,14 @@
 	 * 4. the local side had dedicated bonding as a requirement
 	 * 5. the remote side is using dedicated bonding since in that case
 	 *    also the local requirements are set to dedicated bonding
+	 * 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 (new_key_type == 0x03) {
+		DBG("Storing debug key in runtime memory");
+		device_set_debug_key(device, key);
+	} else if (key_type < 0x03 ||
+				(key_type == 0x06 && old_key_type != 0xff) ||
 				(local_auth > 0x01 && remote_auth > 0x01) ||
 				(local_auth == 0x02 || local_auth == 0x03) ||
 				(remote_auth == 0x02 || remote_auth == 0x03)) {
@@ -709,10 +724,8 @@
 			error("write_link_key: %s (%d)", strerror(-err), -err);
 			return err;
 		}
-
-		stored = TRUE;
 	} else
-		stored = FALSE;
+		temporary = TRUE;
 
 	/* If this is not the first link key set a flag so a subsequent auth
 	 * complete event doesn't trigger SDP */
@@ -724,7 +737,7 @@
 	else if (!bonding && old_key_type == 0xff)
 		hcid_dbus_bonding_process_complete(local, peer, 0);
 
-	if (!stored)
+	if (temporary)
 		device_set_temporary(device, TRUE);
 
 	return 0;
diff --git a/src/dbus-hci.h b/src/dbus-hci.h
index 280f390..a29dd53 100644
--- a/src/dbus-hci.h
+++ b/src/dbus-hci.h
@@ -45,6 +45,11 @@
 				uint8_t *key, uint8_t key_type,
 				int pin_length, uint8_t old_key_type);
 
+gboolean get_adapter_and_device(bdaddr_t *src, bdaddr_t *dst,
+					struct btd_adapter **adapter,
+					struct btd_device **device,
+					gboolean create);
+
 DBusMessage *new_authentication_return(DBusMessage *msg, uint8_t status);
 
 const char *class_to_icon(uint32_t class);
diff --git a/src/device.c b/src/device.c
index de5e8e9..170902e 100644
--- a/src/device.c
+++ b/src/device.c
@@ -143,6 +143,9 @@
 
 	gboolean	authorizing;
 	gint		ref;
+
+	gboolean	has_debug_key;
+	uint8_t		debug_key[16];
 };
 
 static uint16_t uuid_list[] = {
@@ -2430,6 +2433,30 @@
 	return find_record_in_list(device->tmp_records, uuid);
 }
 
+gboolean device_set_debug_key(struct btd_device *device, uint8_t *key)
+{
+	if (key == NULL) {
+		device->has_debug_key = FALSE;
+		return TRUE;
+	}
+
+	memcpy(device->debug_key, key, 16);
+	device->has_debug_key = TRUE;
+
+	return TRUE;
+}
+
+gboolean device_get_debug_key(struct btd_device *device, uint8_t *key)
+{
+	if (!device->has_debug_key)
+		return FALSE;
+
+	if (key != NULL)
+		memcpy(key, device->debug_key, 16);
+
+	return TRUE;
+}
+
 int btd_register_device_driver(struct btd_device_driver *driver)
 {
 	device_drivers = g_slist_append(device_drivers, driver);
diff --git a/src/device.h b/src/device.h
index 79f44f8..5f75e61 100644
--- a/src/device.h
+++ b/src/device.h
@@ -79,6 +79,8 @@
 gboolean device_is_authorizing(struct btd_device *device);
 void device_set_authorizing(struct btd_device *device, gboolean auth);
 void device_set_renewed_key(struct btd_device *device, gboolean renewed);
+gboolean device_set_debug_key(struct btd_device *device, uint8_t *key);
+gboolean device_get_debug_key(struct btd_device *device, uint8_t *key);
 void device_add_connection(struct btd_device *device, DBusConnection *conn,
 				uint16_t handle);
 void device_remove_connection(struct btd_device *device, DBusConnection *conn,
diff --git a/src/hcid.h b/src/hcid.h
index fd80309..9c5f1d6 100644
--- a/src/hcid.h
+++ b/src/hcid.h
@@ -60,6 +60,7 @@
 	gboolean	remember_powered;
 	gboolean	reverse_sdp;
 	gboolean	name_resolv;
+	gboolean	debug_keys;
 
 	uint8_t		scan;
 	uint8_t		mode;
diff --git a/src/main.c b/src/main.c
index ba18523..b4e2219 100644
--- a/src/main.c
+++ b/src/main.c
@@ -201,6 +201,13 @@
 	else
 		main_opts.name_resolv = boolean;
 
+	boolean = g_key_file_get_boolean(config, "General",
+						"DebugKeys", &err);
+	if (err)
+		g_clear_error(&err);
+	else
+		main_opts.debug_keys = boolean;
+
 	main_opts.link_mode = HCI_LM_ACCEPT;
 
 	main_opts.link_policy = HCI_LP_RSWITCH | HCI_LP_SNIFF |
diff --git a/src/main.conf b/src/main.conf
index b252a82..59026df 100644
--- a/src/main.conf
+++ b/src/main.conf
@@ -50,3 +50,8 @@
 # Enable name resolving after inquiry. Set it to 'false' if you don't need
 # remote devices name and want shorter discovery cycle. Defaults to 'true'.
 NameResolving = true
+
+# Enable runtime persistency of debug link keys. Default is false which
+# makes debug link keys valid only for the duration of the connection
+# that they were created for.
+DebugKeys = false
diff --git a/src/security.c b/src/security.c
index 8eebb9b..e8c463c 100644
--- a/src/security.c
+++ b/src/security.c
@@ -51,6 +51,7 @@
 #include "textfile.h"
 
 #include "adapter.h"
+#include "device.h"
 #include "dbus-hci.h"
 #include "storage.h"
 #include "manager.h"
@@ -301,12 +302,17 @@
 
 static void link_key_request(int dev, bdaddr_t *sba, bdaddr_t *dba)
 {
+	struct btd_adapter *adapter;
+	struct btd_device *device;
 	struct hci_auth_info_req req;
 	unsigned char key[16];
 	char sa[18], da[18];
 	uint8_t type;
 	int err;
 
+	if (!get_adapter_and_device(sba, dba, &adapter, &device, FALSE))
+		device = NULL;
+
 	ba2str(sba, sa); ba2str(dba, da);
 	info("link_key_request (sba=%s, dba=%s)", sa, da);
 
@@ -323,26 +329,30 @@
 
 	DBG("kernel auth requirements = 0x%02x", req.type);
 
-	err = read_link_key(sba, dba, key, &type);
-	if (err < 0) {
+	if (main_opts.debug_keys && device && device_get_debug_key(device, key))
+		type = 0x03;
+	else if (read_link_key(sba, dba, key, &type) < 0 || type == 0x03) {
 		/* Link key not found */
 		hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY, 6, dba);
-	} else {
-		/* Link key found */
+		return;
+	}
+
+	/* Link key found */
+
+	DBG("link key type = 0x%02x", type);
+
+	/* Don't use unauthenticated combination keys if MITM is
+	 * required */
+	if (type == 0x04 && req.type != 0xff && (req.type & 0x01))
+		hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_NEG_REPLY,
+								6, dba);
+	else {
 		link_key_reply_cp lr;
+
 		memcpy(lr.link_key, key, 16);
 		bacpy(&lr.bdaddr, dba);
 
-		DBG("stored link key type = 0x%02x", type);
-
-		/* Don't use debug link keys (0x03) and also don't use
-		 * unauthenticated combination keys if MITM is required */
-		if (type == 0x03 || (type == 0x04 && req.type != 0xff &&
-							(req.type & 0x01)))
-			hci_send_cmd(dev, OGF_LINK_CTL,
-					OCF_LINK_KEY_NEG_REPLY, 6, dba);
-		else
-			hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_REPLY,
+		hci_send_cmd(dev, OGF_LINK_CTL, OCF_LINK_KEY_REPLY,
 						LINK_KEY_REPLY_CP_SIZE, &lr);
 	}
 }