#! /bin/bash progname=$(basename $0) OPT_SUMMARY=0 OPT_SOME=1 OPT_MOST=2 OPT_ALL=3 EXIT_SUCCESS=0 EXIT_FILE_OPEN=1 EXIT_CHECK=2 EXIT_HELP=3 declare -a options summaryopts="-subject -dates -issuer -noout" someopts="-text -fingerprint -noout -certopt no_pubkey,no_sigdump,no_version,no_signame,no_serial" mostopts="-text -fingerprint -noout -certopt no_pubkey,no_sigdump" allopts="-text -fingerprint -noout" showDetails=$OPT_SUMMARY doCheck=0 exitStatus=$EXIT_SUCCESS options[$OPT_SUMMARY]=$summaryopts options[$OPT_SOME]=$someopts options[$OPT_MOST]=$mostopts options[$OPT_ALL]=$allopts # Set the exit status to indicate a consistency problem. # This is a function in case we decide that we don't want # to overwrite a more serious error. SetCheckError () { exitStatus=$EXIT_CHECK } x509_show_cert () { local cert="$1" shift local opts=$(eval echo $*) eval openssl x509 $opts <<< "$cert" } x509_show_chain () { local filename="$1" local showDetails=$2 local line= local cert= local newline=$'\n' local firstInFile=1 shift shift local opts=$* local line= local line2= local numCertsInFile=0 local lastIdx=0 local numSelfSigned=0 local seenSelfSignedIdx=0 local seenExtraCert=0 local seenCA=0 local extraCert=0 local curIdx=0 local curSubj="" local curIssuer="" local lastIdx=0 local lastSubj="" local lastIssuer="" local nl=$'\n' # set to "" when not wanted local NL=$'\n' # never changed IFS=$'\n' while read line do if [[ "$line" =~ BEGIN.*CERTIFICATE ]] then cert="$line" while read line do cert+="$newline$line" if [[ "$line" =~ END.*CERTIFICATE ]] then if [ $firstInFile -eq 1 ] then firstInFile=0 else echo "***" fi certInfo=$(x509_show_cert "$cert" $opts) echo "Cert #${curIdx}:" echo "${certInfo}" ((++numCertsInFile)) # get and check Subject and Issuer from this cert while read line2 do case "${line2}" in subject=*) curSubj=$(echo "${line2}" | sed -e 's/^subject=//') ;; issuer=*) curIssuer=$(echo "${line2}" | sed -e 's/^issuer=//') ;; esac done <<< "${certInfo}" # # check for errors or anomalies # if [ $doCheck -ne 0 ] then nl=$'\n' if [ ${numSelfSigned} -ne 0 ] then seenExtraCert=1 SetCheckError echo "${nl}-- Cert #${curIdx} is after self-signed cert #${seenSelfSignedIdx}, so OpenSSL will ignore it." nl="" fi if [ ${curIdx} -gt 0 ] then if [ "${lastIssuer}" != "${curSubj}" ] then SetCheckError echo "${nl}-- Cert #${curIdx} did not sign the previous cert #${lastIdx}." echo " #${lastIdx}: Previous Issuer=\"${lastIssuer}\"" echo " #${curIdx}: Current Subject=\"${curSubj}\"" nl="" fi fi if [ -n "${curIssuer}" -a "${curIssuer}" = "${curSubj}" ] then ((++numSelfSigned)) seenSelfSignedIdx=${curIdx} echo "${nl}-- Cert #${curIdx} is self-signed." nl="" fi lastSubj="${curSubj}" lastIssuer="${curIssuer}" curSubj="" curIssuer="" fi # doCheck lastIdx=${curIdx} ((++curIdx)) cert= fi done fi done < "$filename" exitStatus=$? if [ $doCheck -ne 0 ] then nl=$'\n' if [ $numSelfSigned -ne 0 ] then local lastIdx=$((numCertsInFile - 1)) [ $lastIdx -lt 0 ] && lastIdx=0 echo "${nl}>> File \"${filename}\":" echo " -- contains ${numSelfSigned} self-signed certificate(s);" echo " -- the last self-signed certificate is #${seenSelfSignedIdx};" echo " -- the last certificate in the file is #${lastIdx}." fi if [ ${numSelfSigned} -ne 0 ] then [ ${seenSelfSignedIdx} -ne ${lastIdx} ] && SetCheckError fi fi } CurDetail () { local val=$(($1 == $showDetails)) echo $(boolof $val) } stringof () { echo "\"$1\"" } boolof () { if [ -z "$1" ] then echo "false" elif [ $1 -ne 0 ] then echo "true" else echo "false" fi } notboolof () { if [ -z "$1" ] then echo "true" elif [ $1 -eq 0 ] then echo "true" else echo "false" fi } Help () { [ -n "$1" ] && echo "${progname}: unknown option \"$arg\"" cat <<- _EOF_ ${progname} [opts] files... opts: -h | --help Show this help, then exit. -c | --check Check cert files for problems [current=$(boolof $doCheck)]. -a | --all Show all info about each cert [current=$(CurDetail $OPT_ALL)]. -m | --most Show most info about each cert [current=$(CurDetail $OPT_MOST)]. -s | --some Show some info about each cert [current=$(CurDetail $OPT_SOME)]. -S | --summary Show summary info about each cert [current=$(CurDetail $OPT_SUMMARY)]. The "--all", "--most", "--some", and "--summary" options are ordered by the amount of detailed info they provide, from most to least. Options and files are processed left-to-right, so files are processed using only options to their left; thus later files may be processed with different options, if desired. If no file args are provided then certs are read from stdin. Similarly stdin is read when a file arg of "-" is processed. Any non-certificate lines in an input file are ignored, so ${progname} can be used as a filter in a pipe, e.g.: openssl s_client -connect ibm.com:443 < /dev/null | x509-show-chain or even: openssl s_client -connect ibm.com:443 < /dev/null \\ | x509-show-chain cert1.crt -c - -S cert2.crt ${progname} exit status values: 0 No problems were detected. 1 Some files were not readable. 2 Problems were found within at least one certificate file. 3 Invalid parameters were given, or help was requested. NOTE: If multiple problems were encountered then the exit status will reflect only the last error. _EOF_ exit $EXIT_HELP } process_args () { local opts="$summaryopts" local first=1 for arg in "$@" do case "$arg" in -h | --help) Help ;; -c | --check) doCheck=1 ;; -a | --all) showDetails=$OPT_ALL opts=${options[$OPT_ALL]} ;; -m | --most) showDetails=$OPT_MOST opts=${options[$OPT_MOST]} ;; -s | --some) showDetails=$OPT_SOME opts=${options[$OPT_SOME]} ;; -S | --summary) showDetails=$OPT_SUMMARY opts=${options[$OPT_SUMMARY]} ;; -?*) Help "$arg" ;; *) if [ $first -eq 0 ] then echo else first=0 fi [ "$arg" = "-" ] && arg="/dev/stdin" echo "=== $arg ===" x509_show_chain "$arg" $showDetails $opts ;; esac done # read stdin if no file args if [ $first -ne 0 ] then arg="/dev/stdin" echo "=== $arg ===" x509_show_chain "$arg" $showDetails $opts fi } process_args "$@" exit $exitStatus