#! /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
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #1 | 31204 | Will Kreitzmann |
Released SDP 2024.2.31193 (2025/01/17). Copy Up using 'p4 copy -r -b perforce_software-sdp-dev'. |
||
| //guest/perforce_software/sdp/dev/Unsupported/setup/x509-show-chain | |||||
| #2 | 31160 | Mark Wittenberg |
I squeezed in some time to improve my script: * Added a --check option to check cert files for problems and report them: - certificates following a self-signed cert - certificates not signed by the subsequent cert - identify self-signed certs * It's now usable in a pipeline: - If no file arguments are given then it reads from stdin - Any file argument of - read stdin when that argument is processed - All non-certificate lines in the input are ignored so you can pipe the output of openssl s_client to it without needing to pre-process the data * To make it easier to use it from another script, it now exits with a meaningful exit status: - 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. * I expanded and improved the help text * I renamed the -d | --details option to -s | --some to make the order of increasing detail more obvious - the --all, --most, --some, and --summary options are ordered by the amount of detailed info they provide, from most to least. - this conflicted with the -s | --summary option so I renamed that to -S | --summary |
||
| #1 | 31107 | Mark Wittenberg | Added x509-show-chain cert script. | ||