diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 0a5fc31..5781418 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 29 +#define MONTAUK_BUILD_NUMBER 31 diff --git a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp index 1a69f9e..5299225 100644 --- a/kernel/src/Drivers/USB/Bluetooth/Hci.cpp +++ b/kernel/src/Drivers/USB/Bluetooth/Hci.cpp @@ -793,6 +793,29 @@ namespace Drivers::USB::Bluetooth::Hci { for (int i = 0; i < MAX_CONNECTIONS; i++) { if (g_connections[i].Active && g_connections[i].Handle == handle) { + // Reason 0x05 = Authentication Failure: our stored + // link key no longer matches the remote's (typical + // after another OS's session made the headset evict + // or rotate its copy). Keeping the stale key makes + // EVERY reconnect fail the same way until the + // headset is power-cycled. Do what BlueZ does: + // drop the key, so the next connect sends a + // negative key reply and falls back to fresh SSP + // pairing (Just Works -- fully automated here). + // Invalidate only (no disk I/O in this nested + // context); the dirty flag gets flushed from + // process context. + if (reason == 0x05) { + int idx = FindBondIndex(g_connections[i].BdAddr); + if (idx >= 0) { + g_bonds[idx].valid = false; + memset(g_bonds[idx].key, 0, 16); + g_bondsDirty = true; + KernelLogStream(WARNING, "BT-HCI") + << "Auth failure with stored key: bond dropped;" + << " next connect will re-pair"; + } + } g_connections[i].Active = false; break; } @@ -869,11 +892,13 @@ namespace Drivers::USB::Bluetooth::Hci { memcpy(reply, ¶ms[0], 6); memcpy(&reply[6], g_bonds[idx].key, 16); EnqueueHciCmd(OP_LINK_KEY_REQ_REPLY, reply, 22); + KernelLogStream(INFO, "BT-HCI") << "Link key request: stored key sent"; } else { // Unknown device: tell the controller we have no key so // the remote falls back to fresh Secure Simple Pairing // (Just Works) instead of failing auth (reason 0x05). EnqueueHciCmd(OP_LINK_KEY_REQ_NEG_REPLY, ¶ms[0], 6); + KernelLogStream(INFO, "BT-HCI") << "Link key request: no key, will re-pair"; } } break; @@ -900,6 +925,26 @@ namespace Drivers::USB::Bluetooth::Hci { if (evtParamLen >= 3 && params[0] == 0) { uint8_t enc[3] = { params[1], params[2], 0x01 }; EnqueueHciCmd(OP_SET_CONN_ENCRYPT, enc, 3); + } else if (evtParamLen >= 3) { + // Failed authentication was previously swallowed silently. + // Drop the stale bond here too (the remote may not follow + // up with a reason-5 disconnect on every firmware). + KernelLogStream(WARNING, "BT-HCI") << "Authentication failed, status=" + << (uint64_t)params[0]; + uint16_t handle = (uint16_t)params[1] | ((uint16_t)params[2] << 8); + for (int i = 0; i < MAX_CONNECTIONS; i++) { + if (g_connections[i].Active && g_connections[i].Handle == handle) { + int idx = FindBondIndex(g_connections[i].BdAddr); + if (idx >= 0) { + g_bonds[idx].valid = false; + memset(g_bonds[idx].key, 0, 16); + g_bondsDirty = true; + KernelLogStream(WARNING, "BT-HCI") + << "Stale bond dropped; next connect will re-pair"; + } + break; + } + } } break; } diff --git a/scripts/import-bluez-bond.sh b/scripts/import-bluez-bond.sh new file mode 100755 index 0000000..0506956 --- /dev/null +++ b/scripts/import-bluez-bond.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# import-bluez-bond.sh — share a BlueZ (Linux) Bluetooth pairing with MontaukOS. +# +# Dual-boot problem: the AX211's BD_ADDR override (0xFC31) is cosmetic — the +# baseband keeps answering pages on the FACTORY address, so a peer (headset) +# sees Linux and MontaukOS as ONE device with ONE link-key slot. Each OS +# pairing clobbers the key the other OS depends on, and every reconnect from +# the other OS then dies with Authentication Failure (reason 0x05). +# +# Fix: both OSes share the SAME identity and the SAME link key. This script +# copies a BlueZ link key into MontaukOS's key store (os/btkeys.bin on the +# installed root) and pins MontaukOS's configured MAC to the adapter's factory +# address so the app reports the identity actually used on air. +# +# Usage: +# sudo ./scripts/import-bluez-bond.sh [adapter-mac] [--reverse-key] +# +# Example: +# sudo ./scripts/import-bluez-bond.sh /run/media/$USER/ E4:58:BC:5F:E0:30 +# +# --reverse-key: byte-swap the key (fallback if authentication still fails). +# +# NOTE: replaces the MontaukOS key store with this single bond. Re-pair other +# devices from MontaukOS afterwards if you had more. + +set -euo pipefail + +if [ "$(id -u)" -ne 0 ]; then + echo "error: must run as root (reads /var/lib/bluetooth)" >&2 + exit 1 +fi + +TARGET="${1:?usage: import-bluez-bond.sh [adapter-mac] [--reverse-key]}" +DEVICE="${2:?missing device MAC}" +ADAPTER="${3:-}" +REVERSE=0 +for arg in "$@"; do [ "$arg" = "--reverse-key" ] && REVERSE=1; done +[ "$ADAPTER" = "--reverse-key" ] && ADAPTER="" + +DEVICE="${DEVICE^^}" + +# Auto-detect the adapter: the directory under /var/lib/bluetooth that holds +# this device, preferring a factory (not locally-administered "x2:") address. +if [ -z "$ADAPTER" ]; then + for dir in /var/lib/bluetooth/*/; do + a="$(basename "$dir")" + [ -f "/var/lib/bluetooth/$a/$DEVICE/info" ] || continue + if [ -z "$ADAPTER" ] || [[ "${ADAPTER:1:1}" == "2" && "${a:1:1}" != "2" ]]; then + ADAPTER="$a" + fi + done +fi +[ -n "$ADAPTER" ] || { echo "error: no adapter dir holds a bond for $DEVICE" >&2; exit 1; } + +INFO="/var/lib/bluetooth/$ADAPTER/$DEVICE/info" +[ -f "$INFO" ] || { echo "error: $INFO not found" >&2; exit 1; } +KEY="$(sed -n '/^\[LinkKey\]/,/^\[/{s/^Key=//p}' "$INFO" | head -1)" +[ -n "$KEY" ] || { echo "error: no [LinkKey] Key= in $INFO" >&2; exit 1; } + +[ -d "$TARGET/os" ] || { echo "error: $TARGET/os missing — is the MontaukOS root mounted there?" >&2; exit 1; } + +echo "adapter (factory identity): $ADAPTER" +echo "device: $DEVICE" +echo "key: ${KEY:0:4}... (${#KEY} hex chars)" + +python3 - "$TARGET/os/btkeys.bin" "$DEVICE" "$KEY" "$REVERSE" <<'EOF' +import sys +path, device, keyhex, reverse = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4]) + +# MontaukOS on-disk layout (kernel Hci.cpp): 'BTK1' + 8 x { addr[6], key[16], valid[1] }. +# addr is stored LSB-first (HCI wire order); BlueZ's hex key is already in HCI order. +addr = bytes(int(b, 16) for b in reversed(device.split(":"))) +key = bytes.fromhex(keyhex) +assert len(key) == 16, "link key must be 16 bytes" +if reverse: + key = key[::-1] + +blob = bytearray(b"BTK1") +blob += addr + key + b"\x01" # slot 0: the imported bond +blob += b"\x00" * 23 * 7 # slots 1..7 empty +with open(path, "wb") as f: + f.write(blob) +print(f"wrote {path} ({len(blob)} bytes, 1 bond)") +EOF + +# Pin the configured MAC to the factory address (truthful display; the 0xFC31 +# override is cosmetic on this hardware anyway). +CONF="$TARGET/config/bluetooth.toml" +mkdir -p "$TARGET/config" +if [ -f "$CONF" ] && grep -q '^ *mac *=' "$CONF"; then + sed -i "s/^ *mac *=.*/mac = \"$ADAPTER\"/" "$CONF" +else + printf 'mac = "%s"\n' "$ADAPTER" >> "$CONF" +fi +echo "set $CONF mac = $ADAPTER" +echo "done — boot MontaukOS; the device should reconnect without re-pairing."