Skip to content

Commit 2790ef6

Browse files
committed
Extend FIDO2 BLE support also for Linux
For Windows it was already added via gh#336, so let's also add it for Linux. Unpaired devices are ignored, the user has to pair independently of libfido use using the bluetooth manager provided by the desktop environment.
1 parent 854053f commit 2790ef6

File tree

11 files changed

+927
-5
lines changed

11 files changed

+927
-5
lines changed

.github/workflows/alpine_builds.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
apk -q update
2929
apk add build-base clang clang-analyzer cmake coreutils eudev-dev
3030
apk add git linux-headers openssl-dev sudo zlib-dev pcsc-lite-dev \
31-
libcbor-dev
31+
libcbor-dev elogind-dev
3232
- name: fix permissions on workdir
3333
run: chown root:wheel "${GITHUB_WORKSPACE}"
3434
- name: checkout libfido2

.github/workflows/codeql-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
run: |
3434
sudo apt -q update
3535
sudo apt install -q -y libcbor-dev libudev-dev libz-dev original-awk \
36-
libpcsclite-dev
36+
libpcsclite-dev libsystemd-dev
3737
./.actions/build-linux-gcc
3838
- name: perform codeql analysis
3939
uses: github/codeql-action/analyze@v2

.github/workflows/linux_builds.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
run: |
3939
sudo apt -q update
4040
sudo apt install -q -y libcbor-dev libudev-dev libz-dev \
41-
original-awk mandoc libpcsclite-dev
41+
original-awk mandoc libpcsclite-dev libsystemd-dev
4242
- name: compiler
4343
env:
4444
CC: ${{ matrix.cc }}

.github/workflows/linux_fuzz.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- name: dependencies
2929
run: |
3030
sudo apt -q update
31-
sudo apt install -q -y libudev-dev libpcsclite-dev
31+
sudo apt install -q -y libudev-dev libpcsclite-dev libsystemd-dev
3232
- name: compiler
3333
env:
3434
CC: ${{ matrix.cc }}

.github/workflows/openssl3.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
run: |
3636
sudo apt -q update
3737
sudo apt install -q -y libcbor-dev libudev-dev libz-dev \
38-
original-awk mandoc libpcsclite-dev
38+
original-awk mandoc libpcsclite-dev libsystemd-dev
3939
sudo apt remove -y libssl-dev
4040
if [ "${CC%-*}" == "clang" ]; then
4141
sudo ./.actions/setup_clang "${CC}"

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ option(USE_HIDAPI "Use hidapi as the HID backend" OFF)
4444
option(USE_PCSC "Enable experimental PCSC support" ON)
4545
option(USE_WINHELLO "Abstract Windows Hello as a FIDO device" ON)
4646
option(NFC_LINUX "Enable NFC support on Linux" ON)
47+
option(BLUETOOTH_LINUX "Enable Bluetooth support on Linux" ON)
4748

4849
add_definitions(-D_FIDO_MAJOR=${FIDO_MAJOR})
4950
add_definitions(-D_FIDO_MINOR=${FIDO_MINOR})
@@ -216,6 +217,7 @@ if(MSVC)
216217
add_definitions(-DUSE_WINHELLO)
217218
endif()
218219
set(NFC_LINUX OFF)
220+
set(BLUETOOTH_LINUX OFF)
219221
else()
220222
include(FindPkgConfig)
221223
pkg_search_module(CBOR libcbor)
@@ -255,6 +257,7 @@ else()
255257
endif()
256258
else()
257259
set(NFC_LINUX OFF)
260+
set(BLUETOOTH_LINUX OFF)
258261
endif()
259262

260263
if(MINGW)
@@ -285,6 +288,12 @@ else()
285288
add_definitions(-DUSE_NFC)
286289
endif()
287290

