Securing unencrypted telecommands

Earlier this year while studying for my intermediate and advanced exams the subject came up of controlling a remote station over the ham bands. I think the specific situation was specified as controlling a remote transceiver. I think there are specific clauses for telecommand for satellite control but it was a problem that set my mind in motion. Our license permits us to do this but with two seemingly contradictory terms.

  1. It must be secure as to prevent unauthorized use by others.
  2. Traffic CAN NOT be encrypted

If it were open then it would be subject the the same rules as repeaters. You can operate it but all communication with it over the ham bands must be in the clear.

This makes authentication rather difficult because if we send a plain-text password over the air or even a network it’s trivial to capture the packet and read the password straight out and then use that password to authenticate. Now over a normal network be it WiFi, wired, or even across the internet the solution is simple. Use something like SSH which will encrypt everything and you’re golden. Of course we can’t do this on the ham bands so we need to come up with something else. SSH is also designed for fast networks and while it’s interesting to watch an SSH connection running over a 1200 baud half duplex link it’s not exactly convenient.

Now a few of us on the course have plenty of experience in the security field. I work for a well known cybersecurity company and some of the others do work or have worked for the MOD or various defense contractors in roles with an interest into such forms of security and none of us could come up with an answer there and then. This played on my mind for a while and I was determined to come up with a simple solution. In the normal world of communications where encryption is allowed why not encrypt everything. Most of our communications don’t really warrant encryption as they aren’t sensitive yet it’s easier and good practice to encrypt everything. For example if I log in to a remote server and run the top command to see what’s hogging the CPU the output of the top command wouldn’t normally contain sensitive data however the password I enter is very sensitive as I wouldn’t want someone getting hold of that and logging in and dumping my database full of whatever sensitive data it may hold.

With the above in mind I set out to look at what technologies exist that can be used to achieve the above goals. These days raspberry pi’s and other small Linux boards are dirt cheap and the software is free and open so I have designed this process with that in mind and it’s all written around tools available in Ubuntu, Debian and Raspbian which are also what I’m familiar with as a Linux engineer. It may be possible to rewrite this for other platforms but I stick to what I know but try my best to convey the ideas.

Tools and process

My first port of call was GPG (Gnu Privacy Guard). GPG can both encrypt and sign data. If it’s not something you’ve come across before it’s a system of public and private keys. I won’t go into detail about how to generate keys here as it’s well documented elsewhere but I do want to describe how it works.

I generate myself a pair of keys (public and private) and you generate your key pair and we exchange public keys. Lets say I want to send you an email. How can you verify that the email in your inbox actually came from me. Well if i sign that email with my private key you can verify that signature against the public key we shared earlier. Should I want to encrypt the email I would do so w1ith your public key then the email can only be decrypted with your private key but for our Ham radio use case we’re only interested in the signing part.

GPG takes care of the authentication side of things now we need a method to transmit and receive a message and verify everything for us. For my tests I used NetCat. NetCat can be set up to listen for incoming connections on a TCP port and receive strings of text and can also be used to connect to a remote port and send text however this proved to be problematic as once it’s received the end of the message the service closes. This wasn’t really what I wanted for a telecommand system so I settled on Xinetd. Xinetd listens on a port and when it receives a connection it invokes an executable such as a script and passes the received data into the script for processing and then continues to listen for more connections.

Great so we have a way of sending a signed message over a network and a way of receiving it and verifying it. Now when you sign your message you’re asked to enter your password to unlock your private key. This is fine for us humans sending a message but how can we verify that the reply to the message we sent is genuine if it’s not signed. You can’t always have a person at the other end to enter a password if the remote station is something like a raspberry pi based satellite for example. GPG didn’t really cut the mustard for the response so I decided to look for an alternative. I decided to use a secret file and a hash. In my example I’ve used MD5 although in practice I’d probably use SHA256 or better as MD5 has been found to suffer from vulnerabilities but MD5 proves a point and in circumstances such as an orbiting satellite it might not be something that’s particularly easy to change when what was once the next big thing is found to suffer from a certain vulnerability.

OK so when we build our remote station we create a secret file. It doesn’t have to be anything meaningful, just a string of random gibberish, So long as we have the same file on the remote station as we do locally we can concatenate it to our reply then create a hash of that. We then append the hash to our reply and transmit it. On the receiving end we can then strip the hash off the reply, concatenate the reply with our local copy of the secret file and generate the hash again then compare it with the hash we received to validate it.

Generally the reply isn’t as critical as the command we’re sending we just want to be reasonably sure no one’s trying to interfere and inject their own reply.

Fixing loopholes

Now to sit back and pick holes in the plan. Well when we sign our command with GPG the remote end can verify this if it’s got a copy of our public key and that’s all well and good but if someone were to grab that signed command out of the air what’s to stop them replaying that at a later date… Well nothing. OK so that’s a pretty big hole right there. What can we do about it. When we sign our command it also gets a time stamp burned into the signature. If we can keep the clocks accurate at both ends we can look at the time stamp and verify that it was signed as the command was sent and isn’t an old command being replayed. With GPS keeping the clocks in sync becomes pretty easy and although I may be wrong but spoofing GPS especially in space can’t be too easy and I imagine also highly frowned upon. I place my life in the hands of GPS every time I get on a plane so I’m pretty sure it can be trusted to tell us the right time for a situation such as this.

Getting it on the air

All this works fine over a traditional IP network but how can we do this over the air? Well there’s always AX-25. With soundmodem we can create a 1200 Baud connection using a sound card and couple this with a radio either end and you’ve got yourself an IP connection over the air. George Smart M1GEO wrote an excellent article on Soundmodem and AX-25 which even covers setting up APRS gateways. Well worth a read.

Packaging and automation

Anyway I took the above ideas and wrapped them up in a bash script

The human “Send” script

#!/bin/bash

echo -n "Enter your command: "
read COMMAND
echo "$COMMAND" | gpg --clearsign | nc 127.0.0.1 8888 > /tmp/response
LINECOUNT=$(cat /tmp/response | wc -l)
HASH=$(tail -n1 /tmp/response | cut -d' ' -f1)
head -n$(($LINECOUNT - 1)) /tmp/response > /tmp/responsesum
LOCALHASH=$(cat /tmp/responsesum /home/ubuntu/secret | md5sum | cut -d' ' -f1)
if [ $HASH == $LOCALHASH ];
  then
    echo "Validated response sent at: $(tail -n1 /tmp/responsesum)"
    echo ''
    head -n$(($LINECOUNT - 2)) /tmp/responsesum
  else
    echo "RESPONSE SENT BUT MD5SUM FAILED AT: $(tail -n1 /tmp/responsesum)"
    echo ''
    head -n$(($LINECOUNT - 2)) /tmp/responsesum
fi
rm /tmp/responsesum
rm /tmp/response
exit 0

Line by line explanation:

echo -n "Enter your command: "
read COMMAND

This asks the user to enter the command to be sent to the remote station an example might be ‘uptime’ to get the uptime of the system. This must be a command that produces a single shot output to stdout not something like htop which refreshes it’s output. Top can be used in batch mode.

echo "$COMMAND" | gpg --clearsign | nc 127.0.0.1 8888 > /tmp/response

Here we take our command and sign it then send the output using netcat to a service listening somewhere. In this example I just ran it locally on port 8888 but it could be anywhere out over a network or even sent over RTTY with minimodem. This would be slower and also removes the error checking the TCP protocol provides but it may be useful in certain weak signal scenarios such as a Mars lander or in the UK High altitude ballooning as we can’t use the ham bands in the air and so are restricted to ISM and the very low power that comes with it. We then stick any reply we get into a temporary file so we can verify it.

LINECOUNT=$(cat /tmp/response | wc -l)
HASH=$(tail -n1 /tmp/response | cut -d' ' -f1)
head -n$(($LINECOUNT - 1)) /tmp/response > /tmp/responsesum
LOCALHASH=$(cat /tmp/responsesum /home/ubuntu/secret | md5sum | cut -d' ' -f1)

Here we take apart our response. We first find the number of lines in the response noting that the last line is a hash. We then cut the hash out of the last line which also includes the file name of the hashed file but this is not relevant to us. We then take our reply minus the hash and put it in a new temporary file /tmp/responsesum We then concatenate this with our local copy of the secret and create our own hash

if [ $HASH == $LOCALHASH ];
  then
    echo "Validated response sent at: $(tail -n1 /tmp/responsesum)"
    echo ''
    head -n$(($LINECOUNT - 2)) /tmp/responsesum
  else
    echo "RESPONSE SENT BUT MD5SUM FAILED AT: $(tail -n1 /tmp/responsesum)"
    echo ''
    head -n$(($LINECOUNT - 2)) /tmp/responsesum
fi

Here we compare the hash we were sent to the hash we just generated ourselves. if they match then we also look at the 2nd to last line of the response which is a time stamp appended to the response prior to the hash being generated to allow us to be extra sure that it’s valid. This could be checked automatically but if this message were sent over RTTY and was a generally large message the expected time window maybe rather large so I have left it to human discretion. We then print out the rest of the response.

If the hash check fails we tell the user this and give the time stamp and print the message anyway. It’s then up to the user to decide if the reply is or isn’t genuine. Further investigation may be needed as the hash would fail if a packet were somehow received corrupted and wasn’t corrected for us by the TCP protocol.

rm /tmp/responsesum
rm /tmp/response
exit 0

We then tidy up by deleting the temporary files we created and exiting cleanly

The remote “receive” end:

Now to look at the remote end. First off we need to setup Xinetd to accept our connections. I put the following in /etc/xinetd.d/command and start the service. This listens for connections on port 8888 and starts our receive script and sends it the received data on stdin.

service command
{
        socket_type = stream
        protocol    = tcp
        port        = 8888
        user        = ubuntu
        server      = /home/ubuntu/recieve.sh
        wait        = no
}

Then we have the script it’s self:

#!/bin/bash
cat > /tmp/received
NOW=$(date +%s)
THEN=$(( NOW - 60))

cat /tmp/received | gpg --verify 2>/dev/null
if [ $? = 0 ];
  then
    KEYID=$(cat /tmp/received | gpg --verify 2>&1 | grep -o '6BB3E629')
    if [ $KEYID == '6BB3E629' ];
      then
        SIGTIME=$(cat /tmp/received | gpg --verify 2>&1 | grep 'Signature made *' | cut -d ' ' -f 5,6,7,8,9,10)
        SIGEPOCH=$(date -d "$SIGTIME" +%s)
        if [ $SIGEPOCH -ge $THEN -a $SIGEPOCH -le $NOW ];
          then
            sleep 2
            $(head -n 4  /tmp/received | tail -n 1) > /tmp/reply
            date >> /tmp/reply && cat /tmp/reply /home/ubuntu/secret > /tmp/replyhash && md5sum /tmp/replyhash >> /tmp/reply
            rm /tmp/received
            rm /tmp/replyhash
            sleep 2
            cat /tmp/reply
            rm /tmp/reply
          else
            echo "INVALID TIMESTAMP"
            rm /tmp/received
            exit 1
          fi
      else
        echo "INVALID RSA KEY ID"
        rm /tmp/received
        exit 1
    fi
  else
    echo "INVALID GPG SIGNATURE"
    rm /tmp/received
    exit 1
fi
exit 0

Line by line explanation:

cat > /tmp/received

First we take the input and store it in /tmp so we can check it.

NOW=$(date +%s)
THEN=$(( NOW - 60))

Here we get the time in unix epoch of now and 60 seconds ago. 60 seconds means any command we receive should have been signed in the last 60 seconds or it will be considered invalid and won’t be executed. This may need to be tweaked depending on circumstances but 60 seconds should be enough to send a short command at 1200 Baud. Also for those who grew up in the 70’s and 80’s yes my variable names are a reference to Jimmy Saville (his catchphrase was “now then, now then”)

cat /tmp/received | gpg --verify 2>/dev/null

Here we take our received data and verify the signature is correct and dispose of any output. All we’re interested in is the exit status.

if [ $? = 0 ];

Here we check the exit status. If it’s not valid and thus a non zero exit status we skip down to the bottom of our if statement where we return INVALID GPG SIGNATURE if it’s valid we double check the Key ID is what we expect. Here I’m using an example key ID.

  then
    KEYID=$(cat /tmp/received | gpg --verify 2>&1 | grep -o '6BC3E926')
    if [ $KEYID == '6BC3E926' ];

