Skip to content

eShreder/fallback-wifi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WiFi Push — WiFi setup on Raspberry Pi via BLE

Pushes WiFi credentials from Mac to all Pi devices within BLE range. Like Apple Share WiFi, but for Raspberry Pi.

How it works

sequenceDiagram
    participant Mac as Mac<br>(wifi-push)
    participant Pi as Pi 1..N<br>(wifi-scanner)
    participant WiFi as WiFi Router

    Note over Pi: Service starts on boot<br>WiFi connected → sleeping
    Note over Pi: WiFi drops → BLE scan starts
    Note over Mac: wifi-push --ssid X<br>starts BLE peripheral
    Pi->>Mac: X25519 ephemeral public key
    Note over Mac: Derive shared secret (HKDF)<br>Encrypt (AES-256-GCM)<br>Sign (SSH key)
    Mac->>Pi: Encrypted + signed payload
    Note over Pi: Verify signature<br>Decrypt credentials
    Pi->>WiFi: nmcli connect (SSID + PSK)
    Note over Pi: WiFi restored → back to sleep
Loading

Typical recovery time: ~6 seconds from BLE discovery to WiFi connected.

Components

Component Runs on Description
wifi-push (Go) Mac / Linux BLE peripheral, broadcasts signed WiFi credentials
wifi-scanner (Go) Pi BLE scanner — single static binary, no dependencies

Both components are Go binaries (~4-5 MB). No Python, no pip, no external dependencies. Mac side uses CoreBluetooth via CGo, Linux uses BlueZ D-Bus (pure Go).

Quick setup

Mac: build wifi-push, have an SSH key (~/.ssh/id_rsa or id_ed25519).

Pi (one-time):

# 1. Copy binary
scp wifi-scanner USER@PI:/usr/local/bin/

# 2. Add Mac's SSH key
ssh-copy-id USER@PI

# 3. Set Bluetooth to LE-only mode
sudo sed -i 's/^#ControllerMode = dual/ControllerMode = le/' /etc/bluetooth/main.conf
sudo systemctl restart bluetooth

# 4. Add polkit rule for NetworkManager (see detailed setup below)

# 5. Create systemd service (see detailed setup below)
sudo systemctl enable --now ble-wifi-setup

Detailed instructions for each step — below.

Security

  • Encryption: ECDH (X25519) + AES-256-GCM — each connection is uniquely encrypted
  • Authentication: payload signed with Mac's SSH key (RSA or Ed25519)
  • Verification: Pi verifies signature via ~/.ssh/authorized_keys
  • Replay protection: ephemeral ECDH keys (unique shared secret per connection) + 60s cooldown per SSID
  • Forward secrecy: compromising the SSH key does not reveal past sessions
  • Rate limiting: 30s cooldown per device to prevent BLE spam; handshake flood protection (5 failures/60s blocks device)
  • Payload size limit: 16KB max BLE payload to prevent memory exhaustion
  • No extra keys needed — uses existing SSH keys

Building

# Mac side (wifi-push) — on Mac:
go build -ldflags="-s -w" -o wifi-push ./cmd/wifi-push

# Pi side (wifi-scanner) — cross-compilation:
# Pi 5, Pi 4, Pi Zero 2W (arm64)
GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o wifi-scanner ./cmd/wifi-scanner

# Pi Zero W (arm32)
GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-s -w" -o wifi-scanner ./cmd/wifi-scanner

Pi Setup

1. Copy the binary

scp wifi-scanner USER@PI_HOST:/usr/local/bin/wifi-scanner

2. Add Mac's SSH key to authorized_keys

# On Mac:
ssh-copy-id USER@PI_HOST

The Mac's public key (~/.ssh/id_rsa.pub or ~/.ssh/id_ed25519.pub) will be added to ~/.ssh/authorized_keys on the Pi — the same key is used for WiFi Push verification.

3. Bluetooth — LE-only mode

Set ControllerMode = le in /etc/bluetooth/main.conf:

sudo sed -i 's/^#ControllerMode = dual/ControllerMode = le/' /etc/bluetooth/main.conf
sudo systemctl restart bluetooth

Verify:

bluetoothctl show | grep Powered
# Should be: Powered: yes

sudo btmgmt info | grep "current settings"
# Should contain "le" but NOT "br/edr"

This is required — without it BlueZ tries classic Bluetooth (BR/EDR) to connect to Mac and fails with br-connection-profile-unavailable. Pi only needs BLE for wifi-push.

If Bluetooth is off: sudo rfkill unblock bluetooth && sudo systemctl restart bluetooth.

4. Polkit rule for NetworkManager

The scanner runs as a regular user and manages WiFi via nmcli. Permission is required:

sudo tee /etc/polkit-1/rules.d/50-wifi-push.rules > /dev/null << 'EOF'
polkit.addRule(function(action, subject) {
    var allowed = [
        "org.freedesktop.NetworkManager.network-control",
        "org.freedesktop.NetworkManager.wifi.share.protected",
        "org.freedesktop.NetworkManager.settings.modify.system"
    ];
    if (allowed.indexOf(action.id) !== -1 && subject.isInGroup("netdev")) {
        return polkit.Result.YES;
    }
});
EOF

5. Create systemd service

sudo tee /etc/systemd/system/ble-wifi-setup.service << 'EOF'
[Unit]
Description=BLE WiFi Push Scanner
After=bluetooth.target
Requires=bluetooth.target

[Service]
ExecStart=/usr/local/bin/wifi-scanner
User=USER
Group=USER
SupplementaryGroups=bluetooth netdev
Environment=WIFI_PUSH_AUTHORIZED_KEYS=/home/USER/.ssh/authorized_keys
RuntimeDirectory=wifi-push
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

