#!/bin/bash # trivial check if server cert is OK incl. best effort to download # referenced certificates and CRLs in chain # # jpokorny@redhat.com # # TODO: # - currently, only cl[tl] files supported, not immediate PEM etc.; # also any reference to external resource has to start with URI # (is it a convention or a single case?) # - couldn't get rid of dependency on temporary file as it is read # twice in two substituted commands and neither env. variable nor # file descriptor sharing is suitable (stdin can be read only once, # generally, there is a race between the two?) # - wget vs. certificates? switch to curl? # - remove unneeded subshells? ( '()' -> '{}' ) CA_BUNDLE=/etc/pki/tls/certs/ca-bundle.crt HOMEBUNDLE=~/.pki/tls/certs/ca-bundle.crt #WGET="wget -nv -U '' --ca-certificate <(cat "${CA_BUNDLE}" "${HOMEBUNDLE}")" WGET="wget -nv -U ''" guess_inform() { case "{1##*.}" in der) echo DER;; crt|pem|*) echo PEM;; esac } guess_vercmd() { echo "$1" | grep -q 'X509 CRL' && echo 'crl' || echo 'verify' } vercmd2cmd() { echo "$1" | grep -q 'crl' && echo 'crl' || echo 'x509' } guess_cmd() { echo "YYYYYYY $1" >>/tmp/test case "$(basename "$1")" in crl*|revoke*|*crl) echo "crl";; *crt|*) echo "x509";; esac } cert_pick_file() { [[ "$1" =~ .*://.* ]] && return 1 echo "Trying file" >&2 local inform=$(guess_inform "$1") local cmd=$(guess_cmd "$1") [ -f "$1" ] && openssl ${cmd} -inform "${inform}" -in "$1" } # when CA cert is hosted on https server signed by this very CA cert_pick_url_selfsigned() { [[ "$1" =~ https://.* ]] || return 1 echo "Trying self-signed" >&2 local ret= local start=${1##https://} local host=${start%%/*} local machine=${host%%:*} local port=${host#*:} [ "${port}" = "${machine}" ] && port=443 local cont=${start#*/} local inform=$(guess_inform "${cont}") [ "$(guess_cmd "${cont}")" = "x509" ] || return $? ( echo -e "GET /${cont} /HTTP 1.0\n"; sleep 2 ) \ | openssl s_client -connect "${machine}:${port}" -crlf 2>/dev/null \ | sed -ne '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \ | ( local tmpfile=$(mktemp /tmp/.XXXXXX) cat >${tmpfile} openssl verify -CAfile \ <(awk '/-BEGIN CERTIFICATE-/{if(++i > 2){print; exit;}}{if(i == 2){print;}}' ${tmpfile} \ | cat "${CA_BUNDLE}" "${HOMEBUNDLE}" -) \ <(awk '/-BEGIN CERTIFICATE-/{if(++i > 2){exit;}}{if(i == 1){print;}}' ${tmpfile}) >&2; ret=$? [ $ret -eq 0 ] \ && openssl x509 -inform "${inform}" -in ${tmpfile} rm -- ${tmpfile} return $ret ) } cert_pick_url() { echo "Trying URL.." >&2 local inform=$(guess_inform "$1") local cmd=$(guess_cmd "$1") (if ! ${WGET} "$1" -O- && [[ "$1" =~ https://.* ]]; then local start=${1##https://} local host=${start%%/*} local machine=${host%%:*} local port=${host#*:} [ "${port}" = "${machine}" ] && port=443 ( echo ">>> recursion" >&2 cert_pick_check "${machine}" "${port}" \ || cert_pick_check -nocrl "${machine}" "${port}" echo "<<< recursion" >&2 ) >&2 \ && ${WGET} --no-check-certificate "$1" -O- fi) | openssl ${cmd} -inform "${inform}" } cert_pick_from_server() { echo "Trying from server.." >&2 local server=$1 local port=443 # https local opts= [ $# -ge 2 ] && [[ "${2}" =~ ^[^-].* ]] && shift && port=$1 [ $# -ge 2 ] && opts=$2 ( echo; sleep 2 ) \ | openssl s_client -connect "${server}:${port}" -crlf $opts 2>/dev/null \ | sed -ne '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' \ | awk '/-BEGIN CERTIFICATE-/{if(++i > 1){exit;}}{print;}' } cert_pick() { cert_pick_file "$@" \ || cert_pick_url_selfsigned "$@" \ || cert_pick_url "$@" \ || cert_pick_from_server "$@" } cert_check() { local ret= tmpfile=$(mktemp /tmp/.XXXXXX) cat >${tmpfile} export vercmd=$(local fst; read fst < <(head -n1 "${tmpfile}"); guess_vercmd "${fst}") openssl ${vercmd} $([ "$1" != "0" ] && echo '-crl_check') -CAfile \ <( openssl $(vercmd2cmd "${vercmd}") -noout -text -in ${tmpfile} \ | sed -n 's|.*URI:\(.\+\)|\1|p' \ | xargs -I '{}' bash -c "case \$(guess_cmd '{}') in \ verify) ${WGET} -O- '{}' | openssl x509 -outform PEM;; \ crl) ${WGET} -O- '{}' | openssl crl -outform PEM;; \ *) echo 'Sorry, URI {} not supported' >&2;; \ esac" \ | cat "${CA_BUNDLE}" "${HOMEBUNDLE}" - 2>/dev/null ) \ $(echo "${vercmd}" | grep -q crl && echo '-in') ${tmpfile} >&2 ret=$? [ $ret -eq 0 ] && cat ${tmpfile} unset vercmd rm -- ${tmpfile} echo "$ret" >&2 return $ret } colorize() { # last line = exitcode ( ( test -t 1 || [ $# -ge 1 ] ) \ && sed -u \ -e 's|\(^Trying.*$\)|\x1b[33m\1\x1b[0m|' \ -e 's|\(^Adding.*$\)|\x1b[33m\1\x1b[0m|' \ -e 's|\(^error\s\+.*\)|\x1b[31m\1\x1b[0m|' \ -e 's|\(^\S\+:.*\)|\x1b[32m\1\x1b[0m|' \ -e 's|\(^\S\+\s\+\S\+\s\+URL:.*\)|\x1b[36m\1\x1b[0m|' \ || cat ) | awk 'FNR == 1 { last=$1; while (getline) { print last; last=$0; } exit last}' } cert_pick_check() { local crl=1 [ "$1" = "-nocrl" ] && shift && crl=0 cert_pick "$@" | cert_check $crl } setup() { set -u RESTOREUMASK=$(umask -p) umask 077 } teardown() { ${RESTOREUMASK} unset RESTOREUMASK unset vercmd return $1 } [[ "${BASH_SOURCE[0]}" != "${0}" ]] || \ { [ $# -lt 1 ] \ && echo "usage: $0" \ "[-nocrl] file-or-url-or-server [server-port=443]" \ || { setup; { cert_pick_check "$@"; echo $?; } |& colorize 1; teardown $?; } }