Skip to content

Instantly share code, notes, and snippets.

@johnrvt
Last active March 3, 2022 14:47
Show Gist options
  • Save johnrvt/0fb6910c790daa14d2251104869ee22b to your computer and use it in GitHub Desktop.
Save johnrvt/0fb6910c790daa14d2251104869ee22b to your computer and use it in GitHub Desktop.
Build a certificate chain from a single certificate.
#!/bin/bash
#
# Build a certificate chain from a single certificate.
#
# MIT License
#
# Copyright (c) 2019 Jonathan Ravat
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
set -e
if [ $# != 1 ]; then
echo "$0: missing argument: try '$0 --help'" >&2
exit 1
fi
case "$1" in
-h|--help)
echo "Synopsis: $0 CERTIFICATE"
echo "Build a certificate chain from a single certificate file."
echo
echo "The format expected for the input certificate is the PEM encoding."
echo
echo "Multiple files will be written at the same path as the input certificate:"
echo " - downloaded issuers of the input certificate"
echo " - the generated certificate chain without the root CA, ended with '.chain.crt'"
echo " - the full certificate chain, ended with '.fullchain.crt'"
exit 0
;;
esac
which openssl >/dev/null || echo "$0: missing openssl command. Please install 'openssl'." >&2
which certtool >/dev/null || echo "$0: missing certtool command. Please install 'gnutls-bin'." >&2
which openssl certtool >/dev/null || exit 1
cert="$1"
certpath=$(dirname "$cert")
tmpdir=$(mktemp -d)
certlist=("$cert")
subject_hash=$(openssl x509 -in "$cert" -noout -subject_hash)
issuer_hash=$(openssl x509 -in "$cert" -noout -issuer_hash)
while [ "$subject_hash" != "$issuer_hash" ]; do
# Get intermediate CA
issuer_uri=$(openssl x509 -in "$cert" -noout -text | grep -F "CA Issuers - URI:" | \
sed 's/^.*URI://')
if [ -n "$issuer_uri" ]; then
issuer_filename=$(basename "$issuer_uri")
issuer_cert="$certpath/$issuer_filename"
wget -nv -O "$tmpdir/$issuer_filename" "$issuer_uri"
openssl x509 -in "$tmpdir/$issuer_filename" -out "$issuer_cert" 2>/dev/null || \
openssl x509 -in "$tmpdir/$issuer_filename" -inform DER -out "$issuer_cert"
else
local_issuer=$(ls "/etc/ssl/certs/$issuer_hash"* 2>/dev/null | tail -n 1)
if [ -z "$local_issuer" ]; then
echo "$0: fatal: Issuer '$(openssl x509 -in "$cert" -noout -issuer)' not found locally" >&2
exit 1
fi
path_issuer=$(readlink -f "$local_issuer")
issuer_cert="$certpath/$(basename "$path_issuer")"
openssl x509 -in "$local_issuer" -out "$issuer_cert" 2>/dev/null || \
openssl x509 -in "$local_issuer" -inform DER -out "$issuer_cert"
fi
# Check issuer with subject
issuer_subject_hash=$(openssl x509 -in "$issuer_cert" -noout -subject_hash)
if [ "$issuer_hash" != "$issuer_subject_hash" ]; then
issuer_subject=$(openssl x509 -in "$issuer_cert" -noout -subject)
issuer_expected=$(openssl x509 -in "$cert" -noout -issuer)
echo "$0: fatal: Subject of '$issuer_cert' is not the issuer expected: has '$issuer_subject' but expect '$issuer_expected'" >&2
exit 1
fi
certlist[${#certlist[@]}]="$issuer_cert"
cert="$issuer_cert"
subject_hash=$(openssl x509 -in "$cert" -noout -subject_hash)
issuer_hash=$(openssl x509 -in "$cert" -noout -issuer_hash)
done
# Check for auto-signed certificate
if [ ${#certlist[@]} == 1 ]; then
echo "$0: $1: nothing to do: auto-signed certificate" >&2
exit 2
fi
# Verify chain
verify_opts=(-trusted "${certlist[-1]}")
for (( i=1; i < ${#certlist[@]} - 1; i++ )); do
verify_opts[${#verify_opts[@]}]=-untrusted
verify_opts[${#verify_opts[@]}]="${certlist[$i]}"
done
echo -n "Verify chain: "
openssl verify "${verify_opts[@]}" "$1"
# Generate certificate chains
base_cert=$(echo "$1" | sed -E 's/\.(crt|pem)$//')
chaincert="$base_cert.chain.crt"
fullchaincert="$base_cert.fullchain.crt"
> "$chaincert"
> "$fullchaincert"
for f in "${certlist[@]}"; do
openssl x509 -in "$f" >> "$fullchaincert"
[ "$f" != "${certlist[-1]}" ] && openssl x509 -in "$f" >> "$chaincert"
done
# Check certificate chain
certtool --verify-chain --verify-profile=medium --infile="$fullchaincert"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment