Skip to content

Commit 8fc1352

Browse files
committed
letsencrypt-tlsa.sh fix TLSA creation & add features
- Fix TLSA record creation. - Support both certificate mode. - Support matching type selection. - Support custom labels. - Remove duplicates.
1 parent 7229874 commit 8fc1352

File tree

2 files changed

+114
-9
lines changed

2 files changed

+114
-9
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Miscellaneous scripts for different purposes. Mostly unrelated to each other.
88
| Category | Script & Language | Purpose & Usage |
99
|:---|:---|:---|
1010
| Automation | [`find-inactive-ssh-sessions.sh`](bin/find-inactive-ssh-sessions.sh) <br> Shell (bash) | Find inactive (idle) SSH sessions or kill (`-k`) them.<br>`find-inactive-ssh-sessions.sh [-k] [-i seconds] [-s]`<br>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`.|
11-
| DNS <br> DANE | [`letsencrypt-tlsa.sh`](bin/letsencrypt-tlsa.sh) <br> Shell (bash) | Create TLSA records from the current & backup Let's Encrypt Intermediate CAs. |
11+
| DNS <br> DANE | [`letsencrypt-tlsa.sh`](bin/letsencrypt-tlsa.sh) <br> Shell (bash) | Create TLSA records from the current & backup Let's Encrypt Intermediate CAs. <br> Defaults to `le-ca TLSA 2 1 1` with configurable selector (`-f`) & matching type (`-m`). <br>`find-inactive-ssh-sessions.sh [-f] [-m N] [-l "label [TTL]"] [-h] [2>/dev/null]`|
1212
| Email | [`mail-prepender.sh`](bin/mail-prepender.sh) <br> 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. |
1313
| Git | [`git-find-commits-by-file-hash.sh`](bin/git-find-commits-by-file-hash.sh) <br> 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. <br> `git-find-commits-by-file-hash.sh sha256sum path`|
1414
| Infosec | [`netcat-proxy.sh`](bin/netcat-proxy.sh) <br> Shell (sh) | Creates a simple persistent TCP proxy with netcat & named pipes. <br> `netcat-proxy.sh listenport targethost targetport` |

bin/letsencrypt-tlsa.sh

Lines changed: 113 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,83 @@
11
#!/bin/bash
2+
read -r -d '' USAGE << EOM
23
# ------------------------------------------------------------------------------
34
# Create TLSA records from the current & backup Let's Encrypt Intermediate CAs
45
#
6+
# Usage: letsencrypt-tlsa.sh [-f] [-m N] [-l "label [TTL]"] [-h] [2>/dev/null]
7+
#
8+
# -f Full certificate mode (RFC 6698, 2.1.2 The Selector Field 0).
9+
# Without this option, SubjectPublicKeyInfo (1) is used by default.
10+
#
11+
# -m Matching Type (RFC 6698, 2.1.3); defaults to SHA-256
12+
# 0 Exact match on selected content
13+
# 1 SHA-256 hash of selected content [RFC6234]
14+
# 2 SHA-512 hash of selected content [RFC6234]
15+
#
16+
# -l Label (domain) part. Defaults to le-ca without FQDN.
17+
# Can contain TTL after the label; this has no validation!
18+
# * Example with FQDN, for SMTP: _25._tcp.example.com.
19+
# * Example with TTL, for HTTPS: "_443._tcp.ecample.com. 3600"
20+
#
21+
# -h Help. Prints this and exits (ignoring all other options).
22+
#
23+
# Unique TLSA records will be printed to stdout, everything else to stderr.
24+
# To get a clean output you can paste to your zone file, add 2>/dev/null.
25+
#
526
# Author : Esa Jokinen (oh2fih)
627
# Home : https://github.com/oh2fih/Misc-Scripts
728
# ------------------------------------------------------------------------------
29+
EOM
830

931
SOURCE="/certificates/"
1032
BASE_URL="https://letsencrypt.org"
1133

34+
SELECTOR=1 # SubjectPublicKeyInfo
35+
DIGEST=1 # SHA-256
36+
LABEL="le-ca"
37+
38+
while getopts ":hfm:l:" opt; do
39+
case ${opt} in
40+
h)
41+
echo -e "$USAGE" >&2
42+
echo "# LE Chains of Trust page: ${BASE_URL}${SOURCE}"
43+
exit 1
44+
;;
45+
f)
46+
SELECTOR=0
47+
;;
48+
m)
49+
case $OPTARG in
50+
0|1|2)
51+
LABEL="$OPTARG"
52+
;;
53+
*)
54+
echo "Invalid option: -m must be 0, 1 (SHA-256), or 2 (SHA-512)" >&2
55+
exit 1
56+
;;
57+
esac
58+
;;
59+
l)
60+
DOMAIN="$OPTARG"
61+
;;
62+
\?)
63+
echo "Invalid option: $OPTARG" >&2
64+
exit 1
65+
;;
66+
:)
67+
echo "Invalid option: $OPTARG requires an argument" >&2
68+
exit 1
69+
;;
70+
esac
71+
done
72+
1273
# Check for requirements. Print all unmet requirements at once.
1374

1475
required_command() {
1576
if ! command -v "$1" &> /dev/null; then
1677
if [ -z ${2+x} ]; then
17-
echo -e "\033[0;31mThis script requires ${1}!\033[0m" >&2
78+
echo "This script requires ${1}!" >&2
1879
else
19-
echo -e "\033[0;31mThis script requires ${1} ${2}!\033[0m" >&2
80+
echo "This script requires ${1} ${2}!" >&2
2081
fi
2182
((UNMET=UNMET+1))
2283
fi
@@ -30,6 +91,10 @@ required_command "grep"
3091
required_command "sed"
3192
required_command "awk"
3293

94+
if (( DIGEST == 0 )); then
95+
required_command "hexdump" "for -m 0"
96+
fi
97+
3398
if [ "$UNMET" -gt 0 ]; then
3499
exit 1
35100
fi
@@ -48,16 +113,56 @@ if [ "$INTERMEDIATE_PATHS" = "" ]; then
48113
exit 1
49114
fi
50115

116+
# Helper functions to handle different options
117+
118+
extract_der() {
119+
case "$1" in
120+
0)
121+
openssl x509 -outform DER
122+
;;
123+
1)
124+
openssl x509 -noout -pubkey | openssl pkey -pubin -outform DER
125+
;;
126+
esac
127+
}
128+
129+
digest() {
130+
case "$1" in
131+
0)
132+
hexdump -ve '/1 "%02X"'
133+
;;
134+
1)
135+
openssl dgst -sha256 -hex
136+
;;
137+
2)
138+
openssl dgst -sha512 -hex
139+
;;
140+
esac
141+
}
142+
51143
# Create TLSA records
52144

145+
declare -a RECORDS=()
53146
while IFS= read -r path ; do
54-
echo "[${BASE_URL}${path}]" >&2
55147
PEM=$(curl --silent "${BASE_URL}${path}")
148+
echo "[${BASE_URL}${path}]" >&2
56149
if [[ "$PEM" =~ ^[-]+BEGIN[[:space:]]CERTIFICATE[-]+ ]]; then
57-
echo "$PEM" \
58-
| openssl x509 -outform DER \
59-
| openssl dgst -sha256 -hex \
60-
| awk '{print "le-ca TLSA 2 1 1", $NF}'
61-
fi
150+
TLSA=$(
151+
echo "$PEM" \
152+
| extract_der "$SELECTOR" \
153+
| digest "$DIGEST" \
154+
| sed -e 's/\(.*\)/\U\1/' \
155+
| awk '{print "'"$DOMAIN"' TLSA 2 '"$SELECTOR"' '"$DIGEST"'", $NF}'
156+
)
62157

158+
# Do not print duplicate records to stdout
159+
if [[ ! ${RECORDS[*]} =~ $TLSA ]]; then
160+
echo "$TLSA"
161+
RECORDS+=("$TLSA")
162+
else
163+
echo "(${TLSA})" >&2
164+
fi
165+
else
166+
echo "(Reply was not a PEM encoded certificate)" >&2
167+
fi
63168
done <<< "$INTERMEDIATE_PATHS"

0 commit comments

Comments
 (0)