Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture and Security

flowchart TD
    Client["BLE client (netprov CLI or desktop app)"]
    NM["NetworkManager (system)"]

    subgraph Daemon["netprovd (systemd service, runs as root)"]
        direction LR
        GATT["BLE GATT Server"]
        Session["Auth & Session"]
        Facade["Network Facade"]
        GATT --> Session --> Facade
    end

    Client -->|"GATT over LE (Just Works-encrypted link + app-layer HMAC auth)"| GATT
    Facade -->|D-Bus| NM

Security posture

The sensitive characteristics—ChallengeNonce, AuthResponse, Request, and Response—require an encrypted link through encrypt_read and encrypt_write in crates/server/src/ble/gatt.rs. The Info characteristic, which contains only the model and protocol version, stays unauthenticated and unencrypted.

Because netprovd runs headless, it registers a no-input/no-output BlueZ agent. BlueZ therefore uses Just Works pairing. The characteristics deliberately do not use encrypt_authenticated_*: those flags require an MITM-protected LTK, which a headless peripheral cannot produce.

MITM protection comes from the application layer, where both sides hold a pre-shared key. The server issues a nonce, and the client returns its own nonce plus HMAC(PSK, "client" || Ns || Nc). After verifying it, the server returns HMAC(PSK, "server" || Ns || Nc). The client verifies that tag before sending any request, including Wi-Fi credentials. Domain separation prevents either tag from being replayed as the other.

An active MITM can observe the pre-authentication exchange during Just Works pairing, including the public challenge nonce, but cannot issue commands or impersonate a device without the PSK.