From ba08568d43585c485bb229c4772f19d9c5878323 Mon Sep 17 00:00:00 2001 From: Esa Jokinen Date: Tue, 23 Jul 2024 21:25:07 +0300 Subject: [PATCH] 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. --- README.md | 34 +++++------ bin/letsencrypt-tlsa.sh | 121 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 130 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 2e89c3f..47c5df7 100644 --- a/README.md +++ b/README.md @@ -7,28 +7,28 @@ 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. | -| 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` | -| Infosec | [`partialpassword.sh`](bin/partialpassword.sh)
Shell (bash) | Creates a new wordlist from a wordlist by replacing all ambiguous characters with all their possible combinations.
`partialpassword.sh input.txt output.txt O0 [Il1 ...]` | -| Infosec | [`duplicate-ssh-hostkeys.sh`](bin/duplicate-ssh-hostkeys.sh)
Shell (bash) | Find duplicate SSH host keys in a CIDR range. Examine your network for shared host keys that could potentially be dangerous.
`duplicate-ssh-hostkeys.sh CIDR [HostKeyAlgorithm ...]` | -| Infosec
Automation | [`make-mac-prefixes.py`](bin/make-mac-prefixes.py)
Python 3 | Processes registered MAC address prefixes from [IEEE MA-L Assignments (CSV)](https://standards.ieee.org/products-programs/regauth/) (stdin) to Nmap's [`nmap-mac-prefixes`](https://github.com/nmap/nmap/blob/master/nmap-mac-prefixes) (stdout) with a few additional unregistered OUIs.
`curl https://standards-oui.ieee.org/oui/oui.csv \| make-mac-prefixes.py > nmap-mac-prefixes` | -| WordPress | [`test-cache-enabler.py`](bin/test-cache-enabler.py)
Python 3 | Tests whether the Cache Enabler by KeyCDN (WordPress) is working properly on the URLs given as arguments.
`test-cache-enabler.py https://example.com [...]` | -| Web | [`detect-modified-html-element.sh`](bin/detect-modified-html-element.sh)
Shell (bash) | Checks HTML element changes on a web page since last run.
Recommended to be executed as a SystemD [service](systemd/detect-modified-html-element.service.example). | -| Web | `koronarokotusaika.sh`
Shell (bash) | This script has been removed as koronarokotusaika.fi (bookcovidvaccine.fi) has been shut down on April 28, 2023. | -| Web | [`xxl-product-pricelimiter.sh`](bin/xxl-product-pricelimiter.sh)
Shell (bash) | XXL.fi product price checker / limiter.
`xxl-product-pricelimiter.sh XXL.fi-ProductURL MaxPrice` | +| 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.
Defaults to `le-ca TLSA 2 1 1` with configurable selector (`-f`) & matching type (`-m`).
`letsencrypt-tlsa.sh [-f] [-m N] [-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` | +| Infosec | [`partialpassword.sh`](bin/partialpassword.sh)
Shell (bash) | Creates a new wordlist from a wordlist by replacing all ambiguous characters with all their possible combinations.
`partialpassword.sh input.txt output.txt O0 [Il1 ...]` | +| Infosec | [`duplicate-ssh-hostkeys.sh`](bin/duplicate-ssh-hostkeys.sh)
Shell (bash) | Find duplicate SSH host keys in a CIDR range. Examine your network for shared host keys that could potentially be dangerous.
`duplicate-ssh-hostkeys.sh CIDR [HostKeyAlgorithm ...]` | +| Infosec
Automation | [`make-mac-prefixes.py`](bin/make-mac-prefixes.py)
Python 3 | Processes registered MAC address prefixes from [IEEE MA-L Assignments (CSV)](https://standards.ieee.org/products-programs/regauth/) (stdin) to Nmap's [`nmap-mac-prefixes`](https://github.com/nmap/nmap/blob/master/nmap-mac-prefixes) (stdout) with a few additional unregistered OUIs.
`curl https://standards-oui.ieee.org/oui/oui.csv \| make-mac-prefixes.py > nmap-mac-prefixes` | +| WordPress | [`test-cache-enabler.py`](bin/test-cache-enabler.py)
Python 3 | Tests whether the Cache Enabler by KeyCDN (WordPress) is working properly on the URLs given as arguments.
`test-cache-enabler.py https://example.com [...]` | +| Web | [`detect-modified-html-element.sh`](bin/detect-modified-html-element.sh)
Shell (bash) | Checks HTML element changes on a web page since last run.
Recommended to be executed as a SystemD [service](systemd/detect-modified-html-element.service.example). | +| Web | `koronarokotusaika.sh`
Shell (bash) | This script has been removed as koronarokotusaika.fi (bookcovidvaccine.fi) has been shut down on April 28, 2023. | +| Web | [`xxl-product-pricelimiter.sh`](bin/xxl-product-pricelimiter.sh)
Shell (bash) | XXL.fi product price checker / limiter.
`xxl-product-pricelimiter.sh XXL.fi-ProductURL MaxPrice` | ## Scripts that require `sudo` privileges ([`sbin/`](sbin/)) | Category | Script & Language | Purpose & Usage | |:---|:---|:---| -| Automation | [`autoreboot-on-segfaults.sh`](sbin/autoreboot-on-segfaults.sh)
Shell (sh) | Temporary solution that automatically reboots the system if there has been more than `MAX_SEGFAULTS` segmentation faults on the current boot. Fix the system!
Recommended to be scheduled with a SystemD [service](systemd/autoreboot-on-segfaults.service.example) & [timer](systemd/autoreboot-on-segfaults.service.example).
| -| Automation | [`backup-mysql-databases.sh`](sbin/backup-mysql-databases.sh)
Shell (bash) | Backup all MySQL/MariaDB databases; dump & compress. Overwrites older backups matching the same date pattern. Recommended to be scheduled with a SystemD [service](systemd/backup-mysql-databases.service.example) & [timer](systemd/backup-mysql-databases.timer.example).
| -| Automation | [`create-site.sh`](sbin/create-site.sh)
Shell (bash) | Web hosting automation for Debian with Apache2, PHP-FPM & Let's Encrypt.
`sudo create-site.sh username example.com [www.example.com ...]` | -| Firewall | [`list2bans.sh`](sbin/list2bans.sh)
Shell (bash) | Lists all Fail2Ban jail statuses or jails banning an IP.
`sudo list2bans.sh [ip]` | -| Firewall | [`unfail2ban.sh`](sbin/unfail2ban.sh)
Shell (bash) | Unbans the given IPs from all Fail2Ban jails.
`sudo unfail2ban.sh ip [ip ...]` | +| Automation | [`autoreboot-on-segfaults.sh`](sbin/autoreboot-on-segfaults.sh)
Shell (sh) | Temporary solution that automatically reboots the system if there has been more than `MAX_SEGFAULTS` segmentation faults on the current boot. Fix the system!
Recommended to be scheduled with a SystemD [service](systemd/autoreboot-on-segfaults.service.example) & [timer](systemd/autoreboot-on-segfaults.service.example).
| +| Automation | [`backup-mysql-databases.sh`](sbin/backup-mysql-databases.sh)
Shell (bash) | Backup all MySQL/MariaDB databases; dump & compress. Overwrites older backups matching the same date pattern. Recommended to be scheduled with a SystemD [service](systemd/backup-mysql-databases.service.example) & [timer](systemd/backup-mysql-databases.timer.example).
| +| Automation | [`create-site.sh`](sbin/create-site.sh)
Shell (bash) | Web hosting automation for Debian with Apache2, PHP-FPM & Let's Encrypt.
`sudo create-site.sh username example.com [www.example.com ...]` | +| Firewall | [`list2bans.sh`](sbin/list2bans.sh)
Shell (bash) | Lists all Fail2Ban jail statuses or jails banning an IP.
`sudo list2bans.sh [ip]` | +| Firewall | [`unfail2ban.sh`](sbin/unfail2ban.sh)
Shell (bash) | Unbans the given IPs from all Fail2Ban jails.
`sudo unfail2ban.sh ip [ip ...]` | ## Install & update diff --git a/bin/letsencrypt-tlsa.sh b/bin/letsencrypt-tlsa.sh index eb79cf5..b93a76d 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: 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) + 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 "'"$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"