diff --git a/ChangeLog b/ChangeLog index 55fb3c3a3..442a82738 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,10 @@ Easy-RSA 3 ChangeLog 3.2.0 (TBD) + * docs: Update EasyRSA-Renew-and-Revoke.md (f6c2bf5) (#1109) + * Remove all 'renew' code; replaced by 'expire' code (9d94207) (#1109) + * Introduce commands: 'expire' and 'revoke-expired' (a1890fa) (#1109) + * Keep request files [CSR] when revoking certificates (6d6e8d8) (#1109) * Restrict use of --req-cn to build-ca (0a46164) (#1098) * Remove command 'display-san' (Code removed in 5a06f94) (50e6002) (#1096) * help: Add 'copyext'; How to use --copy-ext and --san (5a06f94) (#1096) diff --git a/doc/EasyRSA-Renew-and-Revoke.md b/doc/EasyRSA-Renew-and-Revoke.md index 7b338d089..5c26c0593 100644 --- a/doc/EasyRSA-Renew-and-Revoke.md +++ b/doc/EasyRSA-Renew-and-Revoke.md @@ -4,9 +4,34 @@ Easy-RSA 3 Certificate Renewal and Revocation Documentation This document explains how the **differing versions** of Easy-RSA 3 work with Renewal and Revocation of Certificates and Private keys. -Thanks to _good luck_, _hard work_ and _co-operation_, these version dependent -differences have been _smoothed-over_. Since version `3.1.1`, Easy-RSA has the -tools required to renew and/or revoke all verified and Valid certifiicates. +Easy-RSA version 3.2.x +---------------------- +v3.2 no longer supports the `renew` command. + +Instead, the process is as follows: +1. Command `expire ` - This will move an existing certificate + from `pki/issued` to `pki/expired`, so that a new certificate + can be signed, using the original request. + + Generally, renewing is required ONLY when a certificate is due to + expire. This means that certificates moved to `pki/expired` are + expected to be expired or to expire in the near future. + +2. Command `sign-req ` - Sign a new certificate. + + This allows ALL command line cutomisations to be used. eg: SAN. + (These customisations do not work correctly with the old `renew`) + +3. If required, Command `revoke-expired` can be used to revoke an + expired certificate in the `pki/expired` directory. + +This approach also allows certificates which have been edited during +`sign-req` to be edited the same way, without the need for excessive +and non-standard code. (Note: OpenSSL allows only one way for edits) + + +Easy-RSA version 3.1.x +---------------------- **UPDATE**: The changes noted for Easy-RSA version 3.1.2 have all been included with diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index b2fdd6c12..54c851a36 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -38,7 +38,8 @@ A list of commands is shown below: build-serverClient-full [ cmd-opts ] inline revoke [ cmd-opts ] - renew + expire + revoke-expired [ cmd-opts ] revoke-renewed [ cmd-opts ] gen-crl update-db @@ -200,39 +201,37 @@ Usage: easyrsa [ OPTIONS.. ] [ cmd-opts.. ]" * NOTE: To create an inline-file the output must be redirected. If the output is incomplete then an error is retruned." ;; - revoke) + revoke*) text=" * revoke [reason] +* revoke-expired [reason] +* revoke-renewed [reason] Revoke a certificate specified by the , - with an optional revocation reason which can be one of: + with an optional revocation [reason] which can be one of: unspecified keyCompromise CACompromise affiliationChanged superseded cessationOfOperation - certificateHold" - ;; - revoke-renewed) - text=" -* revoke-renewed [reason] + certificateHold - Revoke a *renewed* certificate specified by the , - with an optional revocation reason which can be one of: - unspecified - keyCompromise - CACompromise - affiliationChanged - superseded - cessationOfOperation - certificateHold" + revoke-expired and revoke-renewed are functionally equivalent + to revoke, however, they are used to revoke certificates which + have been either 'expired' or 'renewed' by EasyRSA commands." + + opts=" + * [reason] - As shown above." ;; - renew) + expire) text=" -* renew +* expire - Renew a certificate specified by " + Move a certificate specified by + to the 'pki/expired' directory. + + Allows an existing request to be signed again." ;; gen-crl) text=" @@ -515,8 +514,8 @@ These commands require easyrsa-tools.lib to be installed: esac if [ "$err_text" ]; then - print "${err_text}" print "$easyrsa_help_title" + print "${err_text}" else # display the help text print "$easyrsa_help_title" @@ -2644,12 +2643,14 @@ Run easyrsa without commands for usage and command help." shift 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" + # input cert for revocation: issued, expired or renewed + crt_in="${in_dir}/${cert_dir}/${file_name_base}.crt" + # Assign possible "crl_reason" if [ "$1" ]; then crl_reason="$1" @@ -2790,7 +2791,9 @@ revoke_move() { die "Failed to mkdir: $target" done - # move crt, key and req file to renewed_then_revoked folders + # do NOT move the req - can be signed again + + # move crt to renewed_then_revoked folders mv "$crt_in" "$crt_out" || die "Failed to move: $crt_in" # only move the key if we have it @@ -2798,24 +2801,18 @@ revoke_move() { mv "$key_in" "$key_out" || warn "Failed to move: $key_in" fi - # only move the req if we have it - if [ -e "$req_in" ]; then - mv "$req_in" "$req_out" || warn "Failed to move: $req_in" - fi - # remove any pkcs files for pkcs in p12 p7b p8 p1; do if [ -e "$in_dir/issued/$file_name_base.$pkcs" ]; then # issued rm "$in_dir/issued/$file_name_base.$pkcs" || warn "Failed to remove: $file_name_base.$pkcs" + fi - elif [ -e "$in_dir/private/$file_name_base.$pkcs" ]; then + if [ -e "$in_dir/private/$file_name_base.$pkcs" ]; then # private rm "$in_dir/private/$file_name_base.$pkcs" || warn "Failed to remove: $file_name_base.$pkcs" - else - : # ok fi done @@ -2845,6 +2842,18 @@ Failed to remove inline file: # 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() { # pull filename base: [ "$1" ] || user_error "\ Error: didn't find a file base name as the first argument. @@ -2854,25 +2863,25 @@ Run easyrsa without commands for usage and command help." 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" - # key_out is used by inline_creds() - key_out="$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" + # input + in_dir="$EASYRSA_PKI/issued" + crt_in="$in_dir/$file_name_base.crt" + #key_in="$in_dir/private/$file_name_base.key" + #req_in="$in_dir/reqs/$file_name_base.req" + #creds_in="$EASYRSA_PKI/$file_name_base.creds" - # Upgrade CA index.txt.attr - unique_subject = no - if grep -q 'unique_subject = no' \ - "$EASYRSA_PKI/index.txt.attr" - then - : # ok - else - printf '%s\n' 'unique_subject = no' \ - > "$EASYRSA_PKI/index.txt.attr" || \ - die "Incompatible CA configuration" + # output + out_dir="$EASYRSA_PKI/expired" + crt_out="$out_dir/$file_name_base.crt" + + # make output folder + easyrsa_mkdir_p "$EASYRSA_PKI" expired + + # Do not over write existing cert + if [ -e "$crt_out" ]; then + user_error "\ +Existing file must be revoked: +* $crt_out" fi # deprecate ALL options @@ -2880,7 +2889,7 @@ Run easyrsa without commands for usage and command help." case "$1" in nopass) warn "\ -Option 'nopass' is not supported by command 'renew'." +Option 'nopass' is not supported by command '$cmd'." ;; *) user_error "Unknown option: $1" esac @@ -2898,419 +2907,50 @@ 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 cert_serial= 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" ] || \ - easyrsa_mkdir_p "$EASYRSA_PKI" inline || \ - die "Failed to create inline directoy." - - # Extract certificate usage from old cert - cert_type= - ssl_cert_x509v3_eku "$crt_in" cert_type || \ - die "Unknown EKU: $cert_type" - - # Check cert subject against request subject - crt_subj_hash="$( - "$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout \ - -subject | "$EASYRSA_OPENSSL" dgst -sha1 - )" - req_subj_hash="$( - "$EASYRSA_OPENSSL" req -in "$req_in" -noout \ - -subject | "$EASYRSA_OPENSSL" dgst -sha1 - )" - - if [ "$crt_subj_hash" = "$req_subj_hash" ]; then - : # ok - else - die "\ -This certificate cannot be renewed due to inconsistent Subject." - fi - - # Prohibit --copy-ext - renew only supports SAN extention - if [ "$EASYRSA_CP_EXT" ]; then - user_error "Command '$cmd' does not support --copy-ext" - fi - - # Prohibit --san - renew uses SAN from old cert only - if [ "$EASYRSA_SAN" ]; then - user_error "Command '$cmd' does not support --san" - fi - # Set confirm DN and serial - confirm_dn="$(display_dn req "$req_in")" || \ - die "renew: display_dn" + confirm_dn="$(display_dn x509 "$crt_in")" || \ + die "expire: display_dn" confirm_sn=" serial-number = $cert_serial" - # Check cert for SAN - if easyrsa_openssl x509 -in "$crt_in" -noout -text | \ - grep -q '^[[:blank:]]*X509v3 Subject Alternative Name:' - then - # extract cert SAN - crt_x509_san_full="$( - easyrsa_openssl x509 -in "$crt_in" -noout -text | \ - grep -A 1 'X509v3 Subject Alternative Name' - )" || die "renew: crt_x509_san_full: grep -A 1" - - # Strip x509 header - crt_x509_san="$( - echo "$crt_x509_san_full" | \ - grep -v 'X509v3 Subject Alternative Name' - )" || die "renew: crt_x509_san: grep -v" - else - # No cert SAN - crt_x509_san_full= - crt_x509_san= - fi - - # Format confirmation text - if [ "$crt_x509_san" ]; then - confirm_details="\ -$confirm_dn - -$confirm_sn - -$crt_x509_san_full" - else - confirm_details="\ -$confirm_dn - -$confirm_sn" - fi - - # confirm operation by displaying DN: - warn "\ -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 - -The duplicate certificate: -* $dup_crt_by_serial" - - confirm " Continue with renewal: " "yes" " -Please confirm you wish to renew the certificate + # date of expiry + # Equal to: easyrsa-tools.lib - ssl_cert_not_after_date() + # This is left as a reminder that easyrsa does not handle + # dates well and they should be avoided, at all cost. + # This is for confirmation purposes ONLY. + crt_expire="$(openssl x509 -in "$crt_in" -noout -enddate)" || \ + die "expire: enddate" + confirm_ex=" notAfter date = ${crt_expire#*=}" + + # confirm + confirm " Continue with expiry: " yes " +Please confirm you wish to expire the certificate with the following subject: -$confirm_details" - - # move renewed files - # so we can reissue certificate with the same name - renew_move - error_undo_renew_move=1 - - # renew certificate - if EASYRSA_BATCH=1 sign_req "$cert_type" "$file_name_base" - then - unset -v error_undo_renew_move - else - # If sign_req failed then restore cert. - renew_restore_move - unset -v error_undo_renew_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 reason' ('reason' is optional)" - - return 0 -} # => renew() - -# Restore files on failure to renew -renew_restore_move() { - unset -v rrm_err error_undo_renew_move - # restore crt file to PKI folders - if mv "$restore_crt_out" "$restore_crt_in"; then - : # ok - else - warn "Failed to restore: $restore_crt_out" - 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 - for target in issued private reqs - do - easyrsa_mkdir_p "$out_dir" "$target" || - die "Failed to mkdir: $target" - done - - # move crt, key and req file 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" - - # Further file removal is a convenience, only. - # 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 the duplicate certificate - if [ -e "$dup_crt_by_serial" ]; then - rm "$dup_crt_by_serial" || warn "\ -Failed to remove the duplicate certificate: -* $dup_crt_by_serial" - fi - - # 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() - -# revoke-renewed backend -revoke_renewed() { - # 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 - - in_dir="$EASYRSA_PKI/renewed" - 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="$EASYRSA_PKI/$file_name_base.creds" - - # Assign possible "crl_reason" - if [ "$1" ]; then - crl_reason="$1" - shift - - case "$crl_reason" in - unspecified) : ;; - keyCompromise) : ;; - CACompromise) : ;; - affiliationChanged) : ;; - superseded) : ;; - cessationOfOperation) : ;; - certificateHold) : ;; - *) user_error "Illegal reason: $crl_reason" - esac - else - unset -v crl_reason - fi - - # Enforce syntax - if [ "$1" ]; then - user_error "Syntax error: $1" - fi - - # referenced cert must exist: - [ -f "$crt_in" ] || user_error "\ -Unable to revoke as no renewed certificate was found. -Certificate was expected at: -* $crt_in" - - # Verify certificate - verify_file x509 "$crt_in" || user_error "\ -Unable to revoke as the input-file is not a valid certificate. -Certificate was expected at: -* $crt_in" - - # Verify request - if [ -e "$req_in" ]; then - verify_file req "$req_in" || user_error "\ -Unable to verify request. The file is not a valid request. -Request was expected at: -* $req_in" - fi - - # get the serial number of the certificate - cert_serial= - 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" - - # output - out_dir="$EASYRSA_PKI/revoked" - crt_out="$out_dir/certs_by_serial/$cert_serial.crt" - key_out="$out_dir/private_by_serial/$cert_serial.key" - req_out="$out_dir/reqs_by_serial/$cert_serial.req" - - # NEVER over-write a revoked cert, serial must be unique - deny_msg="\ -Cannot revoke this certificate, a conflicting file exists. -*" - [ -e "$crt_out" ] && \ - user_error "$deny_msg certificate: $crt_out" - [ -e "$key_out" ] && \ - user_error "$deny_msg private key: $key_out" - [ -e "$req_out" ] && \ - user_error "$deny_msg request : $req_out" - unset -v deny_msg - - # Set confirm details - confirm_dn="$(display_dn x509 "$crt_in")" || \ - die "revoke: display_dn" - confirm_sn=" serial-number = $cert_serial" - confirm_details="\ $confirm_dn -$confirm_sn - - Reason: ${crl_reason:-None given}" - - # confirm operation by displaying DN: - unset -v if_exist_key_in if_exist_req_in - [ -e "$key_in" ] && if_exist_key_in=" -* $key_in" - [ -e "$req_in" ] && if_exist_req_in=" -* $req_in" - - warn "\ -This process is destructive! - -These files will be MOVED to the 'revoked' sub-directory: -* $crt_in${if_exist_key_in}${if_exist_req_in}" - - confirm " Continue with revocation: " "yes" " - Please confirm you wish to revoke the renewed certificate - with the following subject: -$confirm_details" +$confirm_sn - # Revoke the old (already renewed) certificate - easyrsa_openssl ca -utf8 -revoke "$crt_in" \ - ${crl_reason:+ -crl_reason "$crl_reason"} \ - ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} || \ - die "\ -Failed to revoke renewed certificate: revocation command failed." +$confirm_ex" # => End confirm - # move revoked files - revoke_renewed_move + # move cert to expired dir + mv "$crt_in" "$crt_out" || die "failed to move expired: $crt_in" + # User message notice "\ - * IMPORTANT * - -Revocation was successful. You must run 'gen-crl' and upload -a new CRL to your infrastructure in order to prevent the revoked -certificate from being accepted." - - return 0 -} # => revoke_renewed() - -# move-renewed-revoked -# moves renewed then revoked certificates to the 'revoked' folder -revoke_renewed_move() { - # make sure revoked dirs exist - for target in certs_by_serial private_by_serial reqs_by_serial - do - easyrsa_mkdir_p "$out_dir" "$target" || - die "Failed to mkdir: $target" - done +Certificate has been successfully moved to the expire directory. +* $crt_out - # move crt, key and req file to renewed_then_revoked folders - mv "$crt_in" "$crt_out" || die "Failed to move: $crt_in" - - # only move the key if we have it - if [ -e "$key_in" ]; then - mv "$key_in" "$key_out" || warn "Failed to move: $key_in" - fi +This certificate is still valid, until it expires. +It can be revoked with command 'revoke-expired'. - # only move the req if we have it - if [ -e "$req_in" ]; then - mv "$req_in" "$req_out" || warn "Failed to move: $req_in" - fi +It is now possible to sign a new certificate for '$file_name_base'" - return 0 -} # => revoke_renewed_move() +} # => expire_cert() # gen-crl backend gen_crl() { @@ -3791,10 +3431,11 @@ display_dn - input error" shift 2 # Display DN - print "$(OPENSSL_CONF=/dev/null \ + ssl_out="$( "$EASYRSA_OPENSSL" "$format" -in "$path" -noout -subject \ -nameopt utf8,sep_multiline,space_eq,lname,align)" || \ die "display_dn: SSL command '$format'" + print "$ssl_out" } # => display_dn() # Verify certificate against CA @@ -5697,11 +5338,18 @@ case "$cmd" in ;; revoke) verify_working_env + cert_dir=issued + revoke "$@" + ;; + revoke-expired) + verify_working_env + cert_dir=expired revoke "$@" ;; revoke-renewed) verify_working_env - revoke_renewed "$@" + cert_dir=renewed + revoke "$@" ;; renew) verify_working_env @@ -5713,6 +5361,10 @@ case "$cmd" in verify_working_env import_req "$@" ;; + expire) + verify_working_env + expire_cert "$@" + ;; inline) verify_working_env inline_creds "$@" || \