Introduce 'revoke-renewed'

When easyrsa "renews" a certificate, the current certificate is moved
to a sub-directory for renewed certificates and renamed to the serial
number of the certificate.

This makes it difficult to subsequently revoke the old certificate.

The new behaviour is for easyrsa to move the certificate without
renaming the file.  This means the certificate can be revoked by name.

Once a renewed certificate is revoked, it is moved to the 'revoked'
sub-directory, along with all other revoked certificates.

The same mechanism also manages keys, requests, PKCS and inline files.

Behaviour summary:

* revoke moves certificates to 'revoked' - Unchanged
  Rename the certificate to its serial number - Unchanged

* renew moves certificates to 'renewed' - Unchanged
  renew does not rename the certificate to its serial number - Changed

  Important:
  Only one certificate of a specific name (eg. john) can be renewed
  at the same time.
  To renew another certificate called 'john' the first MUST be revoked.

* revoke-renewed:
  takes the certificate from 'renewed' - Changed
  moves the certifiate to 'revoked' - Changed
  renames the certificate to its serial number - Unchanged

* All revoked certificates are moved to the 'revoked' sub-directory.

Signed-off-by: Richard T Bonhomme <tincantech@protonmail.com>
This commit is contained in:
Richard T Bonhomme 2022-04-27 19:27:34 +01:00
parent 0238b5cdba
commit ef22701878
No known key found for this signature in database
GPG Key ID: 2D767DB92FB6C246

View File