291+
if(BLUETOOTH_LINUX)
292+
add_definitions(-DUSE_BLUETOOTH)
293+
pkg_search_module(SYSTEMD libsystemd REQUIRED)
294+
set(BLUETOOTH_LIBRARIES ${SYSTEMD_LIBRARIES})
295+
endif()
296+
288297
if(WIN32)
289298
if(USE_WINHELLO)
290299
add_definitions(-DUSE_WINHELLO)
@@ -399,6 +408,7 @@ include_directories(${ZLIB_INCLUDE_DIRS})
399408
link_directories(${CBOR_LIBRARY_DIRS})
400409
link_directories(${CRYPTO_LIBRARY_DIRS})
401410
link_directories(${HIDAPI_LIBRARY_DIRS})
411+
link_directories(${BLUETOOTH_LIBRARY_DIRS})
402412
link_directories(${PCSC_LIBRARY_DIRS})
403413
link_directories(${UDEV_LIBRARY_DIRS})
404414
link_directories(${ZLIB_LIBRARY_DIRS})
@@ -468,6 +478,7 @@ message(STATUS "USE_HIDAPI: ${USE_HIDAPI}")
468478
message(STATUS "USE_PCSC: ${USE_PCSC}")
469479
message(STATUS "USE_WINHELLO: ${USE_WINHELLO}")
470480
message(STATUS "NFC_LINUX: ${NFC_LINUX}")
481+
message(STATUS "BLUETOOTH_LINUX: ${BLUETOOTH_LINUX}")
471482

472483
if(BUILD_TESTS)
473484
enable_testing()

src/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ if(FUZZ)
5151
list(APPEND FIDO_SOURCES ../fuzz/wrap.c)
5252
endif()
5353

54+
if(BLUETOOTH_LINUX)
55+
list(APPEND FIDO_SOURCES bluetooth.c bluetooth_linux.c)
56+
endif()
57+
5458
if(NFC_LINUX)
5559
list(APPEND FIDO_SOURCES netlink.c nfc.c nfc_linux.c)
5660
endif()
@@ -123,6 +127,7 @@ list(APPEND TARGET_LIBRARIES
123127
${HIDAPI_LIBRARIES}
124128
${ZLIB_LIBRARIES}
125129
${PCSC_LIBRARIES}
130+
${BLUETOOTH_LIBRARIES}
126131
)
127132

128133
# static library

