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