OPENPGPKEY are a nice feature of modern DNS for email encryption and authentication. Let's see what talk there is about it!
Correction: I have reinterpreted the RFC, and I figured out my original interpretation of it was wrong (I interpreted "octets" as hex digest octets, rather than binary hex hash digest octets on accident). I have done this in a short period (around an hour) and I have republished the corrected version.
# OPENPGPKEY DNS record
I just found out about OPENPGPKEY records in modern DNS while exploring the deSEC homepage. I logged into my deSEC account and there it was - OPENPGPKEY DNS record type.
Well, I wondered - what is it, and how can I set it up.
Which led me to multiple articles and generators, which then led me to the RFC document at https://www.rfc-editor.org/rfc/rfc7929.txt.
I began hand-crafting my own OPENPGPKEY record generator based off the RFC.
# OPENPGPKEY record generator
I read the intro of the aforementioned RFC until I found this:
- Location of the OPENPGPKEY Record
The DNS does not allow the use of all characters that are supported in the "local-part" of email addresses as defined in [RFC5322] and [RFC6530]. Therefore, email addresses are mapped into DNS using the following method:
The "left-hand side" of the email address, called the "local- part" in both the mail message format definition [RFC5322] and in the specification for internationalized email [RFC6530]) is encoded in UTF-8 (or its subset ASCII). If the local-part is written in another charset, it MUST be converted to UTF-8.
The local-part is first canonicalized using the following rules. If the local-part is unquoted, any comments and/or folding whitespace (CFWS) around dots (".") is removed. Any enclosing double quotes are removed. Any literal quoting is removed.
If the local-part contains any non-ASCII characters, it SHOULD be normalized using the Unicode Normalization Form C from [Unicode90]. Recommended normalization rules can be found in Section 10.1 of [RFC6530].
The local-part is hashed using the SHA2-256 [RFC5754] algorithm, with the hash truncated to 28 octets and represented in its hexadecimal representation, to become the left-most label in the prepared domain name.
The string "_openpgpkey" becomes the second left-most label in the prepared domain name.
The domain name (the "right-hand side" of the email address, called the "domain" in [RFC5322]) is appended to the result of step 2 to complete the prepared domain name.
For example, to request an OPENPGPKEY resource record for a user whose email address is "hugh@example.com", an OPENPGPKEY query would be placed for the following QNAME: "c93f1e400f26708f98cb19d936620da35 eec8f72e57f9eec01c1afd6._openpgpkey.example.com". The corresponding RR in the example.com zone might look like (key shortened for formatting):
c9[..]d6._openpgpkey.example.com. IN OPENPGPKEY <base64 public key>
Based off this part alone pretty much, I made a shell script that generated a valid output! It is located at https://gist.github.com/TruncatedDinoSour/a0874bf1e90647a9a49985e531d9d15f licensed under the public domain (CC0) and it looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 | #!/usr/bin/env sh
...
set -eu
main() {
if [ "$#" -ne 2 ]; then
{
echo "Generates an OPENPGPKEY DNS record based off your Email and a Public GPG key ID."
echo "Usage: $0 <email> <GPG key ID>"
} >&2
return 1
fi
# Confirm user localpart
localpart="$(printf -- '%s' "$1" | cut -d'@' -f1 | tr '[:upper:]' '[:lower:]')"
printf -- " * Your email username (localpart) is '\033[1m%s\033[0m', correct? In lowercase, enter either 'y' (for yes) or 'n' (for no): " "$localpart"
read -r yn
if [ "$yn" -ne 'y' ]; then
echo " * Incorrect information provided." >&2
return 1
fi
# Localpart digest is the SHA256 hex digest truncated to 28 octets (which is 56 hex bytes 0x??)
localpart_digest="$(printf -- '%s' "$localpart" | sha256sum | cut -d' ' -f1 | cut -c1-56)"
# And the value has to be the base64-encoded public key, exported as binary
gpg_public_key_b64="$(gpg --export --export-options export-minimal,no-export-attributes -- "$2" | base64 -w 0)"
printf '\n\033[32m%s\033[0m._openpgpkey. \033[90mIN OPENPGPKEY\033[0m \033[1m%s\033[0m\n' "$localpart_digest" "$gpg_public_key_b64"
}
main "$@"
|
(...
just signifies the cut out status message, author, and license)
The main functionality is located in two lines of code:
| # Localpart digest is the SHA256 hex digest truncated to 28 octets (which is 56 hex bytes 0x??)
localpart_digest="$(printf -- '%s' "$localpart" | sha256sum | cut -d' ' -f1 | cut -c1-28)"
# And the value has to be the base64-encoded public key, exported as binary
gpg_public_key_b64="$(gpg --export --export-options export-minimal,no-export-attributes -- "$2" | base64 -w 0)"
|
localpart_digest
SHA-256 hex digest of the localpart (username) of an email address (for example hi@ari.lt becomes just 'hi'). As per the RFC point 3.4 it is truncated to 28 octets (or 56 hex bytes), for which we use the cut
utility.
gpg_public_key_b64
is the public key encoded in base64, pretty self-explanatory. It exports solely the key by using GPG options, ignoring all attributes and only exporting the essential parts of the key, and then encoding it in a single-line of base64.
After which, we just print out the DNS record in a pretty way :D
| <SHA-256 hex hash of the lowercase localpart truncated to 56 characters>._openpgpkey. IN OPENPGPKEY <base64-encoded public GPG/OpenPGP key>
|
And now, you can even verify my key this way by checking the OPENPGPKEY record on d2efaa6dd6ae6136c19944fae329efd3fb2babe1e6eec26982a422aa._openpgpkey.ari.lt
- this is for ari@ari.lt
:)
Peace ✌