From 08bc2bd4546eaf07235f536e9a0f2f0db88d9650 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Thu, 6 Apr 2023 00:10:03 +0100 Subject: [PATCH] Status reports: iso_8601_timestamp_to_seconds(), fix Leap Years Insert the day "February 29th" only after "Feb-28" during leap years. Prepend century (eg. 20 or 19) to a two digit Year value. ISO-8601 Require four digit 'yyyy' Improve verbose output. Signed-off-by: Richard T Bonhomme --- easyrsa3/easyrsa | 259 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 177 insertions(+), 82 deletions(-) diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index ee4f2c6..597741d 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -3770,11 +3770,11 @@ ssl_cert_not_after_date - failed to set var '$*'" # SSL -- v3 -- startdate iso_8601 iso_8601_cert_startdate() { - verbose "NEW: iso_8601_cert_startdate()" + verbose "NEW: iso_8601_cert_startdate" [ "$#" = 2 ] || die "\ -iso_8601_cert_startdate - input error" +iso_8601_cert_startdate: input error" [ -f "$1" ] || die "\ -iso_8601_cert_startdate - missing cert" +iso_8601_cert_startdate: missing cert" # On error return, let the caller decide what to do if fn_ssl_out="$( @@ -3785,25 +3785,26 @@ iso_8601_cert_startdate - missing cert" : # ok else # The caller MUST assess this error - verbose "iso_8601_cert_startdate: GENERATED ERROR" + verbose "\ +iso_8601_cert_startdate: GENERATED ERROR" return 1 fi fn_ssl_out="${fn_ssl_out#*=}" force_set_var "$2" "$fn_ssl_out" || die "\ -iso_8601_cert_startdate - failed to set var '$*'" +iso_8601_cert_startdate: failed to set var '$*'" unset -v fn_ssl_out } # => iso_8601_cert_startdate() # SSL -- v3 -- enddate iso_8601 iso_8601_cert_enddate() { - verbose "NEW: iso_8601_cert_enddate()" + verbose "NEW: iso_8601_cert_enddate" [ "$#" = 2 ] || die "\ -iso_8601_cert_enddate - input error" +iso_8601_cert_enddate: input error" [ -f "$1" ] || die "\ -iso_8601_cert_enddate - missing cert" +iso_8601_cert_enddate: missing cert" # On error return, let the caller decide what to do if fn_ssl_out="$( @@ -3814,29 +3815,74 @@ iso_8601_cert_enddate - missing cert" : # ok else # The caller MUST assess this error - verbose "iso_8601_cert_enddate: GENERATED ERROR" + verbose "\ +iso_8601_cert_enddate: GENERATED ERROR" return 1 fi fn_ssl_out="${fn_ssl_out#*=}" force_set_var "$2" "$fn_ssl_out" || die "\ -iso_8601_cert_enddate - failed to set var '$*'" +iso_8601_cert_enddate: failed to set var '$*'" unset -v fn_ssl_out } # => iso_8601_cert_enddate() # iso_8601_timestamp_to_seconds since epoch iso_8601_timestamp_to_seconds() { - verbose "NEW: iso_8601_timestamp_to_seconds()" + verbose "NEW: iso_8601_timestamp_to_seconds" # check input [ "$#" = 2 ] || die "\ -iso_8601_timestamp_to_seconds - input error" +iso_8601_timestamp_to_seconds: input error" in_date="$1" + verbose "\ +NEW: iso_8601_timestamp_to_seconds: in_date=$in_date" # Consume $in_date string yyyy="${in_date%%-*}" + + # When yyyy is only two digits prepend century + if [ "${#yyyy}" = 2 ]; then + yyyy="${yyyy#0}" + if [ "$yyyy" -lt 70 ]; then + if [ "${#yyyy}" = 2 ]; then + yyyy="20${yyyy}" + else + yyyy="200${yyyy}" + fi + else + yyyy="19${yyyy}" + fi + fi + verbose "\ +NEW: iso_8601_timestamp_to_seconds: yyyy: $yyyy" + + # yyyy must be four digits now + # Caller MUST assess this error + if [ "${#yyyy}" = 4 ]; then + : # ok + else + verbose "\ +NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (yyyy=$yyyy)" + return 1 + fi + + # Leap years + leap_years="$(( (yyyy - 1970 + 2 ) / 4 ))" + is_leap_year="$(( (yyyy - 1970 + 2 ) % 4 ))" + if [ "$is_leap_year" = 0 ]; then + leap_years="$(( leap_years - 1 ))" + leap_day=1 + verbose "\ +NEW: iso_8601_timestamp_to_seconds: is_leap_year=TRUE" + else + leap_day=0 + verbose "\ +NEW: iso_8601_timestamp_to_seconds: is_leap_year=FALSE" + fi + unset -v is_leap_year + in_date="${in_date#*-}" mm="${in_date%%-*}" in_date="${in_date#*-}" @@ -3857,7 +3903,7 @@ iso_8601_timestamp_to_seconds - input error" else # Caller MUST assess this error verbose "\ -NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (TZ)" +NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (TZ=$TZ)" return 1 fi @@ -3865,21 +3911,21 @@ NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (TZ)" case "$mm" in 01) mdays="$(( 0 ))" ;; 02) mdays="$(( 31 ))" ;; - 03) mdays="$(( 31+28 ))" ;; - 04) mdays="$(( 31+28+31 ))" ;; - 05) mdays="$(( 31+28+31+30 ))" ;; - 06) mdays="$(( 31+28+31+30+31 ))" ;; - 07) mdays="$(( 31+28+31+30+31+30 ))" ;; - 08) mdays="$(( 31+28+31+30+31+30+31 ))" ;; - 09) mdays="$(( 31+28+31+30+31+30+31+31 ))" ;; - 10) mdays="$(( 31+28+31+30+31+30+31+31+30 ))" ;; - 11) mdays="$(( 31+28+31+30+31+30+31+31+30+31 ))" ;; - 12) mdays="$(( 31+28+31+30+31+30+31+31+30+31+30 ))" ;; + 03) mdays="$(( 31+28+leap_day ))" ;; + 04) mdays="$(( 31+28+leap_day+31 ))" ;; + 05) mdays="$(( 31+28+leap_day+31+30 ))" ;; + 06) mdays="$(( 31+28+leap_day+31+30+31 ))" ;; + 07) mdays="$(( 31+28+leap_day+31+30+31+30 ))" ;; + 08) mdays="$(( 31+28+leap_day+31+30+31+30+31 ))" ;; + 09) mdays="$(( 31+28+leap_day+31+30+31+30+31+31 ))" ;; + 10) mdays="$(( 31+28+leap_day+31+30+31+30+31+31+30 ))" ;; + 11) mdays="$(( 31+28+leap_day+31+30+31+30+31+31+30+31 ))" ;; + 12) mdays="$(( 31+28+leap_day+31+30+31+30+31+31+30+31+30 ))" ;; # This means the input date was not iso_8601 *) # Caller MUST assess this error verbose "\ -NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (mm)" +NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (mm=$mm)" return 1 esac @@ -3891,9 +3937,6 @@ NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (mm)" MM="${MM#0}" SS="${SS#0}" - # Leap years - leap_years="$(( (yyyy - 1970 + 2 ) / 4 ))" - # Calculate seconds since epoch out_seconds="$(( (( yyyy - 1970 ) * ( 60 * 60 * 24 * 365 )) @@ -3904,11 +3947,11 @@ NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (mm)" + (( MM ) * ( 60 )) + SS ))" || die "\ -iso_8601_timestamp_to_seconds - out_seconds: '$out_seconds'" +iso_8601_timestamp_to_seconds: out_seconds=$out_seconds" # Return out_seconds force_set_var "$2" "$out_seconds" || die "\ -iso_8601_timestamp_to_seconds \ +iso_8601_timestamp_to_seconds: \ - force_set_var - $2 - $out_seconds" unset -v in_date out_seconds leap_years \ @@ -3917,10 +3960,10 @@ iso_8601_timestamp_to_seconds \ # Number of days from NOW@today as timestamp seconds days_to_timestamp_s() { - verbose "REQUIRED: days_to_timestamp_s - uses date." + verbose "REQUIRED: days_to_timestamp_s: uses date" # check input [ "$#" = 2 ] || die "\ -days_to_timestamp_s - input error" +days_to_timestamp_s: input error" in_days="$1" in_seconds="$(( in_days * 60 * 60 * 24 ))" @@ -3950,8 +3993,7 @@ days_to_timestamp_s - input error" # Something else else die "\ -days_to_timestamp_s: -'date' failed for 'in_date': $in_date" +days_to_timestamp_s: 'date +%s' failed" fi # Add period @@ -3959,7 +4001,7 @@ days_to_timestamp_s: # Return timestamp_s force_set_var "$2" "$timestamp_s" || die "\ -days_to_timestamp_s - force_set_var - $2 - $timestamp_s" +days_to_timestamp_s: force_set_var - $2 - $timestamp_s" unset -v in_days in_seconds timestamp_s } # => days_to_timestamp_s() @@ -3967,10 +4009,10 @@ days_to_timestamp_s - force_set_var - $2 - $timestamp_s" # Convert certificate date to timestamp seconds since epoch # Used to verify iso_8601 calculated seconds since epoch cert_date_to_timestamp_s() { - verbose "DEPRECATED: cert_date_to_timestamp_s()" + verbose "DEPRECATED: cert_date_to_timestamp_s" # check input [ "$#" = 2 ] || die "\ -cert_date_to_timestamp_s - input error" +cert_date_to_timestamp_s: input error" #die "* NOT ALLOWED: cert_date_to_timestamp_s()" @@ -4004,12 +4046,12 @@ cert_date_to_timestamp_s - input error" else die "\ cert_date_to_timestamp_s: -'date' failed for 'in_date': $in_date" +'date' failed for in_date=$in_date" fi # Return timestamp_s force_set_var "$2" "$timestamp_s" || die "\ -cert_date_to_timestamp_s - force_set_var - $2 - $timestamp_s" +cert_date_to_timestamp_s: force_set_var - $2 - $timestamp_s" unset -v in_date timestamp_s } # => cert_date_to_timestamp_s() @@ -4017,17 +4059,39 @@ cert_date_to_timestamp_s - force_set_var - $2 - $timestamp_s" # Build a Windows date.exe compatible input field # iso_8601 date db_date_to_iso_8601_date() { - verbose "iso_8601: db_date_to_iso_8601_date()" + verbose "iso_8601: db_date_to_iso_8601_date" # check input [ "$#" = 2 ] || die "\ db_date_to_iso_8601_date - input error" # Expected format: '230612235959Z' in_date="$1" + verbose "db_date_to_iso_8601_date: in_date=$in_date" # Consume $in_date string - yy="${in_date%???????????}" - in_date="${in_date#"$yy"}" + # yyyy is expected to be only 'yy' + yyyy="${in_date%???????????}" + in_date="${in_date#"$yyyy"}" + + # When yyyy is only two digits prepend century + if [ "${#yyyy}" = 2 ]; then + yyyy="${yyyy#0}" + if [ "$yyyy" -lt 70 ]; then + if [ "${#yyyy}" = 2 ]; then + yyyy="20${yyyy}" + else + yyyy="200${yyyy}" + fi + else + if [ "${#yyyy}" = 2 ]; then + yyyy="19${yyyy}" + else + yyyy="190${yyyy}" + fi + fi + fi + verbose "db_date_to_iso_8601_date: yyyy=$yyyy" + mm="${in_date%?????????}" in_date="${in_date#"$mm"}" dd="${in_date%???????}" @@ -4041,26 +4105,25 @@ db_date_to_iso_8601_date - input error" TZ="$in_date" # Assign iso_8601 date - out_date="${yy}-${mm}-${dd} ${HH}:${MM}:${SS}${TZ}" + out_date="${yyyy}-${mm}-${dd} ${HH}:${MM}:${SS}${TZ}" # Return out_date force_set_var "$2" "$out_date" || die "\ -db_date_to_iso_8601_date \ -- force_set_var - $2 - $out_date" +db_date_to_iso_8601_date: force_set_var - $2 - $out_date" - unset -v in_date out_date yy mm dd HH MM SS TZ + unset -v in_date out_date yyyy mm dd HH MM SS TZ } # => db_date_to_iso_8601_date() # Convert default SSL date to iso_8601 date # This may not be feasible, due to different languages # Alow the caller to assess those errors (eg. Fall-back) cert_date_to_iso_8601_date() { - verbose "iso_8601-WIP: cert_date_to_iso_8601_date()" - die "BLOCKED: cert_date_to_iso_8601_date()" + verbose "iso_8601-WIP: cert_date_to_iso_8601_date" + die "BLOCKED: cert_date_to_iso_8601_date" # check input [ "$#" = 2 ] || die "\ -cert_date_to_iso_8601_date - input error" +cert_date_to_iso_8601_date: input error" # Expected format: 'Mar 21 18:25:01 2023 GMT' in_date="$1" @@ -4116,7 +4179,7 @@ cert_date_to_iso_8601_date - input error" # Return iso_8601 date force_set_var "$2" "$out_date" || die "\ -cert_date_to_iso_8601 - force_set_var - $2 - $out_date" +cert_date_to_iso_8601: force_set_var - $2 - $out_date" unset -v in_date out_date yyyy mmm mm dd HH MM SS TZ } # => cert_date_to_iso_8601() @@ -4137,6 +4200,8 @@ read_db() { while read -r db_status db_notAfter db_record; do + verbose "***** Read next record *****" + # Interpret the db/certificate record unset -v db_serial db_cn db_revoke_date db_reason case "$db_status" in @@ -4187,7 +4252,8 @@ read_db() { ;; revoke) # Certs which have been revoked - if [ "$db_status" = R ]; then + case "$db_status" in + R) case "$target" in '') revoke_status ;; *) @@ -4195,11 +4261,15 @@ read_db() { revoke_status fi esac - fi + ;; + *) + : # Ignore ok + esac ;; renew) # Certs which have been renewed but not revoked - if [ "$db_status" = V ]; then + case "$db_status" in + V|E) case "$target" in '') renew_status ;; *) @@ -4207,7 +4277,10 @@ read_db() { renew_status fi esac - fi + ;; + *) + : # Ignore ok + esac ;; *) die "Unrecognised report: $report" esac @@ -4231,6 +4304,7 @@ expire_status() { # The certificate for CN ahould exist but may not if [ -e "$cert_issued" ]; then + verbose "expire_status: cert exists" # get the serial number of the certificate ssl_cert_serial "$cert_issued" cert_serial @@ -4239,12 +4313,12 @@ expire_status() { # an issued cert if [ "$db_serial" != "$cert_serial" ]; then information "\ -serial mismatch: - db_serial: $db_serial - cert_serial: $cert_serial - commonName: $db_cn - cert_issued: $cert_issued" - return 0 +expire_status: SERIAL MISMATCH: + db_serial: $db_serial + cert_serial: $cert_serial + commonName: $db_cn + cert_issued: $cert_issued" + #return 0 fi # Get cert end date in iso_8601 format from SSL @@ -4257,16 +4331,19 @@ serial mismatch: : # ok else verbose "\ -expire_status: ACCEPTED ERROR-1: iso_8601_cert_enddate()" +expire_status: ACCEPTED ERROR-1: \ +iso_8601_cert_enddate()" verbose "\ -expire_status: CONSUMED ERROR: FALL-BACK to default SSL date format" +expire_status: CONSUMED ERROR: \ +FALL-BACK to default SSL date format" ssl_cert_not_after_date \ "$cert_issued" cert_not_after_date verbose "\ -expire_status: FALL-BACK completed" +expire_status(): FALL-BACK completed" fi else + verbose "expire_status: cert does NOT exist" # Translate db date to usable date cert_not_after_date= db_date_to_iso_8601_date \ @@ -4283,7 +4360,7 @@ expire_status: FALL-BACK completed" # Verify dates via 'date +%s' format verbose "\ -expire_status: cert_date_to_timestamp_s() for comparison." +expire_status: cert_date_to_timestamp_s: for comparison" old_cert_expire_date_s= cert_date_to_timestamp_s \ "$cert_not_after_date" old_cert_expire_date_s @@ -4292,18 +4369,24 @@ expire_status: cert_date_to_timestamp_s() for comparison." if [ "$cert_expire_date_s" = "$old_cert_expire_date_s" ] then : # ok - verbose "ABSOLUTE seconds MATCH:" - verbose "cert_expire_date_s= $cert_expire_date_s" - verbose "old_cert_expire_date_s= $old_cert_expire_date_s" + verbose "expire_status: ABSOLUTE seconds MATCH:" + verbose " cert_expire_date_s= $cert_expire_date_s" + verbose " old_cert_expire_date_s= $old_cert_expire_date_s" else + verbose "expire_status: ABSOLUTE seconds do not MATCH:" + verbose " cert_expire_date_s= $cert_expire_date_s" + verbose " old_cert_expire_date_s= $old_cert_expire_date_s" + verbose " difference= \ +$(( cert_expire_date_s - old_cert_expire_date_s ))" + # If there is an error then use --days-margin=10 [ "$EASYRSA_iso_8601_MARGIN" ] || \ - die "expire_status - ABSOLUTE seconds mismatch" + die "expire_status: ABSOLUTE seconds mismatch" # Allows days for margin of error in seconds margin_s="$(( - EASYRSA_iso_8601_MARGIN * (60 * 60 * 24) + EASYRSA_iso_8601_MARGIN * (60 * 60 * 24) + 1 ))" margin_plus_s="$(( old_cert_expire_date_s + margin_s @@ -4316,33 +4399,41 @@ expire_status: cert_date_to_timestamp_s() for comparison." [ "$cert_expire_date_s" -gt "$margin_minus_s" ] then : # ok - verbose "MARGIN seconds ACCEPTED: -cert_expire_date_s= $cert_expire_date_s -old_cert_expire_date_s= $old_cert_expire_date_s -margin_plus_s= $margin_plus_s -margin_minus_s= $margin_minus_s" + verbose "\ +expire_status: MARGIN seconds ACCEPTED: + cert_expire_date_s= $cert_expire_date_s + old_cert_expire_date_s= $old_cert_expire_date_s + difference= \ + $(( cert_expire_date_s - old_cert_expire_date_s )) + margin_plus_s= $margin_plus_s + margin_minus_s= $margin_minus_s" else - verbose "MARGIN seconds REJECTED: -cert_expire_date_s= $cert_expire_date_s -old_cert_expire_date_s= $old_cert_expire_date_s -margin_plus_s= $margin_plus_s -margin_minus_s= $margin_minus_s" + verbose "\ +expire_status: MARGIN seconds REJECTED: + cert_expire_date_s= $cert_expire_date_s + old_cert_expire_date_s= $old_cert_expire_date_s + margin_plus_s= $margin_plus_s + margin_minus_s= $margin_minus_s" die "\ -expire_status - Verify cert expire date EXCESS mismatch!" +expire_status: Verify cert expire date EXCESS mismatch!" fi fi verbose "\ -expire_status: cert_date_to_timestamp_s() comparison complete." +expire_status: cert_date_to_timestamp_s: comparison complete" else verbose "\ -expire_status: ACCEPTED ERROR-2: iso_8601_timestamp_to_seconds()" +expire_status: ACCEPTED ERROR-2: \ +iso_8601_timestamp_to_seconds" verbose "\ -expire_status: CONSUMED ERROR: FALL-BACK to default SSL date format" +expire_status: CONSUMED ERROR: \ +FALL-BACK to default SSL date format" + cert_date_to_timestamp_s \ "$cert_not_after_date" cert_expire_date_s + verbose "\ expire_status: FALL-BACK completed" fi @@ -4361,14 +4452,18 @@ expire_status: FALL-BACK completed" if [ "$cert_expire_date_s" -lt "$cutoff_date_s" ]; then # Cert expires in less than grace period if [ "$cert_expire_date_s" -gt "$now_date_s" ]; then + verbose "expire_status: Valid -> expiring" printf '%s%s\n' \ "$db_status | Serial: $db_serial | " \ "Expires: $cert_not_after_date | CN: $db_cn" else + verbose "expire_status: Expired" printf '%s%s\n' \ "$db_status | Serial: $db_serial | " \ "Expired: $cert_not_after_date | CN: $db_cn" fi + else + verbose "expire_status: Valid -> NOT expiring" fi } # => expire_status()