-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathletsencrypt-tlsa.sh
executable file
·175 lines (156 loc) · 4.23 KB
/
letsencrypt-tlsa.sh
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/bin/bash
read -r -d '' USAGE << EOM
# ------------------------------------------------------------------------------
# Create TLSA records from the current & backup Let's Encrypt Intermediate CAs
#
# Usage: letsencrypt-tlsa.sh [-f] [-m N] [-l "label [TTL]"] [-h] [2>/dev/null]
#
# -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)
DIGEST="$OPTARG"
;;
*)
echo "Invalid option: -m must be 0, 1 (SHA-256), or 2 (SHA-512)" >&2
exit 1
;;
esac
;;
l)
LABEL="$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 "This script requires ${1}!" >&2
else
echo "This script requires ${1} ${2}!" >&2
fi
((UNMET=UNMET+1))
fi
}
UNMET=0
required_command "openssl" "for creating TLSA records"
required_command "curl" "for fetching data"
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
# Get URLs for the Subordinate (Intermediate) CAs, including backups
if (( SELECTOR == 1 )); then
# cross-signed certificates have the same public key
REGEX="/certs/[0-9]+/[0-9a-zA-Z]+.pem"
else
REGEX="/certs/[0-9]+/[0-9a-zA-Z]+(-cross)?.pem"
fi
INTERMEDIATE_PATHS=$(
curl --silent "${BASE_URL}${SOURCE}" \
| sed '/subordinate-intermediate-cas/d' \
| sed '/.summary.Retired..summary./q' \
| grep -oE "$REGEX"
)
if [ "$INTERMEDIATE_PATHS" = "" ]; then
echo "Failed to fetch certificate list from ${BASE_URL}${SOURCE}" >&2
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
PEM=$(curl --silent "${BASE_URL}${path}")
echo "[${BASE_URL}${path}]" >&2
if [[ "$PEM" =~ ^[-]+BEGIN[[:space:]]CERTIFICATE[-]+ ]]; then
TLSA=$(
echo "$PEM" \
| extract_der "$SELECTOR" \
| digest "$DIGEST" \
| sed -e 's/\(.*\)/\U\1/' \
| awk '{print "'"$LABEL"' 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"