From ba32b0ddf2373920e02b053bef5d793441e692b7 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Tue, 26 Nov 2024 18:25:19 +0000 Subject: [PATCH 1/3] easyrsa-tools.lib: New command 'renew-ca' Sign a new CA certificate from the original CA private key. Support all options provided by Easy-RSA, eg. 'critical' attribute. The code is very similar to the standard 'build-ca' command, without the generation of a new private key. The new CA certificate will replace the old one. The old certificate is kept in a list of expired CA certificates: This new file is 'pki/exipred-ca-cert.list' The final replacement of the old CA is guarded by a confirmation. If the confirmation fails then all new data is discarded. easyrsa: Integrate 'renew-ca' into command selection Signed-off-by: Richard T Bonhomme --- dev/easyrsa-tools.lib | 219 ++++++++++++++++++++++++++++++++++++++++++ easyrsa3/easyrsa | 8 +- 2 files changed, 226 insertions(+), 1 deletion(-) diff --git a/dev/easyrsa-tools.lib b/dev/easyrsa-tools.lib index f9f8505c..7f28edf6 100644 --- a/dev/easyrsa-tools.lib +++ b/dev/easyrsa-tools.lib @@ -1009,4 +1009,223 @@ Input is not a valid certificate: fi } # => verify_cert() +# Renew CA certificate +renew_ca_cert() { + # dirs and files + ca_key_file="$EASYRSA_PKI"/private/ca.key + ca_cert_file="$EASYRSA_PKI"/ca.crt + exp_ca_cert_list="$EASYRSA_PKI"/expired-ca.list + + # Set fixed variables + x509=1 + date_stamp=1 + f_name="renew_ca_cert()" + + # Set default CA commonName + [ "$EASYRSA_REQ_CN" = ChangeMe ] || \ + warn "\ +$cmd does not support setting an external commonName." + + # Copy Old CA commonName as default + export EASYRSA_REQ_CN="$( + "$EASYRSA_OPENSSL" x509 -in "$ca_cert_file" \ + -noout -subject -nameopt utf8,multiline | \ + grep 'commonName' | sed -e \ + s\`^[[:blank:]]*commonName[[:blank:]]*=[[:blank:]]\`\` + )" + + # Set ssl batch mode, as required + [ "$EASYRSA_BATCH" ] && ssl_batch=1 + + # create local SSL cnf + write_easyrsa_ssl_cnf_tmp + + # Assign new cert temp-file + out_cert_tmp= + easyrsa_mktemp out_cert_tmp || \ + die "$f_name easyrsa_mktemp out_cert_tmp" + + # Assign old cert temp-file + old_cert_tmp= + easyrsa_mktemp old_cert_tmp || \ + die "$f_name easyrsa_mktemp old_cert_tmp" + + # Write complete CA cert to old cert temp-file + "$EASYRSA_OPENSSL" x509 -in "$ca_cert_file" \ + -text > "$old_cert_tmp" || \ + die "$f_name Write CA cert to temp-file" + + # Find or create x509 CA file + if [ -f "$EASYRSA_EXT_DIR/ca" ]; then + # Use the x509-types/ca file + x509_type_file="$EASYRSA_EXT_DIR/ca" + else + # Use a temp file + write_x509_type_tmp ca + x509_type_file="$write_x509_file_tmp" + fi + + # basicConstraints critical + if grep -q 'Basic Constraints: critical' "$old_cert_tmp" + then + crit_tmp= + easyrsa_mktemp crit_tmp || \ + die "$f_name easyrsa_mktemp BC crit_tmp" + + add_critical_attrib basicConstraints "$x509_type_file" \ + "$crit_tmp" || die "$f_name BC add_critical_attrib" + + # Use the new tmp-file with critical attribute + x509_type_file="$crit_tmp" + verbose "renew_ca_cert: basicConstraints critical OK" + fi + + # keyUsage critical + if grep -q 'Key Usage: critical' "$old_cert_tmp" + then + crit_tmp= + easyrsa_mktemp crit_tmp || \ + die "$f_name easyrsa_mktemp KU crit_tmp" + + add_critical_attrib keyUsage "$x509_type_file" \ + "$crit_tmp" || die "$f_name KU add_critical_attrib" + + # Use the new tmp-file with critical attribute + x509_type_file="$crit_tmp" + verbose "renew_ca_cert: keyUsage critical OK" + fi + + # Find or create x509 COMMON file + if [ -f "$EASYRSA_EXT_DIR/COMMON" ]; then + # Use the x509-types/COMMON file + x509_COMMON_file="$EASYRSA_EXT_DIR/COMMON" + else + # Use a temp file + write_x509_type_tmp COMMON + x509_COMMON_file="$write_x509_file_tmp" + fi + + # Check for insert-marker in ssl config file + if ! grep -q '^#%CA_X509_TYPES_EXTRA_EXTS%' \ + "$EASYRSA_SSL_CONF" + then + die "\ +This openssl config file does not support X509-type 'ca'. +* $EASYRSA_SSL_CONF + +Please update 'openssl-easyrsa.cnf' to the latest Easy-RSA release." + fi + + # Assign awkscript to insert EASYRSA_EXTRA_EXTS + # shellcheck disable=SC2016 # No expand '' - build_ca() + awkscript='\ +{if ( match($0, "^#%CA_X509_TYPES_EXTRA_EXTS%") ) +{ while ( getline<"/dev/stdin" ) {print} next } +{print} }' + + # Assign tmp-file for config + adjusted_ssl_cnf_tmp="" + easyrsa_mktemp adjusted_ssl_cnf_tmp || \ + die "$f_name easyrsa_mktemp adjusted_ssl_cnf_tmp" + + # Insert x509-types COMMON and 'ca' and EASYRSA_EXTRA_EXTS + { + # X509 files + cat "$x509_type_file" "$x509_COMMON_file" + + # User extensions + [ "$EASYRSA_EXTRA_EXTS" ] && \ + print "$EASYRSA_EXTRA_EXTS" + + } | awk "$awkscript" "$EASYRSA_SSL_CONF" \ + > "$adjusted_ssl_cnf_tmp" || \ + die "$f_name Copy X509_TYPES to config failed" + verbose "$f_name insert x509 and extensions OK" + + # Use this new SSL config for the rest of this function + EASYRSA_SSL_CONF="$adjusted_ssl_cnf_tmp" + + # Generate new CA cert: + easyrsa_openssl req -utf8 -new \ + -key "$ca_key_file" \ + -out "$out_cert_tmp" \ + ${ssl_batch:+ -batch} \ + ${x509:+ -x509} \ + ${date_stamp:+ -days "$EASYRSA_CA_EXPIRE"} \ + ${EASYRSA_DIGEST:+ -"$EASYRSA_DIGEST"} \ + ${EASYRSA_NO_PASS:+ "$no_password"} \ + ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ + # EOL + + # Collect New CA text + new_ca_text="$( + "$EASYRSA_OPENSSL" x509 -in "$out_cert_tmp" -noout -text + )" + + # Confirm renewed certificate installation + confirm "Install the new CA certificate ? " yes " +NEW CA CERTIFICATE: + +$new_ca_text + +WARNING !!! + +Your CA certificate is ready to be renewed. (Details above) + +This new CA certificate will completely replace the old one. +The old CA will be archived to the 'expired-ca.list' file. + +Please check the details above are correct, before continuing." + + # Prepare header file for updated old CA list + header_tmp= + easyrsa_mktemp header_tmp || \ + die "$f_name easyrsa_mktemp header_tmp" + + # header and separator text + hdr='# Easy-RSA expired CA certificate list:' + spr='# =====================================' + + # make full header temp-file + printf '%s\n%s\n\n' "$hdr" "$spr" > "$header_tmp" || \ + die "$f_name printf header to header-temp" + + # Prepare old cert list + if [ -f "$exp_ca_cert_list" ]; then + # Assign old cert list temp file + exp_cert_list_tmp= + easyrsa_mktemp exp_cert_list_tmp || \ + die "$f_name easyrsa_mktemp exp_cert_list_tmp" + + # write list to temp-fie, remove header not separators + sed -e s/"^${hdr}$"// \ + "$exp_ca_cert_list" > "$exp_cert_list_tmp" || \ + die "$f_name sed exp_ca_cert_list" + fi + + # Add full old CA Cert to old CA Cert list file + if [ -f "$exp_cert_list_tmp" ]; then + cat "$header_tmp" "$old_cert_tmp" "$exp_cert_list_tmp" \ + > "$exp_ca_cert_list" || \ + die "$f_name cat exp_cert_list_tmp" + else + cat "$header_tmp" "$old_cert_tmp" \ + > "$exp_ca_cert_list" || \ + die "$f_name cat old_cert_tmp" + fi + + # Install renewed CA Cert temp-file as current CA cert + mv -f "$out_cert_tmp" "$ca_cert_file" || \ + die "Failed to install renewed CA temp-file!" + + notice "\ +CA certificate has been successfully renewed. + +Your old CA cerificate has been added to the expired CA list at: +* $exp_ca_cert_list + +Your renewed CA cerificate is at: +* $ca_cert_file" +} # => renew_ca_cert() + # vim: ft=sh nu ai sw=8 ts=8 noet diff --git a/easyrsa3/easyrsa b/easyrsa3/easyrsa index 340f5ebe..0cc7992e 100755 --- a/easyrsa3/easyrsa +++ b/easyrsa3/easyrsa @@ -6011,13 +6011,19 @@ case "$cmd" in verify_working_env show_host "$@" ;; - renew|show-expire|show-revoke|show-renew|verify-cert) + renew-ca|renew|show-expire|show-revoke|show-renew|verify-cert) verify_working_env # easyrsa-tools.lib is required source_easyrsa_tools_lib || tools_error=1 case "$cmd" in + renew-ca) + [ "$tools_error" ] && user_error "$tools_error_txt" + [ -z "$alias_days" ] || \ + export EASYRSA_CA_EXPIRE="$alias_days" + renew_ca_cert "$@" + ;; renew) [ "$tools_error" ] && user_error "$tools_error_txt From 40b04db3e387d08ba431c534b496b1c99bd5f67f Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Tue, 26 Nov 2024 18:35:54 +0000 Subject: [PATCH 2/3] ChangeLog: easyrsa-tools.lib: Introduce new command 'renew-ca' Signed-off-by: Richard T Bonhomme --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index 738ddf92..e15c3535 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ Easy-RSA 3 ChangeLog 3.2.2 (TBD) + * easyrsa-tools.lib: Introduce new command 'renew-ca' (ba32b0d) (#1255) * easyrsa-tools.lib: show-expire, allow --days to be zero (a1033a5) (#1254) * Command 'help': Ignore EASYRSA_SILENT (8804d6b) (#1249) * bugfix: easyrsa-tools.lib: renew SAN, remove excess word 'Address' (af17492) (#1251) From 7e7b41b831261e4d3f2738b6027f23bc1602d3f8 Mon Sep 17 00:00:00 2001 From: Richard T Bonhomme Date: Wed, 27 Nov 2024 21:20:47 +0000 Subject: [PATCH 3/3] doc/EasyRSA-Renew-and-Revoke.md: Add details for command 'renew-ca' Signed-off-by: Richard T Bonhomme --- doc/EasyRSA-Renew-and-Revoke.md | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/doc/EasyRSA-Renew-and-Revoke.md b/doc/EasyRSA-Renew-and-Revoke.md index baee5f9c..d6b4031a 100644 --- a/doc/EasyRSA-Renew-and-Revoke.md +++ b/doc/EasyRSA-Renew-and-Revoke.md @@ -86,22 +86,26 @@ Command Details: `renew` #### `renew` has three different versions: - * `renew` **Version 1**: Easy-RSA version `3.0.6`, `3.0.7` and `3.0.8`. + * `renew` **Version 1**: Easy-RSA versions `3.0.6`, `3.0.7` and `3.0.8`. - Both certificate and private key are rebuilt. - Once a certificate has been renewed it **cannot** be revoked. - * `renew` **Version 2**: Easy-RSA version `3.0.9` and `3.1.0`. + * `renew` **Version 2**: Easy-RSA versions `3.0.9` and `3.1.0`. - Both certificate and private key are rebuilt. - Once a certificate has been renewed it **can** be revoked. - Use command: `revoke-renewed file-name-base [ reason ]` - * `renew` **Version 3**: Easy-RSA version `3.1.1+`. + * `renew` **Version 3**: Easy-RSA versions `3.1.1` through `3.1.7`. - Only certificate is renewed. - The original `renew` command has been renamed to `rebuild`, which rebuilds both certificate and private key. + * `renew` **Version 4**: Easy-RSA version `3.2.0+`. + - Only certificate is renewed. + - Supports standard Easy-RSA X509 extension duplication. + Resolving issues with `renew` version 1 --------------------------------------- @@ -149,7 +153,14 @@ Renewed certificate can be revoked: This is the preferred method to renew a certificate because the original private key is still valid. -`renew` version 3 is **only** available since Easy-RSA version `3.1.1+`. +Using `renew` version 4 +----------------------- + +#### Upgrade Easy-RSA to version `3.2.0+` is required. + +This is the most comprensive version of `renew`, which supports automatic +copying of Easy-RSA X509 extensions. + ---- @@ -186,7 +197,15 @@ an old certificate/key pair, which has been _rebuilt_ by command `rebuild`. Renew CA Certificate ==================== -Easy-RSA Version 3.2.1+ supports a simple way to effectively renew a CA Certificate. +Easy-RSA Version `3.2.2+ includes command `renew-ca`, which will create a new +CA certificate using the original CA key. This new certificate will completely +replace the previous CA certificate. This command can be safely tested without +disturbing your current PKI. The command requires user confirmation before +installing the new CA certificate. The old CA certificate is archived to the +file 'pki/expired-ca.list'. + + +Easy-RSA Version `3.2.1+` supports a simple way to effectively renew a CA Certificate. **Preamble** - Specifically for use with OpenVPN: @@ -246,5 +265,3 @@ Please consider the method outlined here, which requires very little work: `inline` files in the `pki/inline/private` directory include security keys, which MUST only be transmitted over a secure connection, such as `https`. - As of Easy-RSA Version 3.2.1, this is the only supported way to renew an - expired CA certificate.