Skip to content

Instantly share code, notes, and snippets.

@monodot
Created March 15, 2017 14:11
Show Gist options
  • Save monodot/2cc4538efa7a40c9635f6d8cd113cfc1 to your computer and use it in GitHub Desktop.
Save monodot/2cc4538efa7a40c9635f6d8cd113cfc1 to your computer and use it in GitHub Desktop.
#!/usr/bin/env sh
set -e # fail on unhandled error
set -u # fail on undefined variable
#set -x # debug
alias command_exists="type >/dev/null 2>&1"
if command_exists curl; then
alias download_file="curl -s"
elif command_exists wget; then
alias download_file="wget -q -O -"
else
echo "Error: curl or wget is required"
exit 1
fi
if ! command_exists openssl; then
echo "Error: openssl is required"
exit 1
fi
cert_is_pem() {
grep -e "-----" >/dev/null
}
# normalize to PEM
cert_to_pem() {
# bash variables can't contain binary data with null-bytes, so it needs to be stored encoded, and decoded before use
CERT=$(openssl base64)
if ! echo "$CERT" | openssl base64 -d | cert_is_pem; then
echo "$CERT" | openssl base64 -d | openssl x509 -inform der
else
echo "$CERT" | openssl base64 -d | openssl x509
fi
}
# certificate is parsed from OpenSSL text output. dirty solution, but it works
cert_pem_to_text() {
openssl x509 -noout -text
}
cert_get_subject() {
cert_pem_to_text | awk 'BEGIN{FS="Subject: "} NF==2{print $2}'
}
cert_get_issuer_url() {
cert_pem_to_text | awk 'BEGIN{FS="CA Issuers - URI:"} NF==2{print $2}'
}
# run!
if [ $# != 2 ]; then
echo "SSL certificate chain extractor"
echo
echo "Usage: $0 <host> <port>"
echo
echo "Host can be domain (no URI) or IP address"
echo "Port of running SSL service"
exit
fi
HOST="$1"
PORT="$2"
SERVER_CERT="$(openssl s_client -connect $HOST:$PORT </dev/null 2>/dev/null | \
sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p')"
OUTPUT_FILENAME="$HOST-$PORT.crt"
BASE="${OUTPUT_FILENAME%.*}"
EXT="${OUTPUT_FILENAME##*.}"
> "$OUTPUT_FILENAME" # clear output file
# extract the first certificate from input file, to make this script idempotent; normalize to PEM
CURRENT_CERT=$(echo "$SERVER_CERT" | cert_to_pem)
# loop over certificate chain using AIA extension, CA Issuers field
I=0
while true; do
# get certificate subject
CURRENT_SUBJECT=$(echo "$CURRENT_CERT" | cert_get_subject)
echo "$((I+1)): $CURRENT_SUBJECT"
# append certificate to result
echo "$CURRENT_CERT" >> "$OUTPUT_FILENAME"
# split copy
echo "$CURRENT_CERT" > "$BASE""_$I.$EXT"
# get issuer's certificate URL
PARENT_URL=$(echo "$CURRENT_CERT" | cert_get_issuer_url)
if [ -z "$PARENT_URL" ]; then
break
fi
# download issuer's certificate, normalize to PEM
CURRENT_CERT=$(download_file "$PARENT_URL" | cert_to_pem)
I=$((I+1))
done
echo "Certificate chain complete."
echo "Total $((I+1)) certificate(s) written."
echo ""
echo "\033[1m$BASE""_X.$EXT\033[0m: IDs start from 0 (server leaf) to N (trusted root anchor)."
echo "\033[1m$OUTPUT_FILENAME\033[0m: A merged version is also saved."
echo ""
# verify the certificate chain
if ! openssl verify -untrusted "$OUTPUT_FILENAME" "$OUTPUT_FILENAME" > /dev/null; then
echo "Error: verification failed"
exit 1
fi
echo "Verified successfully."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment