diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index cf900d1..48ea8b8 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -34,6 +34,7 @@ Here is the list of commands available with a short syntax reminder. Use the build-client-full [ cmd-opts ] build-server-full [ cmd-opts ] revoke [cmd-opts] + renew [cmd-opts] build-serverClient-full [ cmd-opts ] gen-crl update-db @@ -110,6 +111,11 @@ cmd_help() { superseded cessationOfOperation certificateHold";; + renew) text=" + renew [ cmd-opts ] + Renew a certificate specified by the filename_base" + opts=" + nopass - do not encrypt the private key (default is encrypted)" ;; gen-crl) text=" gen-crl Generate a CRL" ;; @@ -434,7 +440,10 @@ $help_note" [ "$1" = "test" ] && return 0 # verify expected CA-specific dirs: - for i in issued certs_by_serial revoked/certs_by_serial revoked/private_by_serial revoked/reqs_by_serial; do + for i in issued certs_by_serial \ + revoked/certs_by_serial revoked/private_by_serial revoked/reqs_by_serial \ + renewed/certs_by_serial renewed/private_by_serial renewed/reqs_by_serial ; + do [ -d "$EASYRSA_PKI/$i" ] || die "\ Missing expected CA dir: $i (perhaps you need to run build-ca?) $help_note" @@ -527,7 +536,10 @@ current CA keypair. If you intended to start a new CA, run init-pki first." # create necessary files and dirs: err_file="Unable to create necessary PKI files (permissions?)" - for i in issued certs_by_serial revoked/certs_by_serial revoked/private_by_serial revoked/reqs_by_serial; do + for i in issued certs_by_serial \ + revoked/certs_by_serial revoked/private_by_serial revoked/reqs_by_serial \ + renewed/certs_by_serial renewed/private_by_serial renewed/reqs_by_serial; + do mkdir -p "$EASYRSA_PKI/$i" || die "$err_file" done printf "" > "$EASYRSA_PKI/index.txt" || die "$err_file" @@ -957,6 +969,160 @@ input in file: $req_in" } #= move_revoked() +# renew backend +renew() { + verify_ca_init + + # pull filename base: + [ -n "$1" ] || die "\ +Error: didn't find a file base name as the first argument. +Run easyrsa without commands for usage and command help." + crt_in="$EASYRSA_PKI/issued/$1.crt" + + opts="" + if [ "$2" ]; then + opts="$2" + fi + + verify_file x509 "$crt_in" || die "\ +Unable to renew as the input file is not a valid certificate. Unexpected +input in file: $crt_in" + + # confirm operation by displaying DN: + confirm "Continue with renew: " "yes" " +Please confirm you wish to renew the certificate with the following subject: + +$(display_dn x509 "$crt_in") +" # => confirm end + + # referenced cert must exist: + [ -f "$crt_in" ] || die "\ +Unable to renew as no certificate was found. Certificate was expected +at: $crt_in" + + # make safessl-easyrsa.cnf + make_ssl_config + + # Check if old cert is expired or expires within 30 days + expire_date=$( + "$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -enddate | + sed -n 's/^notAfter=//' + ) + expire_date=$(date -d "$expire_date" +%s) + + allow_renew_date=$(date -d '+30day' +%s) + + [ "$expire_date" -gt "$allow_renew_date" ] || die "\ +Certificate expires in more than 30 days. +Renewal not allowed." + + # Extract certificate usage from old cert + cert_ext_key_usage=$( + "$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -ext extendedKeyUsage | + sed -n "2p;n;s/^ *//;p;" + ) + case $cert_ext_key_usage in + "TLS Web Client Authentication") + cert_type=client + ;; + "TLS Web Server Authentication") + cert_type=server + ;; + "TLS Web Server Authentication, TLS Web Client Authentication") + cert_type=serverClient + ;; + esac + + # Use SAN from --subject-alt-name if set else use SAN from old cert + echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName || \ + { + san=$( + "$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -ext subjectAltName | + sed -n "2p;{n;s/ //g;p;}" + ) + export EASYRSA_EXTRA_EXTS="\ +$EASYRSA_EXTRA_EXTS +subjectAltName = $san" + } + + # move renewed files so we can reissue certificate with the same name + # FIXME: Modify revoke() to also work on the renewed certs subdir + move_renewed "$1" + + # renew certificate + # shellcheck disable=SC2086 + build_full $cert_type $1 $opts || die "\ +Failed to renew certificate: renew command failed." + + notice "\ +IMPORTANT!!! + +Renew was successful. +You may want to revoke the old certificate once the new one has been deployed. +" # => notice end + return 0 +} #= renew() + +# move-renewed +# moves renewed certificates to an alternative folder +# allows reissuing certificates with the same name +move_renewed() { + verify_ca_init + + [ -n "$1" ] || die "\ +Error: didn't find a file base name as the first argument. +Run easyrsa without commands for usage and command help." + + crt_in="$EASYRSA_PKI/issued/$1.crt" + key_in="$EASYRSA_PKI/private/$1.key" + req_in="$EASYRSA_PKI/reqs/$1.req" + + verify_file x509 "$crt_in" || die "\ +Unable to move renewed input file. The file is not a valid certificate. Unexpected +input in file: $crt_in" + + verify_file req "$req_in" || die "\ +Unable to move request. The file is not a valid request. Unexpected +input in file: $req_in" + + # get the serial number of the certificate -> serial=XXXX + cert_serial="$("$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -serial)" + # remove the serial= part -> we only need the XXXX part + cert_serial=${cert_serial##*=} + + crt_by_serial="$EASYRSA_PKI/certs_by_serial/$cert_serial.pem" + crt_by_serial_renewed="$EASYRSA_PKI/renewed/certs_by_serial/$cert_serial.crt" + key_by_serial_renewed="$EASYRSA_PKI/renewed/private_by_serial/$cert_serial.key" + req_by_serial_renewed="$EASYRSA_PKI/renewed/reqs_by_serial/$cert_serial.req" + + + # move crt, key and req file to renewed folders + mv "$crt_in" "$crt_by_serial_renewed" + mv "$req_in" "$req_by_serial_renewed" + + # only move the key if we have it + if [ -e "$key_in" ] + then + mv "$key_in" "$key_by_serial_renewed" + fi + + # move the rest of the files (p12, p7, ...) + # shellcheck disable=SC2231 + for file in $EASYRSA_PKI/private/$1\.??? + do + # get file extension + file_ext="${file##*.}" + + mv "$file" "$EASYRSA_PKI/renewed/private_by_serial/$cert_serial.$file_ext" + done + + # remove the duplicate certificate in the certs_by_serial folder + rm "$crt_by_serial" + + return 0 + +} #= move_renewed() + # gen-crl backend gen_crl() { verify_ca_init @@ -1491,6 +1657,9 @@ case "$cmd" in revoke) revoke "$@" ;; + renew) + renew "$@" + ;; import-req) import_req "$@" ;; diff --git a/easyrsa3/openssl-easyrsa.cnf b/easyrsa3/openssl-easyrsa.cnf index 4167031..1139414 100644 --- a/easyrsa3/openssl-easyrsa.cnf +++ b/easyrsa3/openssl-easyrsa.cnf @@ -32,6 +32,9 @@ default_crl_days= $ENV::EASYRSA_CRL_DAYS # how long before next CRL default_md = $ENV::EASYRSA_DIGEST # use public key default MD preserve = no # keep passed DN ordering +# This allows to renew certificates which have not been revoked +unique_subject = no + # A few difference way of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-)