@ -34,6 +34,7 @@ Here is the list of commands available with a short syntax reminder. Use the
build-client-full <filename_base> [ cmd-opts ]
build-server-full <filename_base> [ cmd-opts ]
revoke <filename_base> [cmd-opts]
revoke-renewed <certificate_serial> [cmd-opts]
renew <filename_base> [cmd-opts]
build-serverClient-full <filename_base> [ cmd-opts ]
gen-crl
@ -113,6 +114,17 @@ cmd_help() {
revoke) text="
revoke <filename_base> [reason]
Revoke a certificate specified by the filename_base, with an optional
revocation reason that is one of:
unspecified
keyCompromise
CACompromise
affiliationChanged
superseded
cessationOfOperation
certificateHold";;
revoke-renewed) text="
revoke-renewed <certificate_serial> [reason]
Revoke a renewed certificate specified by its old certificate_serial, with an optional
revocation reason that is one of:
unspecified
keyCompromise
@ -827,7 +839,7 @@ install_data_to_pki () {
# Note that A && B || C is not if-then-else. C may run when A is true
# shellcheck disable=SC2015
[ -n "$EASYRSA_EXT_DIR" ] && [ -e "$EASYRSA_EXT_DIR" ] || \
die "x509-types folder cannot be found"
die "x509-types folder cannot be found: $EASYRSA_EXT_DIR"
# Complete or error
[ -e "$EASYRSA_SAFE_CONF" ] || easyrsa_openssl makesafeconf
@ -910,8 +922,7 @@ 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 \
renewed/certs_by_serial renewed/private_by_serial renewed/reqs_by_serial;
revoked/certs_by_serial revoked/private_by_serial revoked/reqs_by_serial;
do
mkdir -p "$EASYRSA_PKI/$i" || die "$err_file"
done
@ -1392,7 +1403,12 @@ Run easyrsa without commands for usage and command help."
# Assign file_name_base and dust off!
file_name_base="$1"
shift
crt_in="$EASYRSA_PKI/issued/$file_name_base.crt"
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"
# Assign possible "crl_reason"
if [ "$1" ]; then
@ -1436,6 +1452,20 @@ $(display_dn x509 "$crt_in")
Unable to revoke as the input file is not a valid certificate. Unexpected
input in file: $crt_in"
# Verify request
if [ -e "$req_in" ]
then
verify_file req "$req_in" || die "\
Unable to move request. The file is not a valid request.
Unexpected input in file: $req_in"
fi
# 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##*=}"
duplicate_crt_by_serial="$EASYRSA_PKI/certs_by_serial/$cert_serial.pem"
# Revoke certificate
easyrsa_openssl ca -utf8 -revoke "$crt_in" \
${crl_reason+ -crl_reason "$crl_reason"} \
@ -1443,7 +1473,7 @@ input in file: $crt_in"
|| die "Failed to revoke certificate: revocation command failed."
# move revoked files so we can reissue certificates with the same name
move_revoked "$file_name_base"
move_revoked
[ "$EASYRSA_SILENT" ] || print # Separate Notice below
notice "\
@ -1460,72 +1490,57 @@ infrastructure in order to prevent the revoked cert from being accepted."
# moves revoked certificates to an alternative folder
# allows reissuing certificates with the same name
move_revoked() {
verify_ca_init
# Set out_dir
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"
[ -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"
creds_in="$EASYRSA_PKI/$1.creds"
verify_file x509 "$crt_in" || die "\
Unable to move revoked input file. The file is not a valid certificate.
Unexpected input in file: $crt_in"
if [ -e "$req_in" ]
then
verify_file req "$req_in" || die "\
Unable to move request. The file is not a valid request.
Unexpected input in file: $req_in"
fi
# 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_revoked="$EASYRSA_PKI/revoked/certs_by_serial/$cert_serial.crt"
key_by_serial_revoked="$EASYRSA_PKI/revoked/private_by_serial/$cert_serial.key"
req_by_serial_revoked="$EASYRSA_PKI/revoked/reqs_by_serial/$cert_serial.req"
# NEVER over-write a revoked cert, serial number must be unique
[ -e "$crt_out" ] && die "revoked exists: $crt_out"
[ -e "$key_out" ] && die "revoked exists: $key_out"
[ -e "$req_out" ] && die "revoked exists: $req_out"
# make sure revoked dirs exist
[ -d "$EASYRSA_PKI/revoked" ] || \
mkdir "$EASYRSA_PKI/revoked"
[ -d "$EASYRSA_PKI/revoked/certs_by_serial" ] || \
mkdir "$EASYRSA_PKI/revoked/certs_by_serial"
[ -d "$EASYRSA_PKI/revoked/private_by_serial" ] || \
mkdir "$EASYRSA_PKI/revoked/private_by_serial"
[ -d "$EASYRSA_PKI/revoked/reqs_by_serial" ] || \
mkdir "$EASYRSA_PKI/revoked/reqs_by_serial"
if [ ! -d "$out_dir" ]; then
mkdir -p "$out_dir" || die "Failed to mkdir: $out_dir"
fi
for target in certs_by_serial private_by_serial reqs_by_serial; do
[ -d "$out_dir/$target" ] && continue
mkdir -p "$out_dir/$target" \
|| die "Failed to mkdir: $out_dir/$target"
done
# move crt, key and req file to revoked folders
mv "$crt_in" "$crt_by_serial_revoked"
# only move the req if we have it
[ -e "$req_in" ] && mv "$req_in" "$req_by_serial_revoked"
# 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
[ -e "$key_in" ] && mv "$key_in" "$key_by_serial_revoked"
if [ -e "$key_in" ]; then
mv "$key_in" "$key_out" || die "Failed to move: $key_in"
fi
# move the rest of the files (p12, p7, ...)
for file in "$EASYRSA_PKI/private/$1"\.???
do
# get file extension
file_ext="${file##*.}"
# only move the req if we have it
if [ -e "$req_in" ]; then
mv "$req_in" "$req_out" || die "Failed to move: $req_in"
fi
if [ -f "$file" ]; then
mv "$file" \
"$EASYRSA_PKI/revoked/private_by_serial/$cert_serial.$file_ext" \
|| die "Failed to move file: $file"
# move any pkcs files
for pkcs in p12 p7b p8 p1; do
if [ -e "$in_dir/issued/$file_name_base.$pkcs" ]; then
mv "$in_dir/issued/$file_name_base.$pkcs" \
"$out_dir/certs_by_serial/$cert_serial.$pkcs" \
|| die "Failed to move: $file_name_base.$pkcs"
elif [ -e "$in_dir/private/$file_name_base.$pkcs" ]; then
mv "$in_dir/private/$file_name_base.$pkcs" \
"$out_dir/private_by_serial/$cert_serial.$pkcs" \
|| die "Failed to move: $file_name_base.$pkcs"
else
: # ok
fi
done
# remove the duplicate certificate in the certs_by_serial folder
rm "$crt_by_serial" || warn \
rm "$duplicate_crt_by_serial" || warn \
"Failed to remove the duplicate certificate in the certs_by_serial folder"
# remove credentials file (if exists)
@ -1542,23 +1557,39 @@ renew() {
verify_ca_init
# pull filename base:
[ -n "$1" ] || die "\
[ "$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"
# Assign file_name_base and dust off!
file_name_base="$1"
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"
# Upgrade CA index.txt.attr - unique_subject = no
up23_upgrade_ca || die "Failed to upgrade CA to support renewal."
# Append 'nopass'
# Set 'nopass'
opt_nopass=""
if [ "$2" ]; then
opt_nopass="$2"
if [ "$1" ]; then
opt_nopass="$1"
shift
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"
# Enforce syntax
if [ "$1" ]; then
die "Syntax error: $1"
fi
# referenced cert must exist:
[ -f "$crt_in" ] || die "\
Unable to renew as no certificate was found. Certificate was expected
at: $crt_in"
# confirm operation by displaying DN:
confirm "Continue with renew: " "yes" "
@ -1567,15 +1598,29 @@ 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"
# Verify certificate
verify_file x509 "$crt_in" || die "\
Unable to renew as the input file is not a valid certificate. Unexpected
input in file: $crt_in"
# Verify request
if [ -e "$req_in" ]
then
verify_file req "$req_in" || die "\
Unable to move request. The file is not a valid request.
Unexpected input in file: $req_in"
fi
# 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##*=}"
duplicate_crt_by_serial="$EASYRSA_PKI/certs_by_serial/$cert_serial.pem"
# Check if old cert is expired or expires within 30
cert_expire_date="$(
easyrsa_openssl x509 -in "$crt_in" -noout -enddate |
sed 's/^notAfter=//'
easyrsa_openssl x509 -in "$crt_in" -noout -enddate | \
sed 's/^notAfter=//'
)"
case "$easyrsa_uname" in
@ -1638,11 +1683,10 @@ subjectAltName = $san"
fi
# 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"
move_renewed
# renew certificate
build_full "$cert_type" "$1" "$opt_nopass" || die "\
build_full "$cert_type" "$file_name_base" "$opt_nopass" || die "\
Failed to renew certificate: renew command failed."
[ "$EASYRSA_SILENT" ] || print # Separate Notice below
@ -1660,72 +1704,57 @@ You may want to revoke the old certificate once the new one has been deployed."
# moves renewed certificates to an alternative folder
# allows reissuing certificates with the same name
move_renewed() {
verify_ca_init
# Set out_dir
out_dir="$EASYRSA_PKI/renewed"
crt_out="$out_dir/issued/$file_name_base.crt"
key_out="$out_dir/private/$file_name_base.key"
req_out="$out_dir/reqs/$file_name_base.req"
[ -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"
creds_in="$EASYRSA_PKI/$1.creds"
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"
if [ -e "$req_in" ]
then
verify_file req "$req_in" || die "\
Unable to move request. The file is not a valid request.
Unexpected input in file: $req_in"
fi
# 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"
# NEVER over-write a renewed cert, revoke it first
[ -e "$crt_out" ] && die "renewed exists: $crt_out"
[ -e "$key_out" ] && die "renewed exists: $key_out"
[ -e "$req_out" ] && die "renewed exists: $req_out"
# make sure renewed dirs exist
[ -d "$EASYRSA_PKI/renewed" ] || \
mkdir "$EASYRSA_PKI/renewed"
[ -d "$EASYRSA_PKI/renewed/certs_by_serial" ] || \
mkdir "$EASYRSA_PKI/renewed/certs_by_serial"
[ -d "$EASYRSA_PKI/renewed/private_by_serial" ] || \
mkdir "$EASYRSA_PKI/renewed/private_by_serial"
[ -d "$EASYRSA_PKI/renewed/reqs_by_serial" ] || \
mkdir "$EASYRSA_PKI/renewed/reqs_by_serial"
if [ ! -d "$out_dir" ]; then
mkdir -p "$out_dir" || die "Failed to mkdir: $out_dir"
fi
for target in issued private reqs; do
[ -d "$out_dir/$target" ] && continue
mkdir -p "$out_dir/$target" \
|| die "Failed to mkdir: $out_dir/$target"
done
# move crt, key and req file to renewed folders
mv "$crt_in" "$crt_by_serial_renewed"
# only move the req if we have it
[ -e "$req_in" ] && mv "$req_in" "$req_by_serial_renewed"
mv "$crt_in" "$crt_out" || die "Failed to move: $crt_in"
# only move the key if we have it
[ -e "$key_in" ] && mv "$key_in" "$key_by_serial_renewed"
if [ -e "$key_in" ]; then
mv "$key_in" "$key_out" || die "Failed to move: $key_in"
fi
# move the rest of the files (p12, p7, ...)
for file in "$EASYRSA_PKI/private/$1"\.???
do
# get file extension
file_ext="${file##*.}"
# only move the req if we have it
if [ -e "$req_in" ]; then
mv "$req_in" "$req_out" || die "Failed to move: $req_in"
fi
if [ -f "$file" ]; then
mv "$file" \
"$EASYRSA_PKI/renewed/private_by_serial/$cert_serial.$file_ext" \
|| die "Failed to move file: $file"
# move any pkcs files
for pkcs in p12 p7b p8 p1; do
if [ -e "$in_dir/issued/$file_name_base.$pkcs" ]; then
mv "$in_dir/issued/$file_name_base.$pkcs" \
"$out_dir/issued/$file_name_base.$pkcs" \
|| die "Failed to move: $file_name_base.$pkcs"
elif [ -e "$in_dir/private/$file_name_base.$pkcs" ]; then
mv "$in_dir/private/$file_name_base.$pkcs" \
"$out_dir/private/$file_name_base.$pkcs" \
|| die "Failed to move: $file_name_base.$pkcs"
else
: # ok
fi
done
# remove the duplicate certificate in the certs_by_serial folder
rm "$crt_by_serial" || warn \
rm "$duplicate_crt_by_serial" || warn \
"Failed to remove the duplicate certificate in the certs_by_serial folder"
# remove credentials file (if exists)
@ -1737,6 +1766,155 @@ Unexpected input in file: $req_in"
return 0
} #= move_renewed()
# revoke-renewed backend
revoke_renewed() {
verify_ca_init
# pull filename base:
[ "$1" ] || die "\
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 ) : # ok
;;
*) die "Illegal reason: $crl_reason"
esac
else
unset -v crl_reason
fi
# Enforce syntax
if [ "$1" ]; then
die "Syntax error: $1"
fi
# referenced cert must exist:
[ -f "$crt_in" ] || die "\
Unable to revoke as no renewed certificate was found. Certificate was expected
at: $crt_in"
# confirm operation by displaying DN:
confirm "Continue with revocation: " "yes" "
Please confirm you wish to revoke the renewed certificate with serial $1 and the following subject:
$(display_dn x509 "$crt_in")
"
# Verify certificate
verify_file x509 "$crt_in" || die "\
Unable to revoke as the certificate serial does not match an old one of a valid renewed certificate. Unexpected
certificate in file: $crt_in"
# Verify request
if [ -e "$req_in" ]
then
verify_file req "$req_in" || die "\
Unable to move renewed then revoked request. The file is not a valid request. Unexpected
input in file: $req_in"
fi
# get the serial number of the certificate -> serial=XXXX
cert_serial="$(easyrsa_openssl x509 -in "$crt_in" -noout -serial)" \
|| die "renew-revoked - Failed to retrieve certificate serial number"
# remove the serial= part -> we only need the XXXX part
cert_serial="${cert_serial##*=}"
# shellcheck disable=SC2086
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."
# move revoked files
move_renewed_revoked
notice "\
IMPORTANT!!!
Revocation was successful. You must run gen-crl and upload a CRL to your
infrastructure in order to prevent the revoked renewed cert from being accepted.
" # => notice end
return 0
} #= revoke_renewed()
# move-renewed-revoked
# moves renewed then revoked certificates to an alternative folder
move_renewed_revoked() {
# 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 number must be unique
[ -e "$crt_out" ] && die "revoked exists: $crt_out"
[ -e "$key_out" ] && die "revoked exists: $key_out"
[ -e "$req_out" ] && die "revoked exists: $req_out"
# make sure revoked dirs exist
if [ ! -d "$out_dir" ]; then
mkdir -p "$out_dir" || die "move_renewed_revoked - Failed to mkdir: $out_dir"
fi
for target in certs_by_serial private_by_serial reqs_by_serial; do
[ -d "$out_dir/$target" ] && continue
mkdir -p "$out_dir/$target" \
|| die "Failed to mkdir: $out_dir/$target"
done
# 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" || die "Failed to move: $key_in"
fi
# only move the req if we have it
if [ -e "$req_in" ]; then
mv "$req_in" "$req_out" || die "Failed to move: $req_in"
fi
# move any pkcs files
for pkcs in p12 p7b p8 p1; do
if [ -e "$in_dir/issued/$file_name_base.$pkcs" ]; then
mv "$in_dir/issued/$file_name_base.$pkcs" \
"$out_dir/certs_by_serial/$cert_serial.$pkcs" \
|| die "Failed to move: $file_name_base.$pkcs"
elif [ -e "$in_dir/private/$file_name_base.$pkcs" ]; then
mv "$in_dir/private/$file_name_base.$pkcs" \
"$out_dir/private_by_serial/$cert_serial.$pkcs" \
|| die "Failed to move: $file_name_base.$pkcs"
else
: # ok
fi
done
return 0
} # => move_renewed_revoked()
# gen-crl backend
gen_crl() {
verify_ca_init
@ -3165,6 +3343,9 @@ case "$cmd" in
revoke)
revoke "$@"
;;
revoke-renewed)
revoke_renewed "$@"
;;
renew)
renew "$@"
;;