diff --git a/contrib/make_welist_txt.sh b/contrib/make_welist_txt.sh new file mode 100755 index 0000000..9382d88 --- /dev/null +++ b/contrib/make_welist_txt.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +# Print the WeTime v2 DNSSEC TXT record for an executor hostname. +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: make_welist_txt.sh [key] + + Executor hostname or URL, for example we.example.com + [key] Ed25519 private/public PEM file, or the base64 + SubjectPublicKeyInfo DER value to publish. + +If [key] is omitted, the script uses ./private_key.pem when it exists, +otherwise it generates ./private_key.pem and ./public_key.pem. + +The record is published at _welist-key. with value: + v=we1; alg=ed25519; key= +EOF +} + +if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then + usage >&2 + exit 2 +fi + +domain=$1 +key_input=${2:-} + +# Normalize the hostname: strip scheme, path, trailing dot, and lowercase. +domain=${domain#http://} +domain=${domain#https://} +domain=${domain%%/*} +domain=${domain%.} +domain=$(printf '%s' "$domain" | tr '[:upper:]' '[:lower:]') + +# Avoid doubling the prefix if the caller already supplied it. +domain=${domain#_welist-key.} + +if [ -z "$domain" ] || ! printf '%s' "$domain" | grep -Eq '^[a-z0-9.-]+$'; then + echo "error: invalid hostname: $domain" >&2 + echo " use the host only, without scheme, port, or path" >&2 + exit 1 +fi + +if printf '%s' "$domain" | grep -Eq '^[0-9.]+$'; then + echo "error: DNSSEC key discovery requires a hostname, not an IP address" >&2 + exit 1 +fi + +if [ -z "$key_input" ]; then + private_key="./private_key.pem" + public_key="./public_key.pem" + + if [ -f "$private_key" ]; then + key_input=$private_key + echo "Using existing $private_key" >&2 + else + if [ -e "$private_key" ] || [ -e "$public_key" ]; then + echo "error: $private_key or $public_key exists and will not be overwritten" >&2 + exit 1 + fi + + if ! (umask 077; openssl genpkey -algorithm ED25519 -out "$private_key"); then + echo "error: cannot generate $private_key" >&2 + exit 1 + fi + if ! openssl pkey -in "$private_key" -pubout -out "$public_key"; then + echo "error: cannot derive $public_key" >&2 + exit 1 + fi + chmod 600 "$private_key" + chmod 644 "$public_key" + + key_input=$private_key + cat >&2 </dev/null; then + if ! openssl pkey -pubin -in "$key_input" -outform DER -out "$tmp_der" 2>/dev/null; then + echo "error: cannot read an Ed25519 key from $key_input" >&2 + exit 1 + fi + fi +else + printf '%s' "$key_input" | tr -d '[:space:]' \ + | openssl base64 -d -A -out "$tmp_der" 2>/dev/null || { + echo "error: key is neither a PEM file nor a base64 SPKI DER value" >&2 + exit 1 + } +fi + +der_len=$(wc -c < "$tmp_der" | tr -d ' ') +prefix_hex=$(od -An -tx1 -N12 "$tmp_der" | tr -d ' \n') +if [ "$der_len" -ne 44 ] || [ "$prefix_hex" != "302a300506032b6570032100" ]; then + echo "error: key is not a 44-byte Ed25519 SubjectPublicKeyInfo DER" >&2 + exit 1 +fi + +key_b64=$(openssl base64 -A -in "$tmp_der") +value="v=we1; alg=ed25519; key=$key_b64" +name="_welist-key.$domain" + +cat <` generates +`private_key.pem` and `public_key.pem` in the current directory when the key +argument is omitted, then prints the TXT record. The private key is created at +mode `0600`. If `./private_key.pem` already exists, the script reuses it instead +of overwriting it. Move both files to a protected location before starting the +services. + Then point both settings at the new pair and restart `bal-server` and `bal-pusher`: @@ -100,6 +107,10 @@ KEY=$(openssl pkey -in private_key.pem -pubout -outform DER | base64 | tr -d '\n echo "v=we1; alg=ed25519; key=$KEY" ``` +`contrib/make_welist_txt.sh [key]` prints the record name, value, and a +BIND zone line directly. `key` may be a private/public PEM file or an existing +base64 SPKI DER value; omit it to generate a new key pair first. + The value has this form: ```text