src/bluetooth.c

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#include "fido.h"
2+
#include "fido/param.h"
3+
4+
#define CTAPBLE_PING 0x81
5+
#define CTAPBLE_KEEPALIVE 0x82
6+
#define CTAPBLE_MSG 0x83
7+
#define CTAPBLE_CANCEL 0xBE
8+
#define CTAPBLE_ERROR 0xBF
9+
10+
static int
11+
fido_bluetooth_fragment_tx(fido_dev_t *d, uint8_t cmd, const u_char *buf, size_t count)
12+
{
13+
size_t fragment_len = fido_bluetooth_get_cp_size(d);
14+
u_char *frag_buf;
15+
size_t payload;
16+
uint8_t seqnum;
17+
18+
if (fragment_len <= 3)
19+
return -1;
20+
21+
payload = fragment_len - 3;
22+
frag_buf = calloc(1, fragment_len);
23+
if (!frag_buf)
24+
return -1;
25+
26+
frag_buf[0] = cmd;
27+
frag_buf[1] = (count >> 8) & 0xff;
28+
frag_buf[2] = count & 0xff;
29+
if (payload > count)
30+
payload = count;
31+
32+
memcpy(frag_buf + 3, buf, payload);
33+
d->io.write(d->io_handle, frag_buf, payload + 3);
34+
35+
count -= payload;
36+
seqnum = 0;
37+
buf += payload;
38+
while (count > 0) {
39+
payload = fragment_len - 1;
40+
if (payload > count)
41+
payload = count;
42+
43+
memcpy(frag_buf + 1, buf, payload);
44+
frag_buf[0] = seqnum;
45+
if (d->io.write(d->io_handle, frag_buf, payload + 1) < 0)
46+
break;
47+
48+
count -= payload;
49+
buf += payload;
50+
seqnum++;
51+
seqnum &= 0x7F;
52+
}
53+
54+
free(frag_buf);
55+
56+
if (count > 0)
57+
return -1;
58+
59+
return 0;
60+
}
61+
62+
int
63+
fido_bluetooth_tx(fido_dev_t *d, uint8_t cmd, const u_char *buf, size_t count)
64+
{
65+
switch(cmd) {
66+
case CTAP_CMD_INIT:
67+
return FIDO_OK;
68+
case CTAP_CMD_CBOR:
69+
case CTAP_CMD_MSG:
70+
return fido_bluetooth_fragment_tx(d, CTAPBLE_MSG, buf, count);
71+
break;
72+
}
73+
if (cmd == CTAP_CMD_INIT)
74+
return FIDO_OK;
75+
76+
77+
return FIDO_ERR_INTERNAL;
78+
}
79+
80+
static int
81+
rx_init(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
82+
{
83+
(void)ms;
84+
fido_ctap_info_t *attr = (fido_ctap_info_t *)buf;
85+
if (count != sizeof(*attr)) {
86+
fido_log_debug("%s: count=%zu", __func__, count);
87+
return -1;
88+
}
89+
90+
memset(attr, 0, sizeof(*attr));
91+
92+
/* we allow only FIDO2 devices for now for simplicity */
93+
attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG;
94+
memcpy(&attr->nonce, &d->nonce, sizeof(attr->nonce));
95+
96+
return (int)count;
97+
}
98+
99+
static int
100+
rx_fragments(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
101+
{
102+
size_t fragment_len = fido_bluetooth_get_cp_size(d);
103+
uint8_t *reply;
104+
uint8_t seq;
105+
size_t payload;
106+
size_t reply_length;
107+
int ret;
108+
if (fragment_len <= 3) {
109+
return -1;
110+
}
111+
reply = calloc(1, fragment_len);
112+
payload = fragment_len - 3;
113+
if (count < payload)
114+
payload = count;
115+
116+
do {
117+
ret = d->io.read(d->io_handle, reply, payload + 3, ms);
118+
if (ret <= 0)
119+
goto out;
120+
} while (reply[0] == CTAPBLE_KEEPALIVE);
121+
122+
if ((reply[0] != CTAPBLE_MSG) || (ret <= 3)) {
123+
ret = -1;
124+
goto out;
125+
}
126+
ret -= 3;
127+
128+
reply_length = ((size_t)reply[1]) << 8 | reply[2];
129+
if (reply_length > count)
130+
reply_length = count;
131+
132+
if (reply_length < count)
133+
count = reply_length;
134+
135+
memcpy(buf, reply + 3, (size_t)ret);
136+
count -= (size_t)ret;
137+
buf += ret;
138+
seq = 0;
139+
140+
while(count > 0) {
141+
payload = fragment_len - 1;
142+
if (count < payload)
143+
payload = count;
144+
145+
ret = d->io.read(d->io_handle, reply, payload + 1, ms);
146+
if (ret <= 1) {
147+
if (ret >= 0)
148+
ret = -1;
149+
goto out;
150+
}
151+
ret--;
152+
if (reply[0] != seq) {
153+
ret = -1;
154+
goto out;
155+
}
156+
memcpy(buf, reply + 1, (size_t) ret);
157+
158+
seq++;
159+
count -= (size_t) ret;
160+
buf += ret;
161+
}
162+
ret = (int)reply_length;
163+
out:
164+
explicit_bzero(reply, fragment_len);
165+
free(reply);
166+
return ret;
167+
}
168+
169+
int
170+
fido_bluetooth_rx(fido_dev_t *d, uint8_t cmd, u_char *buf, size_t count, int ms)
171+
{
172+
switch(cmd) {
173+
case CTAP_CMD_INIT:
174+
return rx_init(d, buf, count, ms);
175+
case CTAP_CMD_CBOR:
176+
return rx_fragments(d, buf, count, ms);
177+
default:
178+
return FIDO_ERR_INTERNAL;
179+
}
180+
}
181+
182+
bool
183+
fido_is_bluetooth(const char *path)
184+
{
185+
return !strncmp(path, FIDO_BLUETOOTH_PREFIX, strlen(FIDO_BLUETOOTH_PREFIX));
186+
}
187+
188+
int
189+
fido_dev_set_bluetooth(fido_dev_t *d)
190+
{
191+
if (d->io_handle != NULL) {
192+
fido_log_debug("%s: device open", __func__);
193+
return -1;
194+
}
195+
d->io_own = true;
196+
d->io = (fido_dev_io_t) {
197+
fido_bluetooth_open,
198+
fido_bluetooth_close,
199+
fido_bluetooth_read,
200+
fido_bluetooth_write,
201+
};
202+
d->transport = (fido_dev_transport_t) {
203+
fido_bluetooth_rx,
204+
fido_bluetooth_tx,
205+
};
206+
207+
return 0;
208+
}
209+

0 commit comments

Comments
 (0)