summaryrefslogtreecommitdiff
path: root/emulator/smp.c
diff options
context:
space:
mode:
authorJohan Hedberg <johan.hedberg@intel.com>2014-01-07 11:34:49 +0200
committerJohan Hedberg <johan.hedberg@intel.com>2014-01-20 16:26:33 +0200
commitfb922ee610876fe8c8bcbb24f741f259d94ac5d4 (patch)
tree09944aeb60927bd54e211ffc755975a315637638 /emulator/smp.c
parent325c0a39bb9e40fadca4fe545b28d5ae3536919d (diff)
downloadbluez-fb922ee610876fe8c8bcbb24f741f259d94ac5d4.tar.gz
emulator/bthost: Add SMP support
Diffstat (limited to 'emulator/smp.c')
-rw-r--r--emulator/smp.c469
1 files changed, 469 insertions, 0 deletions
diff --git a/emulator/smp.c b/emulator/smp.c
new file mode 100644
index 000000000..33799b237
--- /dev/null
+++ b/emulator/smp.c
@@ -0,0 +1,469 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2013-2014 Intel Corporation
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <endian.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/hci.h"
+
+#include "monitor/bt.h"
+#include "bthost.h"
+
+#ifndef SOL_ALG
+#define SOL_ALG 279
+#endif
+
+#ifndef AF_ALG
+#define AF_ALG 38
+#define PF_ALG AF_ALG
+
+#include <linux/types.h>
+
+struct sockaddr_alg {
+ __u16 salg_family;
+ __u8 salg_type[14];
+ __u32 salg_feat;
+ __u32 salg_mask;
+ __u8 salg_name[64];
+};
+
+struct af_alg_iv {
+ __u32 ivlen;
+ __u8 iv[0];
+};
+
+#define ALG_SET_KEY 1
+#define ALG_SET_IV 2
+#define ALG_SET_OP 3
+
+#define ALG_OP_DECRYPT 0
+#define ALG_OP_ENCRYPT 1
+
+#else
+#include <linux/if_alg.h>
+#endif
+
+#define SMP_CID 0x0006
+
+struct smp {
+ struct bthost *bthost;
+ struct smp_conn *conn;
+ int alg_sk;
+};
+
+struct smp_conn {
+ struct smp *smp;
+ uint16_t handle;
+ bool out;
+ uint8_t ia[6];
+ uint8_t ia_type;
+ uint8_t ra[6];
+ uint8_t ra_type;
+ uint8_t tk[16];
+ uint8_t prnd[16];
+ uint8_t rrnd[16];
+ uint8_t pcnf[16];
+ uint8_t preq[7];
+ uint8_t prsp[7];
+ uint8_t ltk[16];
+};
+
+static int alg_setup(void)
+{
+ struct sockaddr_alg salg;
+ int sk;
+
+ sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
+ if (sk < 0) {
+ printf("socket(AF_ALG): %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(&salg, 0, sizeof(salg));
+ salg.salg_family = AF_ALG;
+ strcpy((char *) salg.salg_type, "skcipher");
+ strcpy((char *) salg.salg_name, "ecb(aes)");
+
+ if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) {
+ printf("bind(AF_ALG): %s\n", strerror(errno));
+ close(sk);
+ return -1;
+ }
+
+ return sk;
+}
+
+static int alg_new(int alg_sk, const uint8_t *key)
+{
+ int sk;
+
+ if (setsockopt(alg_sk, SOL_ALG, ALG_SET_KEY, key, 16) < 0) {
+ printf("setsockopt(ALG_SET_KEY): %s\n", strerror(errno));
+ return -1;
+ }
+
+ sk = accept4(alg_sk, NULL, 0, SOCK_CLOEXEC);
+ if (sk < 0) {
+ printf("accept4(AF_ALG): %s\n", strerror(errno));
+ return -1;
+ }
+
+ return sk;
+}
+
+static int alg_encrypt(int sk, uint8_t in[16], uint8_t out[16])
+{
+ __u32 alg_op = ALG_OP_ENCRYPT;
+ char cbuf[CMSG_SPACE(sizeof(alg_op))];
+ struct cmsghdr *cmsg;
+ struct msghdr msg;
+ struct iovec iov;
+ int ret;
+
+ memset(cbuf, 0, sizeof(cbuf));
+ memset(&msg, 0, sizeof(msg));
+
+ msg.msg_control = cbuf;
+ msg.msg_controllen = sizeof(cbuf);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_ALG;
+ cmsg->cmsg_type = ALG_SET_OP;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(alg_op));
+ memcpy(CMSG_DATA(cmsg), &alg_op, sizeof(alg_op));
+
+ iov.iov_base = in;
+ iov.iov_len = 16;
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ ret = sendmsg(sk, &msg, 0);
+ if (ret < 0) {
+ printf("sendmsg(AF_ALG): %s\n", strerror(errno));
+ return ret;
+ }
+
+ ret = read(sk, out, 16);
+ if (ret < 0)
+ printf("read(AF_ALG): %s\n", strerror(errno));
+
+ return 0;
+}
+
+static int smp_e(int alg_sk, uint8_t key[16], uint8_t in[16], uint8_t out[16])
+{
+ int sk, err;
+
+ sk = alg_new(alg_sk, key);
+ if (sk < 0)
+ return sk;
+
+ err = alg_encrypt(sk, in, out);
+
+ close(sk);
+
+ return err;
+}
+
+static inline void swap128(const uint8_t src[16], uint8_t dst[16])
+{
+ int i;
+ for (i = 0; i < 16; i++)
+ dst[15 - i] = src[i];
+}
+
+static inline void swap56(const uint8_t src[7], uint8_t dst[7])
+{
+ int i;
+ for (i = 0; i < 7; i++)
+ dst[6 - i] = src[i];
+}
+
+typedef struct {
+ uint64_t a, b;
+} u128;
+
+static inline void u128_xor(void *r, const void *p, const void *q)
+{
+ const u128 pp = bt_get_unaligned((const u128 *) p);
+ const u128 qq = bt_get_unaligned((const u128 *) q);
+ u128 rr;
+
+ rr.a = pp.a ^ qq.a;
+ rr.b = pp.b ^ qq.b;
+
+ bt_put_unaligned(rr, (u128 *) r);
+}
+
+static int smp_c1(struct smp_conn *conn, uint8_t rnd[16], uint8_t res[16])
+{
+ uint8_t p1[16], p2[16];
+ int err;
+
+ memset(p1, 0, 16);
+
+ /* p1 = pres || preq || _rat || _iat */
+ swap56(conn->prsp, p1);
+ swap56(conn->preq, p1 + 7);
+ p1[14] = conn->ra_type;
+ p1[15] = conn->ia_type;
+
+ memset(p2, 0, 16);
+
+ /* p2 = padding || ia || ra */
+ baswap((bdaddr_t *) (p2 + 4), (bdaddr_t *) conn->ia);
+ baswap((bdaddr_t *) (p2 + 10), (bdaddr_t *) conn->ra);
+
+ /* res = r XOR p1 */
+ u128_xor((u128 *) res, (u128 *) rnd, (u128 *) p1);
+
+ /* res = e(k, res) */
+ err = smp_e(conn->smp->alg_sk, conn->tk, res, res);
+ if (err)
+ return err;
+
+ /* res = res XOR p2 */
+ u128_xor((u128 *) res, (u128 *) res, (u128 *) p2);
+
+ /* res = e(k, res) */
+ return smp_e(conn->smp->alg_sk, conn->tk, res, res);
+}
+
+static int smp_s1(struct smp_conn *conn, uint8_t r1[16], uint8_t r2[16],
+ uint8_t res[16])
+{
+ memcpy(res, r1 + 8, 8);
+ memcpy(res + 8, r2 + 8, 8);
+
+ return smp_e(conn->smp->alg_sk, conn->tk, res, res);
+}
+
+static bool verify_random(struct smp_conn *conn, const uint8_t rnd[16])
+{
+ uint8_t confirm[16], res[16], key[16];
+ int err;
+
+ err = smp_c1(conn, conn->rrnd, res);
+ if (err < 0)
+ return false;
+
+ swap128(res, confirm);
+
+ if (memcmp(conn->pcnf, confirm, sizeof(conn->pcnf) != 0)) {
+ printf("Confirmation values don't match\n");
+ return false;
+ }
+
+ if (conn->out) {
+ smp_s1(conn, conn->rrnd, conn->prnd, key);
+ swap128(key, conn->ltk);
+ bthost_le_start_encrypt(conn->smp->bthost, conn->handle,
+ conn->ltk);
+ } else {
+ smp_s1(conn, conn->prnd, conn->rrnd, key);
+ swap128(key, conn->ltk);
+ }
+
+ return true;
+}
+
+static void pairing_req(struct smp_conn *conn, const void *data, uint16_t len)
+{
+ struct bthost *bthost = conn->smp->bthost;
+ static const uint8_t rsp[] = { 0x02, /* Pairing Response */
+ 0x03, /* NoInputNoOutput */
+ 0x00, /* OOB Flag */
+ 0x01, /* Bonding - no MITM */
+ 0x10, /* Max key size */
+ 0x00, /* Init. key dist. */
+ 0x01, /* Rsp. key dist. */
+ };
+
+ memcpy(conn->preq, data, sizeof(conn->preq));
+ memcpy(conn->prsp, rsp, sizeof(rsp));
+
+ bthost_send_cid(bthost, conn->handle, SMP_CID, rsp, sizeof(rsp));
+}
+
+static void pairing_rsp(struct smp_conn *conn, const void *data, uint16_t len)
+{
+ memcpy(conn->prsp, data, sizeof(conn->prsp));
+
+ /*bthost_send_cid(bthost, handle, SMP_CID, pdu, req->send_len);*/
+}
+
+static void pairing_cfm(struct smp_conn *conn, const void *data, uint16_t len)
+{
+ struct bthost *bthost = conn->smp->bthost;
+ const uint8_t *cfm = data;
+ uint8_t rsp[17];
+ uint8_t res[16];
+
+ memcpy(conn->pcnf, data + 1, 16);
+
+ rsp[0] = cfm[0];
+ smp_c1(conn, conn->prnd, res);
+ swap128(res, &rsp[1]);
+
+ bthost_send_cid(bthost, conn->handle, SMP_CID, rsp, sizeof(rsp));
+}
+
+static void pairing_rnd(struct smp_conn *conn, const void *data, uint16_t len)
+{
+ struct bthost *bthost = conn->smp->bthost;
+ const uint8_t *rnd = data;
+ uint8_t rsp[17];
+
+ swap128(data + 1, conn->rrnd);
+
+ if (!verify_random(conn, data + 1))
+ return;
+
+ rsp[0] = rnd[0];
+ swap128(conn->prnd, &rsp[1]);
+
+ bthost_send_cid(bthost, conn->handle, SMP_CID, rsp, sizeof(rsp));
+}
+
+void smp_pair(void *conn_data)
+{
+ struct smp_conn *conn = conn_data;
+ struct bthost *bthost = conn->smp->bthost;
+ const uint8_t smp_pair_req[] = { 0x01, /* Pairing Request */
+ 0x03, /* NoInputNoOutput */
+ 0x00, /* OOB Flag */
+ 0x01, /* Bonding - no MITM */
+ 0x10, /* Max key size */
+ 0x00, /* Init. key dist. */
+ 0x01, /* Rsp. key dist. */
+ };
+
+ memcpy(conn->preq, smp_pair_req, sizeof(smp_pair_req));
+
+ bthost_send_cid(bthost, conn->handle, SMP_CID, smp_pair_req,
+ sizeof(smp_pair_req));
+}
+
+void smp_data(void *conn_data, const void *data, uint16_t len)
+{
+ struct smp_conn *conn = conn_data;
+ uint8_t opcode;
+
+ if (len < 1) {
+ printf("Received too small SMP PDU\n");
+ return;
+ }
+
+ opcode = *((const uint8_t *) data);
+
+ switch (opcode) {
+ case 0x01: /* Pairing Request */
+ pairing_req(conn, data, len);
+ break;
+ case 0x02: /* Pairing Response */
+ pairing_rsp(conn, data, len);
+ break;
+ case 0x03: /* Pairing Confirm */
+ pairing_cfm(conn, data, len);
+ break;
+ case 0x04: /* Pairing Random */
+ pairing_rnd(conn, data, len);
+ break;
+ default:
+ break;
+ }
+}
+
+void *smp_conn_add(void *smp_data, uint16_t handle, const uint8_t *ia,
+ const uint8_t *ra, bool conn_init)
+{
+ struct smp *smp = smp_data;
+ struct smp_conn *conn;
+
+ conn = malloc(sizeof(struct smp_conn));
+ if (!conn)
+ return NULL;
+
+ memset(conn, 0, sizeof(*conn));
+
+ conn->smp = smp;
+ conn->handle = handle;
+ conn->out = conn_init;
+
+ conn->ia_type = LE_PUBLIC_ADDRESS;
+ conn->ra_type = LE_PUBLIC_ADDRESS;
+ memcpy(conn->ia, ia, 6);
+ memcpy(conn->ra, ra, 6);
+
+ return conn;
+}
+
+void smp_conn_del(void *conn_data)
+{
+ struct smp_conn *conn = conn_data;
+
+ free(conn);
+}
+
+void *smp_start(struct bthost *bthost)
+{
+ struct smp *smp;
+
+ smp = malloc(sizeof(struct smp));
+ if (!smp)
+ return NULL;
+
+ memset(smp, 0, sizeof(*smp));
+
+ smp->alg_sk = alg_setup();
+ if (smp->alg_sk < 0) {
+ free(smp);
+ return NULL;
+ }
+
+ smp->bthost = bthost;
+
+ return smp;
+}
+
+void smp_stop(void *smp_data)
+{
+ struct smp *smp = smp_data;
+
+ close(smp->alg_sk);
+
+ free(smp);
+}