Skip to content

Instantly share code, notes, and snippets.

@reubenmiller
Last active August 17, 2023 07:29
Show Gist options
  • Save reubenmiller/f2f31f044780cd7b3dfb5d57a0ad271a to your computer and use it in GitHub Desktop.
Save reubenmiller/f2f31f044780cd7b3dfb5d57a0ad271a to your computer and use it in GitHub Desktop.
Download and build a ca-certificate store file using openssl

How to use

Option 1: Configure ca bundle (and download one if necessary) - No openssl required!

  1. Download the configure-ca-bundle.sh script (or either copy paste the contents and write to a file called ./configure-ca-bundle.sh)

  2. Make the script executable

    chmod +x ./configure-ca-bundle.sh
  3. Execute the script

    ./configure-ca-bundle.sh

Option 2: Download certificate from a list of servers - Requires openssl

  1. Download the script (or either copy paste the contents and write to a file called ./download-certs.sh)

  2. Make the script executable

    chmod +x ./download-certs.sh
  3. Execute the script

    ./download-certs.sh
#!/bin/sh
########################################################################################################################
# Check if a device has the correct ca bundle to support TLS communication
#
# The script checks if some urls are correctly verified, and will actively
# download a new ca bundle if the urls don't pass the verification. After the
# new ca bundle is downloaded and the urls are verified again, then the ca
# bundle will be copied to the specified destination (default being /etc/ssl/certs/ca-certificates.crt)
# If the file already exists, the new ca bundle will be appended to the existing file (without checking for duplicates)
#
########################################################################################################################
set -e
sh_c='sh -c'
usage() {
cat <<EOF
USAGE:
$0 [<ca-bundle-file>]
ARGUMENTS:
<ca-bundle-file> CA bundle file where to write the file to if a new bundle is required
Default: /etc/ssl/certs/ca-certificates.crt
OPTIONS:
--online-ca-bundle <url> Online ca bundle url to download from if the current bundle is
not sufficient. Default: https://curl.se/ca/cacert.pem
--dry-run Don't install anything, just let me know what it does
--help Show this help
Other options for online ca bundle sources are:
* https://raw.githubusercontent.com/certifi/python-certifi/master/certifi/cacert.pem
EOF
}
command_exists() {
command -v "$@" > /dev/null 2>&1
}
configure_shell() {
# Check if has sudo rights or if it can be requested
user="$(id -un 2>/dev/null || true)"
sh_c='sh -c'
if [ "$user" != 'root' ]; then
if command_exists sudo; then
sh_c='sudo -E sh -c'
elif command_exists su; then
sh_c='su -c'
else
cat >&2 <<-EOF
Error: this installer needs the ability to run commands as root.
We are unable to find either "sudo" or "su" available to make this happen.
EOF
exit 1
fi
fi
if is_dry_run; then
sh_c="echo"
fi
}
is_dry_run() {
if [ -z "$DRY_RUN" ]; then
return 1
else
return 0
fi
}
if command_exists tedge; then
C8Y_URL=$(tedge config get c8y.url 2>/dev/null)
if [ -n "$C8Y_URL" ]; then
CHECK_URLS="$CHECK_URLS https://$C8Y_URL"
fi
fi
check_dependencies() {
if command_exists curl; then
return 0
elif command_exists wget && (wget --help 2>&1 | grep -q 'ca-certificate'); then
return 0
else
echo "Missing curl or wget. curl or wget is required for this script to work. Note wget must also support the --ca-certificate option!" >&2
exit 1
fi
}
test_url() {
if command_exists curl; then
for i_url in $CHECK_URLS; do
if [ -n "$1" ]; then
printf "Checking url=%s using ca bundle: %s ... " "$i_url" "$1" >&2
if ! SSL_CERT_FILE="$1" curl -sSL "$i_url" >/dev/null 2>&1; then
echo "FAIL" >&2
return 1
fi
echo "PASS" >&2
else
printf "Checking url=%s with default ca bundle ... " "$i_url" >&2
if ! curl -sSL "$i_url" >/dev/null 2>&1; then
echo "FAIL" >&2
return 1
fi
echo "PASS" >&2
fi
done
return 0
elif command_exists wget; then
for i_url in $CHECK_URLS; do
if [ -n "$1" ]; then
printf "Checking url=%s using ca bundle: %s ... " "$i_url" "$1" >&2
if ! wget --ca-certificate="$1" "$i_url" >/dev/null 2>&1; then
echo "FAIL" >&2
return 1
fi
echo "PASS" >&2
else
printf "Checking url=%s with default ca bundle ... " "$i_url" >&2
if ! wget "$i_url" >/dev/null 2>&1; then
echo "FAIL" >&2
return 1
fi
echo "PASS" >&2
fi
done
return 0
else
echo "Unable to check urls as curl or wget is not installed" >&2
exit 1
fi
}
scan_ca_bundle() {
# Look for the first valid certificate
VALID_CA_BUNDLE=
for CA_PATH in $CA_PATHS; do
if [ -z "$VALID_CA_BUNDLE" ]; then
if [ -f "$CA_PATH" ]; then
if test_url "$CA_PATH"; then
VALID_CA_BUNDLE="$CA_PATH"
fi
fi
fi
done
echo "$VALID_CA_BUNDLE"
}
download_bundle() {
TMP_CA="${1:-/tmp/ca.bundle.crt}"
rm -f "$TMP_CA"
if command_exists curl; then
# Download the ca bundle from a trusted source
echo "No valid ca bundle found, downloading one from $ONLINE_CA_BUNDLE" >&2
curl --insecure -sSL "$ONLINE_CA_BUNDLE" > "$TMP_CA"
elif command_exists wget; then
echo "No valid ca bundle found, downloading one from $ONLINE_CA_BUNDLE" >&2
wget --no-check-certificate -O - "$ONLINE_CA_BUNDLE" > "$TMP_CA" 2>/dev/null
else
echo "curl or wget is required to be able to download the ca bundle" >&2
exit 1
fi
if [ ! -f "$TMP_CA" ]; then
echo "Temp ca file has not been created. It probably failed to download. file=$TMP_CA" >&2
exit 1
fi
echo "$TMP_CA"
}
set_tedge_config() {
bundle="$1"
if command_exists tedge; then
$sh_c "tedge config set c8y.root_cert_path '$bundle'"
else
echo '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
echo ""
echo "Warning: tedge is not yet installed so the script can not update the tedge config"
echo " to use the correct ca bundle."
echo ""
echo "After tedge is installed, run:"
echo ""
echo " sudo tedge config set c8y.root_cert_path '$bundle'"
echo ""
echo '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
fi
}
main() {
DEST_CA_BUNDLE="$1"
check_dependencies
configure_shell
if test_url ""; then
echo "Native ca bundle looks ok (without doing anything)" >&2
SYMLINK_COUNT=$(find /etc/ssl/certs/ -type l | wc -l)
if [ "$SYMLINK_COUNT" -lt 5 ] && [ -f /etc/ssl/certs/ca-certificates.crt ]; then
set_tedge_config "/etc/ssl/certs/ca-certificates.crt"
fi
return
fi
CA_BUNDLE=$(scan_ca_bundle)
if [ -z "$CA_BUNDLE" ]; then
CA_BUNDLE=$(download_bundle "/tmp/ca.bundle.crt")
fi
if [ -z "$CA_BUNDLE" ]; then
echo "Warning: Still no valid ca bundle found: $CA_BUNDLE" >&2
return 1
fi
if ! test_url "$CA_BUNDLE"; then
echo "Warning: Still no valid ca bundle found: $CA_BUNDLE" >&2
return 1
fi
if [ -n "$DEST_CA_BUNDLE" ] && [ "$DEST_CA_BUNDLE" != "$CA_BUNDLE" ]; then
if [ -f "$DEST_CA_BUNDLE" ]; then
$sh_c "cp $DEST_CA_BUNDLE $DEST_CA_BUNDLE.bak"
$sh_c "cat '$CA_BUNDLE' >> '$DEST_CA_BUNDLE'"
else
$sh_c "cat '$CA_BUNDLE' >> '$DEST_CA_BUNDLE'"
$sh_c "chown root:root $DEST_CA_BUNDLE"
$sh_c "chmod 644 $DEST_CA_BUNDLE"
fi
CA_BUNDLE="$DEST_CA_BUNDLE"
fi
echo "Found valid ca bundle: $CA_BUNDLE" >&2
set_tedge_config "$CA_BUNDLE"
export SSL_CERT_FILE="$CA_BUNDLE"
}
#
# Parsing
#
CA_PATHS="/etc/ssl/certs/ca-certificates.crt /var/etc/ssl/certs/ca-certificates.crt"
CHECK_URLS="https://cloudsmith.io https://thin-edge.io"
ONLINE_CA_BUNDLE="${ONLINE_CA_BUNDLE:-https://curl.se/ca/cacert.pem}"
DEST_CA_BUNDLE="${DEST_CA_BUNDLE:-/etc/ssl/certs/ca-certificates.crt}"
DRY_RUN=
while [ $# -gt 0 ]; do
case $1 in
--dry-run)
DRY_RUN=1
;;
--online-ca-bundle)
ONLINE_CA_BUNDLE="$2"
shift
;;
--help|-h)
usage
exit 0
;;
--*|-*)
log "Unknown option: $1"
usage
exit 1
;;
*)
DEST_CA_BUNDLE="$1"
;;
esac
# Use if statement as some devices
# don't support math $((arith)) statements
if [ $# -gt 0 ]; then
shift 1
fi
done
main "$DEST_CA_BUNDLE"
#!/bin/sh
set -e
check_dependencies() {
if ! command -V openssl >/dev/null 2>&1; then
echo "Missing openssl. openssl is required to download the certificates"
exit 1
fi
if ! command -V awk >/dev/null 2>&1; then
echo "Missing awk. awk is required to parse the certificates"
exit 1
fi
}
clean() {
sudo rm -f /tmp/ca.crt
touch /tmp/ca.crt
}
download_cert() {
#
# Download the certs from a server, and append them to a temp file
#
echo "Downloading certs: $1 => /tmp/ca.crt"
openssl s_client -host "$1" -port "443" -prexit -showcerts 2>/dev/null < /dev/null \
| awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{ if(/BEGIN CERTIFICATE/){}; print >> "/tmp/ca.crt" }'
}
test_url() {
#
# Test if a url is trusted or not
# Usage:
# test_url <url> [cacert_file]
#
url="https://$1"
if [ $# -ge 2 ]; then
OPTIONS="--cacert $2"
fi
if curl -sSL "$url" $OPTIONS >/dev/null; then
echo "Test ($url): PASS"
else
echo "Test ($url): FAIL"
exit 1
fi
}
main() {
check_dependencies
clean
# Download the certs to the temp ca store
download_cert "dl.cloudsmith.io"
download_cert "thin-edge.io"
if command -V tedge >/dev/null 2>&1; then
download_cert "$(tedge config get c8y.url 2>/dev/null)"
fi
# Test url before replacing the ca-certificate
# If any of the tests fail, the ca-certificates.crt file will not be replaced!
#
# Test urls (if they work now)
echo "Checking urls using the tmp ca file: /tmp/ca.crt"
test_url "dl.cloudsmith.io" "/tmp/ca.crt"
test_url "thin-edge.io" "/tmp/ca.crt"
if command -V tedge >/dev/null 2>&1; then
test_url "$(tedge config get c8y.url 2>/dev/null)" "/tmp/ca.crt"
fi
echo "Moving tmp CA store: /tmp/ca.crt => /etc/ssl/certs/ca-certificates.crt"
sudo mv /tmp/ca.crt /etc/ssl/certs/ca-certificates.crt
sudo chmod 644 /etc/ssl/certs/ca-certificates.crt
sudo tedge config set c8y.root_cert_path /etc/ssl/certs/ca-certificates.crt
}
#
# main
#
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment