From 1137a54cc1e95cb12e8611c15a29c0a22ab9ca02 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Sat, 7 May 2022 01:41:36 +0100 Subject: [PATCH 1/5] Introduce extensible PKI reporting tool framework Comes with 'expiry' and 'revoke' report. Could do with 'renewed-not-revoked' report. Signed-off-by: Richard T Bonhomme --- easyrsa3/easyrsa | 170 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index 5310bee..2f8be9d 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -44,6 +44,8 @@ Here is the list of commands available with a short syntax reminder. Use the show-cert [ cmd-opts ] show-ca [ cmd-opts ] show-crl + show-expire + show-revoke verify import-req export-p1 [ cmd-opts ] @@ -169,6 +171,16 @@ cmd_help() { show-crl Shows details of the current certificate revocation list (CRL) + Human-readable output is shown." ;; + show-expire) text=" + show-expire [ cmd-opts ] + Shows details of expiring certificates + + Human-readable output is shown." ;; + show-revoke) text=" + show-revoke [ cmd-opts ] + Shows details of revoked certificates + Human-readable output is shown." ;; verify) text=" verify @@ -1604,7 +1616,7 @@ revoke_move() { # Set certificate expire date, renew date and variables needed for fixdate cert_dates() { - if [ "$1" ]; then + if [ -e "$1" ]; then # Required for renewal # Call openssl directly, otherwise this is not debug compatible crt_not_before="$("$EASYRSA_OPENSSL" x509 -in "$1" -noout -startdate 2>&1)" \ @@ -1614,6 +1626,9 @@ cert_dates() { || die "cert_dates - crt_not_after: $crt_not_after" crt_not_after="${crt_not_after#*=}" shift + elif [ "$1" ]; then + # Required for status + crt_not_after="$1" else # Required for --fix-offset # This is a fake date to satisfy the 'if expire_date' command test @@ -2533,6 +2548,145 @@ $in_file" OpenSSL failure to process the input" } # => show_ca() +# Fixed format date +# Build a Windows date.exe compatible input field +build_ff_date_string() { + ff_date="$1" + [ "$ff_date" ] || die "ff_date: '$ff_date'" + yy="${ff_date%???????????}" + ff_date="${ff_date#"$yy"}" + mm="${ff_date%?????????}" + ff_date="${ff_date#"$mm"}" + dd="${ff_date%???????}" + ff_date="${ff_date#"$dd"}" + HH="${ff_date%?????}" + ff_date="${ff_date#"$HH"}" + MM="${ff_date%???}" + ff_date="${ff_date#"$MM"}" + SS="${ff_date%?}" + ff_date="${ff_date#"$SS"}" + TZ="$ff_date" + ff_date="${yy}-${mm}-${dd} ${HH}:${MM}:${SS}${TZ}" +} # => build_date_string() + +# SC2295: (info): Expansions inside ${..} need to be quoted separately, +# otherwise they match as patterns. (what-ever that means .. ;-) +# Unfortunately, Windows sh.exe has an absolutely ridiculous bug. +# Try this in sh.exe: t=' '; s="a${t}b${t}c"; echo "${s%%"${t}"*}" + +# Read db +# shellcheck disable=SC2295 +read_db() { + report="$1"; shift + tab_char=' ' + db_in="$EASYRSA_PKI/index.txt" + while read -r crt_stat crt_notAfter crt_record; do + + # Interpret the db/certificate record + unset -v crt_serial crt_cn crt_revokedate crt_reason + case "$crt_stat" in + V) + # Valid + crt_serial="${crt_record%%${tab_char}*}" + crt_record="${crt_record#*${tab_char}}" + crt_cn="${crt_record#*/CN=}"; crt_cn="${crt_cn%%/*}" + crt_file="$EASYRSA_PKI/issued/$crt_cn.crt" + ;; + R) + # Revoked + crt_revokedate="${crt_record%%${tab_char}*}" + crt_reason="${crt_revokedate#*,}" + [ -z "$crt_reason" ] || crt_revokedate="${crt_revokedate%,*}" + crt_record="${crt_record#*${tab_char}}" + + crt_serial="${crt_record%%${tab_char}*}" + crt_record="${crt_record#*${tab_char}}" + crt_cn="${crt_record#*/CN=}"; crt_cn="${crt_cn%%/*}" + ;; + *) die "Unexpected status: $crt_stat" + esac + + # do status report for this record + # TODO: renewed-not-revoked + case "$report" in + expire) if [ "$crt_stat" = V ]; then expire_status; fi ;; + revoke) if [ "$crt_stat" = R ]; then revoke_status; fi ;; + *) die "Unrecognised report: $report" + esac + done < "$db_in" +} # => read_db() + +# Expire status +expire_status() { + build_ff_date_string "$crt_notAfter" + + crt_file="$EASYRSA_PKI/issued/${crt_cn}.crt" + if [ -e "$crt_file" ]; then + # Use cert date + cert_dates "$crt_file" + else + # Use db translated date + cert_dates "$crt_notAfter" + fi + + if [ "$expire_date" -lt "$allow_renew_date" ]; then + # cert expires in less than grace period + printf '%s%s\n' "$crt_stat | Serial: $crt_serial | " \ + "Expires: $ff_date | CN: $crt_cn" + fi +} # => expire_status() + +# Revoke status +revoke_status() { + build_ff_date_string "$crt_revokedate" + + crt_file="$EASYRSA_PKI/revoked/certs_by_serial/$crt_serial.crt" + if [ -e "$crt_file" ]; then + # Use cert file + cert_dates "$crt_file" + else + # Use db translated date + cert_dates "$crt_notAfter" + fi + + printf '%s%s\n' "$crt_stat | Serial: $crt_serial | " \ + "Revoked: $ff_date | Reason: $crt_reason | CN: $crt_cn" +} # => revoke_status() + +# cert status reports +status() { + report="$1" + in_crt="$2" + shift 2 + + verify_ca_init + + case "$report" in + expire) + case "$in_crt" in + all) + print "Showing certificates which expire in less than $EASYRSA_CERT_RENEW days:" + print + read_db expire + ;; + *) print "Coming soon.." + esac + ;; + revoke) + case "$in_crt" in + all) + print "Showing certificates which are revoked:" + print + read_db revoke ;; + *) print "Coming soon.." + esac + ;; + *) + # TODO: renewed-not-revoked + warn "Unrecognised report: $report" + esac +} # => status() + # set_var is not known by shellcheck, therefore: # Fake declare known variables for shellcheck # Use these options without this function: @@ -3693,6 +3847,20 @@ case "$cmd" in verify) verify_cert "$@" ;; + show-expire) + if [ -z "$*" ]; then + status expire all + else + status expire "$@" + fi + ;; + show-revoke) + if [ -z "$*" ]; then + status revoke all + else + status revoke "$@" + fi + ;; upgrade) up23_manage_upgrade_23 "$@" ;; From 89a5aeedaffae612d0e008a99c4af8334b22894c Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Sat, 7 May 2022 02:18:53 +0100 Subject: [PATCH 2/5] Prohibit '--fix-date' for status reports Signed-off-by: Richard T Bonhomme --- easyrsa3/easyrsa | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index 2f8be9d..27a7be1 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -1735,7 +1735,6 @@ The lifetime of the certificate will expire before the date today." [ "$end_fixdate" ] || die "Undefined: end_fixdate" unset -v crt_not_after fi - } # => cert_dates() # renew backend @@ -2551,6 +2550,7 @@ OpenSSL failure to process the input" # Fixed format date # Build a Windows date.exe compatible input field build_ff_date_string() { + unset -v ff_date ff_date="$1" [ "$ff_date" ] || die "ff_date: '$ff_date'" yy="${ff_date%???????????}" @@ -2580,11 +2580,11 @@ read_db() { report="$1"; shift tab_char=' ' db_in="$EASYRSA_PKI/index.txt" - while read -r crt_stat crt_notAfter crt_record; do + while read -r crt_status crt_notAfter crt_record; do # Interpret the db/certificate record unset -v crt_serial crt_cn crt_revokedate crt_reason - case "$crt_stat" in + case "$crt_status" in V) # Valid crt_serial="${crt_record%%${tab_char}*}" @@ -2603,14 +2603,14 @@ read_db() { crt_record="${crt_record#*${tab_char}}" crt_cn="${crt_record#*/CN=}"; crt_cn="${crt_cn%%/*}" ;; - *) die "Unexpected status: $crt_stat" + *) die "Unexpected status: $crt_status" esac # do status report for this record # TODO: renewed-not-revoked case "$report" in - expire) if [ "$crt_stat" = V ]; then expire_status; fi ;; - revoke) if [ "$crt_stat" = R ]; then revoke_status; fi ;; + expire) if [ "$crt_status" = V ]; then expire_status; fi ;; + revoke) if [ "$crt_status" = R ]; then revoke_status; fi ;; *) die "Unrecognised report: $report" esac done < "$db_in" @@ -2631,7 +2631,7 @@ expire_status() { if [ "$expire_date" -lt "$allow_renew_date" ]; then # cert expires in less than grace period - printf '%s%s\n' "$crt_stat | Serial: $crt_serial | " \ + printf '%s%s\n' "$crt_status | Serial: $crt_serial | " \ "Expires: $ff_date | CN: $crt_cn" fi } # => expire_status() @@ -2649,7 +2649,7 @@ revoke_status() { cert_dates "$crt_notAfter" fi - printf '%s%s\n' "$crt_stat | Serial: $crt_serial | " \ + printf '%s%s\n' "$crt_status | Serial: $crt_serial | " \ "Revoked: $ff_date | Reason: $crt_reason | CN: $crt_cn" } # => revoke_status() @@ -2661,6 +2661,9 @@ status() { verify_ca_init + # This does not build, so no need (ban) for fixed dates + unset -v EASYRSA_FIX_OFFSET + case "$report" in expire) case "$in_crt" in From 06078cad430f25f1167607671a01d0750c72be76 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Sat, 7 May 2022 09:20:28 +0100 Subject: [PATCH 3/5] Add 'show-renew' - List of renewed but not revoked certificates Signed-off-by: Richard T Bonhomme --- easyrsa3/easyrsa | 55 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index 27a7be1..ff738b5 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -2609,8 +2609,15 @@ read_db() { # do status report for this record # TODO: renewed-not-revoked case "$report" in - expire) if [ "$crt_status" = V ]; then expire_status; fi ;; - revoke) if [ "$crt_status" = R ]; then revoke_status; fi ;; + expire) # Certs which expire before EASYRSA_CERT_RENEW days + if [ "$crt_status" = V ]; then expire_status; fi + ;; + revoke) # Certs which have been revoked + if [ "$crt_status" = R ]; then revoke_status; fi + ;; + renew) # Certs which have been renewed but not revoked + if [ "$crt_status" = V ]; then renew_status; fi + ;; *) die "Unrecognised report: $report" esac done < "$db_in" @@ -2653,6 +2660,34 @@ revoke_status() { "Revoked: $ff_date | Reason: $crt_reason | CN: $crt_cn" } # => revoke_status() +# Renewed status +renew_status() { + build_ff_date_string "$crt_notAfter" + + crt_file="$EASYRSA_PKI/renewed/issued/${crt_cn}.crt" + if [ -e "$crt_file" ]; then + # Use cert date + cert_dates "$crt_file" + + # get the serial number of the certificate -> serial=XXXX + renewed_crt_serial="$(easyrsa_openssl x509 -in "$crt_file" -noout -serial)" + # remove the serial= part -> we only need the XXXX part + renewed_crt_serial="${renewed_crt_serial##*=}" + + if [ "$crt_serial" = "$renewed_crt_serial" ]; then + # Renewed cert must exist always + printf '%s%s\n' "$crt_status | Serial: $crt_serial | " \ + "Expires: $ff_date | CN: $crt_cn" + else + # Cert is valid but not renewed + : # ok - ignore + fi + else + # Cert is valid but no renewed cert exists + : # ok - ignore + fi +} # => renew_status() + # cert status reports status() { report="$1" @@ -2684,6 +2719,15 @@ status() { *) print "Coming soon.." esac ;; + renew) + case "$in_crt" in + all) + print "Showing certificates which have been renewed but not revoked:" + print + read_db renew ;; + *) print "Coming soon.." + esac + ;; *) # TODO: renewed-not-revoked warn "Unrecognised report: $report" @@ -3864,6 +3908,13 @@ case "$cmd" in status revoke "$@" fi ;; + show-renew) + if [ -z "$*" ]; then + status renew all + else + status renew "$@" + fi + ;; upgrade) up23_manage_upgrade_23 "$@" ;; From d0905bd72aa53f1bb85b20b07e2f6356572bf559 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Sat, 7 May 2022 10:11:10 +0100 Subject: [PATCH 4/5] Add 'show-renew' to help Signed-off-by: Richard T Bonhomme --- easyrsa3/easyrsa | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index ff738b5..b6c00da 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -46,6 +46,7 @@ Here is the list of commands available with a short syntax reminder. Use the show-crl show-expire show-revoke + show-renew verify import-req export-p1 [ cmd-opts ] @@ -181,6 +182,11 @@ cmd_help() { show-revoke [ cmd-opts ] Shows details of revoked certificates + Human-readable output is shown." ;; + show-renew) text=" + show-renew [ cmd-opts ] + Shows details of renewed certificates, which have not been revoked + Human-readable output is shown." ;; verify) text=" verify From 4b6e0bdb457ec384b0ee46be23aa7c8c6a7efc53 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Sat, 7 May 2022 10:51:10 +0100 Subject: [PATCH 5/5] Add '--renew-days' - Option to set EASYRSA_CERT_RENEW days Used with 'renew' to extend the grace period before allowing certificates to be renewed. Used with 'show-expire' to extend the period of the search for certificates which are close to expiring. Also, correct some comments and minor formatting changes. Signed-off-by: Richard T Bonhomme --- easyrsa3/easyrsa | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index b6c00da..999139b 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -290,6 +290,7 @@ General options: Certificate & Request options: (these impact cert/req field values) --days=# : sets the signing validity to the specified number of days +--renew-days=# : Number of days grace period before allowing renewal --fix-offset=# : Generate certificate with fixed start and end dates. : Range 1 to 365 : start date: 01 January 00:00:00 of the current year @@ -2584,6 +2585,7 @@ build_ff_date_string() { # shellcheck disable=SC2295 read_db() { report="$1"; shift + tab_char=' ' db_in="$EASYRSA_PKI/index.txt" while read -r crt_status crt_notAfter crt_record; do @@ -2591,15 +2593,13 @@ read_db() { # Interpret the db/certificate record unset -v crt_serial crt_cn crt_revokedate crt_reason case "$crt_status" in - V) - # Valid + V) # Valid crt_serial="${crt_record%%${tab_char}*}" crt_record="${crt_record#*${tab_char}}" crt_cn="${crt_record#*/CN=}"; crt_cn="${crt_cn%%/*}" crt_file="$EASYRSA_PKI/issued/$crt_cn.crt" ;; - R) - # Revoked + R) # Revoked crt_revokedate="${crt_record%%${tab_char}*}" crt_reason="${crt_revokedate#*,}" [ -z "$crt_reason" ] || crt_revokedate="${crt_revokedate%,*}" @@ -2612,8 +2612,7 @@ read_db() { *) die "Unexpected status: $crt_status" esac - # do status report for this record - # TODO: renewed-not-revoked + # Output selected status report for this record case "$report" in expire) # Certs which expire before EASYRSA_CERT_RENEW days if [ "$crt_status" = V ]; then expire_status; fi @@ -2643,7 +2642,7 @@ expire_status() { fi if [ "$expire_date" -lt "$allow_renew_date" ]; then - # cert expires in less than grace period + # Cert expires in less than grace period printf '%s%s\n' "$crt_status | Serial: $crt_serial | " \ "Expires: $ff_date | CN: $crt_cn" fi @@ -2670,6 +2669,7 @@ revoke_status() { renew_status() { build_ff_date_string "$crt_notAfter" + # Renewed cert must always exist, otherwise this cert has not been renewed crt_file="$EASYRSA_PKI/renewed/issued/${crt_cn}.crt" if [ -e "$crt_file" ]; then # Use cert date @@ -2681,7 +2681,6 @@ renew_status() { renewed_crt_serial="${renewed_crt_serial##*=}" if [ "$crt_serial" = "$renewed_crt_serial" ]; then - # Renewed cert must exist always printf '%s%s\n' "$crt_status | Serial: $crt_serial | " \ "Expires: $ff_date | CN: $crt_cn" else @@ -2702,7 +2701,7 @@ status() { verify_ca_init - # This does not build, so no need (ban) for fixed dates + # This does not build certs, so do not need support for fixed dates unset -v EASYRSA_FIX_OFFSET case "$report" in @@ -2734,9 +2733,7 @@ status() { *) print "Coming soon.." esac ;; - *) - # TODO: renewed-not-revoked - warn "Unrecognised report: $report" + *) warn "Unrecognised report: $report" esac } # => status() @@ -3711,6 +3708,8 @@ while :; do ;; --fix-offset) export EASYRSA_FIX_OFFSET="$val" ;; + --renew-days) + export EASYRSA_CERT_RENEW="$val" ;; --pki-dir) export EASYRSA_PKI="$val" ;; --tmp-dir)