diff --git a/README.md b/README.md
index 2e89c3f..8531c05 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ Miscellaneous scripts for different purposes. Mostly unrelated to each other.
| Category | Script & Language | Purpose & Usage |
|:---|:---|:---|
| Automation | [`find-inactive-ssh-sessions.sh`](bin/find-inactive-ssh-sessions.sh)
Shell (bash) | Find inactive (idle) SSH sessions or kill (`-k`) them.
`find-inactive-ssh-sessions.sh [-k] [-i seconds] [-s]`
Could be used as a [workaround](https://serverfault.com/a/1162840/274176) for OpenSSH < 9.2 that did not have the [sshd_config(5)](https://man.openbsd.org/sshd_config) keywords `ChannelTimeout` & `UnusedConnectionTimeout`.|
-| DNS
DANE | [`letsencrypt-tlsa.sh`](bin/letsencrypt-tlsa.sh)
Shell (bash) | Create TLSA records from the current & backup Let's Encrypt Intermediate CAs. |
+| DNS
DANE | [`letsencrypt-tlsa.sh`](bin/letsencrypt-tlsa.sh)
Shell (bash) | Create TLSA records from the current & backup Let's Encrypt Intermediate CAs.
Defaults to `le-ca TLSA 2 1 1` with configurable selector (`-f`) & matching type (`-m`).
`find-inactive-ssh-sessions.sh [-f] [-m 0-2] [-l label [TTL]] [-h] [2>/dev/null]`|
| Email | [`mail-prepender.sh`](bin/mail-prepender.sh)
Shell (bash) | Prepends (to stdin/stdout) email header strings given in as flags `i`, `I`, `a`, or `A`; after possible mbox `From` & `Return-Path` header lines. Intended as a limited `formail` replacement that ignores the nyanses of the flags and simply prepends the valid (RFC 5322, 2.2) non-empty headers keeping the other headers as is. Flags `x` & `X` are implemented. Any other flags are ignored. |
| Git | [`git-find-commits-by-file-hash.sh`](bin/git-find-commits-by-file-hash.sh)
Shell (bash) | Search Git repository history for commits with SHA-256 checksum of a file. Answers the question "Has this version of this file ever been committed as the file on this path of this Git repository?" and shows a summary (`git show --stat`) of the matching commit(s). The `path` should be relative to the repository root.
`git-find-commits-by-file-hash.sh sha256sum path`|
| Infosec | [`netcat-proxy.sh`](bin/netcat-proxy.sh)
Shell (sh) | Creates a simple persistent TCP proxy with netcat & named pipes.
`netcat-proxy.sh listenport targethost targetport` |
diff --git a/bin/letsencrypt-tlsa.sh b/bin/letsencrypt-tlsa.sh
index eb79cf5..79dcf8d 100755
--- a/bin/letsencrypt-tlsa.sh
+++ b/bin/letsencrypt-tlsa.sh
@@ -1,22 +1,83 @@
#!/bin/bash
+read -r -d '' USAGE << EOM
# ------------------------------------------------------------------------------
# Create TLSA records from the current & backup Let's Encrypt Intermediate CAs
#
+# Usage: find-inactive-ssh-sessions.sh [-f] [-m 0-2] [-l label [TTL]] [-h]
+#
+# -f Full certificate mode (RFC 6698, 2.1.2 The Selector Field 0).
+# Without this option, SubjectPublicKeyInfo (1) is used by default.
+#
+# -m Matching Type (RFC 6698, 2.1.3); defaults to SHA-256
+# 0 Exact match on selected content
+# 1 SHA-256 hash of selected content [RFC6234]
+# 2 SHA-512 hash of selected content [RFC6234]
+#
+# -l Label (domain) part. Defaults to le-ca without FQDN.
+# Can contain TTL after the label; this has no validation!
+# * Example with FQDN, for SMTP: _25._tcp.example.com.
+# * Example with TTL, for HTTPS: "_443._tcp.ecample.com. 3600"
+#
+# -h Help. Prints this and exits (ignoring all other options).
+#
+# Unique TLSA records will be printed to stdout, everything else to stderr.
+# To get a clean output you can paste to your zone file, add 2>/dev/null.
+#
# Author : Esa Jokinen (oh2fih)
# Home : https://github.com/oh2fih/Misc-Scripts
# ------------------------------------------------------------------------------
+EOM
SOURCE="/certificates/"
BASE_URL="https://letsencrypt.org"
+SELECTOR=1 # SubjectPublicKeyInfo
+DIGEST=1 # SHA-256
+LABEL="le-ca"
+
+while getopts ":hfm:l:" opt; do
+ case ${opt} in
+ h)
+ echo -e "$USAGE" >&2
+ echo "# LE Chains of Trust page: ${BASE_URL}${SOURCE}"
+ exit 1
+ ;;
+ f)
+ SELECTOR=0
+ ;;
+ m)
+ case $OPTARG in
+ 0|1|2)
+ LABEL="$OPTARG"
+ ;;
+ *)
+ echo "Invalid option: -m must be 0, 1 (SHA-256), or 2 (SHA-512)" >&2
+ exit 1
+ ;;
+ esac
+ ;;
+ l)
+ DOMAIN="$OPTARG"
+ ;;
+ \?)
+ echo "Invalid option: $OPTARG" >&2
+ exit 1
+ ;;
+ :)
+ echo "Invalid option: $OPTARG requires an argument" >&2
+ exit 1
+ ;;
+ esac
+done
+
# Check for requirements. Print all unmet requirements at once.
required_command() {
if ! command -v "$1" &> /dev/null; then
if [ -z ${2+x} ]; then
- echo -e "\033[0;31mThis script requires ${1}!\033[0m" >&2
+ echo "This script requires ${1}!" >&2
else
- echo -e "\033[0;31mThis script requires ${1} ${2}!\033[0m" >&2
+ echo "This script requires ${1} ${2}!" >&2
fi
((UNMET=UNMET+1))
fi
@@ -30,6 +91,10 @@ required_command "grep"
required_command "sed"
required_command "awk"
+if (( DIGEST == 0 )); then
+ required_command "hexdump" "for -m 0"
+fi
+
if [ "$UNMET" -gt 0 ]; then
exit 1
fi
@@ -48,16 +113,56 @@ if [ "$INTERMEDIATE_PATHS" = "" ]; then
exit 1
fi
+# Helper functions to handle different options
+
+extract_der() {
+ case "$1" in
+ 0)
+ openssl x509 -outform DER
+ ;;
+ 1)
+ openssl x509 -noout -pubkey | openssl pkey -pubin -outform DER
+ ;;
+ esac
+}
+
+digest() {
+ case "$1" in
+ 0)
+ hexdump -ve '/1 "%02X"'
+ ;;
+ 1)
+ openssl dgst -sha256 -hex
+ ;;
+ 2)
+ openssl dgst -sha512 -hex
+ ;;
+ esac
+}
+
# Create TLSA records
+declare -a RECORDS=()
while IFS= read -r path ; do
- echo "[${BASE_URL}${path}]" >&2
PEM=$(curl --silent "${BASE_URL}${path}")
+ echo "[${BASE_URL}${path}]" >&2
if [[ "$PEM" =~ ^[-]+BEGIN[[:space:]]CERTIFICATE[-]+ ]]; then
- echo "$PEM" \
- | openssl x509 -outform DER \
- | openssl dgst -sha256 -hex \
- | awk '{print "le-ca TLSA 2 1 1", $NF}'
- fi
+ TLSA=$(
+ echo "$PEM" \
+ | extract_der "$SELECTOR" \
+ | digest "$DIGEST" \
+ | sed -e 's/\(.*\)/\U\1/' \
+ | awk '{print "'"$DOMAIN"' TLSA 2 '"$SELECTOR"' '"$DIGEST"'", $NF}'
+ )
+ # Do not print duplicate records to stdout
+ if [[ ! ${RECORDS[*]} =~ $TLSA ]]; then
+ echo "$TLSA"
+ RECORDS+=("$TLSA")
+ else
+ echo "(${TLSA})" >&2
+ fi
+ else
+ echo "(Reply was not a PEM encoded certificate)" >&2
+ fi
done <<< "$INTERMEDIATE_PATHS"