41constexpr const char*
TAG =
"GPG_BLE_XSIG";
48constexpr uint8_t kSvcUuid[16] = {
49 0x01, 0x1A, 0x8B, 0x6A, 0x9D, 0x4C, 0x6E, 0x9A,
50 0x7A, 0x4D, 0x5D, 0x8B, 0x30, 0x1F, 0x2F, 0x8E,
52constexpr uint8_t kRxUuid[16] = {
53 0x01, 0x1A, 0x8B, 0x6A, 0x9D, 0x4C, 0x6E, 0x9A,
54 0x7A, 0x4D, 0x5D, 0x8B, 0x31, 0x1F, 0x2F, 0x8E,
56constexpr uint8_t kStatusUuid[16] = {
57 0x01, 0x1A, 0x8B, 0x6A, 0x9D, 0x4C, 0x6E, 0x9A,
58 0x7A, 0x4D, 0x5D, 0x8B, 0x32, 0x1F, 0x2F, 0x8E,
61constexpr uint8_t kOpcWriteStart = 0x11;
62constexpr uint8_t kOpcWriteCont = 0x12;
63constexpr uint8_t kOpcWriteEnd = 0x13;
64constexpr uint8_t kOpcDataStart = 0x91;
65constexpr uint8_t kOpcDataCont = 0x92;
66constexpr uint8_t kOpcDataEnd = 0x93;
68constexpr uint8_t kStatusOk = 0x00;
69constexpr uint8_t kStatusBadPayload = 0x01;
70constexpr uint8_t kStatusStoreFull = 0x02;
71constexpr uint8_t kStatusInternalErr = 0x03;
74constexpr size_t kMinPayloadLen = 1 + 1 + 32 + 20 + 1 + 1;
75constexpr size_t kMaxPayloadLen = 1 + 1 + 64 + 20 + 1 + 63;
79 uint16_t conn_handle = 0xFFFF;
80 size_t expected_len = 0;
81 size_t received_len = 0;
82 uint8_t buf[kMaxPayloadLen];
85EXT_RAM_BSS_ATTR RxSession s_rx = {};
88uint16_t s_rx_handle = 0;
89uint16_t s_status_handle = 0;
90cdc::hal::GattCharacteristic s_chars[2] = {};
91cdc::hal::GattServiceDef s_svc = {};
97 s_rx.conn_handle = 0xFFFF;
98 s_rx.expected_len = 0;
99 s_rx.received_len = 0;
102void sendStatus(uint16_t conn_handle, uint8_t code) {
104 if (!ble || s_status_handle == 0)
return;
105 const uint8_t frame[3] = {kOpcDataEnd, 0x01, code};
106 ble->sendNotification(conn_handle, s_status_handle, frame,
sizeof(frame));
109bool parsePayloadIntoKey(
const uint8_t* data,
size_t len,
gpg_recv_key_t* out) {
110 if (!data || !out)
return false;
111 if (len < kMinPayloadLen)
return false;
114 const uint8_t
curve = data[off++];
117 const uint8_t pubkey_len = data[off++];
120 if (off + pubkey_len + 20 + 1 > len)
return false;
122 std::memset(out, 0,
sizeof(*out));
124 out->pubkey_len = pubkey_len;
125 std::memcpy(out->pubkey, data + off, pubkey_len);
127 std::memcpy(out->fingerprint_v4, data + off, 20);
130 const uint8_t uid_len = data[off++];
131 if (uid_len > 63 || off + uid_len > len)
return false;
132 std::memcpy(out->user_id, data + off, uid_len);
133 out->user_id[uid_len] =
'\0';
135 out->received_at =
static_cast<uint32_t
>(std::time(
nullptr));
141 out->received_at, out->fingerprint_v5);
147 if (!parsePayloadIntoKey(s_rx.buf, s_rx.received_len, &key)) {
148 LOG_W(
TAG,
"Rejecting malformed payload (%u bytes)",
149 static_cast<unsigned>(s_rx.received_len));
150 sendStatus(s_rx.conn_handle, kStatusBadPayload);
155 sendStatus(s_rx.conn_handle, kStatusStoreFull);
159 sendStatus(s_rx.conn_handle, kStatusOk);
160 if (s_callback) s_callback(key);
161 LOG_I(
TAG,
"Received key from %s (%u B)", key.user_id,
162 static_cast<unsigned>(s_rx.received_len));
166int onRxWrite(uint16_t conn_handle, uint16_t ,
167 const uint8_t* data, uint16_t len)
169 if (!data || len == 0)
return 0;
170 const uint8_t opcode = data[0];
173 case kOpcWriteStart: {
174 if (len < 3)
return 0;
175 const size_t total = (
static_cast<size_t>(data[1]) << 8) | data[2];
176 if (total == 0 || total > kMaxPayloadLen) {
177 sendStatus(conn_handle, kStatusBadPayload);
182 s_rx.conn_handle = conn_handle;
183 s_rx.expected_len = total;
184 s_rx.received_len = 0;
185 const size_t chunk = len - 3;
187 const size_t to_copy = std::min<size_t>(chunk,
sizeof(s_rx.buf));
188 std::memcpy(s_rx.buf, data + 3, to_copy);
189 s_rx.received_len = to_copy;
191 if (s_rx.received_len >= s_rx.expected_len) finalizeRx();
196 if (!s_rx.active || conn_handle != s_rx.conn_handle)
return 0;
197 const size_t chunk = len - 1;
198 const size_t remaining =
sizeof(s_rx.buf) - s_rx.received_len;
199 const size_t to_copy = std::min(chunk, remaining);
200 std::memcpy(s_rx.buf + s_rx.received_len, data + 1, to_copy);
201 s_rx.received_len += to_copy;
202 if (opcode == kOpcWriteEnd || s_rx.received_len >= s_rx.expected_len) {
214size_t buildOwnKeyPayload(uint8_t* out,
size_t out_size) {
228 uint8_t pubkey[64] = {0};
235 const size_t uid_len = strnlen(status.user_id,
sizeof(status.user_id));
236 const size_t total = 1 + 1 + pubkey_len + 20 + 1 + uid_len;
237 if (total > out_size)
return 0;
241 out[off++] = pubkey_len;
242 std::memcpy(out + off, pubkey, pubkey_len);
244 std::memcpy(out + off, status.fingerprint, 20);
246 out[off++] =
static_cast<uint8_t
>(uid_len);
247 std::memcpy(out + off, status.user_id, uid_len);
259 LOG_W(
TAG,
"BluetoothController unavailable, deferring init");
267 s_chars[0].
onWrite = onRxWrite;
268 s_chars[0].
onRead =
nullptr;
275 s_chars[1].
onRead =
nullptr;
281 if (!ble->registerGattService(s_svc)) {
282 LOG_W(
TAG,
"registerGattService failed");
286 LOG_I(
TAG,
"GPG cross-sign service registered (rx=%u status=%u)",
287 static_cast<unsigned>(s_rx_handle),
288 static_cast<unsigned>(s_status_handle));
302 bool awaiting_disc =
false;
303 uint16_t conn_handle = 0xFFFF;
304 uint16_t peer_rx = 0;
305 uint8_t payload[kMaxPayloadLen];
306 size_t payload_len = 0;
307 size_t payload_off = 0;
309EXT_RAM_BSS_ATTR SendSession s_tx = {};
314void cancelSend(
const char* why) {
315 LOG_W(
TAG,
"Send aborted: %s", why ? why :
"?");
318 if (s_tx.conn_handle != 0xFFFF) ble->disconnectHandle(s_tx.conn_handle);
319 if (s_disc_token != 0xFFFF) ble->removeServiceDiscoveryCallback(s_disc_token);
320 if (s_write_token != 0xFFFF) ble->removeWriteCompleteCallback(s_write_token);
322 s_disc_token = 0xFFFF;
323 s_write_token = 0xFFFF;
324 s_tx = SendSession{};
327bool writeNextChunk() {
329 if (!ble || !s_tx.active)
return false;
331 const uint16_t mtu = ble->getMtu();
332 if (s_tx.payload_off == 0) {
334 const size_t header = 3;
335 const size_t chunk = std::min<size_t>(mtu - header, s_tx.payload_len);
337 if (header + chunk >
sizeof(buf))
return false;
338 buf[0] = kOpcWriteStart;
339 buf[1] = (s_tx.payload_len >> 8) & 0xFF;
340 buf[2] = s_tx.payload_len & 0xFF;
341 std::memcpy(buf + 3, s_tx.payload, chunk);
342 s_tx.payload_off = chunk;
343 return ble->writeCharacteristic(s_tx.conn_handle, s_tx.peer_rx,
344 buf, header + chunk,
true);
348 const size_t header = 1;
349 const size_t remaining = s_tx.payload_len - s_tx.payload_off;
350 const size_t chunk = std::min<size_t>(mtu - header, remaining);
351 const bool last = (chunk == remaining);
353 if (header + chunk >
sizeof(buf))
return false;
354 buf[0] = last ? kOpcWriteEnd : kOpcWriteCont;
355 std::memcpy(buf + 1, s_tx.payload + s_tx.payload_off, chunk);
356 s_tx.payload_off += chunk;
357 return ble->writeCharacteristic(s_tx.conn_handle, s_tx.peer_rx,
358 buf, header + chunk,
true);
361void onSendDiscovery(uint16_t conn_handle,
362 const cdc::hal::IBluetoothController::DiscoveredService* svc,
365 if (!s_tx.active || conn_handle != s_tx.conn_handle)
return;
366 if (!svc || !complete)
return;
375 if (s_tx.peer_rx == 0) { cancelSend(
"RX char not found");
return; }
376 s_tx.awaiting_disc =
false;
380void onSendWriteComplete(uint16_t conn_handle, uint16_t ,
int status) {
381 if (!s_tx.active || conn_handle != s_tx.conn_handle)
return;
382 if (status != 0) { cancelSend(
"write failed");
return; }
383 if (s_tx.payload_off >= s_tx.payload_len) {
384 LOG_I(
TAG,
"Send complete, %u bytes",
static_cast<unsigned>(s_tx.payload_len));
387 if (ble) ble->disconnectHandle(s_tx.conn_handle);
388 s_tx = SendSession{};
389 s_disc_token = 0xFFFF;
390 s_write_token = 0xFFFF;
399 if (s_tx.active)
return false;
402 if (!ble)
return false;
404 s_tx.payload_len = buildOwnKeyPayload(s_tx.payload,
sizeof(s_tx.payload));
405 if (s_tx.payload_len == 0)
return false;
407 s_disc_token = ble->addServiceDiscoveryCallback(onSendDiscovery);
408 s_write_token = ble->addWriteCompleteCallback(onSendWriteComplete);
411 s_tx.awaiting_disc =
true;
412 s_tx.payload_off = 0;
413 if (!ble->connect(addr, addr_type)) {
414 cancelSend(
"connect failed");
421 s_tx.conn_handle = 0xFFFE;
422 ble->discoverServiceByUuid(s_tx.conn_handle,
uint8_t gpg_storage_sig_slot(void)
static bool s_initialized
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_I(tag, fmt,...)
static GpgRecvStore & instance()
#define CDC_CURVE_ED25519
bool gpg_get_status(gpg_status_t *status)
Fills status from the OpenPGP card-application state.
constexpr uint8_t READ_ENC
constexpr uint8_t WRITE_ENC
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
ISecureElement * getSecureElementInstance()
Returns singleton secure-element stub instance.
bool ble_gpg_xsig_send(const uint8_t addr[6], uint8_t addr_type)
Push the badge's own public key to a peer.
bool ble_gpg_xsig_init()
Initialise the GPG cross-sign BLE endpoint.
void ble_gpg_xsig_set_received_callback(XsigReceivedCallback cb)
Install / remove the "key received" notification.
void(*)(const gpg_recv_key_t &key) XsigReceivedCallback
Callback invoked when a remote badge has finished pushing a key.
bool calculateFingerprintV5(uint8_t curve, const uint8_t *pubkey, size_t pubkey_len, uint32_t created_at, uint8_t out_fp[32])
Compute the V5 / RFC 9580 OpenPGP fingerprint (SHA-256, 32 bytes).
static BleUuid from128(const uint8_t v[16])
GattWriteCallback onWrite
uint8_t numCharacteristics
GattCharacteristic * characteristics
DiscoveredCharacteristic characteristics[MAX_DISCOVERED_CHARS]
uint8_t numCharacteristics
One GPG public key received from another badge.
Snapshot of the current OpenPGP card-application state for UI display.