From 7b52ef3322df0146e15be0b595178771445a078f Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Sun, 28 Jul 2024 23:18:35 +0100 Subject: [PATCH 1/6] Prepare and improve interface for use of easyrsa-tools.lib: renew Add specific error message for 'renew'. Re-insert 'renew_restore_move', to undo changes made by 'renew'. Signed-off-by: Richard T Bonhomme --- easyrsa3/easyrsa | 93 +++++++++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index a20cb9f8..4d5376b5 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -940,6 +940,9 @@ cleanup() { [ "$error_build_full_cleanup" ] && \ rm -f "$crt_out" "$req_out" "$key_out" + # Restore files when renew is interrupted + [ "$error_undo_renew_move" ] && renew_restore_move + if [ "${secured_session%/*}" ] && \ [ -d "$secured_session" ] then @@ -2731,7 +2734,7 @@ ${confirm_critical_attribs}${confirm_dn}" # Confirm the user wishes to sign this request # The foreign_request confirmation is not required # for build_full: - if [ "$do_build_full" ]; then + if [ "$local_request" ]; then unset -v foreign_request else foreign_request="\ @@ -2743,7 +2746,7 @@ source or that you have verified the request checksum \ with the sender.$NL" fi - confirm "Confirm request details: " "yes" "\ + confirm "Confirm requested details: " "yes" "\ ${foreign_request}You are about to sign the following certificate: $confirm_details" # => confirm end @@ -2901,14 +2904,14 @@ Warning! An inline file for name '$name' already exists: * $inline_out" - # Set to modify sign-req confirmation message - do_build_full=1 - # create request verbose "build_full: BEGIN gen_req" gen_req "$name" batch verbose "build_full: END gen_req" + # Set to modify sign-req confirmation message + local_request=1 + # Recreate temp-session and # drop edits to SSL Conf file remove_secure_session @@ -3330,17 +3333,6 @@ Failed to remove inline file: return 0 } # => revoke_move() -# renew backend -renew() { - print " -To renew a certificate, please use commands: -* expire -* sign-req - -See help for details.${NL}" - cleanup -} # => renew() - # Move expired cert out of pki/issued to pki/expired # to allow renewal expire_cert() { @@ -5462,7 +5454,7 @@ unset -v \ alias_days \ prohibit_no_pass \ invalid_vars \ - do_build_full error_build_full_cleanup \ + local_request error_build_full_cleanup \ selfsign_eku \ internal_batch mv_temp_error \ easyrsa_exit_with_error error_info \ @@ -5864,12 +5856,6 @@ case "$cmd" in cert_dir=renewed/issued revoke "$@" ;; - renew) - verify_working_env - [ -z "$alias_days" ] || \ - export EASYRSA_CERT_EXPIRE="$alias_days" - renew "$@" - ;; import-req) verify_working_env import_req "$@" @@ -5927,7 +5913,7 @@ case "$cmd" in verify_working_env show_host "$@" ;; - show-expire|show-revoke|show-renew) + renew|show-expire|show-revoke|show-renew) verify_working_env # easyrsa-tools.lib is required @@ -5936,36 +5922,61 @@ case "$cmd" in # shellcheck disable=SC1090 # can't follow non-constant.. . "$EASYRSA_TOOLS_LIB" || \ die "Source failed: $EASYRSA_TOOLS_LIB" - unset -v EASYRSA_TOOLS_CALLER + unset -v EASYRSA_TOOLS_CALLER tools_error + + verbose "EASYRSA_TOOLS_LIB: $EASYRSA_TOOLS_LIB" + verbose "EASYRSA_TOOLS_VERSION: $EASYRSA_TOOLS_VERSION" + + # Verify tools version + if [ "$EASYRSA_TOOLS_VERSION" -lt 321 ]; then + warn "\ +EasyRSA Tools version is out of date: +* EASYRSA_TOOLS_VERSION: $EASYRSA_TOOLS_VERSION" + fi else - user_error "Missing: easyrsa-tools.lib + tools_error="Missing: easyrsa-tools.lib -Use of Status Reports requires Easy-RSA tools library, source: +Use of command '$cmd' requires Easy-RSA tools library, source: * https://github.com/OpenVPN/easy-rsa/dev/easyrsa-tools.lib Place a copy of easyrsa-tools.lib in a standard system location." fi - # Verify tools version - if [ "$EASYRSA_TOOLS_VERSION" = 1.0.1 ]; then - verbose "EASYRSA_TOOLS_VERSION: $EASYRSA_TOOLS_VERSION" - else - warn "\ -EasyRSA Tools version is out of date: -* EASYRSA_TOOLS_VERSION: $EASYRSA_TOOLS_VERSION" - fi - case "$cmd" in + renew) + if [ "$tools_error" ]; then + user_error "$tools_error + +A certificate can be renewed without EasyRSA Tools. Expire the certificate +using command 'expire' and sign the original request with 'sign-req'." + else + [ -z "$alias_days" ] || \ + export EASYRSA_CERT_EXPIRE="$alias_days" + renew "$@" + fi + ;; show-expire) - [ -z "$alias_days" ] || \ - export EASYRSA_PRE_EXPIRY_WINDOW="$alias_days" - status expire "$@" + if [ "$tools_error" ]; then + user_error "$tools_error" + else + [ -z "$alias_days" ] || \ + export EASYRSA_PRE_EXPIRY_WINDOW="$alias_days" + status expire "$@" + fi ;; show-revoke) - status revoke "$@" + if [ "$tools_error" ]; then + user_error "$tools_error" + else + status revoke "$@" + fi ;; show-renew) - status renew "$@" + if [ "$tools_error" ]; then + user_error "$tools_error" + else + status renew "$@" + fi ;; *) die "Unknown command: '$cmd'" From 30fe311dc1fcc2c25929ce44353c716d68e2f631 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Sun, 28 Jul 2024 23:19:26 +0100 Subject: [PATCH 2/6] easyrsa-tools.lib: New version of 'renew' To 'renew' a certificate, there are pre-requisites that must be met. Attribuyes from the original certificate must match the new certificate. The attributes supported by EasyRSA are now all correctly 'renewed'. Signed-off-by: Richard T Bonhomme --- dev/easyrsa-tools.lib | 256 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 255 insertions(+), 1 deletion(-) diff --git a/dev/easyrsa-tools.lib b/dev/easyrsa-tools.lib index a8432de2..c788b3aa 100644 --- a/dev/easyrsa-tools.lib +++ b/dev/easyrsa-tools.lib @@ -9,7 +9,7 @@ if [ -z "$EASYRSA_TOOLS_CALLER" ]; then fi # Set tools version -EASYRSA_TOOLS_VERSION=1.0.1 +export EASYRSA_TOOLS_VERSION=321 # Get certificate start date # shellcheck disable=2317 # Unreach - ssl_cert_not_before_date() @@ -921,3 +921,257 @@ before they can be revoked." read_db } # => status() + +# renew backend +renew() { + # pull filename base: + [ "$1" ] || user_error "\ +Error: didn't find a file base name as the first argument. +Run easyrsa without commands for usage and command help." + + # Assign file_name_base and dust off! + file_name_base="$1" + shift + + # Assign input files + in_dir="$EASYRSA_PKI" + crt_in="$in_dir/issued/${file_name_base}.crt" + key_in="$in_dir/private/${file_name_base}.key" + req_in="$in_dir/reqs/${file_name_base}.req" + creds_in="$in_dir/${file_name_base}.creds" + inline_in="$in_dir/inline/${file_name_base}.inline" + + # Upgrade CA index.txt.attr - unique_subject = no + print 'unique_subject = no' > "$EASYRSA_PKI/index.txt.attr" || \ + die "Failed to upgrade CA to support renewal." + + # deprecate ALL options + while [ "$1" ]; do + case "$1" in + nopass) + warn "\ +Option 'nopass' is not supported by command 'renew'." + ;; + *) user_error "Unknown option: $1" + esac + shift + done + + # Verify certificate + if [ -f "$crt_in" ]; then + verify_file x509 "$crt_in" || user_error "\ +Input file is not a valid certificate: +* $crt_in" + else + user_error "\ +Missing certificate file: +* $crt_in" + fi + + # Verify request + if [ -e "$req_in" ]; then + verify_file req "$req_in" || user_error "\ +Input file is not a valid request: +* $req_in" + else + user_error "\ +Missing request file: +* $req_in" + fi + + # get the serial number of the certificate + ssl_cert_serial "$crt_in" cert_serial || \ + die "$cmd: Failed to get cert serial number!" + + # Duplicate cert by serial file + dup_dir="$EASYRSA_PKI/certs_by_serial" + dup_crt_by_serial="$dup_dir/${cert_serial}.pem" + + # Set out_dir + out_dir="$EASYRSA_PKI/renewed" + crt_out="$out_dir/issued/${file_name_base}.crt" + + # NEVER over-write a renewed cert, revoke it first + deny_msg="\ +Cannot renew this certificate, a conflicting file exists: +*" + [ -e "$crt_out" ] && \ + user_error "$deny_msg certificate: $crt_out" + unset -v deny_msg + + # Make inline directory + [ -d "$EASYRSA_PKI/inline" ] || \ + mkdir -p "$EASYRSA_PKI/inline" || \ + die "Failed to create inline directoy." + + # Extract certificate usage from old cert + ssl_cert_x509v3_eku "$crt_in" cert_type + + # --san-crit + unset -v EASYRSA_SAN_CRIT + if grep -q 'X509v3 Subject Alternative Name: critical' "$crt_in" + then + export EASYRSA_SAN_CRIT='critical,' + verbose "renew: --san-crit ENABLED" + fi + + # Use SAN from old cert ONLY + if grep 'X509v3 Subject Alternative Name' "$crt_in"; then + EASYRSA_SAN="$( + "$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -text | \ + grep -A 1 'X509v3 Subject Alternative Name' | \ + sed -e s/'^\ *'// \ + -e /'X509v3 Subject Alternative Name'/d + )" || die "renew - EASYRSA_SAN: easyrsa_openssl subshell" + verbose "renew: EASYRSA_SAN: ${EASYRSA_SAN}" + + export EASYRSA_EXTRA_EXTS="\ +$EASYRSA_EXTRA_EXTS +subjectAltName = ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}" + verbose "renew: EASYRSA_EXTRA_EXTS: ${EASYRSA_EXTRA_EXTS}" + fi + + # --bc-crit + if grep -q 'X509v3 Basic Constraints: critical' "$crt_in" + then + export EASYRSA_BC_CRIT=1 + verbose "renew: --bc-crit ENABLED" + fi + + # --ku-crit + if grep -q 'X509v3 Key Usage: critical' "$crt_in" + then + export EASYRSA_KU_CRIT=1 + verbose "renew: --ku-crit ENABLED" + fi + + # --eku-crit + if grep -q 'X509v3 Extended Key Usage: critical' "$crt_in" + then + export EASYRSA_EKU_CRIT=1 + verbose "renew: --eku-crit ENABLED" + fi + + # confirm operation by displaying Warning + confirm "Continue with 'renew' ? " yes "\ +WARNING: This process is destructive! + +These files will be MOVED to the 'renewed' sub-directory: +* $crt_in + +These files will be DELETED: +All PKCS files for commonName: $file_name_base + +The inline credentials files: +* $creds_in +* $inline_in" + + # move renewed files + # so we can reissue certificate with the same name + renew_move + error_undo_renew_move=1 + + # Set to modify sign-req confirmation message + local_request=1 + + # renew certificate + # EASYRSA_BATCH=1 + if sign_req "$cert_type" "$file_name_base" + then + unset -v error_undo_renew_move + else + # If renew failed then restore cert. + # Otherwise, issue a warning + renew_restore_move + die "Renewal has failed to build a new certificate." + fi + + # inline it + # Over write existing because renew is successful + if inline_creds "$file_name_base" > "$inline_in" + then + notice "\ +Inline file created: +* $inline_in" + else + warn "\ +INCOMPLETE Inline file created: +* $inline_in" + fi + + # Success messages + notice "\ +Renew was successful. + + * IMPORTANT * + +Renew has created a new certificate, to replace the old one. + +To revoke the old certificate, once the new one has been deployed, +use command 'revoke-renewed $file_name_base'" + + return 0 +} # => renew() + +# Restore files on failure to renew +renew_restore_move() { + # restore crt file to PKI folders + rrm_err= + if mv "$restore_crt_out" "$restore_crt_in"; then + : # ok + else + warn "Failed to restore: $restore_crt_in" + rrm_err=1 + fi + + # messages + if [ "$rrm_err" ]; then + warn "Failed to restore renewed files." + else + notice "\ +Renew FAILED but files have been successfully restored." + fi + + return 0 +} # => renew_restore_move() + +# renew_move +# moves renewed certificates to the 'renewed' folder +# allows reissuing certificates with the same name +renew_move() { + # make sure renewed dirs exist + easyrsa_mkdir "$out_dir" + easyrsa_mkdir "$out_dir"/issued + + # move crt to renewed folders + # After this point, renew is possible! + restore_crt_in="$crt_in" + restore_crt_out="$crt_out" + mv "$crt_in" "$crt_out" || \ + die "Failed to move: $crt_in" + + # Remove files that can be recreated: + # remove any pkcs files + for pkcs in p12 p7b p8 p1; do + # issued + rm -f "$in_dir/issued/$file_name_base.$pkcs" + # private + rm -f "$in_dir/private/$file_name_base.$pkcs" + done + + # remove credentials file + if [ -e "$creds_in" ]; then + rm "$creds_in" || warn "\ +Failed to remove credentials file: +* $creds_in" + fi + + # remove inline file + if [ -e "$inline_in" ]; then + rm "$inline_in" || warn "\ +Failed to remove inline file: +* $inline_in" + fi + + return 0 +} # => renew_move() From 81256ce75833cf55a143b9237c85779f57ac4842 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Mon, 29 Jul 2024 19:39:57 +0100 Subject: [PATCH 3/6] renew: Move SAN critical into SAN detected step Signed-off-by: Richard T Bonhomme --- dev/easyrsa-tools.lib | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/dev/easyrsa-tools.lib b/dev/easyrsa-tools.lib index c788b3aa..0a961888 100644 --- a/dev/easyrsa-tools.lib +++ b/dev/easyrsa-tools.lib @@ -1007,14 +1007,6 @@ Cannot renew this certificate, a conflicting file exists: # Extract certificate usage from old cert ssl_cert_x509v3_eku "$crt_in" cert_type - # --san-crit - unset -v EASYRSA_SAN_CRIT - if grep -q 'X509v3 Subject Alternative Name: critical' "$crt_in" - then - export EASYRSA_SAN_CRIT='critical,' - verbose "renew: --san-crit ENABLED" - fi - # Use SAN from old cert ONLY if grep 'X509v3 Subject Alternative Name' "$crt_in"; then EASYRSA_SAN="$( @@ -1025,6 +1017,15 @@ Cannot renew this certificate, a conflicting file exists: )" || die "renew - EASYRSA_SAN: easyrsa_openssl subshell" verbose "renew: EASYRSA_SAN: ${EASYRSA_SAN}" + # --san-crit + unset -v EASYRSA_SAN_CRIT + if grep -q 'X509v3 Subject Alternative Name: critical' \ + "$crt_in" + then + export EASYRSA_SAN_CRIT='critical,' + verbose "renew: --san-crit ENABLED" + fi + export EASYRSA_EXTRA_EXTS="\ $EASYRSA_EXTRA_EXTS subjectAltName = ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}" From 12d8fef9fcb9ce0276256e150416e6a7b60d77fc Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Mon, 29 Jul 2024 22:36:10 +0100 Subject: [PATCH 4/6] renew: Disable unsupported options Signed-off-by: Richard T Bonhomme --- dev/easyrsa-tools.lib | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev/easyrsa-tools.lib b/dev/easyrsa-tools.lib index 0a961888..0e94b00e 100644 --- a/dev/easyrsa-tools.lib +++ b/dev/easyrsa-tools.lib @@ -1053,6 +1053,9 @@ subjectAltName = ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}" verbose "renew: --eku-crit ENABLED" fi + # Disable options not supported by renew + unset -v EASYRSA_CP_EXTS EASYRSA_AUTO_SAN EASYRSA_NEW_SUBJECT + # confirm operation by displaying Warning confirm "Continue with 'renew' ? " yes "\ WARNING: This process is destructive! From 4786a142a31c95420d0b6cb2ce1a06bcc80e8648 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Wed, 31 Jul 2024 00:06:27 +0100 Subject: [PATCH 5/6] renew: Ensure request and certificate commonName matches Signed-off-by: Richard T Bonhomme --- dev/easyrsa-tools.lib | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dev/easyrsa-tools.lib b/dev/easyrsa-tools.lib index 0e94b00e..0d8a69f4 100644 --- a/dev/easyrsa-tools.lib +++ b/dev/easyrsa-tools.lib @@ -979,6 +979,21 @@ Missing request file: * $req_in" fi + # Get cert commonName + cert_CN="$( + display_dn x509 "$crt_in" | grep 'commonName' + )" || die "renew - display_dn of cert failed" + + # Get req commonName + req_CN="$( + display_dn req "$req_in" | grep 'commonName' + )" || die "renew - display_dn of req failed" + + # For renew, cert_CN must match req_CN + [ "$cert_CN" = "$req_CN" ] || user_error \ + "Certificate cannot be renewed due to commonName mismatch" + verbose "renew - cert_CN MATCH req_CN" + # get the serial number of the certificate ssl_cert_serial "$crt_in" cert_serial || \ die "$cmd: Failed to get cert serial number!" From 561f97bd8048e3d589a9c21ff784efecabd5a24f Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Wed, 31 Jul 2024 20:15:45 +0100 Subject: [PATCH 6/6] ChangeLog: Re-enable command 'renew' (V2): Requires EasyRSA Tools lib Signed-off-by: Richard T Bonhomme --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index afe4f3c8..35a50391 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ Easy-RSA 3 ChangeLog 3.2.1 (TBD) + * Re-enable command 'renew' (version 2): Requires EasyRSA Tools (30fe311) (#1195) * bug-fix: revoke: Pass the correct certificate location (24d5514) * vars.example: Add flags for auto-SAN and X509 critical attribute (a41dfcc) * Global option --eku-crit: Mark X509 extendedKeyUsage as critical (ca09211)