207 lines
4.3 KiB
Bash
207 lines
4.3 KiB
Bash
|
#!/bin/sh
|
||
|
set -ef
|
||
|
|
||
|
ocsp_fetch_timeout=15
|
||
|
ocsp_fetch_retries=3
|
||
|
ocsp_fetch_retry_delay=5
|
||
|
ocsp_valid_threshold=86400
|
||
|
|
||
|
usage() {
|
||
|
cat >&2 <<-EOF
|
||
|
# usage: ${0##*/} <command> [args...]
|
||
|
# ${0##*/} get-uri <in:cert.pem>
|
||
|
# ${0##*/} is-valid <in:issuer.pem> <in:cert.pem> <in:ocsp.der>
|
||
|
# ${0##*/} is-expiring <in:issuer.pem> <in:cert.pem> <in:ocsp.der>
|
||
|
# ${0##*/} fetch <in:issuer.pem> <in:cert.pem> <out:ocsp.der>
|
||
|
EOF
|
||
|
exit "${1:-0}"
|
||
|
}
|
||
|
[ $# != 0 ] || usage
|
||
|
|
||
|
## $1 - X509 in PEM format
|
||
|
ossl_x509_verify_fmt() {
|
||
|
openssl x509 -in "$1" -noout >/dev/null
|
||
|
}
|
||
|
|
||
|
## $1 - cert
|
||
|
ossl_ocsp_uri() {
|
||
|
openssl x509 -in "$1" -noout -ocsp_uri \
|
||
|
| head -n 1
|
||
|
}
|
||
|
|
||
|
## $1 - chain
|
||
|
## $2 - cert
|
||
|
## $3 - ocsp uri
|
||
|
## $4 - ocsp response
|
||
|
ossl_ocsp_fetch() {
|
||
|
openssl ocsp \
|
||
|
-timeout "${ocsp_fetch_timeout}" \
|
||
|
-nonce \
|
||
|
-issuer "$1" -cert "$2" -url "$3" -respout "$4"
|
||
|
}
|
||
|
|
||
|
## $1 - ocsp response
|
||
|
ossl_ocsp_verify_fmt() {
|
||
|
openssl ocsp \
|
||
|
-noverify -respin "$1"
|
||
|
}
|
||
|
|
||
|
## $1 - chain
|
||
|
## $2 - cert
|
||
|
## $3 - ocsp response
|
||
|
ossl_ocsp_read() {
|
||
|
openssl ocsp \
|
||
|
-issuer "$1" -cert "$2" -respin "$3" -resp_text
|
||
|
}
|
||
|
|
||
|
## $1 - chain
|
||
|
## $2 - cert
|
||
|
## $3 - ocsp response
|
||
|
ossl_ocsp_verify() {
|
||
|
ossl_ocsp_read "$@" >/dev/null
|
||
|
}
|
||
|
|
||
|
## stdin - output of ossl_ocsp_read()
|
||
|
ossl_ocsp_next_update() {
|
||
|
sed -En '/^\s*[Nn]ext [Uu]pdate:\s*(\S.+\S)\s*$/{s//\1/;p;q}'
|
||
|
}
|
||
|
|
||
|
unset arg_ok cmd chain cert ocsp_uri ocsp_resp
|
||
|
arg_ok=
|
||
|
while : ; do
|
||
|
[ -n "$1" ] || break
|
||
|
cmd="$1"
|
||
|
case "$1" in
|
||
|
get-uri )
|
||
|
[ -n "$2" ] || break
|
||
|
[ -s "$2" ] || break
|
||
|
ossl_x509_verify_fmt "$2" || break
|
||
|
cert="$2"
|
||
|
;;
|
||
|
is-valid | is-expiring | fetch )
|
||
|
[ -n "$2" ] || break
|
||
|
[ -s "$2" ] || break
|
||
|
ossl_x509_verify_fmt "$2" || break
|
||
|
chain="$2"
|
||
|
[ -n "$3" ] || break
|
||
|
[ -s "$3" ] || break
|
||
|
ossl_x509_verify_fmt "$3" || break
|
||
|
cert="$3"
|
||
|
[ -n "$4" ] || break
|
||
|
ocsp_resp="$4"
|
||
|
## OCSP response validation is handled later and in various ways (!)
|
||
|
## e.g. "is-valid" (cmd_is_valid) validates OCSP response as expected
|
||
|
## but "is-expiring" (cmd_is_expiring) returns success for invalid OCSP response
|
||
|
## which means OCSP response should be updated ASAP
|
||
|
;;
|
||
|
*) break ;;
|
||
|
esac
|
||
|
arg_ok=1
|
||
|
break ; done
|
||
|
[ -n "${arg_ok}" ] || usage 1
|
||
|
unset arg_ok
|
||
|
|
||
|
## OCSP URI is used only in "get-uri" and "fetch" commands
|
||
|
## but implicitly required for all actions
|
||
|
ocsp_uri=$(ossl_ocsp_uri "${cert}") || exit 1
|
||
|
if [ -z "${ocsp_uri}" ] ; then
|
||
|
env printf '%s: unable to extract OCSP URI from %q\n' "${0##*/}" "${cert}" >&2
|
||
|
exit 1
|
||
|
fi
|
||
|
## early command handling
|
||
|
if [ "${cmd}" = 'get-uri' ] ; then
|
||
|
printf '%s\n' "${ocsp_uri}"
|
||
|
exit 0
|
||
|
fi
|
||
|
|
||
|
## $1 - chain
|
||
|
## $2 - cert
|
||
|
## $3 - ocsp response
|
||
|
cmd_is_valid() {
|
||
|
ossl_ocsp_verify_fmt "$3" || return 1
|
||
|
ossl_ocsp_verify "$1" "$2" "$3" || return 1
|
||
|
}
|
||
|
|
||
|
## $1 - chain
|
||
|
## $2 - cert
|
||
|
## $3 - ocsp response
|
||
|
cmd_is_expiring() {
|
||
|
cmd_is_valid "$1" "$2" "$3" || return 0
|
||
|
|
||
|
local need_update next ts_now ts_next ts_diff
|
||
|
|
||
|
need_update=1
|
||
|
while : ; do
|
||
|
next=$(ossl_ocsp_read "$1" "$2" "$3" 2>/dev/null | ossl_ocsp_next_update)
|
||
|
[ -n "${next}" ] || break
|
||
|
|
||
|
ts_now=$(date '+%s')
|
||
|
ts_next=$(date -d "${next}" '+%s')
|
||
|
[ -n "${ts_next}" ] || break
|
||
|
[ ${ts_now} -lt ${ts_next} ] || break
|
||
|
|
||
|
ts_diff=$((ts_next - ts_now))
|
||
|
[ ${ts_diff} -le ${ocsp_valid_threshold} ] || need_update=0
|
||
|
break ; done
|
||
|
|
||
|
if [ "${need_update}" = 0 ] ; then
|
||
|
env printf '%q has valid and fresh OCSP response\n' "$2" >&2
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
## $1 - chain
|
||
|
## $2 - cert
|
||
|
## $3 - ocsp uri
|
||
|
## $4 - ocsp response
|
||
|
cmd_fetch() {
|
||
|
local t i r
|
||
|
|
||
|
t=$(mktemp) ; : "${t:?}"
|
||
|
|
||
|
for i in $(seq 1 "${ocsp_fetch_retries}") ; do
|
||
|
i= ## no-op
|
||
|
|
||
|
if ossl_ocsp_fetch "$1" "$2" "$3" "$t" ; then
|
||
|
break
|
||
|
fi
|
||
|
: > "$t"
|
||
|
sleep "${ocsp_fetch_retry_delay}"
|
||
|
done
|
||
|
|
||
|
r=
|
||
|
while : ; do
|
||
|
[ -s "$t" ] || break
|
||
|
cmd_is_valid "$1" "$2" "$t" || break
|
||
|
r=1
|
||
|
break ; done
|
||
|
if [ -z "$r" ] ; then
|
||
|
env printf 'unable to fetch OCSP response for %q via %q\n' "$2" "$3" >&2
|
||
|
rm -rf "$t"
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
r=
|
||
|
while : ; do
|
||
|
touch "$4" || break
|
||
|
tee "$4" < "$t" >/dev/null || break
|
||
|
chmod 0644 "$4" || break
|
||
|
r=1
|
||
|
break ; done
|
||
|
if [ -z "$r" ] ; then
|
||
|
env printf 'unable to save OCSP response for %q into %q\n' "$2" "$4" >&2
|
||
|
rm -rf "$t"
|
||
|
return 1
|
||
|
fi
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
case "${cmd}" in
|
||
|
is-valid ) cmd_is_valid "${chain}" "${cert}" "${ocsp_resp}" ;;
|
||
|
is-expiring ) cmd_is_expiring "${chain}" "${cert}" "${ocsp_resp}" ;;
|
||
|
fetch ) cmd_fetch "${chain}" "${cert}" "${ocsp_uri}" "${ocsp_resp}" ;;
|
||
|
esac
|