LoTW PKI

Fri Feb 28, 2025

ARRL’s Logbook of the World has a full public key infrastructure.

This can be handy if you need to bootstrap some kind of auth or PKI from a large group of existing users.

For all of this, you’ll have to replace my callsign with yours.

Once you have a callsign set up in TQSL, that program will write out your certificate and the authorities that signed it in PEM format.

$ l .tqsl
...
drwx------  2 mike   4096 Mar 28  2024 keys/
drwx------  2 mike   4096 Feb 28 05:33 certs/

$ l .tqsl/certs .tqsl/keys/
.tqsl/keys/:
...
-rw-r--r-- 1 mike 1627 Mar 28  2024 W2FBI

.tqsl/certs:
...
-rw-r--r-- 1 mike 5253 Feb 28 05:19 root
-rw-r--r-- 1 mike 4583 Feb 28 05:19 authorities
-rw-r--r-- 1 mike 1680 Feb 28 05:19 user

certs/user is your signed certificate, with your public key. keys/{CALLSIGN} has your private key in it.

You can manually extract the private key to PEM format if you like, by copying everything between -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY----- to a new file (make sure to keep those lines!).

Or you could

sed -n '/-----BEGIN PRIVATE KEY-----/,/-----END PRIVATE KEY-----/p' ~/.tqsl/keys/W2FBI | sed '1s/^<.*>//' > ~/.tqsl/keys/W2FBI

Now, cd to ~/.tqsl/certs/ and you can then cat root authorities > chain.pem.

Check your cert is properly signed like you expect with openssl verify -CAfile chain.pem user

$ openssl verify -CAfile chain.pem  user
user: OK

If you get OK, it’s good to go.

Now you can happily make a pkcs12 bundle: openssl pkcs12 -export -in ~/.tqsl/certs/user -inkey ~/.tqsl/keys/W2FBI.pem -certfile ~/.tqsl/certs/chain.pem -out W2FBI.p12 -name W2FBI

Here’s a script you can use if you like. gpgsm is massively preferable for almost any actual usage, but you can see how openssl smime can be used too. Strictly speaking the P12 import to gpgsm will already have all the authorities, but it does not hurt to re-import things as they are de-duplicated, and I find it useful to remind me.

#!/usr/bin/env bash

usage() {
    echo "Usage: $0 <callsign> <command> <input_file> [output_file]"
    echo "Commands:"
    echo "  sign     - Sign a file"
    echo "  verify   - Verify a signed file"
    echo "  gpgsm-enroll  - Import authorities and keypair/cert for <callsign> to gpgsm"
    echo "                  (gpgsm preferred for encryption/decryption)"
    echo "Examples:"
    echo "  $0 sign message.txt signed.txt"
    echo "  $0 verify signed.txt verified.txt"
    exit 1
}
CALLSIGN=$1

KEYDIR=~/.tqsl/keys
_KEY=$KEYDIR/$CALLSIGN
KEY=$KEYDIR/$CALLSIGN.pem
CERTDIR=~/.tqsl/certs
CERT=$CERTDIR/user
CHAIN=$CERTDIR/chain.pem
P12=$KEYDIR/$CALLSIGN.p12
P12_nopass=$KEYDIR/$CALLSIGN.nopass.p12

if [ ! -f $_KEY ] || [ ! -f $CERT ]; then
    echo "Missing cert or key for callsign \"$CALLSIGN\""
    usage
fi


command=$2
if [ "x$command" != "xgpgsm-enroll" ]; then
    if [ $# -lt 4 ]; then
        usage
    fi
    echo
fi
infile=$3
outfile=${4:-${infile%.*}_output.txt}  # Default output name if not specified


if [ ! -f $CHAIN ]; then
    echo "Making chain.pem for first run to $CHAIN"
    cat $CERTDIR/root $CERTDIR/authorities > $CHAIN
    openssl verify -CAfile $CHAIN $CERT
fi
if [ ! -f $KEY ]; then
    echo "Extracting private key for first run to $KEY"
    sed -n '/-----BEGIN PRIVATE KEY-----/,/-----END PRIVATE KEY-----/p' $_KEY | sed '1s/^<.*>//' > $KEY
fi
if [ ! -f $P12 ]; then
    echo "Exporting keypair to $P12 with password \"$CALLSIGN\""
    openssl pkcs12 -export \
        -in "$CERT" \
        -inkey "$KEY" \
        -certfile "$CHAIN" \
        -out "$P12" \
        -name "$CALLSIGN" \
        -passout pass:$CALLSIGN
fi
if [ ! -f $P12_nopass ]; then
    echo "Exporting keypair to $P12_nopass with no password"
    openssl pkcs12 -export \
        -in "$CERT" \
        -certfile "$CHAIN" \
        -inkey "$KEY" \
        -out "$P12_nopass" \
        -name "$CALLSIGN" \
        -passout pass:
fi




case $command in
    gpgsm-enroll)
    	gpgsm --import $CHAIN 
    	gpgsm --pinentry-mode loopback --import $P12_nopass
	;;
    reset)
    	rm $KEY $CHAIN $P12 $P12_nopass
	;;
    sign)
        openssl smime -sign \
            -in "$infile" \
            -out "$outfile" \
            -signer "$CERT" \
            -inkey "$KEY"
        echo "Signed file saved as $outfile"
        ;;

    verify)
        openssl smime -verify \
            -in "$infile" \
            -CAfile "$CHAIN" \
            -out "$outfile"
        echo "Verified content saved as $outfile"
        ;;

    *)
        echo "Unknown command: $command"
        usage
        ;;
esac