1#!/bin/sh 2# 3# cert-staple.sh - retrieve from CA and manage OCSP stapling info for leaf cert 4# 5# Copyright(c) 2022 Glenn Strauss gstrauss()gluelogic.com All rights reserved 6# License: BSD 3-clause (same as lighttpd) 7 8CERT_PEM="$1" # input (cert.pem) 9CHAIN_PEM="$2" # input (chain.pem) 10OCSP_DER="$3" # output symlink (staple.der) 11 12OCSP_TMP="" # temporary file 13next_delta=90000 # 25 hours 14 15if [ -z "$CERT_PEM" ] || [ -z "$CHAIN_PEM" ] || [ -z "$OCSP_DER" ] \ 16 || [ ! -f "$CERT_PEM" ] || [ ! -f "$CHAIN_PEM" ]; then 17 echo 1>&2 "usage: cert-staple.sh cert.pem chain.pem staple.der" 18 exit 1 19fi 20 21errexit() { 22 [ -n "$OCSP_TMP" ] && rm -f "$OCSP_TMP" 23 exit 1 24} 25 26# short-circuit if Next Update is > $next_delta in the future 27next_ts=$(readlink "$OCSP_DER" 2>/dev/null) 28if [ -n "$next_ts" ]; then 29 next_ts="${next_ts##*.}" 30 ts=$(date +%s) 31 ts=$(( $ts + $next_delta )) 32 if [ -n "$next_ts" ] && [ "$next_ts" -gt "$ts" ]; then 33 exit 0 34 fi 35fi 36 37# get URI of OCSP responder from certificate 38OCSP_URI=$(openssl x509 -in "$CERT_PEM" -ocsp_uri -noout) 39[ $? = 0 ] && [ -n "$OCSP_URI" ] || exit 1 40 41# exception for (unsupported, end-of-life) older versions of OpenSSL 42OCSP_HOST= 43OPENSSL_VERSION=$(openssl version) 44if [ "${OPENSSL_VERSION}" != "${OPENSSL_VERSION#OpenSSL 1.0.}" ]; then 45 # get authority from URI 46 OCSP_HOST=$(echo "$OCSP_URI" | cut -d/ -f3) 47fi 48 49# get OCSP response from OCSP responder 50OCSP_TMP="$OCSP_DER.$$" 51OCSP_RESP=$(openssl ocsp -issuer "$CHAIN_PEM" -cert "$CERT_PEM" -respout "$OCSP_TMP" -noverify -no_nonce -url "$OCSP_URI" ${OCSP_HOST:+-header Host "$OCSP_HOST"}) 52[ $? = 0 ] || errexit 53 54# parse OCSP response from OCSP responder 55# 56#$CERT_PEM: good 57# This Update: Jun 5 21:00:00 2020 GMT 58# Next Update: Jun 12 21:00:00 2020 GMT 59 60ocsp_status="$(printf %s "$OCSP_RESP" | head -1)" 61[ "$ocsp_status" = "$CERT_PEM: good" ] || errexit 62 63next_update="$(printf %s "$OCSP_RESP" | grep 'Next Update:')" 64next_date="$(printf %s "$next_update" | sed 's/.*Next Update: //')" 65[ -n "$next_date" ] || errexit 66sysname=$(uname -s) 67if [ "$sysname" = "FreeBSD" ] || \ 68 [ "$sysname" = "OpenBSD" ] || \ 69 [ "$sysname" = "DragonFly" ]; then 70 ocsp_expire=$(date -j -f "%b %e %T %Y %Z" "$next_date" "+%s") 71else 72 ocsp_expire=$(date -d "$next_date" +%s) 73fi 74 75# validate OCSP response 76ocsp_verify=$(openssl ocsp -issuer "$CHAIN_PEM" -verify_other "$CHAIN_PEM" -cert "$CERT_PEM" -respin "$OCSP_TMP" -no_nonce -out /dev/null 2>&1) 77[ "$ocsp_verify" = "Response verify OK" ] || errexit 78 79# rename and update symlink to install OCSP response to be used in OCSP stapling 80OCSP_OUT="$OCSP_DER.$ocsp_expire" 81mv "$OCSP_TMP" "$OCSP_OUT" || errexit 82OCSP_TMP="" 83ln -sf "${OCSP_OUT##*/}" "$OCSP_DER" || errexit 84 85# debug: display text output of OCSP .der file 86#openssl ocsp -respin "$OCSP_DER" -resp_text -noverify 87 88# remove old OCSP responses which have expired 89now=$(date +%s) 90for i in "$OCSP_DER".*; do 91 ts="${i#${OCSP_DER}.}" 92 if [ -n "$ts" ] && [ "$ts" -lt "$now" ]; then 93 rm -f "$i" 94 fi 95done 96