SSH over bluetooth - cleanly
In my previous two posts I set up a login prompt on a bluetooth serial port and then switched to running SSH on it.
I explicitly did not set up an IP network over bluetooth as I want to minimize the number of configurations (e.g. IP address) and increase the chance of it working when needed.
E.g. firewall misconfiguration or Linux’s various “clever” network managers that tend to wipe out network interface configs would have more of a shared fate with the primary access method (SSH over normal network).
This post is about how to accomplish this more properly.
The problems now being solved are:
-
It wasn’t entirely reliable. The
rfcomm
tool is pretty buggy. -
There was no authentication of the Bluetooth channel. Not as much a problem when doing SSH, but if there are passwords then there could be a man-in-the-middle attack.
-
The server side had to remain discoverable forever. So anyone who scans for nearby bluetooth devices would see your servers, and would be able to connect, possibly brute forcing passwords. Not as much of a problem if running SSH with password authentication turned off, but why broadcast the name of a server if you don’t have to?
So here we’ll instead explicitly pair with a PIN just once, and then turn off discoverability.
If you’re happy with no PIN and being discoverable all the time, then
aside from the actual SSH connect and listen commands you can just run
hciconfig hci0 piscan
and then the SSH commands.
Step 0: Install my helpers on both server and client
git clone https://github.com/ThomasHabets/bthelper
cd bthelper
./bootstrap.sh && ./configure && make && make install
1. On the server: get the MAC
This will be needed by the client to pair and to connect.
$ hcitool dev
Devices:
hci0 AA:BB:CC:DD:EE:FF
2. On the server: Start a PIN agent
By default PINs will be skipped (or just use a well-known like 0000), it seems.
At first I tried activating an agent inside bluetoothctl, but from what I can tell it’s only “reactive”, in that it’ll ask the user only if it’s needed. This means that if both sides use this agent then they will “just work” without bothering with a PIN.
So we need an agent that goes “no, really. You need a PIN”.
$ sudo apt install bluez-tools
$ sudo bt-agent
Let this run in the terminal during pairing, as this agent will make up a PIN and will print it to stdout, which you’ll need during pairing.
3. On the server: Turn off built-in agent and enable discoverability
# bluetoothctl
[bluetooth]# agent off
[bluetooth]# discoverable yes
This makes the server show up in scans, and is required (as far as I can tell) in order to pair.
Note that the device and its name are now visible to anyone within range.
4. On the client: pair
sudo bluetoothctl
[bluetooth]# scan on
[bluetooth]# pair AA:BB:CC:DD:EE:FF
[CHG] Device AA:BB:CC:DD:EE:FF Connected: yes
Request passkey
[agent] Enter passkey (number in 0-999999): <enter number from server's agent here>
[…]
[CHG] Device AA:BB:CC:DD:EE:FF Paired: yes
Now the two devices are paired and share a link key. You can inspect
it in on both sides in /var/lib/bluetooth/<my mac address>/<peer mac
address>/info
as LinkKey
.
When the link key is set up there’s no longer any need for the server to be discoverable.
Now you can turn scanning back off
[bluetooth]# scan off
5. On the server: turn discover mode back off, and agent back on
[bluetooth]# discoverable no
[bluetooth]# agent on
6. On the server: kill the bt-agent
Control-C will do it.
7. On the server: Set up SSH service on an RFCOMM channel
Here I randomly pick 3. It’s possible to choose channel 0, and then register it with the “UUID to channel name” lookup service that is SDP.
But here I want minimal complexity and dependency, so I’m skipping that.
# cat > /etc/systemd/system/bluetooth-ssh.service
[Unit]
Description=Bluetooth SSH
After=bluetooth.service
Requires=bluetooth.service
[Service]
ExecStart=/usr/local/bin/bt-listener -c 3 -t 127.0.0.1:22
Restart=always
RestartSec=10
StartLimitIntervalSec=0
[Install]
WantedBy=multi-user.target
^D
# systemctl enable bluetooth-ssh
# systemctl start bluetooth-ssh
This can also be done with rfcomm watch
, but I found it to be very
unstable and CPU-heavy (~35% CPU on raspberry pi when idling). It also
needlessly allocates a /dev/rfcomm<N>
device (requiring running as
root). And it looks like rfcomm
in bluez is deprecated anyway.
But it can also be done with socat exec:"/usr/local/bin/bt-listener
-c 3" tcp:127.0.0.1:22
, but this doesn’t allow for concurrent
connections, and requires waiting for RestartSec
seconds before a
new connection can be started after another.
8. On the client: Configure an SSH alias
$ cat >> ~/.ssh/config
Host servernamehere-console
ProxyCommand /usr/local/bin/bt-connecter AA:BB:CC:DD:EE:FF 3
Now you should be able to ssh servernamehere-console
.