trust-store-generators/get-certs.sh

140 lines
4.0 KiB
Bash
Executable File

#!/bin/sh
# Download TLS certificates of hosts specified in the `hosts` file.
set -o errexit # (-e) exit immediately if any command has a non-zero exit status
set -o nounset # (-u) don't accept undefined variables
#set -o xtrace # for debugging
# Go where this script is.
cd "$(dirname "$0")" || exit
# Create certs directory, if it does not exist.
mkdir -p certs
# Define function for fetching a certificate.
fetch_cert() (
hp="$1" # host and port
timeout="${2:-}"
torsocks="${3:-}"
# For .onion domains, connect using Tor.
if [ -z "${hp##*.onion:*}" ]; then
torsocks='torsocks'
fi
# If a Tor connection was requested and torsocks is not installed, return.
if [ "$torsocks" != '' ] && ! command -v torsocks >/dev/null; then
>&2 echo "torsocks not available ($hp)"
return
fi
response=$($timeout $torsocks openssl s_client -connect "$hp" </dev/null 2>/dev/null \
| sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')
# If no response and Tor was not used, try again.
if [ -z "$response" ] && [ -z "$torsocks" ]; then
sleep 5
response=$($timeout openssl s_client -connect "$hp" </dev/null 2>/dev/null \
| sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')
fi
echo "$response"
)
# Loop through all hosts.
while read -r host; do
printf "%s" "$host"
port='1965' # default Gemini port
# Check if host contains square brackets:
# [IPv4], [IPv6], [IPv4]:port or [IPv6]:port.
if expr "$host" : '^\[.*\]' >/dev/null; then
if expr "$host" : '^\[.*\]\:\([0-9]*\)$' >/dev/null; then
port=$(expr "$host" : '^\[.*\]\:\([0-9]*\)$')
fi
host=$(expr "$host" : '^\[\(.*\)\]')
# No square brackets, just host:port.
# If $host contains only a single ":", then it is not an IPv6 address.
elif [ "$(echo "$host" | tr -dc ':' | wc -c)" = 1 ]; then
port=$(echo "$host" | cut -d ':' -f 2)
host=$(echo "$host" | cut -d ':' -f 1)
fi
# Hostname to punycode.
host=$(echo "$host" | idn --allow-unassigned)
# Hostname to lowercase.
host=$(echo "$host" | tr '[:upper:]' '[:lower:]')
# Generate host_and_port string.
if [ -z "${host##*:*}" ]; then
# If host is an IPv6 address, add brackets around it.
host_and_port="[$host]:$port"
else
host_and_port="$host:$port"
fi
# Get cert.
cert=$(fetch_cert "$host_and_port" 'timeout 10')
if [ -z "$cert" ]; then
>&2 echo # empty line
>&2 echo "$host_and_port - connection failed"
fi
# If "tor" option is used, then connect again via Tor,
# to check if we get the same cert from a different network perspective.
if [ "${1:-}" = 'tor' ] && [ -n "${host##*.onion}" ]; then
# If torsocks is not installed, return.
if ! command -v torsocks >/dev/null; then
>&2 echo "torsocks not available ($host_and_port)"
exit 1
fi
cert_via_tor=$(fetch_cert "$host_and_port" 'timeout 25' 'torsocks')
if [ -z "$cert_via_tor" ]; then
[ -n "$cert" ] && >&2 echo # output empty line to stderr if cert was downloaded without Tor
>&2 echo "$host_and_port - Tor connection failed"
elif [ -n "$cert" ] && [ "$cert" != "$cert_via_tor" ]; then
>&2 echo "$host_and_port - Tor VERIFICATION FAILED (certs don't match)!!!"
# In this case, don't save any certificate to file.
# Output both certificates to stderr instead.
>&2 echo "CERT:"
>&2 echo "$cert"
>&2 echo "CERT VIA TOR:"
>&2 echo "$cert_via_tor"
continue
else
# If direct connection failed and Tor connection succeeded,
# use the cert received via Tor.
cert="$cert_via_tor"
fi
fi
if [ -n "$cert" ]; then
# If we got a cert back, then the host and port were valid,
# so they are safe to include in a file name.
# Convert from punycode to unicode, if needed.
host_and_port=$(echo "$host_and_port" | idn --allow-unassigned --idna-to-unicode)
echo "$cert" > "certs/${host_and_port}.pem"
printf ' - OK'
else
printf ' - failed'
fi
echo # newline
sleep 0.3
done < hosts
echo OK