This section does the check on the GPG key ID to make sure it’s what we expect it to be by grepping it out of the output of the GPG verify command and comparing it to our known key ID. If this check fails we skip down to echo “INVALID TIMESTAMP” which informs the sender the time stamp criteria was not met and then we clean up and exit.

      then
        SIGTIME=$(cat /tmp/received | gpg --verify 2>&1 | grep 'Signature made *' | cut -d ' ' -f 5,6,7,8,9,10)
        SIGEPOCH=$(date -d "$SIGTIME" +%s)

Here we’re taking the time stamp in human readable format from the gpg verification and converting it to Unix epoch

        if [ $SIGEPOCH -ge $THEN -a $SIGEPOCH -le $NOW ];

Here we check the signature time stamp against the time NOW and THEN (60 seconds ago) to be considered valid the signature time stamp must be within the last 60 seconds so that any replayed commands outside of that 60 second window are rejected with the response INVALID TIMESTAMP.

          then
            sleep 2
            $(head -n 4  /tmp/received | tail -n 1) > /tmp/reply

After waiting a few seconds to ensure everything is properly settled we execute the command as received and dump the output in a temporary file.

            date >> /tmp/reply && cat /tmp/reply /home/ubuntu/secret > /tmp/replyhash && md5sum /tmp/replyhash >> /tmp/reply

Here we append the date in human readable form to the reply and concatenate that with the secret file in /tmp/replyhash. We then create a hash of that file and append that to our reply.

            rm /tmp/received
            rm /tmp/replyhash
            sleep 2

Here we clean up after all the processing above and wait a couple of seconds more before sending the response.

            cat /tmp/reply

This sends the output of our command together with the date and time it was executed and the hash made using the existing output and the contents of the secret file.

            rm /tmp/reply

Now we clean up by deleting the last of our temporary file.

          else
            echo "INVALID TIMESTAMP"
            rm /tmp/received
            exit 1
          fi
      else
        echo "INVALID RSA KEY ID"
        rm /tmp/received
        exit 1
    fi
  else
    echo "INVALID GPG SIGNATURE"
    rm /tmp/received
    exit 1

This section deals with all the possible reasons why we might have found our command to be invalid and sends a response stating the reason before cleaning up and exiting.

fi
exit 0

If everything went as planned and we received a valid command and sent a response we can now exit cleanly.

A working example

ubuntu@bs-dev:~$ netstat -tln | grep 8888
tcp        0      0 0.0.0.0:8888            0.0.0.0:*               LISTEN
ubuntu@bs-dev:~$ ./send.sh
Enter your comand: uptime

You need a passphrase to unlock the secret key for
user: "Benjamin Shephard <bshephard@webroot.com>"
4096-bit RSA key, ID 6BB3E629, created 2016-12-13

Validated response sent at: Wed Dec 28 19:27:55 UTC 2016

 19:27:55 up 15 days,  7:47,  1 user,  load average: 0.00, 0.00, 0.00
ubuntu@bs-dev:~$

Here we can see that we have our xinetd service listening on port 8888. I run the script and issue my command, enter the passphrase for my GPG key and hit rnter. Seconds later the reply comes back, validates and prints the output of my command.

In summary

I think I’ve managed to come up with a fairly simple solution here and I wanted to put it out there for others to review as I’m only human and I do make mistakes from time to time.

I get that I shouldn’t be using MD5 in this day and age but my idea here was also to consider how technology ages and it’s not always accessible to perform upgrades. It should be possible to create a new service with Xinetd on another port using the ideas here then switch over cleanly or even upgrade packages remotely by ASCII encoding them and sending them as part of the command. That may prove to be a time consuming process at very low speeds.

I wanted to design something that can cope with various modes of unmanned and inaccessible operation. This might be a satellite, High altitude balloon or even a floating device at sea. Small low powered computing devices are making such experiments more accessible to the amateur every day but sometimes the legalities of what one can and can’t do as an amateur pose some interesting problems.

Comments? Ping me a message on Twitter I’ll be adding a contact form to this site at some point in the near future. It’s still very much a work in progress as a site but I really wanted to get this written down before I forgot it all.

73 for now Ben

Ben Shephard avatar
About Ben Shephard
A radio amatuer based in Nottinghamshire, UK. IO93ic