Replace USER with your username on the Pi.

6. Enable and start

sudo systemctl daemon-reload
sudo systemctl enable --now ble-wifi-setup.service

Usage

On Mac:

# Push WiFi to all Pi devices in range (password will be prompted)
./wifi-push --ssid "HomeNetwork"

# Via environment variable
WIFI_PUSH_PASSWORD="secret123" ./wifi-push --ssid "HomeNetwork"

# With specific key and duration
./wifi-push --ssid "Office" --key ~/.ssh/id_rsa --duration 60

Broadcasts a BLE beacon for 120 seconds (default). All Pi devices in range will pick up the settings.

Scanner behavior

The scanner operates in two modes:

WiFi state Scanner behavior
Connected Sleeping — BLE radio off, CPU ~0%
Disconnected Actively scanning BLE every 10 seconds

WiFi check occurs every --scan-interval seconds (default 10). If booted without WiFi — scanner starts BLE scan immediately.

Both wifi-scanner and wifi-push handle SIGTERM/SIGINT for graceful shutdown (clean BLE disconnect, D-Bus cleanup).

Resources on Pi Zero 2W: ~12 MB RAM, ~0% CPU in idle mode.

CLI flags

wifi-push

wifi-push [flags]

  --ssid STRING            WiFi SSID (required)
  --password STRING        WiFi password (or WIFI_PUSH_PASSWORD env; otherwise — prompt)
  --key PATH               path to SSH private key (default ~/.ssh/id_rsa)
  --duration N             broadcast duration in seconds (default 120, range 10-3600)

wifi-scanner

wifi-scanner [flags]

  --authorized-keys PATH   path to authorized_keys (default ~/.ssh/authorized_keys)
  --scan-interval N        seconds between scans (default 10, range 1-300)

Environment variable WIFI_PUSH_AUTHORIZED_KEYS is also supported.

BLE protocol

  • Service UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
  • Handshake UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567892 (write — Pi → Mac)
  • Payload UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567891 (read — Mac → Pi)
  • Advertised name: wifi-push

Handshake (Pi → Mac): [32B X25519 ephemeral public key]

Encrypted payload (Mac → Pi, max 16 KiB):

[32B mac_eph_pub][12B nonce][2B ct_len][AES-GCM ciphertext+tag][2B sig_len][signature]

Signature covers: mac_eph_pub + nonce + ciphertext (encrypt-then-sign).

HKDF: SHA-256, salt=nil, info=wifi-push-v2.

Project structure

cmd/
  wifi-push/main.go               — Mac CLI: key loading, BLE peripheral
  wifi-scanner/main.go            — Pi CLI: key loading, BLE scanner
internal/
  ble/scanner.go                  — BLE scanner orchestrator (Pi, BlueZ D-Bus)
  ble/connection.go               — BLE GATT connect + read logic
  ble/signals.go                  — D-Bus signal handling
  ble/wificheck.go                — WiFi state checking (nmcli)
  bluez/adapter.go                — shared BlueZ adapter discovery (D-Bus)
  bluez/constants.go              — shared BlueZ/D-Bus constants
  config/constants.go             — protocol constants and payload sizes
  config/validate.go              — SSID/PSK validation + conn-name sanitization
  crypto/ecdh.go                  — X25519 ECDH + HKDF + AES-256-GCM encrypt/decrypt
  crypto/sign.go                  — RSA / Ed25519 signing
  crypto/verify.go                — RSA / Ed25519 signature verification
  keys/authorized_keys.go         — SSH authorized_keys parsing
  keys/private_key.go             — SSH private key loading
  keys/trusted_key.go             — TrustedKey interface + implementations
  protocol/protocol.go            — payload parsing + decryption (Pi)
  protocol/encrypt.go             — payload assembly + encryption (Mac)
  push/push.go                    — shared types
  push/peripheral_linux.go        — BLE peripheral via BlueZ D-Bus
  push/peripheral_darwin.go       — BLE peripheral via CoreBluetooth (CGo)
  push/peripheral_darwin.m        — Objective-C CoreBluetooth wrapper
  push/handshake.go               — handshake processing helpers
  push/ratelimit.go               — per-device BLE handshake rate limiting
  wifi/wifi.go                    — WifiApplier interface
  wifi/nmcli.go                   — NmcliApplier: WiFi setup via nmcli

Management

# Logs
sudo journalctl -u ble-wifi-setup -f

# Status (shows sleeping/scanning)
sudo systemctl status ble-wifi-setup

# Restart
sudo systemctl restart ble-wifi-setup

# Stop
sudo systemctl stop ble-wifi-setup

Troubleshooting

Problem Solution
No valid public keys found Check the path to authorized_keys and that ssh-copy-id was run
No Bluetooth adapter found sudo systemctl restart bluetooth + sudo rfkill unblock bluetooth
br-connection-profile-unavailable Set ControllerMode = le in /etc/bluetooth/main.conf
Insufficient privileges Add the polkit rule (step 4) and make sure the user is in the netdev group
WiFi not connecting nmcli device wifi list — check if Pi can see the network
timeout waiting for services Restart bluetooth and ble-wifi-setup
Scanner doesn't start after reboot Verify: systemctl is-enabled ble-wifi-setup = enabled

Limitations

  • BLE range ~10m
  • wifi-push on macOS requires CGo (Xcode Command Line Tools)
  • RSA-4096 signature ~512 bytes — requires MTU negotiation (CoreBluetooth handles this automatically)

About

Push WiFi credentials from Mac to Raspberry Pi over BLE using SSH keys

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors