#!/bin/sh

# Easy-RSA 3 -- A Shell-based CA Utility
#
# Copyright (C) 2023 - The Open-Source OpenVPN development community.
# A full list of contributors can be found on Github at:
#   https://github.com/OpenVPN/easy-rsa/graphs/contributors
#
# This code released under version 2 of the GNU GPL; see COPYING
# and the Licensing/ directory of this project for full licensing
# details.

# Help/usage output to stdout
usage() {
	# command help:
	print "
Easy-RSA 3 usage and overview

USAGE: easyrsa [global-options] COMMAND [command-options]

To get detailed usage and help for a command, use:
  ./easyrsa help COMMAND

For a list of global-options, use:
  ./easyrsa help options

For a list of extra test commands, use:
  ./easyrsa help more

A list of commands is shown below:
  init-pki [ cmd-opts ]
  build-ca [ cmd-opts ]
  gen-dh
  gen-req <file_name_base> [ cmd-opts ]
  sign-req <type> <file_name_base> [ cmd-opts ]
  build-client-full <file_name_base> [ cmd-opts ]
  build-server-full <file_name_base> [ cmd-opts ]
  build-serverClient-full <file_name_base> [ cmd-opts ]
  inline <file_name_base>
  revoke <file_name_base> [ cmd-opts ]
  renew <file_name_base>
  revoke-renewed <file_name_base> [ cmd-opts ]
  rewind-renew <certificate_serial_number>
  rebuild <file_name_base> [ cmd-opts ]
  gen-crl
  update-db
  show-req <file_name_base> [ cmd-opts ]
  show-cert <file_name_base> [ cmd-opts ]
  show-ca [ cmd-opts ]
  show-crl
  show-expire <file_name_base> (Optional)
  show-revoke <file_name_base> (Optional)
  show-renew <file_name_base> (Optional)
  verify-cert <file_name_base>
  import-req <request_file_path> <short_name_base>
  export-p1 <file_name_base> [ cmd-opts ]
  export-p7 <file_name_base> [ cmd-opts ]
  export-p8 <file_name_base> [ cmd-opts ]
  export-p12 <file_name_base> [ cmd-opts ]
  set-pass <file_name_base> [ cmd-opts ]
  upgrade <type>"

	# collect/show dir status:
	text_only=1
	work_dir="${EASYRSA:-undefined}"
	pki_dir="${EASYRSA_PKI:-undefined}"

	# CA Status
	if verify_ca_init test; then
		CA_cert="$EASYRSA_PKI/ca.crt"
		CA_status="   CA status: OK"
		CA_subject="$(
			OPENSSL_CONF=/dev/null \
				"$EASYRSA_OPENSSL" x509 -in "$CA_cert" \
					-noout -subject -nameopt multiline
			)"
		CA_subject="  CA subject: ${CA_subject#subject=}"
		CA_status="${CA_status}${NL}${CA_subject}"
	else
		CA_status="   CA status: CA has not been built"
	fi

	if [ "$invalid_vars" ]; then
		ivmsg="
   *WARNING*: \
Invalid vars setting for EASYRSA and/or EASYRSA_PKI${NL}"
	else
		unset -v ivmsg
	fi

	# Print details
	print "
DIRECTORY STATUS (commands would take effect on these locations)
     EASYRSA: $work_dir
         PKI: $pki_dir
   vars-file: ${EASYRSA_VARS_FILE:-Missing or undefined}${ivmsg}
  x509-types: ${EASYRSA_EXT_DIR:-Missing or undefined}
$CA_status"

	# if the vars file in use is not in the PKI
	# and not user defined then Show the messages
	prefer_vars_in_pki_msg
} # => usage()

# Detailed command help
# When called with no args, calls usage(),
# otherwise shows help for a command
# Please maintain strict indentation rules.
# Commands are TAB indented, while text is SPACE indented.
# 'case' indentation is minimalistic.
cmd_help() {
	unset -v text err_text opts text_only
	case "$1" in
	init-pki|clean-all)
		text="
* init-pki [ cmd-opts ]

      Removes & re-initializes the PKI directory for a new PKI"

		opts="
      * hard    - Recursively delete the PKI directory (default).
      * soft    - Keep the named PKI directory and PKI 'vars' file
                  intact."
	;;
	build-ca)
		text="
* build-ca [ cmd-opts ]

      Creates a new CA"

		opts="
      * raw-ca  - ONLY use SSL binary to input CA password
        raw       (Equivalent to global option '--raw-ca')

      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')

      * subca   - Create an intermediate CA keypair and request
        intca     (default is a root CA)"
	;;
	gen-dh)
		text="
* gen-dh

      Generates DH (Diffie-Hellman) parameters file"
	;;
	gen-req)
		text="
* gen-req <file_name_base> [ cmd-opts ]

      Generate a standalone-private-key and certificate-signing-request

      This request is suitable for sending to a remote CA for signing."

		opts="
      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')
      * text    - Include certificate text in request"
	;;
	sign|sign-req)
		text="
* sign-req <type> <file_name_base> [ cmd-opts ]

      Sign a certificate request of the defined type.

      <type> must be a known type.
      eg: 'client', 'server', 'serverClient', 'ca' or a user-added type.
      All supported types are listed in the x509-types directory.

      This request file must exist in the reqs/ dir and have a .req file
      extension. See 'import-req' for importing from other sources."
		opts="
      * preserve - Use the DN-field order of the CSR not the CA."
	;;
	build|build-client-full|build-server-full|build-serverClient-full)
		text="
* build-client-full <file_name_base> [ cmd-opts ]
* build-server-full <file_name_base> [ cmd-opts ]
* build-serverClient-full <file_name_base> [ cmd-opts ]

      Generate a keypair and sign locally.

      This mode uses the <file_name_base> as the X509 commonName."

		opts="
      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')"
	;;
	inline)
		text="
* inline <file_name_base>

      Print inline data for <file_name_base>, with key and CA.

  * NOTE: To create an inline-file the output must be redirected.
          If the output is incomplete then an error is retruned."
	;;
	revoke)
		text="
* revoke <file_name_base> [reason]

      Revoke a certificate specified by the <file_name_base>,
      with an optional revocation reason which can be one of:
        unspecified
        keyCompromise
        CACompromise
        affiliationChanged
        superseded
        cessationOfOperation
        certificateHold"
	;;
	revoke-renewed)
		text="
* revoke-renewed <file_name_base> [reason]

      Revoke a *renewed* certificate specified by the <file_name_base>,
      with an optional revocation reason which can be one of:
        unspecified
        keyCompromise
        CACompromise
        affiliationChanged
        superseded
        cessationOfOperation
        certificateHold"
	;;
	rebuild)
		text="
* rebuild <file_name_base> [ cmd-opts ]

      Rebuild a certificate and key specified by <file_name_base>"

		opts="
      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')"
	;;
	renew)
		text="
* renew <file_name_base>

      Renew a certificate specified by <file_name_base>"
	;;
	rewind|rewind-renew)
		text="
* rewind-renew <certificate_serial_number>

      Rewind an EasyRSA version 3.0 'style' renewed certificate.
      Once 'rewind' has completed the certificate can be revoked
      by using: 'revoke-renewed <file_name_base> [reason]'

  * NOTE: This does NOT 'unrenew' or 'unrevoke' a certificate.
    Ref : https://github.com/OpenVPN/easy-rsa/issues/578"
	;;
	gen-crl)
		text="
* gen-crl

      Generate a certificate revocation list [CRL]"
	;;
	update-db)
		text="
* update-db

      Update the index.txt database

      This command will use the system time to update the status of
      issued certificates."
	;;
	make-safe-ssl)
		text="
* make-safe-ssl

      Generate a safe SSL config file"
	;;
	show-req|show-cert)
		text="
* show-req  <file_name_base> [ cmd-opts ]
* show-cert <file_name_base> [ cmd-opts ]

      Shows details of the req or cert referenced by <file_name_base>

      Human-readable output is shown, including any requested cert
      options when showing a request."

		opts="
      * full    - show full req/cert info, including pubkey/sig data"
	;;
	show-ca)
		text="
* show-ca [ cmd-opts ]

      Shows details of the Certificate Authority [CA] certificate

      Human-readable output is shown."

		opts="
      * full    - show full CA info, including pubkey/sig data"
	;;
	show-crl)
		text="
* show-crl

      Shows details of the current certificate revocation list (CRL)

      Human-readable output is shown."
	;;
	show-expire)
		text="
* show-expire [ <file_name_base> ]

      Shows details of *all* expiring certificates
      Use --renew-days=NN to extend the grace period (Default 90 days)
      Optionally, check *only* <file_name_base> certificate"
	;;
	show-revoke)
		text="
* show-revoke [ <file_name_base> ]

      Shows details of *all* revoked certificates.
      Optionally, check *only* <file_name_base> certificate"
	;;
	show-renew)
		text="
* show-renew [ <file_name_base> ]

      Shows details of renewed certificates, which have not been revoked
      Optionally, check *only* <file_name_base> certificate"
	;;
	verify|verify-cert)
		text="
* verify-cert <file_name_base> [ cmd-opts ]

      Verify certificate against CA

      Returns the current validity of the certificate."

		opts="
      * batch   - On failure to verify, return error (1) to caller"
	;;
	import-req)
		text="
* import-req <request_file_path> <short_name_base>

      Import a certificate request from a file

      This will copy the specified file into the reqs/ dir in
      preparation for signing.

      The <short_name_base> is the <file_name_base> to create.

      Example usage:
        import-req /some/where/bob_request.req bob"
	;;
	export-p12)
		text="
* export-p12 <file_name_base> [ cmd-opts ]

      Export a PKCS#12 file with the keypair,
      specified by <file_name_base>"

		opts="
      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')
      * noca    - Do not include the ca.crt file in the PKCS12 output
      * nokey   - Do not include the private key in the PKCS12 output
      * usefn   - Use <file_name_base> as friendly name"
	;;
	export-p7)
		text="
* export-p7 <file_name_base> [ cmd-opts ]

      Export a PKCS#7 file with the pubkey,
      specified by <file_name_base>"

		opts="
      * noca    - Do not include the ca.crt file in the PKCS7 output"
	;;
	export-p8)
		text="
* export-p8 <file_name_base> [ cmd-opts ]

      Export a PKCS#8 file with the private key,
      specified by <file_name_base>"

		opts="
      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')"
	;;
	export-p1)
		text="
* export-p1 <file_name_base> [ cmd-opts ]

      Export a PKCS#1 (RSA format) file with the pubkey,
      specified by <file_name_base>"

		opts="
      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')"
	;;
	set-pass|set-ed-pass|set-rsa-pass|set-ec-pass)
		text="
* set-pass <file_name_base> [ cmd-opts ]

      Set a new passphrase for the private key specified by <file_name_base>

  DEPRECATED: 'set-rsa-pass' and 'set-ec-pass'"

		opts="
      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')
      * file    - (Advanced) Treat the file as a raw path, not a short-name"
	;;
	upgrade)
		text="
* upgrade <type>

      Upgrade EasyRSA PKI and/or CA.

      Upgrade <type> must be one of:

      * pki - Upgrade EasyRSA v2.x PKI to v3.x PKI (includes CA below)
      * ca  - Upgrade EasyRSA v3.0.5 CA or older to v3.0.6 CA or later."
	;;
	altname|subjectaltname|san)
		text_only=1
		text="
* Option: --subject-alt-name=SAN_FORMAT_STRING

      This global option adds a subjectAltName to the request or issued
      certificate. It MUST be in a valid format accepted by openssl or
      req/cert generation will fail. Note that including multiple such
      names requires them to be comma-separated; further invocations of
      this option will REPLACE the value.

      Examples of the SAN_FORMAT_STRING shown below:

      * DNS:alternate.example.net
      * DNS:primary.example.net,DNS:alternate.example.net
      * IP:203.0.113.29
      * email:alternate@example.net"
	;;
	--days|days)
		text_only=1
		text="
* Option: --days=DAYS

      This global option is an alias for one of the following:
      * Expiry days for a new CA.
        eg: '--days=3650 build-ca'
      * Expiry days for new/renewed certificate.
        eg: '--days=1095 renew server'
      * Expiry days for certificate revokation list.
        eg: '--days=180 gen-crl'
      * Cutoff days for command: show-expire.
        eg: '--days=90 show-expire'"
	;;
	--req-cn|req-cn)
		text_only=1
		text="
* Option: --req-cn=NAME

      This specific option can set the CSR commonName.

      Can only be used in BATCH mode for the following commands:
      * To build a new CA [or Sub-CA]:
        eg: '--batch --req-cn=NAME build-ca [subca]'
      * To generate a certificate signing request:
        eg: '--batch --req-cn=NAME gen-req <file_name_base>'"
	;;
	more|test|xtra|extra|ext)
		# Test features
		text_only=1
		text="
  Print vars.example here-doc to stdout:
    make-vars

  Make safessl-easyrsa.cnf file:
    mss|make-safe-ssl

  Check <SERIAL> number is unique:
    serial|check-serial <SERIAL>

  Display DN of certificate:
    display-dn <file_name_base>

  Display SAN of certificate:
    display-san <file_name_base>

  Generate default SAN of request:
    default-san <file_name_base>

  Display EKU of certificate:
    x509-eku <file_name_base>"
	;;
	opts|options)
		opt_usage
	;;
	"")
		usage ;;
	*)
		err_text="
  Unknown command: '$1' \
(try without commands for a list of commands)"
		easyrsa_exit_with_error=1
	esac

	if [ "$err_text" ]; then
		print "${err_text}"
	else
		# display the help text
		[ "$text" ] && print "$text"

		if [ "$text_only" ]; then
			: # ok - No opts message required
		else
			print "
Available command options [ cmd-opts ]:
${opts:-
      * No supported command options}"
		fi
	fi
} # => cmd_help()

# Options usage
opt_usage() {
	text_only=1
	print "
Easy-RSA Global Option Flags

The following global-options may be provided before the command.
Options specified at runtime override env-vars and any 'vars'
file in use.

Unless noted, non-empty values to options are mandatory.

General options:

--version       : Prints EasyRSA version and build information
--batch         : Set automatic (no-prompts when possible) mode
--silent|-s     : Disable all warnings, notices and information
--sbatch        : Combined --silent and --batch operating mode
--silent-ssl|-S : Silence SSL output (Requires batch mode)

--nopass|no-pass: Do not use passwords
                  Can NOT be used with --passin or --passout
--passin=ARG    : Set -passin ARG for openssl (eg: pass:xEasyRSAy)
--passout=ARG   : Set -passout ARG for openssl (eg: pass:xEasyRSAy)
--raw|raw-ca    : Build CA with password via RAW SSL input

--vars=FILE     : Define a specific 'vars' file to use for Easy-RSA config
                  (Default vars file is in the current working directory)
--pki=DIR       : Declare the PKI directory
                  (Default PKI directory is sub-directory 'pki')
                  See Advanced.md for in depth usage.

--ssl-conf=FILE : Define a specific OpenSSL config file for Easy-RSA to use
                  (Default config file is in the EasyRSA PKI directory)
--force-safe-ssl: Always generate a safe SSL config file
                  (Default: Generate Safe SSL config once per instance)

--tmp-dir=DIR   : Declare the temporary directory
                  (Default temporary directory is the EasyRSA PKI directory)
--keep-tmp=NAME : Keep the original temporary session by name: NAME
                  NAME is a sub-directory of the dir declared by --tmp-dir
                  This option ALWAYS over-writes a sub-dir of the same name.

Certificate & Request options: (these impact cert/req field values)

--notext|no-text: Create certificates without human readable text
--days=#        : Sets the signing validity to the specified number of days
                  Applies to other commands. For details, see: 'help days'
--startdate=DATE: Sets the SSL option '-startdate' (Format 'YYYYMMDDhhmmssZ')
--enddate=DATE  : Sets the SSL option '-enddate' (Format 'YYYYMMDDhhmmssZ')

--digest=ALG    : Digest to use in the requests & certificates
--keysize=#     : Size in bits of keypair to generate (RSA Only)
--use-algo=ALG  : Crypto alg to use: choose rsa (default), ec or ed
--curve=NAME    : For elliptic curve, sets the named curve
                  (Default: algo ec: secp384r1, algo ed: ed25519)

--subca-len=#   : Path length of signed intermediate CA certificates
--copy-ext      : Copy included request X509 extensions (namely subjAltName)
--san|--subject-alt-name=<subjectAltName>
                : Add a subjectAltName.
                  For more info and syntax, see: 'easyrsa help altname'

Distinguished Name mode:

--dn-mode=MODE  : Distinguished Name mode to use 'cn_only' (Default) or 'org'

--req-cn=NAME   : Set CSR commonName to NAME. For details, see: 'help req-cn'

  Distinguished Name Organizational options: (only used with '--dn-mode=org')
  --req-c=CC           : Country code (2-letters)
  --req-st=NAME        : State/Province
  --req-city=NAME      : City/Locality
  --req-org=NAME       : Organization
  --req-email=NAME     : Email addresses
  --req-ou=NAME        : Organizational Unit
  --req-serial=VALUE   : Entity serial number (Only used when declared)

Deprecated features:

--ns-cert             : Include deprecated Netscape extensions
--ns-comment=COMMENT  : Include deprecated Netscape comment (may be blank)"
} # => opt_usage()

# Wrapper around printf - clobber print since it's not POSIX anyway
# print() is used internally, so MUST NOT be silenced.
# shellcheck disable=SC1117
print() {
	printf '%s\n' "$*"
} # => print()

# Exit fatally with a message to stderr
# present even with EASYRSA_BATCH as these are fatal problems
die() {
	print "
Easy-RSA error:

$1
"
	# error_info is currently unused
	if [ "$error_info" ]; then
		print "${error_info}${NL}"
	fi

	# show host info
	show_host

	# exit to cleanup()
	exit "${2:-1}"
} # => die()

# User errors, less noise than die()
user_error() {
	print "
EasyRSA version $EASYRSA_version

Error
-----
$1"
	easyrsa_exit_with_error=1
	cleanup
} # => user_error()

# verbose information
verbose() {
	[ "$EASYRSA_VERBOSE" ] || return 0
	printf '%s\n' "  > $*"
} # => verbose()

# non-fatal warning output
warn() {
	[ "$EASYRSA_SILENT" ] && return
	print "
WARNING
=======
$1"
} # => warn()

# informational notices to stdout
notice() {
	[ "$EASYRSA_SILENT" ] && return
	print "
Notice
------
$1"
} # => notice()

# Helpful information
information() {
	[ "$EASYRSA_SILENT" ] && return
	print "$1"
} # => information()

# intent confirmation helper func
# returns without prompting in EASYRSA_BATCH
confirm() {
	[ "$EASYRSA_BATCH" ] && return
	prompt="$1"
	value="$2"
	msg="$3"
	input=""
	print "\
$msg

Type the word '$value' to continue, or any other input to abort."
	printf %s "  $prompt"
	# shellcheck disable=SC2162 # read without -r will mangle ..
	read input
	printf '\n'
	[ "$input" = "$value" ] && return
	easyrsa_exit_with_error=1
	notice "Aborting without confirmation."
	cleanup
} # => confirm()

# Generate random hex
# Cannot use easyrsa-openssl() due to chicken vs egg,
# easyrsa_openssl() creates temp-files,
# which needs `openssl rand`.
# Redirect error-out, ignore complaints of missing config
easyrsa_random() {
	case "$1" in
	(*[!1234567890]*|0*|"") : ;; # invalid input
	(*)
		# Only return on success
		if OPENSSL_CONF=/dev/null \
			"$EASYRSA_OPENSSL" rand -hex "$1"
		then
			return
		fi
	esac
	die "easyrsa_random failed"
} # => easyrsa_random()

# Create session directory atomically or fail
secure_session() {
	# Session is already defined
	[ "$secured_session" ] && \
		die "session overload"

	# temporary directory must exist
	if [ "$EASYRSA_TEMP_DIR" ] && \
		[ -d "$EASYRSA_TEMP_DIR" ]
	then
		: # ok
	else
		die "secure_session - Missing temporary directory:
* $EASYRSA_TEMP_DIR"
	fi

	for i in 1 2 3; do
		session="$(
			easyrsa_random 4
			)" || die "secure_session - session"
		secured_session="${EASYRSA_TEMP_DIR}/${session}"

		# atomic:
		if mkdir "$secured_session"; then
			# New session requires safe-ssl conf
			unset -v mktemp_counter \
				OPENSSL_CONF safe_ssl_cnf_tmp \
				working_safe_ssl_conf
			easyrsa_err_log="$secured_session/error.log"
			verbose "\
secure_session: CREATED: $secured_session"
			return
		fi
	done
	die "secure_session failed"
} # => secure_session()

# Remove secure session
remove_secure_session() {
	if [ "${secured_session%/*}" ] && \
		[ -d "$secured_session" ]
	then
		# Always remove temp-session
		if rm -rf "$secured_session"; then
			verbose "\
remove_secure_session: DELETED: $secured_session"
			unset -v secured_session mktemp_counter \
				OPENSSL_CONF safe_ssl_cnf_tmp \
				working_safe_ssl_conf
			return
		fi
	fi
	die "remove_secure_session: $secured_session"
} # => remove_secure_session()

# Create temp-file atomically or fail
# WARNING: Running easyrsa_openssl in a subshell
# will hide error message and verbose messages
# from easyrsa_mktemp()
easyrsa_mktemp() {
	[ "$#" = 1 ] || die "\
easyrsa_mktemp - input error"

	# session directory must exist
	[ "$secured_session" ] || die "\
easyrsa_mktemp - Temporary session undefined"

	# Update counter
	mktemp_counter="$(( mktemp_counter + 1 ))"

	# Assign internal temp-file name
	t="${secured_session}/temp.${mktemp_counter}"

	# Create shotfile
	for h in x y z; do
		shotfile="${t}.${h}"
		if [ -e "$shotfile" ]; then
			verbose "\
easyrsa_mktemp: shot-file EXISTS: $shotfile"
			continue
		else
			printf "" > "$shotfile" || die "\
easyrsa_mktemp: create shotfile failed (1) $1"

			# Create temp-file or die
			# subshells do not update mktemp_counter,
			# which is why this extension is required.
			# Current max required is 3 attempts
			for i in 1 2 3 4 5 6 7 8 9; do
				want_tmp_file="${t}.${i}"

				# Warn to error log file for max reached
				[ "$EASYRSA_MAX_TEMP" -gt "$i" ] || print "\
Max temp-file limit $i, hit for: $1" >> "$easyrsa_err_log"

				if [ -e "$want_tmp_file" ]; then
					verbose "\
easyrsa_mktemp: temp-file EXISTS: $want_tmp_file"
					continue
				else
					# atomic:
					if [ "$easyrsa_host_os" = win ]; then
						set -o noclobber
					fi

					if mv "$shotfile" "$want_tmp_file"; then
						# Assign external temp-file name
						if force_set_var "$1" "$want_tmp_file"
						then
							verbose "\
easyrsa_mktemp: $1 OK: $want_tmp_file"

							if [ "$easyrsa_host_os" = win ]; then
								set +o noclobber
							fi
							unset -v want_tmp_file shotfile
							return
						else
							die "\
easyrsa_mktemp - force_set_var $1 failed"
						fi
					fi
				fi
			done
		fi
	done

	# In case of subshell abuse, report to error log
	err_msg="\
easyrsa_mktemp - failed for: $1 @ attempt=$i
want_tmp_file: $want_tmp_file"
	print "$err_msg" >> "$easyrsa_err_log"
	die "$err_msg"
} # => easyrsa_mktemp()

# remove temp files and do terminal cleanups
cleanup() {
	# In case of subshell abuse, display error log file
	if [ -f "$easyrsa_err_log" ]; then
		print
		cat "$easyrsa_err_log"
		print
	fi

	if [ "${secured_session%/*}" ] && \
		[ -d "$secured_session" ]
	then
		# Remove temp-session or create temp-snapshot
		if [ "$EASYRSA_KEEP_TEMP" ]
		then
			# skip on black-listed directory names, with a warning
			if [ -e "$EASYRSA_TEMP_DIR/$EASYRSA_KEEP_TEMP" ]
			then
				warn "\
Prohibited value for --keep-tmp: '$EASYRSA_KEEP_TEMP'
Temporary session not preserved."
			else
				# create temp-snapshot
				keep_tmp="$EASYRSA_TEMP_DIR/tmp/$EASYRSA_KEEP_TEMP"
				mkdir -p "$keep_tmp"
				rm -rf "$keep_tmp"
				mv -f "$secured_session" "$keep_tmp"
				print "Temp session preserved: $keep_tmp"
			fi
		else
			# remove temp-session
			remove_secure_session || \
				warn "cleanup - remove_secure_session failed"
		fi
	fi

	# These cleanup routines must be called after die()
	# because the relate commands can die in subshells.
	# Remove files when build_full()->sign_req() is interrupted
	[ "$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
	# Restore files when rebuild is interrupted
	[ "$error_undo_rebuild_move" ] && rebuild_restore_move

	# shellcheck disable=SC3040
	# In POSIX sh, set option [name] is undefined
	case "$prompt_restore" in
		0) : ;; # Not required
		1) [ -t 1 ] && stty echo ;;
		2) set -o echo ;;
		*) warn "prompt_restore: '$prompt_restore'"
	esac

	# Get a clean line
	[ "$EASYRSA_SILENT" ] || print

	# Clear traps
	trap - 0 1 2 3 6 15

	# Exit: Known errors
	# -> confirm(): aborted
	# -> verify_cert(): verify failed --batch mode
	# -> check_serial_unique(): not unique --batch mode
	# -> user_error(): User errors but not die()
	if [ "$easyrsa_exit_with_error" ]; then
		verbose "Exit: Known errors = true"
		exit 1
	fi

	# Exit: SIGINT
	if [ "$1" = 2 ]; then
		verbose "exit SIGINT = true"
		kill -2 "$$"
	fi

	# Exit: Final Success
	if [ "$1" = ok ]; then
		# if there is no error
		# then 'cleanup ok' is called
		verbose "Exit: Final Success = true"
		exit 0
	fi

	# Exit: Final Fail
	# if 'cleanup' is called without 'ok'
	# then an error occurred
	verbose "Exit: Final Fail = true"
	exit 1
} # => cleanup()

# Make a copy safe SSL config file
make_safe_ssl() {
	easyrsa_openssl makesafeconf

	notice "\
Safe SSL config file created at:
* $EASYRSA_SAFE_CONF"
	verbose "\
make_safe_ssl: NEW SSL cnf file: $safe_ssl_cnf_tmp"
} # => make_safe_ssl_copy()

# Escape hazardous characters
# Auto-escape hazardous characters:
# '&' - Workaround 'sed' behavior
# '$' - Workaround 'easyrsa' based limitation
# This is required for all SSL libs, otherwise,
# there are unacceptable differences in behavior
escape_hazard() {
	if [ "$EASYRSA_FORCE_SAFE_SSL" ] || \
		[ "$makesafeconf" ]
	then
		# Always run
		verbose "escape_hazard: FORCED"
	elif [ "$working_safe_org_conf" ]; then
		# Has run once
		verbose "escape_hazard: BYPASSED"
		return
	else
		# Run once
		verbose "escape_hazard: RUN-ONCE"
	fi

	# Set run once
	working_safe_org_conf=1

	# Assign temp-file
	escape_hazard_tmp=""
	easyrsa_mktemp escape_hazard_tmp || die \
		"escape_hazard - easyrsa_mktemp escape_hazard_tmp"

	# write org fields to org temp-file and escape '&' and '$'
	print "\
export EASYRSA_REQ_COUNTRY=\"$EASYRSA_REQ_COUNTRY\"
export EASYRSA_REQ_PROVINCE=\"$EASYRSA_REQ_PROVINCE\"
export EASYRSA_REQ_CITY=\"$EASYRSA_REQ_CITY\"
export EASYRSA_REQ_ORG=\"$EASYRSA_REQ_ORG\"
export EASYRSA_REQ_OU=\"$EASYRSA_REQ_OU\"
export EASYRSA_REQ_EMAIL=\"$EASYRSA_REQ_EMAIL\"
export EASYRSA_REQ_SERIAL=\"$EASYRSA_REQ_SERIAL\"\
" | sed -e s\`'\&'\`'\\\&'\`g \
		-e s\`'\$'\`'\\\$'\`g \
			> "$escape_hazard_tmp" || die "\
escape_hazard - Failed to write temp-file"

	# Reload fields from fully escaped temp-file
	# shellcheck disable=SC1090 # can't follow ...
	(. "$escape_hazard_tmp") || die "\
escape_hazard - Failed to source temp-file"

	verbose "escape_hazard: COMPLETED"
	# shellcheck disable=SC1090 # can't follow ...
	. "$escape_hazard_tmp"
} # => escape_hazard()

# Replace environment variable names with current value
# and write to temp-file or return error from sed
expand_ssl_config() {
	if [ "$EASYRSA_FORCE_SAFE_SSL" ] || \
		[ "$makesafeconf" ]
	then
		# Always run
		verbose "expand_ssl_config: FORCED"
	elif [ "$working_safe_ssl_conf" ]; then
		# Has run once
		verbose "expand_ssl_config: BYPASSED"
		return
	elif [ "$ssl_lib" = libressl ]; then
		# Always run
		verbose "expand_ssl_config: REQUIRED"
	elif [ "$ssl_lib" = openssl ]; then
		# OpenSSl does not require a safe config
		verbose "expand_ssl_config: IGNORED"
		return
	else
		# do NOT Run
		die "expand_ssl_config: EXCEPTION"
	fi

	# Set run once
	working_safe_ssl_conf=1
	verbose "expand_ssl_config: RUN-ONCE"

	# Assign temp-file
	safe_ssl_cnf_tmp=""
	easyrsa_mktemp safe_ssl_cnf_tmp || die "\
expand_ssl_config - \
easyrsa_mktemp safe_ssl_cnf_tmp"

	# Rewrite
	# shellcheck disable=SC2016 # No expansion inside ''
	if sed \
\
-e s\`'$dir'\`\
\""$EASYRSA_PKI"\"\`g \
\
-e s\`'$ENV::EASYRSA_PKI'\`\
\""$EASYRSA_PKI"\"\`g \
\
-e s\`'$ENV::EASYRSA_CERT_EXPIRE'\`\
\""$EASYRSA_CERT_EXPIRE"\"\`g \
\
-e s\`'$ENV::EASYRSA_CRL_DAYS'\`\
\""$EASYRSA_CRL_DAYS"\"\`g \
\
-e s\`'$ENV::EASYRSA_DIGEST'\`\
\""$EASYRSA_DIGEST"\"\`g \
\
-e s\`'$ENV::EASYRSA_KEY_SIZE'\`\
\""$EASYRSA_KEY_SIZE"\"\`g \
\
-e s\`'$ENV::EASYRSA_DN'\`\
\""$EASYRSA_DN"\"\`g \
\
-e s\`'$ENV::EASYRSA_REQ_CN'\`\
\""$EASYRSA_REQ_CN"\"\`g \
\
-e s\`'$ENV::EASYRSA_REQ_COUNTRY'\`\
\""$EASYRSA_REQ_COUNTRY"\"\`g \
\
-e s\`'$ENV::EASYRSA_REQ_PROVINCE'\`\
\""$EASYRSA_REQ_PROVINCE"\"\`g \
\
-e s\`'$ENV::EASYRSA_REQ_CITY'\`\
\""$EASYRSA_REQ_CITY"\"\`g \
\
-e s\`'$ENV::EASYRSA_REQ_ORG'\`\
\""$EASYRSA_REQ_ORG"\"\`g \
\
-e s\`'$ENV::EASYRSA_REQ_OU'\`\
\""$EASYRSA_REQ_OU"\"\`g \
\
-e s\`'$ENV::EASYRSA_REQ_EMAIL'\`\
\""$EASYRSA_REQ_EMAIL"\"\`g \
\
-e s\`'$ENV::EASYRSA_REQ_SERIAL'\`\
\""$EASYRSA_REQ_SERIAL"\"\`g \
\
	"$EASYRSA_SSL_CONF" > "$safe_ssl_cnf_tmp"
	then
		verbose "expand_ssl_config: COMPLETED"
	else
		return 1
	fi
} # => expand_ssl_config()

# Easy-RSA meta-wrapper for SSL
# WARNING: Running easyrsa_openssl in a subshell
# will hide error message and verbose messages
#
# The expansion here takes place on EASYRSA_SSL_CONF,
# which may have already been replaced by a temp-file
# with the extensions having been inserted by build-ca,
# sign-req or gen-req.
easyrsa_openssl() {
	openssl_command="$1"; shift

	# Do not allow 'rand' here, see easyrsa_random()
	case "$openssl_command" in
		rand)
			die "easyrsa_openssl: Illegal SSL command: rand"
		;;
		makesafeconf) makesafeconf=1 ;;
		*) :
	esac

	# Auto-escape hazardous characters
	escape_hazard || \
		die "easyrsa_openssl - escape_hazard failed"

	# Rewrite SSL config
	expand_ssl_config || \
		die "easyrsa_openssl - expand_ssl_config failed"

	# VERIFY safe temp-file exists
	if [ -e "$safe_ssl_cnf_tmp" ]; then
		verbose "\
easyrsa_openssl: Safe SSL conf OK: $safe_ssl_cnf_tmp"
		export OPENSSL_CONF="$safe_ssl_cnf_tmp"
	else
		verbose "\
easyrsa_openssl: No Safe SSL conf, FALLBACK to default"
		export OPENSSL_CONF="$EASYRSA_SSL_CONF"
	fi

	# Execute command - Return on success
	if [ "$openssl_command" = "makesafeconf" ]; then
		# COPY temp-file to safessl-easyrsa.cnf
		unset -v makesafeconf
		cp -f "$safe_ssl_cnf_tmp" "$EASYRSA_SAFE_CONF" && \
				return
		die "easyrsa_openssl: makesafeconf FAILED"
	fi

	# Exec SSL
	if [ "$EASYRSA_SILENT_SSL" ] && [ "$EASYRSA_BATCH" ]
	then
		"$EASYRSA_OPENSSL" "$openssl_command" "$@" \
			2>/dev/null && \
				return
	else
		"$EASYRSA_OPENSSL" "$openssl_command" "$@" && \
				return
	fi

	# Always fail here
	die "\
easyrsa_openssl - Command has failed:
* $EASYRSA_OPENSSL $openssl_command $*"
} # => easyrsa_openssl()

# Verify the SSL library is functional
# and establish version dependencies
verify_ssl_lib() {
	# Run once only
	[ "$verify_ssl_lib_ok" ] && return
	verify_ssl_lib_ok=1
	unset -v openssl_v3

	# redirect std-err, ignore missing ssl/openssl.cnf
	val="$(
		OPENSSL_CONF=/dev/null "$EASYRSA_OPENSSL" version
		)"
	ssl_version="$val"

	# SSL lib name
	case "${val%% *}" in
	OpenSSL)
		ssl_lib=openssl
	;;
	LibreSSL)
		ssl_lib=libressl
	;;
	*)
		error_msg="$("$EASYRSA_OPENSSL" version 2>&1)"
		user_error "\
* OpenSSL must either exist in your PATH
  or be defined in your vars file.

Invalid SSL output for 'version':

$error_msg"
	esac

	# Set SSL version dependent $no_password option
	osslv_major="${val#* }"
	osslv_major="${osslv_major%%.*}"
	case "$osslv_major" in
	1) no_password='-nodes' ;;
	2) no_password='-nodes' ;;
	3)
		case "$ssl_lib" in
			openssl)
				openssl_v3=1
				no_password='-noenc'
			;;
			libressl)
				no_password='-nodes'
			;;
			*) die "Unexpected SSL library: $ssl_lib"
		esac
	;;
	*) die "Unexpected SSL version: $osslv_major"
	esac
} # => verify_ssl_lib()

# Basic sanity-check of PKI init and complain if missing
verify_pki_init() {
	help_note="\
Run easyrsa without commands for usage and command help."

	# Check for defined EASYRSA_PKI
	[ "$EASYRSA_PKI" ] || die "\
EASYRSA_PKI env-var undefined"

	# check that the pki dir exists
	[ -d "$EASYRSA_PKI" ] || user_error "\
EASYRSA_PKI does not exist (perhaps you need to run init-pki)?
Expected to find the EASYRSA_PKI at:
* $EASYRSA_PKI

$help_note"

	# verify expected dirs present:
	for i in private reqs; do
		[ -d "$EASYRSA_PKI/$i" ] || user_error "\
Missing expected directory: $i

(perhaps you need to run init-pki?)

$help_note"
	done
	unset -v help_note
} # => verify_pki_init()

# Verify core CA files present
verify_ca_init() {
	help_note="\
Run easyrsa without commands for usage and command help."

	# Verify expected files are present.
	# Allow files to be regular files (or symlinks),
	# but also pipes, for flexibility with ca.key
	for i in ca.crt private/ca.key \
			index.txt index.txt.attr serial
	do
		if [ ! -f "$EASYRSA_PKI/$i" ] && \
			[ ! -p "$EASYRSA_PKI/$i" ]
		then
			[ "$1" = "test" ] && return 1
			user_error "\
Missing expected CA file: $i

(perhaps you need to run build-ca?)

$help_note"
		fi
	done

	# When operating in 'test' mode, return success.
	# test callers don't care about CA-specific dir structure
	[ "$1" = "test" ] && return 0

	# verify expected CA-specific dirs:
	for i in issued certs_by_serial
	do
		[ -d "$EASYRSA_PKI/$i" ] || user_error "\
Missing expected CA dir: $i

(perhaps you need to run build-ca?)

$help_note"
	done

	# explicitly return success for callers
	unset -v help_note
	return 0
} # => verify_ca_init()

# init-pki backend:
init_pki() {
	# Process command options
	reset="hard"
	while [ "$1" ]; do
		case "$1" in
			hard-reset|hard) reset="hard" ;;
			soft-reset|soft) reset="soft" ;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	# EasyRSA will NOT do 'rm -rf /'
	case "$EASYRSA_PKI" in
		.|..|./|../|.//*|..//*|/|//*|\\|?:|'')
			user_error "Invalid PKI: $EASYRSA_PKI"
	esac

	# If EASYRSA_PKI exists, confirm before deletion
	if [ -e "$EASYRSA_PKI" ]; then
		confirm "Confirm removal: " "yes" "
WARNING!!!

You are about to remove the EASYRSA_PKI at:
* $EASYRSA_PKI

and initialize a fresh PKI here."

		# now remove it:
		case "$reset" in
		hard)
			# # # shellcheck disable=SC2115 # Use "${var:?}"
			rm -rf "$EASYRSA_PKI" || \
				die "init-pki hard reset failed."
		;;
		soft)
		# There is no unit test for a soft reset
			for i in ca.crt crl.pem \
				issued private reqs inline revoked renewed \
				serial serial.old index.txt index.txt.old \
				index.txt.attr index.txt.attr.old \
				ecparams certs_by_serial
			do
				# # # shellcheck disable=SC2115 # Use "${var:?}"
				target="$EASYRSA_PKI/$i"
				if [ "${target%/*}" ]; then
					rm -rf "$target" || \
						die "init-pki soft reset(1) failed!"
				else
					die "init-pki soft reset(2) failed!"
				fi
			done
		;;
		*)
			user_error "Unknown reset type: $reset"
		esac
	fi

	# new dirs:
	for i in private reqs inline; do
		mkdir -p "$EASYRSA_PKI/$i" || \
			die "\
Failed to create PKI file structure (permissions?)"
	done

	# Install data-files into ALL new PKIs
	install_data_to_pki init-pki || \
		warn "\
Failed to install required data-files to PKI. (init)"

	notice "\
'init-pki' complete; you may now create a CA or requests.

Your newly created PKI dir is:
* $EASYRSA_PKI"

	# Installation information
	# if $no_new_vars then there are one or more known vars
	# which are not in the PKI. All further commands will fail
	# until vars is manually corrected
	if [ "$no_new_vars" ]; then
		warn "\
A vars file has not been created in your new PKI because
conflicting vars files have been found elsewhere."
		prefer_vars_in_pki_msg
	else
		unset -v EASYRSA_VARS_FILE
		select_vars
		information "
Using Easy-RSA configuration:
* ${EASYRSA_VARS_FILE:-undefined}"
	fi

	# For new PKIs , pki/vars was auto-created, show message
	if [ "$new_vars_true" ]; then
		information "
IMPORTANT:
  Easy-RSA 'vars' template file has been created in your new PKI.
  Edit this 'vars' file to customise the settings for your PKI.
  To use a global vars file, use global option --vars=<FILE>"

	else
		prefer_vars_in_pki_msg
	fi
	verbose "\
init_pki: x509-types dir ${EASYRSA_EXT_DIR:-Not found}"
} # => init_pki()

# Must be used in two places, so made it a function
prefer_vars_in_pki_msg() {
	if [ "$vars_in_pki" ] || [ "$user_vars_true" ] ||
		[ "$EASYRSA_NO_VARS" ]
	then
		return
	fi

	# Never show this message
	return

	information "
IMPORTANT:
  The preferred location for 'vars' is within the PKI folder.
  To silence this message move your 'vars' file to your PKI
  or declare your 'vars' file with option: --vars=<FILE>"
} # => prefer_vars_in_pki_msg()

# Copy data-files from various sources
install_data_to_pki() {
#
# Explicitly find and optionally copy data-files to the PKI.
# During 'init-pki' this is the new default.
# During all other functions these requirements are tested for
# and files will be copied to the PKI, if they do not already
# exist there.
#
# One reason for this is to make packaging work.

	context="$1"
	shift

	# Set required sources
	vars_file='vars'
	vars_file_example='vars.example'
	ssl_cnf_file='openssl-easyrsa.cnf'
	x509_types_dir='x509-types'

	# "$EASYRSA_PKI" - Preferred
	# "$EASYRSA" - Old default and Windows
	# "$PWD" - Usually the same as above, avoid
	# "${0%/*}" - Usually the same as above, avoid
	# '/usr/local/share/easy-rsa' - Default user installed
	# '/usr/share/easy-rsa' - Default system installed
	# Room for more..
	# '/etc/easy-rsa' - Last resort

	# Find and optionally copy data-files, in specific order
	for area in \
		"$EASYRSA_PKI" \
		"$EASYRSA" \
		"$PWD" \
		"${0%/*}" \
		'/usr/local/share/easy-rsa' \
		'/usr/share/easy-rsa' \
		'/etc/easy-rsa' \
		# EOL
	do
		if [ "$context" = x509-types-only ]; then
			# Find x509-types ONLY
			# Declare in preferred order, first wins
			# beaten by command line.
			[ -e "${area}/${x509_types_dir}" ] && set_var \
				EASYRSA_EXT_DIR "${area}/${x509_types_dir}"
		else
			# Find x509-types ALSO
			# Declare in preferred order, first wins
			# beaten by command line.
			[ -e "${area}/${x509_types_dir}" ] && set_var \
				EASYRSA_EXT_DIR "${area}/${x509_types_dir}"

			# Find other files - Omitting "$vars_file"
			# shellcheck disable=2066 # Loop will only run once
			for source in \
				"$ssl_cnf_file" \
				# EOL
			do
				# Find each item
				[ -e "${area}/${source}" ] || continue

				# If source does not exist in PKI then copy it
				if [ -e "${EASYRSA_PKI}/${source}" ]; then
					continue
				else
					cp "${area}/${source}" "$EASYRSA_PKI" || die \
						"Failed to copy to PKI: ${area}/${source}"
				fi
			done
		fi
	done

	# Short circuit for x509-types-only
	if [ "$context" = x509-types-only ]; then
		verbose "\
install_data_to_pki: $context - COMPLETED"
		return
	fi

	# Create PKI/vars from PKI/example
	unset -v new_vars_true
	if [ "$user_vars_true" ] || \
		[ "$no_new_vars" ]
	then
		: # ok - Do not make a PKI/vars if another vars exists
		verbose "\
install_data_to_pki: $context - Not creating pki/vars"
	else
		case "$context" in
		init-pki)
			# Only create for 'init-pki', if one does not exist
			# 'init-pki soft' should have it's own 'vars' file
			if [ -e "${EASYRSA_PKI}/${vars_file_example}" ] && \
				[ ! -e "${EASYRSA_PKI}/${vars_file}" ]
			then
				# Failure means that no vars will exist and
				# 'cp' will generate an error message
				# This is not a fatal error
				if cp "${EASYRSA_PKI}/${vars_file_example}" \
					"${EASYRSA_PKI}/${vars_file}"
				then
					new_vars_true=1
					vars="${EASYRSA_PKI}/${vars_file}"
					verbose "\
install_data_to_pki: $context - vars = '$vars'"
				else
					unset -v new_vars_true vars
					warn "\
install_data_to_pki: $context - Failed to install vars file"
				fi
			fi
		;;
		vars-setup)
			: ;; # No change to current 'vars' required
		x509-types-only)
			die "install_data_to_pki - unexpected context" ;;
		'')
			die "install_data_to_pki - unspecified context" ;;
		*)
			die "install_data_to_pki - unknown context: $context"
		esac
	fi

	# Check PKI is updated - Omit unnecessary checks
	if [ -e "${EASYRSA_PKI}/${ssl_cnf_file}" ]; then
		: # ok
	else
		create_openssl_easyrsa_cnf > \
			"${EASYRSA_PKI}/${ssl_cnf_file}" || die "\
install_data_to_pki - Missing: '$ssl_cnf_file'"
		verbose "\
install_data_to_pki: $context - create_openssl_easyrsa_cnf OK"
	fi

	[ -d "$EASYRSA_EXT_DIR" ] || verbose "\
install_data_to_pki: $context - Missing: '$x509_types_dir'"
	verbose "install_data_to_pki: $context - COMPLETED"
} # => install_data_to_pki ()

# Disable terminal echo, if possible, otherwise warn
hide_read_pass()
{
	# 3040 - In POSIX sh, set option [name] is undefined
	# 3045 - In POSIX sh, some-command-with-flag is undefined
	# shellcheck disable=SC3040,SC3045
	if stty -echo 2>/dev/null; then
		prompt_restore=1
		read -r "$@"
		stty echo
	elif (set +o echo 2>/dev/null); then
		prompt_restore=2
		set +o echo
		read -r "$@"
		set -o echo
	elif (echo | read -r -s 2>/dev/null) ; then
		read -r -s "$@"
	else
		warn "\
Could not disable echo. Password will be shown on screen!"
		read -r "$@"
	fi
	prompt_restore=0
	return 0
} # => hide_read_pass()

# Get passphrase
get_passphrase() {
	t="$1"; shift || die "password malfunction"
	while :; do
		r=""
		printf '\n%s' "$*"
		hide_read_pass r

		if [ "${#r}" -lt 4 ]; then
			printf '\n%s\n' \
				"Passphrase must be at least 4 characters!"
		else
			force_set_var "$t" "$r" || die "Passphrase error!"
			unset -v r t
			print
			return 0
		fi
	done
} # => get_passphrase()

# build-ca backend:
build_ca() {
	cipher="-aes256"
	unset -v sub_ca ssl_batch date_stamp x509 error_info
	while [ "$1" ]; do
		case "$1" in
			intca|subca) sub_ca=1 ;;
			nopass)
				[ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1
			;;
			raw-ca|raw) EASYRSA_RAW_CA=1 ;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	out_key="$EASYRSA_PKI/private/ca.key"
	# setup for an intermediate CA
	if [ "$sub_ca" ]; then
		# Generate a CSR
		out_file="$EASYRSA_PKI/reqs/ca.req"
	else
		# Generate a certificate
		out_file="$EASYRSA_PKI/ca.crt"
		date_stamp=1
		x509=1
	fi

	# RAW mode must take priority
	if [ "$EASYRSA_RAW_CA" ]; then
		unset -v EASYRSA_NO_PASS EASYRSA_PASSOUT EASYRSA_PASSIN
		verbose "build-ca: CA password RAW method"
	else
		# If encrypted then create the CA key with AES256 cipher
		if [ "$EASYRSA_NO_PASS" ]; then
			unset -v cipher
		else
			unset -v no_password
		fi
	fi

	# Test for existing CA, and complain if already present
	if verify_ca_init test; then
		user_error "\
Unable to create a CA as you already seem to have one set up.
If you intended to start a new CA, run init-pki first."
	fi

	# If a private key exists, an intermediate ca was created
	# but not signed.
	# Notify user and require a signed ca.crt or a init-pki:
	if [ -f "$out_key" ]; then
		user_error "\
A CA private key exists but no ca.crt is found in your PKI:
* $EASYRSA_PKI

Refusing to create a new CA as this would overwrite your
current CA. To start a new CA, run init-pki first."
	fi

	# create necessary dirs:
	err_msg="\
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
		mkdir -p "$EASYRSA_PKI/$i" || die "$err_msg"
	done

	# create necessary files:
	printf "" > \
		"$EASYRSA_PKI/index.txt" || die "$err_msg"
	printf '%s\n' 'unique_subject = no' \
		> "$EASYRSA_PKI/index.txt.attr" || die "$err_msg"
	printf '%s\n' "01" \
		> "$EASYRSA_PKI/serial" || die "$err_msg"
	unset -v err_msg

	# Set ssl batch mode, as required
	# --req-cn must be used with --batch,
	# otherwise use default
	if [ "$EASYRSA_BATCH" ]; then
		ssl_batch=1
	else
		export EASYRSA_REQ_CN=ChangeMe
	fi

	# Default CA commonName
	if [ "$EASYRSA_REQ_CN" = ChangeMe ]; then
		if [ "$sub_ca" ]; then
			export EASYRSA_REQ_CN="Easy-RSA Sub-CA"
		else
			export EASYRSA_REQ_CN="Easy-RSA CA"
		fi
	fi

	# Check for insert-marker in ssl config file
	if [ "$EASYRSA_EXTRA_EXTS" ]; then
		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
	fi

	# Assign cert and key temp files
	out_key_tmp=""
	easyrsa_mktemp out_key_tmp || \
		die "build_ca - easyrsa_mktemp out_key_tmp"
	out_file_tmp=""
	easyrsa_mktemp out_file_tmp || \
		die "build_ca - easyrsa_mktemp out_file_tmp"

	# Get passphrase from user if necessary
	if [ "$EASYRSA_RAW_CA" ]
	then
		# Passphrase will be provided
		confirm "
       Accept ?  " yes "\
Raw CA mode
===========

  CA password must be input THREE times:

    1. Set the password.
    2. Confirm the password.
    3. Use the password. (Create the Root CA)"

	elif [ "$EASYRSA_NO_PASS" ]
	then
		: # No passphrase required

	elif [ "$EASYRSA_PASSOUT" ] && [ "$EASYRSA_PASSIN" ]
	then
		: # passphrase defined
		# Both --passout and --passin
		# must be defined for a CA with a password

	else
		# Assign passphrase vars
		# Heed shellcheck SC2154
		p=""
		q=""

		# Get passphrase p
		get_passphrase p \
			"Enter New CA Key Passphrase: "

		# Confirm passphrase q
		get_passphrase q \
			"Confirm New CA Key Passphrase: "

		# Validate passphrase
		if [ "$p" ] && [ "$p" = "$q" ]; then
			# CA password via temp-files
			in_key_pass_tmp=""
			easyrsa_mktemp in_key_pass_tmp || \
				die "build_ca - in_key_pass_tmp"
			out_key_pass_tmp=""
			easyrsa_mktemp out_key_pass_tmp || \
				die "build_ca - out_key_pass_tmp"
			printf "%s" "$p" > "$in_key_pass_tmp" || \
				die "in_key_pass_tmp: write"
			printf "%s" "$p" > "$out_key_pass_tmp" || \
				die "out_key_pass_tmp: write"
			unset -v p q
		else
			unset -v p q
			user_error "Passphrases do not match!"
		fi
	fi

	# Assign tmp-file for config
	raw_ssl_cnf_tmp=""
	easyrsa_mktemp raw_ssl_cnf_tmp || \
		die "build_ca - easyrsa_mktemp raw_ssl_cnf_tmp"

	# Assign awkscript to insert EASYRSA_EXTRA_EXTS
	# shellcheck disable=SC2016 # vars don't expand in ''
	awkscript='\
{if ( match($0, "^#%CA_X509_TYPES_EXTRA_EXTS%") )
	{ while ( getline<"/dev/stdin" ) {print} next }
 {print}
}'

	# Insert x509-types COMMON and 'ca' and EASYRSA_EXTRA_EXTS
	{
		# 'ca' file
		if [ -f "$EASYRSA_EXT_DIR/ca" ]; then
			cat "$EASYRSA_EXT_DIR/ca"
		else
			create_x509_type ca
		fi

		# COMMON file
		if [ -f "$EASYRSA_EXT_DIR/COMMON" ]; then
			cat "$EASYRSA_EXT_DIR/COMMON"
		else
			create_x509_type COMMON
		fi

		# User extentions
		[ "$EASYRSA_EXTRA_EXTS" ] && \
			print "$EASYRSA_EXTRA_EXTS"

	} | awk "$awkscript" "$EASYRSA_SSL_CONF" \
			> "$raw_ssl_cnf_tmp" || \
			die "Copying X509_TYPES to config file failed"
	verbose "build-ca: insert x509 and extensions OK"

	# Use this new SSL config for the rest of this function
	EASYRSA_SSL_CONF="$raw_ssl_cnf_tmp"

	# Generate CA Key
	if [ "$EASYRSA_RAW_CA" ]; then
		case "$EASYRSA_ALGO" in
		rsa)
			if easyrsa_openssl genpkey \
				-algorithm "$EASYRSA_ALGO" \
				-pkeyopt \
					rsa_keygen_bits:"$EASYRSA_ALGO_PARAMS" \
				-out "$out_key_tmp" \
				${cipher:+ "$cipher"}
			then
				: # ok
			else
				die "Failed create CA private key"
			fi
		;;
		ec)
			if easyrsa_openssl genpkey \
				-paramfile "$EASYRSA_ALGO_PARAMS" \
				-out "$out_key_tmp" \
				${cipher:+ "$cipher"}
			then
				: # ok
			else
				die "Failed create CA private key"
			fi
		;;
		ed)
			if easyrsa_openssl genpkey \
				-algorithm "$EASYRSA_CURVE" \
				-out "$out_key_tmp" \
				${cipher:+ "$cipher"}
			then
				: # ok
			else
				die "Failed create CA private key"
			fi
		;;
		*)	die "Unknown algorithm: $EASYRSA_ALGO"
		esac

		verbose "\
build_ca: CA key password created via RAW"

	else
		case "$EASYRSA_ALGO" in
		rsa)
		easyrsa_openssl genpkey \
			-algorithm "$EASYRSA_ALGO" \
			-pkeyopt rsa_keygen_bits:"$EASYRSA_ALGO_PARAMS" \
			-out "$out_key_tmp" \
			${cipher:+ "$cipher"} \
			${EASYRSA_PASSOUT:+ -pass "$EASYRSA_PASSOUT"} \
			${out_key_pass_tmp:+ -pass file:"$out_key_pass_tmp"} \
				|| die "Failed create CA private key"
		;;
		ec)
		easyrsa_openssl genpkey \
			-paramfile "$EASYRSA_ALGO_PARAMS" \
			-out "$out_key_tmp" \
			${cipher:+ "$cipher"} \
			${EASYRSA_PASSOUT:+ -pass "$EASYRSA_PASSOUT"} \
			${out_key_pass_tmp:+ -pass file:"$out_key_pass_tmp"} \
				|| die "Failed create CA private key"
		;;
		ed)
		easyrsa_openssl genpkey \
			-algorithm "$EASYRSA_CURVE" \
			-out "$out_key_tmp" \
			${cipher:+ "$cipher"} \
			${EASYRSA_PASSOUT:+ -pass "$EASYRSA_PASSOUT"} \
			${out_key_pass_tmp:+ -pass file:"$out_key_pass_tmp"} \
				|| die "Failed create CA private key"
		;;
		*)	die "Unknown algorithm: $EASYRSA_ALGO"
		esac

		[ "$EASYRSA_NO_PASS" ] || verbose "\
build_ca: CA key password created via temp-files"
	fi

	# Generate the CA keypair:
	if [ "$EASYRSA_RAW_CA" ]; then
		if easyrsa_openssl req -utf8 -new \
			-key "$out_key_tmp" \
			-out "$out_file_tmp" \
			${x509:+ -x509} \
			${date_stamp:+ -days "$EASYRSA_CA_EXPIRE"} \
			${EASYRSA_DIGEST:+ -"$EASYRSA_DIGEST"}
		then
			: # ok
			unset -v error_info
		else
			die "Failed to build the CA keypair."
		fi

		verbose "\
build_ca: CA certificate password created via RAW"

	else
		easyrsa_openssl req -utf8 -new \
			-key "$out_key_tmp" -keyout "$out_key_tmp" \
			-out "$out_file_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"} \
			${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \
			${in_key_pass_tmp:+ -passin file:"$in_key_pass_tmp"} \
			${out_key_pass_tmp:+ -passout file:"$out_key_pass_tmp"} \
				|| die "Failed to build the CA keypair"

		[ "$EASYRSA_NO_PASS" ] || verbose "\
build_ca: CA certificate password created via temp-files"
	fi

	# Move temp-files to output files
	mv "$out_key_tmp" "$out_key" || {
		die "Failed to move key temp-file"
	}
	mv "$out_file_tmp" "$out_file" || {
		rm -f "$out_key" # Also remove the key
		die "Failed to move cert temp-file"
	}

	# Success messages
	if [ "$sub_ca" ]; then
		notice "\
Your intermediate CA request is at:
* $out_file
  and now must be sent to your parent CA for signing.

Place your resulting cert at:
* $EASYRSA_PKI/ca.crt
  prior to signing operations."
	else
		notice "\
CA creation complete. Your new CA certificate is at:
* $out_file"
	fi

	return 0
} # => build_ca()

# gen-dh backend:
gen_dh() {
	out_file="$EASYRSA_PKI/dh.pem"

	# check to see if we already have a dh parameters file
	if [ -e "$out_file" ]; then
		if [ "$EASYRSA_BATCH" ]; then
			# if batch is enabled, die
			user_error "\
DH parameters file already exists
at: $out_file"
		else
			# warn the user, allow to force overwrite
			confirm "Overwrite?  " "yes" "\
DH parameters file already exists
at: $out_file"
		fi
	fi

	# Create a temp file
	# otherwise user abort leaves an incomplete dh.pem
	tmp_dh_file=""
	easyrsa_mktemp tmp_dh_file || \
		die "gen_dh - easyrsa_mktemp tmp_dh_file"

	# Generate dh.pem
	OPENSSL_CONF=/dev/null \
		"$EASYRSA_OPENSSL" dhparam -out "$tmp_dh_file" \
			"$EASYRSA_KEY_SIZE" || \
				die "Failed to generate DH params"

	# Validate dh.pem
	OPENSSL_CONF=/dev/null \
		"$EASYRSA_OPENSSL" dhparam -in "$tmp_dh_file" \
			-check -noout || \
				die "Failed to validate DH params"

	mv -f "$tmp_dh_file" "$out_file" || \
		die "Failed to move temp DH file"

	notice "
DH parameters of size $EASYRSA_KEY_SIZE created at:
* $out_file"

	return 0
} # => gen_dh()

# gen-req and key backend:
gen_req() {
	# pull filename, use as default interactive CommonName
	[ "$1" ] || user_error "\
Error: gen-req must have a file-name-base as the first argument.
Run easyrsa without commands for usage and commands."

	file_name_base="$1"
	shift # scrape off file-name-base

	# Initialisation
	unset -v text ssl_batch

	# Set ssl batch mode and Default commonName, as required
	if [ "$EASYRSA_BATCH" ]; then
		ssl_batch=1
		# If EASYRSA_REQ_CN is set to something other than
		# 'ChangeMe' then keep user defined value
		if [ "$EASYRSA_REQ_CN" = ChangeMe ]; then
			export EASYRSA_REQ_CN="$file_name_base"
		fi
	else
		# --req-cn must be used with --batch
		# otherwise use file-name
		export EASYRSA_REQ_CN="$file_name_base"
	fi

	# Output files
	key_out="$EASYRSA_PKI/private/${file_name_base}.key"
	req_out="$EASYRSA_PKI/reqs/${file_name_base}.req"

	# function opts support
	while [ "$1" ]; do
		case "$1" in
			text) text=1 ;;
			nopass)
				[ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1
			;;
			# batch flag supports internal caller build_full()
			batch) ssl_batch=1 ;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	# don't wipe out an existing private key without confirmation
	if [ -f "$key_out" ]; then
		confirm "Confirm key overwrite: " "yes" "\

WARNING!!!

An existing private key was found at $key_out
Continuing with key generation will replace this key."
	fi

	# When EASYRSA_EXTRA_EXTS is defined,
	# append it to openssl's [req] section:
	if [ "$EASYRSA_EXTRA_EXTS" ]; then
		# Check for insert-marker in ssl config file
		if ! grep -q '^#%EXTRA_EXTS%' "$EASYRSA_SSL_CONF"
		then
			die "\
This openssl config file does \
does not support EASYRSA_EXTRA_EXTS.
* $EASYRSA_SSL_CONF

Please update 'openssl-easyrsa.cnf' \
to the latest Easy-RSA release."
		fi

		# Setup & insert the extra ext data keyed by magic line
		extra_exts="
req_extensions = req_extra
[ req_extra ]
$EASYRSA_EXTRA_EXTS"
		# vars don't expand in single quote
		# shellcheck disable=SC2016
		awkscript='
{if ( match($0, "^#%EXTRA_EXTS%") )
	{ while ( getline<"/dev/stdin" ) {print} next }
 {print}
}'
		# Assign temp-file for confg
		raw_ssl_cnf_tmp=""
		easyrsa_mktemp raw_ssl_cnf_tmp || \
			die "gen_req - easyrsa_mktemp raw_ssl_cnf_tmp"

		# Insert $extra_exts @ %EXTRA_EXTS% in SSL Config
		print "$extra_exts" | \
			awk "$awkscript" "$EASYRSA_SSL_CONF" \
			> "$raw_ssl_cnf_tmp" || \
				die "Writing SSL config to temp file failed"

		# Use this SSL config for the rest of this function
		EASYRSA_SSL_CONF="$raw_ssl_cnf_tmp"
	fi

	# Name temp files
	key_out_tmp=""
	easyrsa_mktemp key_out_tmp || \
		die "gen_req - easyrsa_mktemp key_out_tmp"
	req_out_tmp=""
	easyrsa_mktemp req_out_tmp || \
		die "gen_req - easyrsa_mktemp req_out_tmp"

	# Set algorithm options
	algo_opts=""
	case "$EASYRSA_ALGO" in
		rsa|ec)
			# Set elliptic curve parameters-file
			# or RSA bit-length
			algo_opts="$EASYRSA_ALGO:$EASYRSA_ALGO_PARAMS"
		;;
		ed)
			# Set Edwards curve name
			algo_opts="$EASYRSA_CURVE"
		;;
		*)	die "gen_req - Unknown algorithm: $EASYRSA_ALGO"
	esac

	# Generate request
	if easyrsa_openssl req -utf8 -new -newkey "$algo_opts" \
		-keyout "$key_out_tmp" \
		-out "$req_out_tmp" \
		${EASYRSA_NO_PASS:+ "$no_password"} \
		${text:+ -text} \
		${ssl_batch:+ -batch} \
		${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"}
	then
		: # ok
	else
		die "Failed to generate request"
	fi

	# Move temp-files to target-files
	mv "$key_out_tmp" "$key_out" || {
		die "Failed to move key temp-file"
	}
	mv "$req_out_tmp" "$req_out" || {
		rm -f "$key_out" # Also remove the key
		die "Failed to move req temp-file"
	}

	# Success messages
	notice "\
Private-Key and Public-Certificate-Request files created.
Your files are:
* req: $req_out
* key: $key_out${do_build_full:+ $NL}"

	return 0
} # => gen_req()

# common signing backend
sign_req() {
	crt_type="$1"
	file_name_base="$2"

	# Check argument sanity:
	[ "$file_name_base" ] || user_error "\
Incorrect number of arguments provided to sign-req:
expected 2, got $# (see command help for usage)"

	req_in="$EASYRSA_PKI/reqs/$file_name_base.req"
	crt_out="$EASYRSA_PKI/issued/$file_name_base.crt"
	shift 2

	# Check for preserve-dn
	while [ "$1" ]; do
		case "$1" in
			preserve*)
				export EASYRSA_PRESERVE_DN=1
			;;
			*)
				warn "Ignoring unknown option '$1'"
		esac
		shift
	done

	# Cert type must NOT be COMMON
	[ "$crt_type" = COMMON ] && user_error "\
Invalid certificate type: '$crt_type'"

	# Request file must exist
	[ -e "$req_in" ] || user_error "\
No request found for the input: '$file_name_base'
Expected to find the request at:
* $req_in"

	# Certificate file must NOT exist
	[ ! -e "$crt_out" ] || user_error "\
Cannot sign this request for '$file_name_base'.
Conflicting certificate exists at:
* $crt_out"

	# Confirm input is a cert req
	verify_file req "$req_in" || user_error "\
The certificate request file is not in a valid X509 format:
* $req_in"

	# Randomize Serial number
	if [ "$EASYRSA_RAND_SN" != no ]; then
		serial=""
		check_serial=""
		unset -v serial_is_unique
		for i in 1 2 3 4 5; do
			serial="$(
				easyrsa_random 16
				)" || die "sign_req - easyrsa_random"

			# Check for duplicate serial in CA db
			if check_serial_unique "$serial" batch; then
				serial_is_unique=1
				break
			fi
		done

		# Check for unique_serial
		[ "$serial_is_unique" ] || die "\
sign_req - Randomize Serial number failed:

$check_serial"

		# Print random $serial to pki/serial file
		# for use by SSL config
		print "$serial" > "$EASYRSA_PKI/serial" || \
			die "sign_req - write serial to file"
		unset -v serial check_serial serial_is_unique
	fi

	# When EASYRSA_CP_EXT is defined,
	# adjust openssl's [default_ca] section:
	if [ "$EASYRSA_CP_EXT" ]; then
		# Check for insert-marker in ssl config file
		if ! grep -q '^#%COPY_EXTS%' "$EASYRSA_SSL_CONF"
		then
			die "\
This openssl config file does \
not support option '--copy-ext'.
* $EASYRSA_SSL_CONF

Please update 'openssl-easyrsa.cnf' \
to the latest Easy-RSA release."
		fi

		# Setup & insert the copy_extensions data
		# keyed by a magic line
		copy_exts="copy_extensions = copy"
		# shellcheck disable=SC2016 # vars don't expand ''
		awkscript='
{if ( match($0, "^#%COPY_EXTS%") )
	{ while ( getline<"/dev/stdin" ) {print} next }
 {print}
}'
		# Assign temp-file for confg
		raw_ssl_cnf_tmp=""
		easyrsa_mktemp raw_ssl_cnf_tmp || \
			die "sign_req - easyrsa_mktemp raw_ssl_cnf_tmp"

		print "$copy_exts" | \
			awk "$awkscript" "$EASYRSA_SSL_CONF" \
				> "$raw_ssl_cnf_tmp" || die "\
Writing 'copy_exts' to SSL config temp-file failed"

		# Use this SSL config for the rest of this function
		EASYRSA_SSL_CONF="$raw_ssl_cnf_tmp"
		verbose "sign_req: Using '$copy_exts'"
	fi

	# Find or create x509-type file
	if [ -f "$EASYRSA_EXT_DIR/$crt_type" ]; then
		# Use the x509-types/$crt_type file
		x509_type_file="$EASYRSA_EXT_DIR/$crt_type"
	else
		# Use a temp file
		x509_type_tmp=""
		easyrsa_mktemp x509_type_tmp || \
			die "sign_req - easyrsa_mktemp x509_type_tmp"

		create_x509_type "$crt_type" > "$x509_type_tmp" || \
			die "sign_req - create_x509_type $crt_type"

		x509_type_file="$x509_type_tmp"
	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
		x509_COMMON_tmp=""
		easyrsa_mktemp x509_COMMON_tmp || \
			die "sign_req - easyrsa_mktemp x509_COMMON_tmp"

		create_x509_type COMMON > "$x509_COMMON_tmp" || \
			die "sign_req - create_x509_type COMMON"

		x509_COMMON_file="$x509_COMMON_tmp"
	fi

	# Support a dynamic CA path length when present:
	unset -v basicConstraints
	if [ "$crt_type" = "ca" ] && [ "$EASYRSA_SUBCA_LEN" ]
	then
		# Print the last occurence of basicContraints in
		# x509-types/ca
		# If basicContraints is not defined then bail
		# shellcheck disable=SC2016 # vars don't expand ''
		awkscript='\
/^[[:blank:]]*basicConstraints[[:blank:]]*=/ { bC=$0 }
END { if (length(bC) == 0 ) exit 1; print bC }'
		basicConstraints="$(
			awk "$awkscript" "$x509_type_file"
			)" || die "\
basicConstraints is not defined, cannot use 'pathlen'"
		verbose "sign_req: Using basicConstraints pathlen"
	fi

	# Deprecated Netscape extension support
	case "$EASYRSA_NS_SUPPORT" in
		[yY][eE][sS])

		confirm "Confirm use of Netscape extensions: " yes \
			"WARNING: Netscape extensions are DEPRECATED!"

		# Netscape extension
		case "$crt_type" in
			serverClient)
				ns_cert_type="nsCertType = serverClient" ;;
			server)
				ns_cert_type="nsCertType = server" ;;
			client)
				ns_cert_type="nsCertType = client" ;;
			ca)
				ns_cert_type="nsCertType = sslCA" ;;
			*)
				ns_cert_type="nsCertType = $crt_type"
		esac
		verbose "sign_req: Using $ns_cert_type"
	;;
	*)
		# ok No NS support required
		unset -v ns_cert_type
	esac

	# Generate the extensions file for this cert:
	ext_tmp=""
	easyrsa_mktemp ext_tmp || \
		die "sign_req - easyrsa_mktemp ext_tmp"

	# Begin output redirect
	{
		# Append $cert-type extensions
		cat "$x509_COMMON_file" "$x509_type_file"

		# Support a dynamic CA path length when present:
		if [ "$basicConstraints" ]; then
			print "$basicConstraints, pathlen:$EASYRSA_SUBCA_LEN"
		fi

		# Deprecated Netscape extension support
		if [ "$ns_cert_type" ]; then
			print "$ns_cert_type"
			print "nsComment = \"$EASYRSA_NS_COMMENT\""
		fi

		# Add user SAN from --subject-alt-name
		if [ "$EASYRSA_EXTRA_EXTS" ]; then
			print "$EASYRSA_EXTRA_EXTS"
		else
			# or default server SAN
			# If type is server and no subjectAltName was
			# requested then add one to the extensions file
			if [ "$crt_type" = 'server' ] || \
				[ "$crt_type" = 'serverClient' ];
			then
				# req san or default server SAN
				san="$(display_san req "$req_in")"
				if [ "$san" ]; then
					print "subjectAltName = $san"
				else
					default_server_san "$req_in"
				fi
			fi
		fi
	} > "$ext_tmp" || die "\
Error message: $error_msg

Failed to create temp extension file (bad permissions?) at:
* $ext_tmp"
	verbose "sign_req: Generated extensions file OK"

	# Set valid_period message
	if [ "$EASYRSA_END_DATE" ]; then
		valid_period="
until date '$EASYRSA_END_DATE'"
	else
		valid_period="
for '$EASYRSA_CERT_EXPIRE' days"
	fi

	# Display the request subject in an easy-to-read format
	# Confirm the user wishes to sign this request
	# The foriegn_request confirmation is not required
	# for build_full:
	if [ "$do_build_full" ]; then
		unset -v foriegn_request
	else
		foriegn_request="\
Please check over the details shown below for accuracy. \
Note that this request
has not been cryptographically verified. Please be sure \
it came from a trusted
source or that you have verified the request checksum \
with the sender.$NL"
	fi

	confirm "Confirm request details: " "yes" "\
You are about to sign the following certificate:
${foriegn_request}Request subject, to be signed as a \
$crt_type certificate ${valid_period}:

$(display_dn req "$req_in")" # => confirm end

	# Assign temp cert file
	crt_out_tmp=""
	easyrsa_mktemp crt_out_tmp || \
		die "sign_req - easyrsa_mktemp crt_out_tmp"

	# sign request
	easyrsa_openssl ca -utf8 -batch \
		-in "$req_in" -out "$crt_out_tmp" \
		-extfile "$ext_tmp" \
		${EASYRSA_PRESERVE_DN:+ -preserveDN} \
		${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
		${EASYRSA_NO_TEXT:+ -notext} \
		${EASYRSA_CERT_EXPIRE:+ -days "$EASYRSA_CERT_EXPIRE"} \
		${EASYRSA_START_DATE:+ -startdate "$EASYRSA_START_DATE"} \
		${EASYRSA_END_DATE:+ -enddate "$EASYRSA_END_DATE"} \
			|| die "\
Signing failed (openssl output above may have more detail)"
	verbose "sign_req: signed cert '$file_name_base' OK"

	mv "$crt_out_tmp" "$crt_out" || \
		die "Failed to move temp-file to certificate."

	# Success messages
	notice "\
Certificate created at:
* $crt_out"

	return 0
} # => sign_req()

# Check serial in db
check_serial_unique() {
	[ "$1" ] || user_error "Serial number required!"
	case "$1" in
	(*[!1234567890abcdef]*)
		user_error "Invalid serial number: '$1'"
	;;
	*)
		: # ok
	esac

	unset -v unique_serial_true

	# Check for openssl -status of serial number
	# Always errors out - Do not capture error
	# unset EASYRSA_SILENT_SSL to capure all output
	# Do NOT unset check_serial for sign-req error msg
	check_serial="$(
		unset -v EASYRSA_SILENT_SSL
		easyrsa_openssl ca -status "$1" 2>&1
		)" || :

	# Check for duplicate serial in CA db
	case "$check_serial" in
		(*"not present in db"*)
			unique_serial_true=1
			verbose "check_serial_unique: unique_serial=true"
		;;
		*)
			: # Some other response
			verbose "check_serial_unique: unique_serial=false"
	esac

	# In batch mode return result only
	if [ "$2" = batch ] || [ "$EASYRSA_BATCH" ]; then
		if [ "$unique_serial_true" ]; then
			unset -v unique_serial_true
			return 0
		else
			unset -v unique_serial_true
			return 1
		fi
	fi

	# Otherwise, show result to user
	# and do not return any error code
	print "
check_serial_status RESULT:
========================================

$check_serial

========================================
COMPLETE"
} # => check_serial_unique()

# common build backend
# used to generate+sign in 1 step
build_full() {
	# pull filename base:
	[ "$2" ] || user_error "\
Error: didn't find a file base name as the first argument.
Run easyrsa without commands for usage and commands."

	crt_type="$1"
	name="$2"
	shift 2

	req_out="$EASYRSA_PKI/reqs/$name.req"
	key_out="$EASYRSA_PKI/private/$name.key"
	crt_out="$EASYRSA_PKI/issued/$name.crt"

	# function opts support
	while [ "$1" ]; do
		case "$1" in
			nopass)
				[ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1
			;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	# abort on existing req/key/crt files
	err_exists="\
file already exists. Aborting build to avoid overwriting this file.
If you wish to continue, please use a different name.
Conflicting file found at:
*"
	[ -e "$req_out" ] && \
		user_error "Request $err_exists $req_out"
	[ -e "$key_out" ] && \
		user_error "Key $err_exists $key_out"
	[ -e "$crt_out" ] && \
		user_error "Certificate $err_exists $crt_out"
	unset -v err_exists

	# Make inline directory
	[ -d "$EASYRSA_PKI/inline" ] ||	\
		mkdir -p "$EASYRSA_PKI/inline" || \
			die "Failed to create inline directoy."

	# Confirm over write inline file
	inline_out="$EASYRSA_PKI/inline/$name.inline"
	[ -e "$inline_out" ] && \
		confirm "Confirm OVER-WRITE existing inline file ? " y "\
Warning!

An inline file for name '$name' already exists:
* $inline_out"

	# Set commonName
	[ "$EASYRSA_REQ_CN" = ChangeMe ] || user_error "\
Option conflict:
* '$cmd' does not support setting an external commonName"
	EASYRSA_REQ_CN="$name"

	# create request
	do_build_full=1
	gen_req "$name" batch

	# Sign it
	error_build_full_cleanup=1
	if sign_req "$crt_type" "$name"; then
		unset -v error_build_full_cleanup do_build_full
	else
		die "\
Failed to sign '$name' - \
See error messages above for details."
	fi

	# inline it
	if inline_creds "$name" > "$inline_out"; then
		notice "\
Inline file created:
* $inline_out"
	else
		warn "\
INCOMPLETE Inline file created:
* $inline_out"
	fi

	return 0
} # => build_full()

# Print inline data for file_name_base
inline_creds () {
	[ "$1" ] || die "inline_creds - Missing file_name_base"

	# Source files
	crt_source="${EASYRSA_PKI}/issued/${1}.crt"
	key_source="${EASYRSA_PKI}/private/${1}.key"
	ca_source="$EASYRSA_PKI/ca.crt"
	incomplete=0

	# Generate data
	if [ -e "$crt_source" ]; then
		# Get EasyRSA cert type
		ssl_cert_x509v3_eku "$crt_source" type_data

		crt_data="\
<cert>
$(cat "$crt_source")
</cert>"
	else
		# Set EasyRSA cert type to 'undefined'
		type_data=undefined
		incomplete=1
		crt_data="\
<cert>
* Paste your user certificate here *
</cert>"
	fi

	if [ -e "$key_source" ]; then
		key_data="\
<key>
$(cat "$key_source")
</key>"
	else
		incomplete=1
		key_data="\
<key>
* Paste your private key here *
</key>"
	fi

	if [ -e "$ca_source" ]; then
		ca_data="\
<ca>
$(cat "$ca_source")
</ca>"
	else
		incomplete=1
		ca_data="\
<ca>
* Paste your CA certificate here *
</ca>"
	fi

	# Print data
	print "\
# Easy-RSA Type: ${type_data}
# Name: ${1}

$crt_data

$key_data

$ca_data
"
	# If inline file is incomplete then return error
	return "$incomplete"
} # => inline_creds ()

# revoke backend
revoke() {
	# 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"
	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"

	# 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:
	[ -e "$crt_in" ] || user_error "\
Unable to revoke as no 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
	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/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

	# Check for key and request files
	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"

	# confirm operation by displaying DN:
	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}

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 revocation: " "yes" "
Please confirm that you wish to revoke the certificate
with the following subject:

  $(display_dn x509 "$crt_in")

  serial-number: $cert_serial

  Reason: ${crl_reason:-None given}"

	# Revoke certificate
	easyrsa_openssl ca -utf8 -revoke "$crt_in" \
		${crl_reason:+ -crl_reason "$crl_reason"} \
		${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
			|| die "\
Failed to revoke certificate: revocation command failed."

	# move revoked files
	# so we can reissue certificates with the same name
	revoke_move

	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()

# revoke_move
# moves revoked certificates to the 'revoked' folder
# allows reissuing certificates with the same name
revoke_move() {
	for target in "$out_dir" \
		"$out_dir/certs_by_serial" \
		"$out_dir/private_by_serial" \
		"$out_dir/reqs_by_serial"
	do
		[ -d "$target" ] && continue
		mkdir -p "$target" ||
			die "Failed to mkdir: $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" || 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"

		elif [ -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

	# 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
} # => revoke_move()

# 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"
	# 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"

	# Upgrade CA index.txt.attr - unique_subject = no
	up23_upgrade_ca || \
		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

	# Use SAN from --san if set else use SAN from old cert
	if echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName
	then
		: # ok - Use current subjectAltName
	else
		san="$(
easyrsa_openssl x509 -in "$crt_in" -noout -text | sed -n \
"/X509v3 Subject Alternative Name:\
/{n;s/IP Address:/IP:/g;s/ //g;p;}"
		)" || die "renew - san: easyrsa_openssl subshell"

		[ "$san" ] && export EASYRSA_EXTRA_EXTS="\
$EASYRSA_EXTRA_EXTS
subjectAltName = $san"
	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
with the following subject:

  $(display_dn x509 "$crt_in")

  serial-number: $cert_serial"

	# 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 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 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 "$out_dir" \
		"$out_dir/issued" \
		"$out_dir/private" \
		"$out_dir/reqs"
	do
		[ -d "$target" ] && continue
		mkdir -p "$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
	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

	# 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:

  $(display_dn x509 "$crt_in")

  serial-number: $cert_serial

  Reason: ${crl_reason:-None given}"

	# 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."

	# move revoked files
	revoke_renewed_move

	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 "$out_dir" \
		"$out_dir/certs_by_serial" \
		"$out_dir/private_by_serial" \
		"$out_dir/reqs_by_serial"
	do
		[ -d "$target" ] && continue
		mkdir -p "$target" ||
			die "Failed to mkdir: $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" || 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

	return 0
} # => revoke_renewed_move()

# Move renewed certs_by_serial to the new renew layout
rewind_renew() {
	# pull filename base: serial number
	[ "$1" ] || user_error "\
Error: didn't find a serial number 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 "$#" # No options supported

	cert_serial="$file_name_base"
	in_dir="$EASYRSA_PKI/renewed"
	crt_in="$in_dir/certs_by_serial/${file_name_base}.crt"
	key_in="$in_dir/private_by_serial/${file_name_base}.key"
	req_in="$in_dir/reqs_by_serial/${file_name_base}.req"

	# referenced cert must exist:
	[ -f "$crt_in" ] || user_error "\
Unable to rewind as no certificate was found.
Certificate was expected at:
* $crt_in"

	# Verify certificate
	verify_file x509 "$crt_in" || user_error "\
Unable to rewind 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 commonName of the certificate via DN
	crt_cn="$(
		easyrsa_openssl x509 -in "$crt_in" -noout \
		-subject -nameopt utf8,multiline | grep \
			'^[[:blank:]]*commonName[[:blank:]]*=[[:blank:]]'
		)" || die "Failed to find commonName in certificate"
	crt_cn="${crt_cn#*= }"

	# Set out_dir
	out_dir="$EASYRSA_PKI/renewed"
	crt_out="$out_dir/issued/${crt_cn}.crt"
	key_out="$out_dir/private/${crt_cn}.key"
	req_out="$out_dir/reqs/${crt_cn}.req"

	# Create out_dir
	for newdir in issued private reqs; do
		mkdir -p "$out_dir/$newdir" || \
			die "Failed to create: $out_dir/$newdir"
	done

	# NEVER over-write a renewed cert, revoke it first
	deny_msg="\
Cannot rewind 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

	warn "\
This process is destructive!

These files will be MOVED to the 'renewed' sub-directory:
* $crt_in
* $key_in
* $req_in"

	confirm "  Continue with rewind-renew: " "yes" "
Please confirm you wish to rewind-renew the certificate
with the following subject:

  $(display_dn x509 "$crt_in")

  serial-number: $cert_serial
"	# => confirm end

	# move crt, key and req file to renewed 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
		if mv "$key_in" "$key_out"; then
			: # ok
		else
			# Attempt restore
			mv -f "$crt_out" "$crt_in"
			die "Failed to move: $key_in"
		fi
	fi

	# only move the req if we have it
	if [ -e "$req_in" ]; then
		if mv "$req_in" "$req_out"; then
			: # ok
		else
			# Attempt restore
			mv -f "$crt_out" "$crt_in"
			mv -f "$key_out" "$key_in"
			die "Failed to move: $req_in"
		fi
	fi

	# Success message
	notice "\
Rewind is successful.

Common Name  : $crt_cn
Serial number: $cert_serial

To revoke use: 'revoke-renewed $crt_cn'"
} # => rewind_renew()

# rebuild backend
rebuild() {
	# 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"
	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
	up23_upgrade_ca || \
		die "Failed to upgrade CA to support renewal."

	# Set 'nopass'
	while [ "$1" ]; do
		case "$1" in
			nopass)
				[ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1
			;;
			*) user_error "Unknown option: $1"
		esac
		shift
	done

	# referenced cert must exist:
	[ -f "$crt_in" ] || user_error "\
Unable to rebuild as no certificate was found.
Certificate was expected at:
* $crt_in"

	# Verify certificate
	verify_file x509 "$crt_in" || user_error "\
Unable to rebuild 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
	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"
	key_out="$out_dir/private/${file_name_base}.key"
	req_out="$out_dir/reqs/${file_name_base}.req"

	# NEVER over-write a renewed cert, revoke it first
	deny_msg="\
Cannot rebuild 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

	# Extract certificate usage from old cert
	cert_ext_key_usage="$(
		easyrsa_openssl x509 -in "$crt_in" -noout -text |
		sed -n "/X509v3 Extended Key Usage:/{n;s/^ *//g;p;}"
		)"

	case "$cert_ext_key_usage" in
		"TLS Web Client Authentication")
			cert_type=client
			;;
		"TLS Web Server Authentication")
			cert_type=server
			;;
		"TLS Web Server Auth"*", TLS Web Client Auth"*)
			cert_type=serverClient
			;;
		*) die "Unknown key usage: $cert_ext_key_usage"
	esac

	# Use SAN from --subject-alt-name, if set
	# else use SAN from old cert
	if echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName
	then
		: # ok - Use current subjectAltName
	else
		san="$(
		easyrsa_openssl x509 -in "$crt_in" -noout -text | sed -n \
		"/X509v3 Subject Alternative Name:/{n;s/IP Address:/IP:/g;s/ //g;p;}"
			)"

		[ "$san" ] && export EASYRSA_EXTRA_EXTS="\
$EASYRSA_EXTRA_EXTS
subjectAltName = $san"
	fi

	# 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 'renewed' sub-directory:
* $crt_in${if_exist_key_in}${if_exist_req_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

IMPORTANT: The new key will${EASYRSA_NO_PASS:+ NOT} \
be password protected."

	confirm "  Continue with rebuild: " "yes" "
Please confirm you wish to renew the certificate
with the following subject:

  $(display_dn x509 "$crt_in")

  serial-number: $cert_serial"

	# move renewed files so we can reissue
	# certificate with the same name
	rebuild_move
	error_undo_rebuild_move=1

	# rebuild certificate
	if EASYRSA_BATCH=1 build_full "$cert_type" "$file_name_base"
	then
		unset -v error_undo_rebuild_move
	else
		# If rebuild failed then restore cert, key and req.
		# Otherwise, issue a warning. If *restore* fails
		# then at least the file-names are not serial-numbers
		rebuild_restore_move
		die "\
Rebuild has failed to build a new certificate/key pair."
	fi

	# Success messages
	notice "Rebuild was successful.

                    * IMPORTANT *

Rebuild has created a new certificate and key, to replace
both old files.

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
} # => rebuild()

# Restore files on failure to rebuild
rebuild_restore_move() {
	unset -v rrm_err error_undo_renew_move
	# restore crt, key and req 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

	# only restore the key if we have it
	if [ -e "$restore_key_out" ]; then
		if mv "$restore_key_out" "$restore_key_in"; then
			: # ok
		else
			warn "Failed to restore: $restore_key_out"
			rrm_err=1
		fi
	fi

	# only restore the req if we have it
	if [ -e "$restore_req_out" ]; then
		if mv "$restore_req_out" "$restore_req_in"; then
			: # ok
		else
			warn "Failed to restore: $restore_req_out"
			rrm_err=1
		fi
	fi

	# messages
	if [ "$rrm_err" ]; then
		warn "Failed to restore renewed files."
	else
		notice "\
Rebuild FAILED but files have been successfully restored."
	fi

	return 0
} # => rebuild_restore_move()

# rebuild_move
# moves renewed certificates to the 'renewed' folder
# allows reissuing certificates with the same name
rebuild_move() {
	# make sure renewed dirs exist
	for target in "$out_dir" \
		"$out_dir/issued" \
		"$out_dir/private" \
		"$out_dir/reqs"
	do
		[ -d "$target" ] && continue
		mkdir -p "$target" ||
			die "Failed to mkdir: $target"
	done

	# move crt, key and req file to renewed folders
	restore_crt_in="$crt_in"
	restore_crt_out="$crt_out"
	mv "$crt_in" "$crt_out" || die "Failed to move: $crt_in"

	# only move the key if we have it
	restore_key_in="$key_in"
	restore_key_out="$key_out"
	if [ -e "$key_in" ]; then
		mv "$key_in" "$key_out" || warn "Failed to move: $key_in"
	fi

	# only move the req if we have it
	restore_req_in="$req_in"
	restore_req_out="$req_out"
	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"

		elif [ -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

	# 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
} # => rebuild_move()

# gen-crl backend
gen_crl() {
	out_file="$EASYRSA_PKI/crl.pem"

	out_file_tmp=""
	easyrsa_mktemp out_file_tmp || \
		die "gen_crl - easyrsa_mktemp out_file_tmp"

	if [ -r "$out_file" ]; then
		cp -p "$out_file" "$out_file_tmp" || \
			warn "Failed to preserve CRL file permissions."
	fi

	easyrsa_openssl ca -utf8 -gencrl -out "$out_file_tmp" \
		${EASYRSA_CRL_DAYS:+ -days "$EASYRSA_CRL_DAYS"} \
		${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} || \
			die "CRL Generation failed."

	mv ${EASYRSA_BATCH:+ -f} "$out_file_tmp" "$out_file" || \
		die "Failed to update CRL file."

	notice "\
An updated CRL has been created:
* $out_file"

	return 0
} # => gen_crl()

# import-req backend
import_req() {
	# pull passed paths
	in_req="$1"
	short_name="$2"
	out_req="$EASYRSA_PKI/reqs/$2.req"

	[ "$short_name" ] || user_error "\
Unable to import: incorrect command syntax.
Run easyrsa without commands for usage and command help."

	# Request file must exist
	[ -e "$in_req" ] || user_error "\
No request found for the input: '$2'
Expected to find the request at:
* $in_req"

	verify_file req "$in_req" || user_error "\
The certificate request file is not in a valid X509 format:
* $req_in"

	# destination must not exist
	[ -e "$out_req" ] && user_error "\
Please choose a different name for your imported request file.
Conflicting file already exists at:
* $out_req"

	# now import it
	cp "$in_req" "$out_req"

	notice "\
Request successfully imported with short-name: $short_name
This request is now ready to be signed."

	return 0
} # => import_req()

# export pkcs#12, pkcs#7, pkcs#8 or pkcs#1
export_pkcs() {
	pkcs_type="$1"
	shift

	[ "$1" ] || user_error "\
Unable to export '$pkcs_type': incorrect command syntax.
Run easyrsa without commands for usage and command help."

	file_name_base="$1"
	shift

	crt_in="$EASYRSA_PKI/issued/$file_name_base.crt"
	key_in="$EASYRSA_PKI/private/$file_name_base.key"
	crt_ca="$EASYRSA_PKI/ca.crt"

	# opts support
	cipher=-aes256
	want_ca=1
	want_key=1
	unset -v nokeys friendly_name
	while [ "$1" ]; do
		case "$1" in
			noca)
				want_ca=""
			;;
			nokey)
				want_key=""
				# Undocumented OpenSSL feature: option
				# -nokeys will ignore missing -inkey file
				# No doubt, the reason for the extra -inkey
				nokeys=-nokeys
			;;
			nopass)
				[ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1
			;;
			usefn)
				friendly_name="$file_name_base"
			;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	# Required options - PKCS, rhymes with mess
	case "$pkcs_type" in
		p12|p7)
			: # ok
		;;
		p8|p1)
			want_key=1
		;;
		*) die "Unknown PKCS type: $pkcs_type"
	esac

	# Check for CA, if required
	if [ "$want_ca" ]; then
		case "$pkcs_type" in
		p12|p7)
			# verify_ca_init() here, otherwise not required
			if verify_ca_init test; then
				: # ok
			else
				warn "\
Missing CA Certificate, expected at:
* $crt_ca"
				confirm "
  Continue without CA Certificate (EG: option 'noca') ? " yes "
Your PKI does not include a CA Certificate.
You can export your User Certificate to a $pkcs_type file
but the CA Certificate will not be included."

				# --batch mode does not allow
				# on-the-fly command changes
				if [ "$EASYRSA_BATCH" ]; then
					die "export-$pkcs_type: Missing CA"
				fi
				want_ca=""
			fi
		;;
		p8|p1)
			: # Not required
		;;
		*) die "Unknown PKCS type: $pkcs_type"
		esac
	fi

	# Check for key, if required
	if [ "$want_key" ]; then
		if [ -e "$key_in" ]; then
			: #ok
		else
			case "$pkcs_type" in
			p12)
				warn "\
Missing Private Key, expected at:
* $key_in"
				confirm "
  Continue without Private Key (EG: option 'nokey') ? " yes "
Your PKI does not include a Private Key for '$file_name_base'.
You can export your User Certificate to a '$pkcs_type' file
but the Private Key will not be included."

				# --batch mode does not allow
				# on-the-fly command changes
				if [ "$EASYRSA_BATCH" ]; then
					die "export-$pkcs_type: Missing key"
				fi
				nokeys=-nokeys
			;;
			p8|p1)
				user_error "\
Missing Private Key, expected at:
* $key_in"
			;;
			p7)
				: # Not required
			;;
			*) die "Unknown PKCS type: $pkcs_type"
			esac
		fi
	fi

	# Check for certificate, if required
	if [ -e "$crt_in" ]; then
		: # ok
	else
		case "$pkcs_type" in
		p12|p7)
			user_error "\
Missing User Certificate, expected at:
* $crt_in"
		;;
		p8|p1)
			: # Not required
		;;
		*) die "Unknown PKCS type: $pkcs_type"
		esac
	fi

	# For 'nopass' PKCS requires an explicit empty password
	if [ "$EASYRSA_NO_PASS" ]; then
		EASYRSA_PASSIN=pass:
		EASYRSA_PASSOUT=pass:
		unset -v cipher # pkcs#1 only
	fi

	# Complete export
	case "$pkcs_type" in
	p12)
		pkcs_out="$EASYRSA_PKI/private/$file_name_base.p12"

		# export the p12:
		easyrsa_openssl pkcs12 -export \
			-in "$crt_in" \
			-out "$pkcs_out" \
			${nokeys} \
			-inkey "$key_in" \
			${want_ca:+ -certfile "$crt_ca"} \
			${friendly_name:+ -name "$friendly_name"} \
			${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
			${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \
				|| die "Failed to export PKCS#12"
	;;
	p7)
		pkcs_out="$EASYRSA_PKI/issued/$file_name_base.p7b"

		# export the p7:
		easyrsa_openssl crl2pkcs7 -nocrl \
			-certfile "$crt_in" \
			-out "$pkcs_out" \
			${want_ca:+ -certfile "$crt_ca"} \
				|| die "Failed to export PKCS#7"
	;;
	p8)
		pkcs_out="$EASYRSA_PKI/private/$file_name_base.p8"

		# export the p8:
		easyrsa_openssl pkcs8 -topk8 \
			-in "$key_in" \
			-out "$pkcs_out" \
			${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
			${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \
				|| die "Failed to export PKCS#8"
	;;
	p1)
		pkcs_out="$EASYRSA_PKI/private/$file_name_base.p1"

		# OpenSSLv3 requires -traditional for PKCS#1
		# Otherwise, OpenSSLv3 outputs PKCS#8
		[ "$verify_ssl_lib_ok" ] || \
			die "export_pkcs.p1: verify_ssl_lib_ok FAIL"

		if [ "$openssl_v3" ]; then
			traditional=-traditional
		else
			unset -v traditional
		fi

		# export the p1:
		easyrsa_openssl rsa \
			-in "$key_in" \
			-out "$pkcs_out" \
			${traditional} \
			${cipher} \
			${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
			${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \
				|| die "Failed to export PKCS#1"
	;;
	*) die "Unknown PKCS type: $pkcs_type"
	esac

	notice "\
Successful export of $pkcs_type file. Your exported file is at:
* $pkcs_out"

	return 0
} # => export_pkcs()

# set-pass backend legacy
set_pass_legacy() {
	# key type, supplied internally
	# from frontend command call (rsa/ec)
	key_type="$1"
	shift

	[ "$1" ] || user_error "\
Unable to set password: incorrect command syntax.
Run easyrsa without commands for usage and command help."

	# values supplied by the user:
	raw_file="$1"
	shift

	file="$EASYRSA_PKI/private/${raw_file}.key"

	# parse command options
	cipher="-aes256"
	unset -v nopass
	while [ "$1" ]; do
		case "$1" in
			nopass)
				[ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1
			;;
			file) file="$raw_file" ;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	# If nopass then do not encrypt else encrypt with password.
	if [ "$EASYRSA_NO_PASS" ]; then
		unset -v cipher
	fi

	[ -e "$file" ] || user_error "\
Missing private key: expected to find the private key file at:
* $file"

	notice "\
If the key is encrypted then you must supply the current password.
${cipher:+You will then enter a new password for this key.$NL}"

	# Set password
	out_key_tmp=""
	easyrsa_mktemp out_key_tmp || \
		die "set_pass_legacy - easyrsa_mktemp out_key_tmp"

	easyrsa_openssl "$key_type" -in "$file" -out "$out_key_tmp" \
		${cipher:+ "$cipher"} \
		${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
		${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} || die "\
Failed to change the private key passphrase.
See above for possible openssl error messages."

	# Move old key-file out of the way
	mv "$file" "${file}.tmp" || \
		die "Failed to move the old-key file."

	# Move new key-file into place
	if mv "$out_key_tmp" "$file"; then
		rm -f "${file}.tmp"
	else
		mv -f "${file}.tmp" "$file"
		die "Failed to update the private key file."
	fi

	notice "Key passphrase successfully changed"

	return 0
} # => set_pass_legacy()

# set-pass backend
set_pass() {
	# values supplied by the user:
	raw_file="$1"
	file="$EASYRSA_PKI/private/$raw_file.key"

	if [ "$raw_file" ]; then
		shift
	else
		user_error "\
Missing argument: no name/file supplied."
	fi

	# parse command options
	cipher="-aes256"
	while [ "$1" ]; do
		case "$1" in
			nopass)
				[ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1
			;;
			file) file="$raw_file" ;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	# If nopass then do not encrypt else encrypt with password.
	if [ "$EASYRSA_NO_PASS" ]; then
		unset -v cipher
	fi

	[ -e "$file" ] || user_error "\
Missing private key: expected to find the private key file at:
* $file"

	notice "\
If the key is encrypted then you must supply the current password.
${cipher:+You will then enter a new password for this key.$NL}"

	# Set password
	out_key_tmp=""
	easyrsa_mktemp out_key_tmp || \
		die "set_pass - easyrsa_mktemp out_key_tmp"

	easyrsa_openssl pkey -in "$file" -out "$out_key_tmp" \
		${cipher:+ "$cipher"} \
		${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
		${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} || \
			die "Failed to change the private key passphrase."

	# Move old key-file out of the way
	mv "$file" "${file}.tmp" || \
		die "Failed to move the old-key file."

	# Move new key-file into place
	if mv "$out_key_tmp" "$file"; then
		rm -f "${file}.tmp"
	else
		mv -f "${file}.tmp" "$file"
		die "Failed to update the private key file."
	fi

	key_update=changed
	[ "$EASYRSA_NO_PASS" ] && key_update=removed
	notice "Key passphrase successfully $key_update"
} # => set_pass()

# update-db backend
update_db() {
	easyrsa_openssl ca -utf8 -updatedb \
		${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} || \
			die "Failed to perform update-db."
} # => update_db()

# Display subjectAltName
display_san() {
	[ "$#" = 2 ] || die "\
display_san - input error"

	format="$1"
	path="$2"
	shift 2

	if echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName; then
		# Print user defined SAN
			print "$(\
		echo "$EASYRSA_EXTRA_EXTS" | grep subjectAltName | \
		sed 's/^[[:space:]]*subjectAltName[[:space:]]*=[[:space:]]*//'
		)"

	else
		# Generate a SAN
			san="$(
		x509v3san='X509v3 Subject Alternative Name:'
		easyrsa_openssl "$format" -in "$path" -noout -text | sed -n \
		"/${x509v3san}/{n;s/ //g;s/IPAddress:/IP:/g;s/RegisteredID/RID/;p;}"
		)"

		# Print auto SAN
		[ "$san" ] && print "$san"
	fi
} # => display_san()

# display cert DN info on a req/X509, passed by full pathname
display_dn() {
	[ "$#" = 2 ] || die "\
display_dn - input error"

	format="$1"
	path="$2"
	shift 2

	# Display DN
	name_opts="utf8,sep_multiline,space_eq,lname,align"
	print "$(
		easyrsa_openssl "$format" -in "$path" -noout -subject \
			-nameopt "$name_opts"
		)"

	# Display SAN, if present
	san="$(display_san "$format" "$path")"
	if [ "$san" ]; then
		print ""
		print "X509v3 Subject Alternative Name:"
		print "    $san"
	fi
} # => display_dn()

# generate default SAN from req/X509, passed by full pathname
default_server_san() {
	[ "$#" = 1 ] || die "\
default_server_san - input error"

	path="$1"
	shift

	# Command line support for <file_name_base>
	if [ -e "$path" ]; then
		: # ok
	else
		path="${EASYRSA_PKI}/reqs/${path}.req"
		[ -e "$path" ] || \
			user_error "Missing file: $path"
	fi

	# Extract CN from DN
	cn="$(
		easyrsa_openssl req -in "$path" -noout -subject \
			-nameopt sep_multiline |
				awk -F'=' '/^  *CN=/{print $2}'
		)"

	# See: https://github.com/OpenVPN/easy-rsa/issues/576
	# Select default SAN
	if echo "$cn" | grep -q \
		-E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'
	then
		print "subjectAltName = IP:$cn"
	else
		print "subjectAltName = DNS:$cn"
	fi
} # => default_server_san()

# Verify certificate against CA
verify_cert() {
	# pull filename base:
	[ "$1" ] || user_error "\
Error: didn't find a <file-name-base> 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

	# function opts support
	while [ "$1" ]; do
		case "$1" in
			# batch flag, return status [0/1] to calling
			# program.  Otherwise, exit 0 on completion.
			batch) EASYRSA_BATCH=1 ;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	in_dir="$EASYRSA_PKI"
	ca_crt="$in_dir/ca.crt"
	crt_in="$in_dir/issued/$file_name_base.crt"

	# Cert file must exist
	[ -e "$crt_in" ] || user_error "\
No certificate found for the input: '$crt_in'"

	# Verify file is a valid cert
	verify_file x509 "$crt_in" || user_error "\
Input is not a valid certificate: $crt_in"

	# Silent SSL or not
	if [ "$EASYRSA_SILENT_SSL" ]; then
		# Test SSL out
		# openssl direct call because error is expected
		if OPENSSL_CONF=/dev/null "$EASYRSA_OPENSSL" verify \
			-CAfile "$ca_crt" "$crt_in" 1>/dev/null
		then
			verify_cert_ok=1
		else
			unset -v verify_cert_ok
		fi
	else
		if OPENSSL_CONF=/dev/null \
			"$EASYRSA_OPENSSL" verify \
				-CAfile "$ca_crt" "$crt_in"
		then
			verify_cert_ok=1
		else
			unset -v verify_cert_ok
		fi
	fi

	# Return cert status
	if [ "$verify_cert_ok" ]; then
		notice "\
  Certificate name:   $file_name_base
  Verfication status: GOOD"
	else
		notice "\
  Certificate name:   $file_name_base
  Verfication status: FAILED"
		# Exit with error (batch mode)
		if [ "$EASYRSA_BATCH" ]; then
			# exit with error at cleanup
			easyrsa_exit_with_error=1
			# Return error for internal callers
			return 1
		fi
	fi
} # => verify_cert()

# verify a file seems to be a valid req/X509
verify_file() {
	format="$1"
	path="$2"
	easyrsa_openssl "$format" -in "$path" -noout 2>/dev/null
} # => verify_file()

# show-* command backend
# Prints req/cert details in a readable format
show() {
	type="$1"
	name="$2"
	in_file=""
	format=""
	[ "$name" ] || user_error "\
Missing expected <file_name_base> argument.
Run easyrsa without commands for usage help."
	shift 2

	# opts support
	type_opts="-${type}opt"
	out_opts="no_pubkey,no_sigdump"
	name_opts="utf8,sep_multiline,space_eq,lname,align"
	while [ "$1" ]; do
		case "$1" in
			full) out_opts= ;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	# Determine cert/req type (v2)
	case "$type" in
	cert)
		in_file="$EASYRSA_PKI/issued/$name.crt"
		format="x509"
	;;
	req)
		in_file="$EASYRSA_PKI/reqs/$name.req"
		format="req"
	;;
	crl)
		in_file="$EASYRSA_PKI/$name.pem"
		format="crl"
		unset -v type_opts out_opts name_opts
	;;
	*) die "Unrecognised type: $type"
	esac

	# Verify file exists and is of the correct type
	[ -e "$in_file" ] || user_error "\
No such '$type' type file with a <file_name_base> of '$name'.
Expected to find this file at:
* $in_file"

	verify_file "$format" "$in_file" || user_error "\
This file is not a valid $type file:
* $in_file"

	notice "\
Showing '$type' details for: '$name'

This file is stored at:
* $in_file${NL}"

	easyrsa_openssl "$format" -in "$in_file" -noout -text \
		${type_opts:+ "$type_opts" "$out_opts"} \
		${name_opts:+ -nameopt "$name_opts"} || \
			die "OpenSSL failure to process the input"
} # => show()

# show-ca command backend
# Prints CA cert details in a readable format
show_ca() {
	# opts support
	out_opts="no_pubkey,no_sigdump"
	name_opts="utf8,sep_multiline,space_eq,lname,align"
	while [ "$1" ]; do
		case "$1" in
			full) out_opts= ;;
			*) warn "Ignoring unknown command option: '$1'"
		esac
		shift
	done

	in_file="$EASYRSA_PKI/ca.crt"
	format="x509"

	# Verify file exists and is of the correct type
	[ -e "$in_file" ] || user_error "\
No such $type file with a basename of '$name' is present.
Expected to find this file at:
$in_file"

	verify_file "$format" "$in_file" || user_error "\
This file is not a valid $type file:
$in_file"

	notice "\
Showing details for CA certificate, at:
* $in_file${NL}"

	easyrsa_openssl "$format" -in "$in_file" -noout -text \
		-nameopt "$name_opts" -certopt "$out_opts" || \
			die "OpenSSL failure to process the input"
} # => show_ca()

# Certificate X509v3 Extended Key Usage
ssl_cert_x509v3_eku() {
	[ "$1" ] || die "ssl_cert_x509v3_eku - Missing input"

	# check input file name
	if [ -e "$1" ]; then
		__crt="$1"
	else
		__crt="${EASYRSA_PKI}/issued/${1}.crt"
		[ -e "$__crt" ] || \
			die "ssl_cert_x509v3_eku - Missing cert '$__crt'"
	fi

	# Set output variable
	__var="$2"
	shift "$#"

	# required variables
	__pattern="X509v3 Extended Key Usage:"
	__cli="TLS Web Client Authentication"
	__srv="TLS Web Server Authentication"
	__srv_cli="${__srv}, ${__cli}"

	# Extract certificate usage from old cert
	__eku="$(
		easyrsa_openssl x509 -in "${__crt}" -noout -text | \
			sed -n "/${__pattern}/{n;s/^ *//g;p;}"
		)"

	case "$__eku" in
	"$__cli")
		__type=client
	;;
	"$__srv")
		__type=server
	;;
	"$__srv_cli")
		__type=serverClient
	;;
	*) die "Unknown key usage: $__eku"
	esac

	# Set variable to return
	if [ "$__var" ]; then
		force_set_var "$__var" "$__type"
	else
		information "${NL}* EasyRSA Certificate type: $__type"
	fi
	unset -v __crt __var __pattern __eku __type
} # => ssl_cert_x509v3_eku()

# get the serial number of the certificate -> serial=XXXX
ssl_cert_serial() {
	[ "$#" = 2 ] || die "ssl_cert_serial - input error"
	[ -f "$1" ] || die "ssl_cert_serial - missing cert"

	fn_ssl_out="$(
		easyrsa_openssl x509 -in "$1" -noout -serial
		)"  || die "ssl_cert_serial - failed: -serial"
	# remove the serial= part -> we only need the XXXX part
	fn_ssl_out="${fn_ssl_out##*=}"

	force_set_var "$2" "$fn_ssl_out" || \
		die "ssl_cert_serial - failed to set var '$*'"

	unset -v fn_ssl_out
} # => ssl_cert_serial()

# Get certificate start date
ssl_cert_not_before_date() {
	verbose "DEPRECATED: ssl_cert_not_before_date()"
	[ "$#" = 2 ] || die "\
ssl_cert_not_before_date - input error"
	[ -f "$1" ] || die "\
ssl_cert_not_before_date - missing cert"

	fn_ssl_out="$(
		easyrsa_openssl x509 -in "$1" -noout -startdate
		)" || die "\
ssl_cert_not_before_date - failed: -startdate"

	fn_ssl_out="${fn_ssl_out#*=}"

	force_set_var "$2" "$fn_ssl_out" || die "\
ssl_cert_not_before_date - failed to set var '$*'"

	unset -v fn_ssl_out
} # => ssl_cert_not_before_date()

# Get certificate end date
ssl_cert_not_after_date() {
	verbose "DEPRECATED: ssl_cert_not_after_date()"
	[ "$#" = 2 ] || die "\
ssl_cert_not_after_date - input error"
	[ -f "$1" ] || die "\
ssl_cert_not_after_date - missing cert"

	fn_ssl_out="$(
		easyrsa_openssl x509 -in "$1" -noout -enddate
		)" || die "\
ssl_cert_not_after_date - failed: -enddate"

	fn_ssl_out="${fn_ssl_out#*=}"

	force_set_var "$2" "$fn_ssl_out" || die "\
ssl_cert_not_after_date - failed to set var '$*'"

	unset -v fn_ssl_out
} # => ssl_cert_not_after_date()

# SSL -- v3 -- startdate iso_8601
iso_8601_cert_startdate() {
	verbose "NEW: iso_8601_cert_startdate"
	[ "$#" = 2 ] || die "\
iso_8601_cert_startdate: input error"
	[ -f "$1" ] || die "\
iso_8601_cert_startdate: missing cert"

	# On error return, let the caller decide what to do
	if fn_ssl_out="$(
		easyrsa_openssl x509 -in "$1" -noout \
			-startdate -dateopt iso_8601
		)"
	then
		: # ok
	else
		# The caller MUST assess this error
		verbose "\
iso_8601_cert_startdate: GENERATED ERROR"
		return 1
	fi

	fn_ssl_out="${fn_ssl_out#*=}"

	force_set_var "$2" "$fn_ssl_out" || die "\
iso_8601_cert_startdate: failed to set var '$*'"

	unset -v fn_ssl_out
} # => iso_8601_cert_startdate()

# SSL -- v3 -- enddate iso_8601
iso_8601_cert_enddate() {
	verbose "NEW: iso_8601_cert_enddate"
	[ "$#" = 2 ] || die "\
iso_8601_cert_enddate: input error"
	[ -f "$1" ] || die "\
iso_8601_cert_enddate: missing cert"

	# On error return, let the caller decide what to do
	if fn_ssl_out="$(
		easyrsa_openssl x509 -in "$1" -noout \
			-enddate -dateopt iso_8601
		)"
	then
		: # ok
	else
		# The caller MUST assess this error
		verbose "\
iso_8601_cert_enddate: GENERATED ERROR"
		return 1
	fi

	fn_ssl_out="${fn_ssl_out#*=}"

	force_set_var "$2" "$fn_ssl_out" || die "\
iso_8601_cert_enddate: failed to set var '$*'"

	unset -v fn_ssl_out
} # => iso_8601_cert_enddate()

# iso_8601_timestamp_to_seconds since epoch
iso_8601_timestamp_to_seconds() {
	verbose "NEW: iso_8601_timestamp_to_seconds"
	# check input
	[ "$#" = 2 ] || die "\
iso_8601_timestamp_to_seconds: input error"

	in_date="$1"
	verbose "\
NEW: iso_8601_timestamp_to_seconds: in_date=$in_date"

	# Consume $in_date string
	yyyy="${in_date%%-*}"

	# When yyyy is only two digits prepend century
	if [ "${#yyyy}" = 2 ]; then
		yyyy="${yyyy#0}"
		if [ "$yyyy" -lt 70 ]; then
			if [ "${#yyyy}" = 2 ]; then
				yyyy="20${yyyy}"
			else
				yyyy="200${yyyy}"
			fi
		else
			yyyy="19${yyyy}"
		fi
	fi
	verbose "\
NEW: iso_8601_timestamp_to_seconds: yyyy: $yyyy"

	# yyyy must be four digits now
	# Caller MUST assess this error
	if [ "${#yyyy}" = 4 ]; then
		: # ok
	else
		verbose "\
NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (yyyy=$yyyy)"
		return 1
	fi

	# Leap years
	leap_years="$(( (yyyy - 1970 + 2 ) / 4 ))"
	is_leap_year="$(( (yyyy - 1970 + 2 ) % 4 ))"
	if [ "$is_leap_year" = 0 ]; then
		leap_years="$(( leap_years - 1 ))"
		leap_day=1
		verbose "\
NEW: iso_8601_timestamp_to_seconds: is_leap_year=TRUE"
	else
		leap_day=0
		verbose "\
NEW: iso_8601_timestamp_to_seconds: is_leap_year=FALSE"
	fi
	unset -v is_leap_year

	in_date="${in_date#*-}"
	mm="${in_date%%-*}"
	in_date="${in_date#*-}"
	dd="${in_date%% *}"
	in_date="${in_date#* }"
	HH="${in_date%%:*}"
	in_date="${in_date#*:}"
	MM="${in_date%%:*}"
	in_date="${in_date#*:}"
	SS="${in_date%?}"
	in_date="${in_date#??}"
	TZ="$in_date"
	unset -v in_date

	# Check that TZ is a single character
	if [ "${#TZ}" = 1 ]; then
		: # ok
	else
		# Caller MUST assess this error
		verbose "\
NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (TZ=$TZ)"
		return 1
	fi

	# number of days per month
	case "$mm" in
	01) mdays="$(( 0 ))" ;;
	02) mdays="$(( 31 ))" ;;
	03) mdays="$(( 31+28+leap_day ))" ;;
	04) mdays="$(( 31+28+leap_day+31 ))" ;;
	05) mdays="$(( 31+28+leap_day+31+30 ))" ;;
	06) mdays="$(( 31+28+leap_day+31+30+31 ))" ;;
	07) mdays="$(( 31+28+leap_day+31+30+31+30 ))" ;;
	08) mdays="$(( 31+28+leap_day+31+30+31+30+31 ))" ;;
	09) mdays="$(( 31+28+leap_day+31+30+31+30+31+31 ))" ;;
	10) mdays="$(( 31+28+leap_day+31+30+31+30+31+31+30 ))" ;;
	11) mdays="$(( 31+28+leap_day+31+30+31+30+31+31+30+31 ))" ;;
	12) mdays="$(( 31+28+leap_day+31+30+31+30+31+31+30+31+30 ))" ;;
	# This means the input date was not iso_8601
	*)
		# Caller MUST assess this error
		verbose "\
NEW: iso_8601_timestamp_to_seconds: GENERATED ERROR (mm=$mm)"
		return 1
	esac

	# Remove leading ZERO. eg: SS = 09
	[ "$yyyy" = "${yyyy#0}" ] || die "Leading zero: yyyy: $yyyy"
	mm="${mm#0}"
	dd="${dd#0}"
	HH="${HH#0}"
	MM="${MM#0}"
	SS="${SS#0}"

	# Calculate seconds since epoch
	out_seconds="$((
		(( yyyy - 1970 ) * ( 60 * 60 * 24 * 365 ))
		+ (( leap_years ) * ( 60 * 60 * 24 ))
		+ (( mdays ) * ( 60 * 60 * 24 ))
		+ (( dd - 1 ) * ( 60 * 60 * 24 ))
		+ (( HH ) * ( 60 * 60 ))
		+ (( MM ) * ( 60 ))
		+ SS
		))" || die "\
iso_8601_timestamp_to_seconds: out_seconds=$out_seconds"

	# Return out_seconds
	force_set_var "$2" "$out_seconds" || die "\
iso_8601_timestamp_to_seconds: \
- force_set_var - $2 - $out_seconds"

	unset -v in_date out_seconds leap_years \
		yyyy mm dd HH MM SS TZ
} # => iso_8601_timestamp_to_seconds()

# Number of days from NOW@today as timestamp seconds
days_to_timestamp_s() {
	verbose "REQUIRED: days_to_timestamp_s: uses date"
	# check input
	[ "$#" = 2 ] || die "\
days_to_timestamp_s: input error"

	in_days="$1"
	in_seconds="$(( in_days * 60 * 60 * 24 ))"

	# There are NO OS dependencies for this use of date
	# OS dependencies
	# Linux and Windows
	# date.exe does not allow +%s as input
	# MacPorts GNU date
	if timestamp_s="$(
			date +%s 2>/dev/null
			)"
	then : # ok

	# Darwin, BSD
	elif timestamp_s="$(
			date +%s 2>/dev/null
			)"
	then : # ok

	# busybox
	elif timestamp_s="$(
			busybox date +%s 2>/dev/null
			)"
	then : # ok

	# Something else
	else
		die "\
days_to_timestamp_s: 'date +%s' failed"
	fi

	# Add period
	timestamp_s="$(( timestamp_s + in_seconds ))"

	# Return timestamp_s
	force_set_var "$2" "$timestamp_s" || die "\
days_to_timestamp_s: force_set_var - $2 - $timestamp_s"

	unset -v in_days in_seconds timestamp_s
} # => days_to_timestamp_s()

# Convert certificate date to timestamp seconds since epoch
# Used to verify iso_8601 calculated seconds since epoch
cert_date_to_timestamp_s() {
	verbose "DEPRECATED: cert_date_to_timestamp_s"
	# check input
	[ "$#" = 2 ] || die "\
cert_date_to_timestamp_s: input error"

#die "* NOT ALLOWED: cert_date_to_timestamp_s()"

	in_date="$1"

	# OS dependencies
	# Linux and Windows
	# date.exe does not allow +%s as input
	# MacPorts GNU date
	if timestamp_s="$(
			date -d "$in_date" +%s \
				2>/dev/null
			)"
	then : # ok

	# Darwin, BSD
	elif timestamp_s="$(
			date -j -f '%b %d %T %Y %Z' \
				"$in_date" +%s 2>/dev/null
			)"
	then : # ok

	# busybox
	elif timestamp_s="$(
			busybox date -D "%b %e %H:%M:%S %Y" \
				-d "$in_date" +%s 2>/dev/null
			)"
	then : # ok

	# Something else
	else
		die "\
cert_date_to_timestamp_s:
'date' failed for in_date=$in_date"
	fi

	# Return timestamp_s
	force_set_var "$2" "$timestamp_s" || die "\
cert_date_to_timestamp_s: force_set_var - $2 - $timestamp_s"

	unset -v in_date timestamp_s
} # => cert_date_to_timestamp_s()

# Build a Windows date.exe compatible input field
# iso_8601 date
db_date_to_iso_8601_date() {
	verbose "iso_8601: db_date_to_iso_8601_date"
	# check input
	[ "$#" = 2 ] || die "\
db_date_to_iso_8601_date - input error"

	# Expected format: '230612235959Z'
	in_date="$1"
	verbose "db_date_to_iso_8601_date: in_date=$in_date"

	# Consume $in_date string
	# yyyy is expected to be only 'yy'
	yyyy="${in_date%???????????}"
	in_date="${in_date#"$yyyy"}"

	# When yyyy is only two digits prepend century
	if [ "${#yyyy}" = 2 ]; then
		yyyy="${yyyy#0}"
		if [ "$yyyy" -lt 70 ]; then
			if [ "${#yyyy}" = 2 ]; then
				yyyy="20${yyyy}"
			else
				yyyy="200${yyyy}"
			fi
		else
			if [ "${#yyyy}" = 2 ]; then
				yyyy="19${yyyy}"
			else
				yyyy="190${yyyy}"
			fi
		fi
	fi
	verbose "db_date_to_iso_8601_date: yyyy=$yyyy"

	mm="${in_date%?????????}"
	in_date="${in_date#"$mm"}"
	dd="${in_date%???????}"
	in_date="${in_date#"$dd"}"
	HH="${in_date%?????}"
	in_date="${in_date#"$HH"}"
	MM="${in_date%???}"
	in_date="${in_date#"$MM"}"
	SS="${in_date%?}"
	in_date="${in_date#"$SS"}"
	TZ="$in_date"

	# Assign iso_8601 date
	out_date="${yyyy}-${mm}-${dd} ${HH}:${MM}:${SS}${TZ}"
	verbose "db_date_to_iso_8601_date: out_date=$out_date"

	# Return out_date
	force_set_var "$2" "$out_date" || die "\
db_date_to_iso_8601_date: force_set_var - $2 - $out_date"

	unset -v in_date out_date yyyy mm dd HH MM SS TZ
} # => db_date_to_iso_8601_date()

# Convert default SSL date to iso_8601 date
# This may not be feasible, due to different languages
# Alow the caller to assess those errors (eg. Fall-back)
cert_date_to_iso_8601_date() {
	verbose "iso_8601-WIP: cert_date_to_iso_8601_date"
	die "BLOCKED: cert_date_to_iso_8601_date"

	# check input
	[ "$#" = 2 ] || die "\
cert_date_to_iso_8601_date: input error"

	# Expected format: 'Mar 21 18:25:01 2023 GMT'
	in_date="$1"

	# Consume in_date string
	mmm="${in_date%% *}"
	in_date="${in_date#"$mmm" }"
	dd="${in_date%% *}"
	in_date="${in_date#"$dd" }"
	HH="${in_date%%:*}"
	in_date="${in_date#"$HH":}"
	MM="${in_date%%:*}"
	in_date="${in_date#"$MM":}"
	SS="${in_date%% *}"
	in_date="${in_date#"$SS" }"
	yyyy="${in_date%% *}"
	in_date="${in_date#"$yyyy" }"
	TZ="$in_date"

	# Assign month number by abbreviation
	case "$mmm" in
	Jan) mm="01" ;;
	Feb) mm="02" ;;
	Mar) mm="03" ;;
	Apr) mm="04" ;;
	May) mm="05" ;;
	Jun) mm="06" ;;
	Jul) mm="07" ;;
	Aug) mm="08" ;;
	Sep) mm="09" ;;
	Oct) mm="10" ;;
	Nov) mm="11" ;;
	Dec) mm="12" ;;
	*)
		information "Only english dates are currently supported."
		warn "cert_date_to_iso_8601_date - Unknown month: '$mmm'"
		# The caller is REQUIRED to assess this error
		return 1
	esac

	# Assign signle letter timezone from abbreviation
	case "$TZ" in
	GMT) TZ=Z ;;
	*)
		information "Only english dates are currently supported."
		warn "cert_date_to_iso_8601_date - Unknown timezone: '$TZ'"
		# The caller is REQUIRED to assess this error
		return 1
	esac

	# Assign iso_8601 date
	out_date="${yyyy}-${mm}-${dd} ${HH}:${MM}:${SS}${TZ}"

	# Return iso_8601 date
	force_set_var "$2" "$out_date" || die "\
cert_date_to_iso_8601: force_set_var - $2 - $out_date"

	unset -v in_date out_date yyyy mmm  mm dd HH MM SS TZ
} # => cert_date_to_iso_8601()

# SC2295: Expansion inside ${..} need to be quoted separately,
# otherwise they match as patterns. (what-ever that means ;-)
# Unfortunately, Windows sh.exe has an weird bug.
# Try in sh.exe: t='   '; s="a${t}b${t}c"; echo "${s%%"${t}"*}"

# Read db
# shellcheck disable=SC2295
read_db() {
	TCT='	' # tab character
	db_in="$EASYRSA_PKI/index.txt"
	pki_r_issued="$EASYRSA_PKI/renewed/issued"
	pki_r_by_sno="$EASYRSA_PKI/renewed/certs_by_serial"
	unset -v target_found

	while read -r db_status db_notAfter db_record; do

		verbose "***** Read next record *****"

		# Recreate temp session
		remove_secure_session || \
			die "read_db - remove_secure_session"
		secure_session || \
			die "read_db - secure_session"

		# Interpret the db/certificate record
		unset -v db_serial db_cn db_revoke_date db_reason
		case "$db_status" in
		V|E)
			# Valid
			db_serial="${db_record%%${TCT}*}"
			db_record="${db_record#*${TCT}}"
			db_cn="${db_record#*/CN=}"; db_cn="${db_cn%%/*}"
			cert_issued="$EASYRSA_PKI/issued/$db_cn.crt"
			cert_r_issued="$pki_r_issued/$db_cn.crt"
			cert_r_by_sno="$pki_r_by_sno/$db_serial.crt"
		;;
		R)
			# Revoked
			db_revoke_date="${db_record%%${TCT}*}"
			db_reason="${db_revoke_date#*,}"
			if [ "$db_reason" = "$db_revoke_date" ]; then
				db_reason="None given"
			else
				db_revoke_date="${db_revoke_date%,*}"
			fi
			db_record="${db_record#*${TCT}}"

			db_serial="${db_record%%${TCT}*}"
			db_record="${db_record#*${TCT}}"
			db_cn="${db_record#*/CN=}"; db_cn="${db_cn%%/*}"
		;;
		*) die "Unexpected status: $db_status"
		esac

		# Output selected status report for this record
		case "$report" in
		expire)
		# Certs which expire before EASYRSA_PRE_EXPIRY_WINDOW days
			case "$db_status" in
			V|E)
				case "$target" in
				'') expire_status ;;
				*)
					if [ "$target" = "$db_cn" ]; then
						expire_status
					fi
				esac
			;;
			*)
				: # Ignore ok
			esac
		;;
		revoke)
		# Certs which have been revoked
			case "$db_status" in
			R)
				case "$target" in
				'') revoke_status ;;
				*)
					if [ "$target" = "$db_cn" ]; then
						revoke_status
					fi
				esac
			;;
			*)
				: # Ignore ok
			esac
		;;
		renew)
		# Certs which have been renewed but not revoked
			case "$db_status" in
			V|E)
				case "$target" in
				'') renew_status ;;
				*)
					if [ "$target" = "$db_cn" ]; then
						renew_status
					fi
				esac
			;;
			*)
				: # Ignore ok
			esac
		;;
		*) die "Unrecognised report: $report"
		esac

		# Is db record for target found
		if [ "$target" = "$db_cn" ]; then
			target_found=1
		fi

	done < "$db_in"

	# Check for target found/valid commonName, if given
	if [ "$target" ]; then
		[ "$target_found" ] || \
			warn "Certificate for $target was not found"
	fi
} # => read_db()

# Expire status
expire_status() {
	unset -v expire_status_cert_exists
	pre_expire_window_s="$((
		EASYRSA_PRE_EXPIRY_WINDOW * 60*60*24
		))"

	# The certificate for CN should exist but may not
	unset -v expire_status_cert_exists
	if [ -e "$cert_issued" ]; then

		verbose "expire_status: cert exists"
		expire_status_cert_exists=1

		# get the serial number of the certificate
		ssl_cert_serial "$cert_issued" cert_serial

		# db serial must match certificate serial, otherwise
		# this is a renewed cert which has been replaced by
		# an issued cert
		if [ "$db_serial" != "$cert_serial" ]; then
			information "\
  expire_status: SERIAL MISMATCH
  db_serial:     $db_serial
  cert_serial:   $cert_serial
  commonName:    $db_cn
  cert_issued:   $cert_issued${NL}"
			#return 0
		fi

		# Get cert end date in iso_8601 format from SSL
		# or fall-back to old format
		# Redirect SSL error to /dev/null here not in function
		cert_not_after_date=
		if iso_8601_cert_enddate \
			"$cert_issued" cert_not_after_date 2>/dev/null
		then
			: # ok
		else
			verbose "\
expire_status: ACCEPTED ERROR-1: \
from iso_8601_cert_enddate"
			verbose "\
expire_status: CONSUMED ERROR: \
FALL-BACK to default SSL date format"
			ssl_cert_not_after_date \
				"$cert_issued" cert_not_after_date
			verbose "\
expire_status: FALL-BACK completed"
		fi

	else
		verbose "expire_status: cert does NOT exist"
		# Translate db date to 8601_date
		cert_not_after_date=
		db_date_to_iso_8601_date \
			"$db_notAfter" cert_not_after_date

		# Translate 8601_date to time-stamp-seconds
		iso_8601_timestamp_to_seconds \
			"$cert_not_after_date" cert_expire_date_s
		# Cert does not exist
	fi

	# Only verify if there is a certificate
	if [ "$expire_status_cert_exists" ]; then

		# Check cert expiry against window
		# openssl direct call because error is expected
		if OPENSSL_CONF=/dev/null \
			"$EASYRSA_OPENSSL" x509 -in "$cert_issued" \
				-noout -checkend "$pre_expire_window_s" \
				1>/dev/null
		then
			expire_msg="will NOT expire"
			will_not_expire=1
			unset -v will_expire
		else
			expire_msg="will expire"
			will_expire=1
			unset -v will_not_expire
		fi
		verbose "expire_status: SSL checkend: $expire_msg"

		# Get timestamp seconds for certificate expiry date
		# Redirection for errout is not necessary here
		cert_expire_date_s=
		if iso_8601_timestamp_to_seconds \
				"$cert_not_after_date" cert_expire_date_s
		then
			: # ok

			# Verify dates via 'date +%s' format
			verbose "\
expire_status: cert_date_to_timestamp_s: for comparison"
			old_cert_expire_date_s=
			cert_date_to_timestamp_s \
				"$cert_not_after_date" old_cert_expire_date_s

			# Prove this works
			if [ "$cert_expire_date_s" = "$old_cert_expire_date_s" ]
			then
				verbose "\
expire_status: ABSOLUTE seconds MATCH:
    cert_expire_date_s=     $cert_expire_date_s
    old_cert_expire_date_s= $old_cert_expire_date_s"
			else
				verbose "\
expire_status: ABSOLUTE seconds do not MATCH:
    cert_expire_date_s=     $cert_expire_date_s
    old_cert_expire_date_s= $old_cert_expire_date_s
    difference=             \
$(( cert_expire_date_s - old_cert_expire_date_s ))"

				# If there is an error then use --days-margin=10
				[ "$EASYRSA_iso_8601_MARGIN" ] || \
					die "\
expire_status - ABSOLUTE seconds mismatch: Use --allow-margin=N"

				# Allows days for margin of error in seconds
				margin_s="$((
					EASYRSA_iso_8601_MARGIN * (60 * 60 * 24) + 1
					))"
				margin_plus_s="$((
					old_cert_expire_date_s + margin_s
					))"
				margin_minus_s="$((
					old_cert_expire_date_s - margin_s
					))"

				if [ "$cert_expire_date_s" -lt "$margin_plus_s" ] &&
					[ "$cert_expire_date_s" -gt "$margin_minus_s" ]
				then
					: # ok
					verbose "\
expire_status: MARGIN seconds ACCEPTED:
    cert_expire_date_s=     $cert_expire_date_s
    old_cert_expire_date_s= $old_cert_expire_date_s
    difference=             \
    $(( cert_expire_date_s - old_cert_expire_date_s ))
    margin_plus_s=          $margin_plus_s
    margin_minus_s=         $margin_minus_s"
				else
					verbose "\
expire_status: MARGIN seconds REJECTED:
    cert_expire_date_s=     $cert_expire_date_s
    old_cert_expire_date_s= $old_cert_expire_date_s
    margin_plus_s=          $margin_plus_s
    margin_minus_s=         $margin_minus_s"

					die "\
expire_status: Verify cert expire date EXCESS mismatch!"
				fi
			fi

			verbose "\
expire_status: cert_date_to_timestamp_s: comparison complete"

		else
			verbose  "\
expire_status: ACCEPTED ERROR-2: \
iso_8601_timestamp_to_seconds"
			verbose "\
expire_status: CONSUMED ERROR: \
FALL-BACK to default SSL date format"

			cert_date_to_timestamp_s \
				"$cert_not_after_date" cert_expire_date_s

			verbose "\
expire_status: FALL-BACK completed"
		fi
	fi

	# Convert number of days to a timestamp in seconds
	cutoff_date_s=
	days_to_timestamp_s \
		"$EASYRSA_PRE_EXPIRY_WINDOW" cutoff_date_s

	# Get the current date/time as a timestamp in seconds
	now_date_s=
	days_to_timestamp_s \
		0 now_date_s

	# Compare and print output
	if [ "$cert_expire_date_s" -lt "$cutoff_date_s" ]; then
		# Cert expires in less than grace period
		if [ "$will_not_expire" ]; then
			die "\
EasyRSA: will expire - SSL: will NOT expire"
		fi
		if [ "$cert_expire_date_s" -gt "$now_date_s" ]; then
			verbose "expire_status: Valid -> expiring"
			printf '%s%s\n' \
				"$db_status | Serial: $db_serial | " \
				"Expires: $cert_not_after_date | CN: $db_cn"
		else
			verbose "expire_status: Expired"
			printf '%s%s\n' \
				"$db_status | Serial: $db_serial | " \
				"Expired: $cert_not_after_date | CN: $db_cn"
		fi
	else
		if [ "$will_expire" ]; then
			die "\
EasyRSA: will NOT expire - SSL: will expire"
		fi
		verbose "expire_status: Valid -> NOT expiring"
	fi
} # => expire_status()

# Revoke status
revoke_status() {
	# Translate db date to usable date
	cert_revoke_date=
	db_date_to_iso_8601_date "$db_revoke_date" cert_revoke_date

	printf '%s%s%s\n' \
		"$db_status | Serial: $db_serial | " \
		"Revoked: $cert_revoke_date | " \
		"Reason: $db_reason | CN: $db_cn"
} # => revoke_status()

# Renewed status
# renewed certs only remain in the renewed folder until revoked
# Only ONE renewed cert with unique CN can exist in renewed folder
renew_status() {
	# Does a Renewed cert exist ?
	# files in issued are file name, or in serial are SerialNumber
	unset -v \
		cert_file_in cert_is_issued cert_is_serial renew_is_old

	# Find renewed/issued/CN
	if [ -e "$cert_r_issued" ]; then
		cert_file_in="$cert_r_issued"
		cert_is_issued=1
	fi

	# Find renewed/cert_by_serial/SN
	if [ -e "$cert_r_by_sno" ]; then
		cert_file_in="$cert_r_by_sno"
		cert_is_serial=1
		renew_is_old=1
	fi

	# Both should not exist
	if [ "$cert_is_issued" ] && [ "$cert_is_serial" ]; then
		die "Too many certs"
	fi

	# If a renewed cert exists
	if [ "$cert_file_in" ]; then
		# get the serial number of the certificate
		ssl_cert_serial "$cert_file_in" cert_serial

		# db serial must match certificate serial, otherwise
		# this is an issued cert that replaces a renewed cert
		if [ "$db_serial" != "$cert_serial" ]; then
			information "\
serial mismatch:
  db_serial:    $db_serial
  cert_serial:  $cert_serial
  cert_file_in: $cert_file_in"
			return 0
		fi

		# Use cert date
		# Assigns cert_not_after_date
		ssl_cert_not_after_date \
			"$cert_file_in" cert_not_after_date

		# Highlight renewed/cert_by_serial
		if [ "$renew_is_old" ]; then
			printf '%s%s\n' \
				"*** $db_status | Serial: $db_serial | " \
				"Expires: $cert_not_after_date | CN: $db_cn"
		else
			printf '%s%s\n' \
				"$db_status | Serial: $db_serial | " \
				"Expires: $cert_not_after_date | CN: $db_cn"
		fi

	else
		# Cert is valid but not renewed
		: # ok - ignore
	fi
} # => renew_status()

# cert status reports
status() {
	[ "$#" -gt 0 ] || die "status - input error"
	report="$1"
	target="$2"

	# test fix: https://github.com/OpenVPN/easy-rsa/issues/819
	export LC_TIME=C.UTF-8

	# If no target file then add Notice
	if [ -z "$target" ]; then
		# Select correct Notice
		case "$report" in
		expire)
			notice "\
* Showing certificates which expire in less than \
$EASYRSA_PRE_EXPIRY_WINDOW days (--days):"
		;;
		revoke)
			notice "\
* Showing certificates which are revoked:"
		;;
		renew)
			notice "\
* Showing certificates which have been renewed but NOT revoked:

*** Marks those which require 'rewind-renew' \
before they can be revoked."
		;;
		*) warn "Unrecognised report: $report"
		esac
	fi

	# Create report
	read_db

} # => status()

# set_var is not known by shellcheck, therefore:
# Fake declare known variables for shellcheck
# Use these options without this function:
# -o all -e 2250,2244,2248 easyrsa
satisfy_shellcheck() {
	die "Security feature enabled!"
	# Add more as/if required

	# Enable the heredoc for a peek
#cat << SC2154
	EASYRSA=
	EASYRSA_OPENSSL=
	EASYRSA_PKI=
	EASYRSA_DN=
	EASYRSA_REQ_COUNTRY=
	EASYRSA_REQ_PROVINCE=
	EASYRSA_REQ_CITY=
	EASYRSA_REQ_ORG=
	EASYRSA_REQ_EMAIL=
	EASYRSA_REQ_OU=
	EASYRSA_ALGO=
	EASYRSA_KEY_SIZE=
	EASYRSA_CURVE=
	EASYRSA_CA_EXPIRE=
	EASYRSA_CERT_EXPIRE=
	EASYRSA_PRE_EXPIRY_WINDOW=
	EASYRSA_CRL_DAYS=
	EASYRSA_NS_SUPPORT=
	EASYRSA_NS_COMMENT=
	EASYRSA_TEMP_DIR=
	EASYRSA_REQ_CN=
	EASYRSA_DIGEST=

	EASYRSA_SSL_CONF=
	EASYRSA_SAFE_CONF=
	OPENSSL_CONF=

	#EASYRSA_KDC_REALM=

	EASYRSA_RAND_SN=
	KSH_VERSION=
#SC2154

} # => satisfy_shellcheck()

# Identify host OS
detect_host() {
	unset -v \
		easyrsa_ver_test easyrsa_host_os easyrsa_host_test \
			easyrsa_win_git_bash

	# Detect Windows
	[ "${OS}" ] && easyrsa_host_test="${OS}"

	# shellcheck disable=SC2016 # expansion inside '' blah
	easyrsa_ksh=\
'@(#)MIRBSD KSH R39-w32-beta14 $Date: 2013/06/28 21:28:57 $'

	[ "${KSH_VERSION}" = "${easyrsa_ksh}" ] && \
		easyrsa_host_test="${easyrsa_ksh}"
	unset -v easyrsa_ksh

	# If not Windows then nix
	if [ "${easyrsa_host_test}" ]; then
		easyrsa_host_os=win
		easyrsa_uname="${easyrsa_host_test}"
		easyrsa_shell="$SHELL"
		# Detect Windows git/bash
		if [ "${EXEPATH}" ]; then
			easyrsa_shell="$SHELL (Git)"
			easyrsa_win_git_bash="${EXEPATH}"
			# If found then set openssl NOW!
			#[ -e /usr/bin/openssl ] && \
			#	set_var EASYRSA_OPENSSL /usr/bin/openssl
		fi
	else
		easyrsa_host_os=nix
		easyrsa_uname="$(uname 2>/dev/null)"
		easyrsa_shell="${SHELL:-undefined}"
	fi

	easyrsa_ver_test="${EASYRSA_version%%~*}"
	if [ "$easyrsa_ver_test" ]; then
		host_out="Host: $EASYRSA_version"
	else
		host_out="Host: dev"
	fi

	host_out="\
$host_out | $easyrsa_host_os | $easyrsa_uname | $easyrsa_shell"
	host_out="\
${host_out}${easyrsa_win_git_bash+ | "$easyrsa_win_git_bash"}"
	unset -v easyrsa_ver_test easyrsa_host_test
} # => detect_host()

# Extra diagnostics
show_host() {
	[ "$EASYRSA_SILENT" ] && return
	print_version
	print "$host_out"
	[ "$EASYRSA_DEBUG" ] || return 0
	case "$easyrsa_host_os" in
	win) set ;;
	nix) env ;;
	*) print "Unknown host OS: $easyrsa_host_os"
	esac
} # => show_host()

# Verify the selected algorithm parameters
verify_algo_params() {
	case "$EASYRSA_ALGO" in
	rsa)
		# Set RSA key size
		EASYRSA_ALGO_PARAMS="$EASYRSA_KEY_SIZE"
	;;
	ec)
		# Verify Elliptic curve
		EASYRSA_ALGO_PARAMS=""
		easyrsa_mktemp EASYRSA_ALGO_PARAMS || \
			die "\
verify_algo_params - easyrsa_mktemp EASYRSA_ALGO_PARAMS"

		# Create the required ecparams file
		# call openssl directly because error is expected
		 OPENSSL_CONF=/dev/null \
			"$EASYRSA_OPENSSL" ecparam \
				-name "$EASYRSA_CURVE" \
				-out "$EASYRSA_ALGO_PARAMS" \
				1>/dev/null || die "\
Failed to generate ecparam file (permissions?) at:
* $EASYRSA_ALGO_PARAMS"
	;;
	ed)
		# Verify Edwards curve
		# call openssl directly because error is expected
		 OPENSSL_CONF=/dev/null \
			"$EASYRSA_OPENSSL" genpkey \
				-algorithm "$EASYRSA_CURVE" \
				1>/dev/null || die "\
Edwards Curve $EASYRSA_CURVE not found."
	;;
	*) user_error "\
Unknown algorithm '$EASYRSA_ALGO': Must be 'rsa', 'ec' or 'ed'"
	esac
	verbose "\
verify_algo_params: Params verified for algo '$EASYRSA_ALGO'"
} # => verify_algo_params()

# Check for conflicting input options
mutual_exclusions() {
	# --nopass cannot be used with --passout
	if [ "$EASYRSA_PASSOUT" ]; then
		# --passout MUST take priority over --nopass
		[ "$EASYRSA_NO_PASS" ] && warn "\
Option --passout cannot be used with --nopass|nopass."
		unset -v EASYRSA_NO_PASS
		prohibit_no_pass=1
	fi

	# --silent-ssl requires --batch
	if [ "$EASYRSA_SILENT_SSL" ]; then
		[ "$EASYRSA_BATCH" ] || warn "\
Option --silent-ssl requires batch mode --batch."
	fi

	# --startdate requires --enddate
	# otherwise, --days counts from now
	if [ "$EASYRSA_START_DATE" ]; then
		[ "$EASYRSA_END_DATE" ] || user_error "\
Use of --startdate requires use of --enddate."
	fi

	# --enddate may over-rule EASYRSA_CERT_EXPIRE
	if [ "$EASYRSA_END_DATE" ]; then
		case "$cmd" in
			sign-req|build-*-full|renew|rebuild)
				# User specified alias_days IS over-ruled
				if [ "$alias_days" ]; then
					warn "\
Option --days is over-ruled by option --enddate."
				fi
				unset -v EASYRSA_CERT_EXPIRE alias_days
			;;
			*)
				warn "\
EasyRSA '$cmd' does not support --startdate or --enddate"
				unset -v EASYRSA_START_DATE EASYRSA_END_DATE
		esac
	fi

	# Insecure Windows directory
	if [ "$easyrsa_host_os" = win ]; then
		if echo "$PWD" | grep -q '/Prog.*/OpenVPN/easy-rsa'
		then
			verbose "\
Using Windows-System-Folders for your PKI is NOT SECURE!
Your Easy-RSA PKI CA Private Key is WORLD readable.

To correct this problem, it is recommended that you either:
* Copy Easy-RSA to your User folders and run it from there, OR
* Define your PKI to be in your User folders. EG:
  'easyrsa --pki-dir=\"C:/Users/<your-user-name>/easy-rsa/pki\"\
 <command>'"
		fi
	fi

	verbose "mutual_exclusions: COMPLETED"
} # => mutual_exclusions()

# Select vars in order preference:
# Here sourcing of 'vars' if present occurs.
# If not present, defaults are used to support
# running without a sourced config format.
select_vars() {
	# No vars file will be used
	if [ "$EASYRSA_NO_VARS" ]; then
		verbose "select_vars: EASYRSA_NO_VARS"
		unset -v EASYRSA_VARS_FILE
		# skip the rest of this function
		return

	# User specified vars file will be used ONLY
	elif [ "$EASYRSA_VARS_FILE" ]; then
		# Takes priority, nothing to do
		verbose "select_vars: EASYRSA_VARS_FILE"

	# This is where auto-load goes bananas
	else

		# User specified PKI; if vars exists, use it ONLY
		if [ "$EASYRSA_PKI" ]; then
			if [ -e "$EASYRSA_PKI/vars" ]; then
				verbose "select_vars: source EASYRSA_PKI/vars"
				set_var EASYRSA_VARS_FILE "$EASYRSA_PKI/vars"
			fi
		fi

		# User specified EASYRSA; if vars exists, use it ONLY
		if [ "$EASYRSA" ]; then
			if [ -e "$EASYRSA/vars" ]; then
				verbose "select_vars: EASYRSA/vars"
				set_var EASYRSA_VARS_FILE "$EASYRSA/vars"
			fi
		fi

		# Default PKI; if vars exists, use it ONLY
		if [ -e "$PWD/pki/vars" ] && \
			[ -z "$EASYRSA_PKI" ] && \
			[ -z "$EASYRSA" ]
		then
			# Prevent vars from changing expected PKI.
			# A vars in the PKI MUST always imply EASYRSA_PKI
			# This is NOT backward compatible
			# Use expected value comparison for v3.1.7
			if [ -z "$EASYRSA_VARS_FILE" ]; then
				expected_EASYRSA="$PWD"
				expected_EASYRSA_PKI="$PWD/pki"
			fi

			# Use this for v3.2.0
			# If the pki/vars sets a different PKI then
			# there will be no PKI in the default /pki
			#set_var EASYRSA "$PWD"
			#set_var EASYRSA_PKI "$EASYRSA/pki"

			verbose "select_vars: PWD/pki/vars"
			set_var EASYRSA_VARS_FILE "$PWD/pki/vars"
		fi

		# Default working dir; if vars exists, use it ONLY
		if [ -e "$PWD/vars" ]; then
			verbose "select_vars: PWD/vars"
			set_var EASYRSA_VARS_FILE "$PWD/vars"
		fi
	fi

	# User info
	if [ -z "$EASYRSA_VARS_FILE" ]; then
		[ "$require_pki" ] && information "\
No Easy-RSA 'vars' configuration file exists!"
		EASYRSA_NO_VARS=1
	fi
} # => select_vars()

# Source a vars file
source_vars() {
	# Never use vars file
	if [ "$EASYRSA_NO_VARS" ]; then
		verbose "source_vars: EASYRSA_NO_VARS"
		return
	fi

	# File to be sourced
	target_file="$1"

	# 'vars' MUST not be a directory
	[ -d "$target_file" ] && user_error "\
Missing vars file:
* $target_file"

	# 'vars' now MUST exist
	[ -e "$target_file" ] || user_error "\
Missing vars file:
* $target_file"

	# Installation information
	[ "$require_pki" ] && information "\
Using Easy-RSA 'vars' configuration:
* $target_file"

	# Sanitize vars
	if grep -q \
		-e 'EASYRSA_PASSIN' -e 'EASYRSA_PASSOUT' \
		-e '[^(]`[^)]' \
		"$target_file"
	then
		user_error "\
One or more of these problems has been found in your 'vars' file:
* $target_file

* Use of 'EASYRSA_PASSIN' or 'EASYRSA_PASSOUT':
Storing password information in the 'vars' file is not permitted.

* Use of unsupported characters:
These characters are not supported: \` backtick

Please, correct these errors and try again."
	fi

	# Sanitize vars
	if grep -q \
		-e '[[:blank:]]export[[:blank:]]*' \
		-e '[[:blank:]]unset[[:blank:]]*' \
		"$target_file"
	then
		user_error "\
One or more of these problems has been found in your 'vars' file:
* $target_file

* Use of 'export':
Remove 'export' or replace it with 'set_var'.

* Use of 'unset':
Remove 'unset' ('force_set_var' may also work)."
	fi

	# Enable sourcing 'vars'
	# shellcheck disable=SC2034 # appears unused
	EASYRSA_CALLER=1
	easyrsa_path="$PATH"
	# shellcheck disable=SC2123 # PATH is the shell ..
	PATH=./

	# Test sourcing 'vars' in a subshell
	# shellcheck disable=1090 # can't follow .. vars
	if ( . "$target_file" ); then
		# Source 'vars' now
		# shellcheck disable=1090 # can't follow .. vars
		. "$target_file" || \
			die "Failed to source the '$target_file' file."
	else
		PATH="$easyrsa_path"
		die "Failed to dry-run the '$target_file' file."
	fi

	PATH="$easyrsa_path"
	verbose "source_vars: sourced OK '$target_file'"
	unset -v EASYRSA_CALLER easyrsa_path target_file
} # => source_vars()

# Set defaults
default_vars() {
	# Set defaults, preferring existing env-vars if present
	set_var EASYRSA					"$PWD"
	set_var EASYRSA_OPENSSL			openssl
	set_var EASYRSA_PKI				"$EASYRSA/pki"
	set_var EASYRSA_DN				cn_only
	set_var EASYRSA_REQ_COUNTRY		"US"
	set_var EASYRSA_REQ_PROVINCE	"California"
	set_var EASYRSA_REQ_CITY		"San Francisco"
	set_var EASYRSA_REQ_ORG			"Copyleft Certificate Co"
	set_var EASYRSA_REQ_EMAIL		me@example.net
	set_var EASYRSA_REQ_OU			"My Organizational Unit"
	set_var EASYRSA_REQ_SERIAL		""
	set_var EASYRSA_ALGO			rsa
	set_var EASYRSA_KEY_SIZE		2048

	case "$EASYRSA_ALGO" in
	rsa)
		: # ok
		# default EASYRSA_KEY_SIZE must always be set
		# it must NOT be set selectively because it is
		# present in the SSL config file
	;;
	ec)
		set_var EASYRSA_CURVE		secp384r1
	;;
	ed)
		set_var EASYRSA_CURVE		ed25519
	;;
	*) user_error "\
Algorithm '$EASYRSA_ALGO' is invalid: Must be 'rsa', 'ec' or 'ed'"
	esac

	set_var EASYRSA_CA_EXPIRE		3650
	set_var EASYRSA_CERT_EXPIRE		825
	set_var \
		EASYRSA_PRE_EXPIRY_WINDOW	90
	set_var EASYRSA_CRL_DAYS		180
	set_var EASYRSA_NS_SUPPORT		no
	set_var EASYRSA_NS_COMMENT		\
		"Easy-RSA (3.1.7) Generated Certificate"

	set_var EASYRSA_TEMP_DIR		"$EASYRSA_PKI"
	set_var EASYRSA_REQ_CN			ChangeMe
	set_var EASYRSA_DIGEST			sha256

	set_var EASYRSA_SSL_CONF		\
		"$EASYRSA_PKI/openssl-easyrsa.cnf"
	set_var EASYRSA_SAFE_CONF		\
		"$EASYRSA_PKI/safessl-easyrsa.cnf"

	set_var EASYRSA_KDC_REALM		"CHANGEME.EXAMPLE.COM"

	set_var EASYRSA_MAX_TEMP		4
} # => default_vars()

# Validate expected values for EASYRSA and EASYRSA_PKI
validate_default_vars() {
	unset -v unexpected_error

	# Keep checks separate
	# EASYRSA
	if [ "$expected_EASYRSA" ]; then
		[ "$expected_EASYRSA" = "$EASYRSA" ] || \
			unexpected_error="\
       EASYRSA: $EASYRSA
      Expected: $expected_EASYRSA"
	fi

	# EASYRSA_PKI
	if [ "$expected_EASYRSA_PKI" ]; then
		if [ "$expected_EASYRSA_PKI" = "$EASYRSA_PKI" ]; then
			: # ok
		else
			if [ "$unexpected_error" ]; then
				# Add a new-line Extra separator, for clarity
				unexpected_error="${unexpected_error}${NL}${NL}"
			fi
			unexpected_error="${unexpected_error}\
   EASYRSA_PKI: $EASYRSA_PKI
      Expected: $expected_EASYRSA_PKI"
		fi
	fi

	# Return no error
	[ -z "$unexpected_error" ] && return

	# This is an almost unacceptable error
	invalid_vars=1
	[ "$ignore_vars" ] || user_error "\
The values in the vars file have unexpectedly changed the values for
EASYRSA and/or EASYRSA_PKI. The default pki/vars file is forbidden to
change these values.

     vars-file: $EASYRSA_VARS_FILE

${unexpected_error}"
} # => validate_default_vars()

# Verify working environment
verify_working_env() {
	# Verify SSL Lib - One time ONLY
	verify_ssl_lib

	# Find x509-types but do not fail
	# Not fatal here, used by 'help'
	install_data_to_pki x509-types-only

	# For commands which 'require a PKI' and PKI exists
	if  [ "$require_pki" ]; then
		# Verify PKI is initialised
		verify_pki_init

		# Temp dir MUST exist
		if [ -d "$EASYRSA_TEMP_DIR" ]; then

			# Temp dir session
			secure_session || die "\
verify_working_env - secure-session failed"

			# Install data-files into ALL PKIs
			# This will find x509-types
			# and export EASYRSA_EXT_DIR or die.
			# Other errors only require warning.
			install_data_to_pki vars-setup || warn "\
verify_working_env - install_data_to_pki vars-setup failed"

			# Verify selected algorithm and parameters
			verify_algo_params

			# Check $working_safe_ssl_conf, to build
			# a fully configured safe ssl conf, on the
			# next invocation of easyrsa_openssl()
			if [ "$working_safe_ssl_conf" ]; then
				die "working_safe_ssl_conf must not be set!"
			fi

			# Verify CA is initialised
			if [ "$require_ca" ]; then
				verify_ca_init
			fi

			# Last setup msg
			information "
Using SSL:
* $EASYRSA_OPENSSL $ssl_version"

		else
			# The directory does not exist
			user_error "\
Temporary directory does not exist:
* $EASYRSA_TEMP_DIR"
		fi
	fi
	verbose "verify_working_env: COMPLETED"
} # => verify_working_env()

# variable assignment by indirection.
# Sets '$1' as the value contained in '$2'
# and exports (may be blank)
set_var() {
	[ -z "$*" ] && return
	[ -z "$3" ] || \
		user_error "set_var - excess input '$*'"
	case "$1" in
		*=*) user_error "set_var - var '$1'"
	esac
	eval "export \"$1\"=\"\${$1-$2}\"" && return
	die "set_var - eval '$*'"
} # => set_var()

# sanatize and set var
# nix.sh/win.sh/busybox.sh never return error from unset
# when an invalid variable name 'a=b' is used with a value
# to set, eg. 'c'; This causes EasyRSA to execute:
# eval "export a=b=c". 'set_var EASYRSA_PKI=pki' results in
# $EASYRSA_PKI being set to 'pki=pki-', without error!
# Guard against this possible user error with 'case'.
force_set_var() {
	[ -z "$3" ] || \
		user_error "force_set_var - excess input '$*'"
	case "$1" in
		*=*) user_error "force_set_var - var '$1'"
	esac
	# Guard unset with '|| die', just in case
	unset -v "$1" || die "force_set_var - unset '$1'"
	set_var "$1" "$2" && return
	die "force_set_var - set_var '$*'"
} # => force_set_var()



############################################################################
#
# Create X509-type files
create_x509_type() {
	case "$1" in
	COMMON)
		cat <<- "X509_TYPE_COMMON"
		X509_TYPE_COMMON
	;;
	serverClient)
		create_x509_type_easyrsa
		cat <<- "X509_TYPE_SERV_CLI"
		extendedKeyUsage = serverAuth,clientAuth
		X509_TYPE_SERV_CLI
	;;
	server)
		create_x509_type_easyrsa
		cat <<- "X509_TYPE_SERV"
		extendedKeyUsage = serverAuth
		X509_TYPE_SERV
	;;
	client)
		create_x509_type_easyrsa
		cat <<- "X509_TYPE_CLI"
		extendedKeyUsage = clientAuth
		X509_TYPE_CLI
	;;
	ca)
		cat <<- "X509_TYPE_CA"
		basicConstraints = CA:TRUE
		subjectKeyIdentifier = hash
		authorityKeyIdentifier = keyid:always,issuer:always
		keyUsage = cRLSign, keyCertSign
		X509_TYPE_CA
	;;
	*)
		# Unknown type: User MUST supply the X509 file
		die "create_x509_type - Unknown X509 type: '$1'"
	esac
} # => create_x509_type()

# Create x509-type/easyrsa
# This could be COMMON but not is not suitable for a CA
create_x509_type_easyrsa() {
	cat <<- "X509_TYPE_EASYRSA"
		basicConstraints = CA:FALSE
		subjectKeyIdentifier = hash
		authorityKeyIdentifier = keyid,issuer:always
		keyUsage = digitalSignature,keyEncipherment
		X509_TYPE_EASYRSA
} # => create_x509_type_easyrsa()

# Create vars.example - Minimum settings only
create_vars_example() {
	cat << "VARS_EXAMPLE"
# Easy-RSA 3 parameter settings

# NOTE: If you installed Easy-RSA from your package manager, do not edit
# this file in place -- instead, you should copy the entire easy-rsa directory
# to another location so future upgrades do not wipe out your changes.

# HOW TO USE THIS FILE
#
# vars.example contains built-in examples to Easy-RSA settings. You MUST name
# this file "vars" if you want it to be used as a configuration file. If you
# do not, it WILL NOT be automatically read when you call easyrsa commands.
#
# It is not necessary to use this config file unless you wish to change
# operational defaults. These defaults should be fine for many uses without
# the need to copy and edit the "vars" file.
#
# All of the editable settings are shown commented and start with the command
# "set_var" -- this means any set_var command that is uncommented has been
# modified by the user. If you are happy with a default, there is no need to
# define the value to its default.

# NOTES FOR WINDOWS USERS
#
# Paths for Windows  *MUST* use forward slashes, or optionally double-escaped
# backslashes (single forward slashes are recommended.) This means your path
# to the openssl binary might look like this:
# "C:/Program Files/OpenSSL-Win32/bin/openssl.exe"

# A little housekeeping: DO NOT EDIT THIS SECTION
#
# Easy-RSA 3.x does not source into the environment directly.
# Complain if a user tries to do this:
if [ -z "$EASYRSA_CALLER" ]; then
	echo "You appear to be sourcing an Easy-RSA *vars* file. This is" >&2
	echo "no longer necessary and is disallowed. See the section called" >&2
	echo "*How to use this file* near the top comments for more details." >&2
	return 1
fi

# DO YOUR EDITS BELOW THIS POINT

# If your OpenSSL command is not in the system PATH, you will need to define
# the path here. Normally this means a full path to the executable, otherwise
# you could have left it undefined here and the shown default would be used.
#
# Windows users, remember to use paths with forward-slashes (or escaped
# back-slashes.) Windows users should declare the full path to the openssl
# binary here if it is not in their system PATH.
#
#set_var EASYRSA_OPENSSL	"openssl"
#
# This sample is in Windows syntax -- edit it for your path if not using PATH:
#set_var EASYRSA_OPENSSL	"C:/Program Files/OpenSSL-Win32/bin/openssl.exe"

# Define X509 DN mode.
#
# This is used to adjust which elements are included in the Subject field
# as the DN ("Distinguished Name"). Note that in 'cn_only' mode the
# Organizational fields, listed further below, are not used.
#
# Choices are:
#   cn_only  - Use just a commonName value.
#   org      - Use the "traditional" format:
#              Country/Province/City/Org/Org.Unit/email/commonName
#
#set_var EASYRSA_DN	"cn_only"

# Organizational fields (used with "org" mode and ignored in "cn_only" mode).
# These are the default values for fields which will be placed in the
# certificate.  Do not leave any of these fields blank, although interactively
# you may omit any specific field by typing the "." symbol (not valid for
# email).
#
# NOTE: The following characters are not supported
#       in these "Organizational fields" by Easy-RSA:
#       back-tick (`)
#
#set_var EASYRSA_REQ_COUNTRY	"US"
#set_var EASYRSA_REQ_PROVINCE	"California"
#set_var EASYRSA_REQ_CITY	"San Francisco"
#set_var EASYRSA_REQ_ORG	"Copyleft Certificate Co"
#set_var EASYRSA_REQ_EMAIL	"me@example.net"
#set_var EASYRSA_REQ_OU		"My Organizational Unit"

# Preserve the Distinguished Name field order
# of the certificate signing request
# *Only* effective in --dn-mode=org
#
#set_var EASYRSA_PRESERVE_DN	1

# Set no password mode - This will create the entire PKI without passwords.
# This can be better managed by choosing which entity private keys should be
# encrypted with the following command line options:
# Global option '--no-pass' or command option 'nopass'.
#
#set_var EASYRSA_NO_PASS	1

# Choose a size in bits for your keypairs. The recommended value is 2048.
# Using 2048-bit keys is considered more than sufficient for many years into
# the future. Larger keysizes will slow down TLS negotiation and make key/DH
# param generation take much longer. Values up to 4096 should be accepted by
# most software. Only used when the crypto alg is rsa, see below.
#
#set_var EASYRSA_KEY_SIZE	2048

# The default crypto mode is rsa; ec can enable elliptic curve support.
# Note that not all software supports ECC, so use care when enabling it.
# Choices for crypto alg are: (each in lower-case)
#  * rsa
#  * ec
#  * ed
#
#set_var EASYRSA_ALGO		rsa

# Define the named curve, used in ec & ed modes:
#
#set_var EASYRSA_CURVE		secp384r1

# In how many days should the root CA key expire?
#
#set_var EASYRSA_CA_EXPIRE	3650

# In how many days should certificates expire?
#
#set_var EASYRSA_CERT_EXPIRE	825

# How many days until the next CRL publish date?  Note that the CRL can still
# be parsed after this timeframe passes. It is only used for an expected next
# publication date.
#
#set_var EASYRSA_CRL_DAYS	180

# Random serial numbers by default.
# Set to 'no' for the old incremental serial numbers.
#
#set_var EASYRSA_RAND_SN	"yes"

# Cut-off window for checking expiring certificates.
#
#set_var EASYRSA_PRE_EXPIRY_WINDOW	90

# Define directory for temporary subdirectories.
#
#set_var EASYRSA_TEMP_DIR	"$EASYRSA_PKI"
VARS_EXAMPLE
} # => create_vars_example()

# Create openssl-easyrsa.cnf
create_openssl_easyrsa_cnf() {
	cat << "SSL_CONFIG"
# For use with Easy-RSA 3.0+ and OpenSSL or LibreSSL

####################################################################
[ ca ]
default_ca	= CA_default		# The default ca section

####################################################################
[ CA_default ]

dir		= $ENV::EASYRSA_PKI	# Where everything is kept
certs		= $dir			# Where the issued certs are kept
crl_dir		= $dir			# Where the issued crl are kept
database	= $dir/index.txt	# database index file.
new_certs_dir	= $dir/certs_by_serial	# default place for new certs.

certificate	= $dir/ca.crt		# The CA certificate
serial		= $dir/serial		# The current serial number
crl		= $dir/crl.pem		# The current CRL
private_key	= $dir/private/ca.key	# The private key
RANDFILE	= $dir/.rand		# private random number file

x509_extensions	= basic_exts		# The extensions to add to the cert

# A placeholder to handle the --copy-ext feature:
#%COPY_EXTS%	# Do NOT remove or change this line as --copy-ext support requires it

# This allows a V2 CRL. Ancient browsers don't like it, but anything Easy-RSA
# is designed for will. In return, we get the Issuer attached to CRLs.
crl_extensions	= crl_ext

default_days	= $ENV::EASYRSA_CERT_EXPIRE	# how long to certify for
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 different ways 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 :-)
policy		= policy_anything

# For the 'anything' policy, which defines allowed DN fields
[ policy_anything ]
countryName		= optional
stateOrProvinceName	= optional
localityName		= optional
organizationName	= optional
organizationalUnitName	= optional
commonName		= supplied
emailAddress		= optional
serialNumber	= optional

####################################################################
# Easy-RSA request handling
# We key off $DN_MODE to determine how to format the DN
[ req ]
default_bits		= $ENV::EASYRSA_KEY_SIZE
default_keyfile	= privkey.pem
default_md		= $ENV::EASYRSA_DIGEST
distinguished_name	= $ENV::EASYRSA_DN
x509_extensions		= easyrsa_ca	# The extensions to add to the self signed cert

# A placeholder to handle the $EXTRA_EXTS feature:
#%EXTRA_EXTS%	# Do NOT remove or change this line as $EXTRA_EXTS support requires it

####################################################################
# Easy-RSA DN (Subject) handling

# Easy-RSA DN for cn_only support:
[ cn_only ]
commonName		= Common Name (eg: your user, host, or server name)
commonName_max		= 64
commonName_default	= $ENV::EASYRSA_REQ_CN

# Easy-RSA DN for org support:
[ org ]
countryName			= Country Name (2 letter code)
countryName_default		= $ENV::EASYRSA_REQ_COUNTRY
countryName_min			= 2
countryName_max			= 2

stateOrProvinceName		= State or Province Name (full name)
stateOrProvinceName_default	= $ENV::EASYRSA_REQ_PROVINCE

localityName			= Locality Name (eg, city)
localityName_default		= $ENV::EASYRSA_REQ_CITY

0.organizationName		= Organization Name (eg, company)
0.organizationName_default	= $ENV::EASYRSA_REQ_ORG

organizationalUnitName		= Organizational Unit Name (eg, section)
organizationalUnitName_default	= $ENV::EASYRSA_REQ_OU

commonName			= Common Name (eg: your user, host, or server name)
commonName_max			= 64
commonName_default		= $ENV::EASYRSA_REQ_CN

emailAddress			= Email Address
emailAddress_default		= $ENV::EASYRSA_REQ_EMAIL
emailAddress_max		= 64

serialNumber		= Serial-number (eg, device serial-number)
serialNumber_default	= $ENV::EASYRSA_REQ_SERIAL

####################################################################
# Easy-RSA cert extension handling

# This section is effectively unused as the main script sets extensions
# dynamically. This core section is left to support the odd usecase where
# a user calls openssl directly.
[ basic_exts ]
basicConstraints	= CA:FALSE
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid,issuer:always

# The Easy-RSA CA extensions
[ easyrsa_ca ]

# PKIX recommendations:

subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always,issuer:always

# This could be marked critical, but it's nice to support reading by any
# broken clients who attempt to do so.
basicConstraints = CA:true

# Limit key usage to CA tasks. If you really want to use the generated pair as
# a self-signed cert, comment this out.
keyUsage = cRLSign, keyCertSign

# nsCertType omitted by default. Let's try to let the deprecated stuff die.
# nsCertType = sslCA

# A placeholder to handle the $X509_TYPES and CA extra extensions $EXTRA_EXTS:
#%CA_X509_TYPES_EXTRA_EXTS%	# Do NOT remove or change this line as $X509_TYPES and EXTRA_EXTS demands it

# CRL extensions.
[ crl_ext ]

# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.

# issuerAltName=issuer:copy
authorityKeyIdentifier=keyid:always,issuer:always
SSL_CONFIG
} # => create_openssl_easyrsa_cnf()



############################################################################
# Upgrade v2 PKI to v3 PKI

# You can report problems on the normal openvpn support channels:
# --------------------------------------------------------------------------
#   1. The Openvpn Forum: https://forums.openvpn.net/viewforum.php?f=31
#   2. The #easyrsa IRC channel at libera.chat
#   3. Info: https://community.openvpn.net/openvpn/wiki/easyrsa-upgrade
# --------------------------------------------------------------------------
#

up23_fail_upgrade ()
{
	# Replace die()
	unset -v EASYRSA_BATCH
	notice "
============================================================================
The update has failed but NOTHING has been lost.

ERROR: $1
----------------------------------------------------------------------------

Further info:
* https://community.openvpn.net/openvpn/wiki/easyrsa-upgrade#ersa-up23-fails

Easyrsa3 upgrade FAILED
============================================================================
"
	exit 9
} #=> up23_fail_upgrade ()

up23_verbose ()
{
	[ "$VERBOSE" ] || return 0
	printf "%s\n" "$1"
} #=> up23_verbose ()

up23_verify_new_pki ()
{
	# Fail now, before any changes are made

	up23_verbose "> Verify DEFAULT NEW PKI does not exist .."
	EASYRSA_NEW_PKI="$EASYRSA/pki"
	[ -d "$EASYRSA_NEW_PKI" ] \
	&& up23_fail_upgrade "DEFAULT NEW PKI exists: $EASYRSA_NEW_PKI"

	up23_verbose "> Verify VERY-SAFE-PKI does not exist .."
	EASYRSA_SAFE_PKI="$EASYRSA/VERY-SAFE-PKI"
	[ -d "$EASYRSA_SAFE_PKI" ] \
	&& up23_fail_upgrade "VERY-SAFE-PKI exists: $EASYRSA_SAFE_PKI"

	up23_verbose "> Verify openssl-easyrsa.cnf does exist .."
	EASYRSA_SSL_CNFFILE="$EASYRSA/openssl-easyrsa.cnf"
	[ -f "$EASYRSA_SSL_CNFFILE" ] \
	|| up23_fail_upgrade "cannot find $EASYRSA_SSL_CNFFILE"

	up23_verbose "> Verify vars.example does exist .."
	EASYRSA_VARSV3_EXMP="$EASYRSA/vars.example"
	[ -f "$EASYRSA_VARSV3_EXMP" ] \
	|| up23_fail_upgrade "cannot find $EASYRSA_VARSV3_EXMP"

	up23_verbose "> OK"
	up23_verbose "  Initial dirs & files are in a workable state."
} #=> up23_verify_new_pki ()

# shellcheck disable=SC2154
up23_verify_current_pki ()
{
	up23_verbose "> Verify CURRENT PKI vars .."

	# This can probably be improved
	EASYRSA_NO_REM="$(grep '^set ' "$EASYRSA_VER2_VARSFILE")"

	# This list may not be complete
	# Not required: DH_KEY_SIZE PKCS11_MODULE_PATH PKCS11_PIN
	for i in KEY_DIR KEY_SIZE KEY_COUNTRY KEY_PROVINCE \
	KEY_CITY KEY_ORG KEY_EMAIL KEY_CN KEY_NAME KEY_OU
	do
		# Effectively, source the v2 vars file
		UNIQUE="set $i"
		KEY_grep="$(printf "%s\n" "$EASYRSA_NO_REM" | grep "$UNIQUE")"
		KEY_value="${KEY_grep##*=}"
		set_var $i "$KEY_value"
	done

	[ -d "$KEY_DIR" ] || up23_fail_upgrade "Cannot find CURRENT PKI KEY_DIR: $KEY_DIR"

	up23_verbose "> OK"
	up23_verbose "  Current CURRENT PKI vars uses PKI in: $KEY_DIR"
} #=> up23_verify_current_pki ()

# shellcheck disable=SC2154
up23_verify_current_ca ()
{
	up23_verbose "> Find CA .."
	# $KEY_DIR is assigned in up23_verify_current_pki ()
	[ -f "$KEY_DIR/ca.crt" ] \
	|| up23_fail_upgrade "Cannot find current ca.crt: $KEY_DIR/ca.crt"
	up23_verbose "> OK"

	# If CA is already verified then return
	in_file="$KEY_DIR/ca.crt"
	[ "$CURRENT_CA_IS_VERIFIED" = "$in_file" ] && return 0
	format="x509"

	# Current CA is unverified
	# Extract the current CA details
	name_opts="utf8,sep_multiline,space_eq,lname,align"
	CA_SUBJECT="$(
		easyrsa_openssl $format -in "$in_file" -subject -noout \
			-nameopt "$name_opts"
		)"

	# Extract individual elements
	CA_countryName="$(printf "%s\n" "$CA_SUBJECT" \
	| grep countryName | sed "s\`^.*=\ \`\`g")"
	CA_stateOrProvinceName="$(printf "%s\n" "$CA_SUBJECT" \
	| grep stateOrProvinceName | sed "s\`^.*=\ \`\`g")"
	CA_localityName="$(printf "%s\n" "$CA_SUBJECT" \
	| grep localityName | sed "s\`^.*=\ \`\`g")"
	CA_organizationName="$(printf "%s\n" "$CA_SUBJECT" \
	| grep organizationName | sed "s\`^.*=\ \`\`g")"
	CA_organizationalUnitName="$(printf "%s\n" "$CA_SUBJECT" \
	| grep organizationalUnitName | sed "s\`^.*=\ \`\`g")"
	CA_emailAddress="$(printf "%s\n" "$CA_SUBJECT" \
	| grep emailAddress | sed "s\`^.*=\ \`\`g")"

	# Match the current CA elements to the vars file settings
	CA_vars_match=1
	[ "$CA_countryName" = "$KEY_COUNTRY" ] || CA_vars_match=0
	[ "$CA_stateOrProvinceName" = "$KEY_PROVINCE" ] || CA_vars_match=0
	[ "$CA_localityName" = "$KEY_CITY" ] || CA_vars_match=0
	[ "$CA_organizationName" = "$KEY_ORG" ] || CA_vars_match=0
	[ "$CA_organizationalUnitName" = "$KEY_OU" ] || CA_vars_match=0
	[ "$CA_emailAddress" = "$KEY_EMAIL" ] || CA_vars_match=0

	if [ "$CA_vars_match" -eq 1 ]
	then
		CURRENT_CA_IS_VERIFIED="partially"
	else
		warn "CA certificate does not match vars file settings"
	fi

	opts="-certopt no_pubkey,no_sigdump"
	if [ ! "$EASYRSA_BATCH" ]
	then
		up23_show_current_ca
	elif [ "$VERBOSE" ]
	then
		up23_show_current_ca
	fi
	confirm "* Confirm CA shown above is correct: " "yes" \
	"Found current CA at: $KEY_DIR/ca.crt"
	CURRENT_CA_IS_VERIFIED="$in_file"
} #=> up23_verify_current_ca ()

up23_show_current_ca ()
{
	name_opts="utf8,sep_multiline,space_eq,lname,align"
	printf "%s\n" "-------------------------------------------------------------------------"
	# $opts is always set here
	# shellcheck disable=SC2086 # Ignore unquoted variables
	easyrsa_openssl $format -in "$in_file" -noout -text \
		-nameopt "$name_opts" $opts || die "\
	OpenSSL failure to process the input CA certificate: $in_file"
	printf "%s\n" "-------------------------------------------------------------------------"
} #=> up23_show_current_ca ()

up23_backup_current_pki ()
{
	up23_verbose "> Backup current PKI .."

	mkdir -p "$EASYRSA_SAFE_PKI" \
	|| up23_fail_upgrade "Failed to create safe PKI dir: $EASYRSA_SAFE_PKI"

	cp -r "$KEY_DIR" "$EASYRSA_SAFE_PKI" \
	|| up23_fail_upgrade "Failed to copy $KEY_DIR to $EASYRSA_SAFE_PKI"

	# EASYRSA_VER2_VARSFILE is either version 2 *nix ./vars or Win vars.bat
	cp "$EASYRSA_VER2_VARSFILE" "$EASYRSA_SAFE_PKI" \
	|| up23_fail_upgrade "Failed to copy $EASYRSA_VER2_VARSFILE to EASYRSA_SAFE_PKI"

	up23_verbose "> OK"
	up23_verbose "  Current PKI backup created in: $EASYRSA_SAFE_PKI"
} #=> up23_backup_current_pki ()

up23_create_new_pki ()
{
	# Dirs: renewed and revoked are created when used.
	up23_verbose "> Create NEW PKI .."
	up23_verbose ">> Create NEW PKI dirs .."
	for i in private reqs issued certs_by_serial
	do
		mkdir -p "$EASYRSA_PKI/$i" \
		|| up23_fail_upgrade "Failed to Create NEW PKI dir: $EASYRSA_PKI/$i"
	done
	up23_verbose ">> OK"

	up23_verbose ">> Copy database to NEW PKI .."
	# Failure for these is not optional
	# Files ignored: index.txt.old serial.old
	for i in index.txt serial ca.crt index.txt.attr
	do
		cp "$KEY_DIR/$i" "$EASYRSA_PKI" \
		|| up23_fail_upgrade "Failed to copy $KEY_DIR/$i to $EASYRSA_PKI"
	done
	up23_verbose ">> OK"

	up23_verbose ">> Copy current PKI to NEW PKI .."
	for i in "csr.reqs" "pem.certs_by_serial" "crt.issued" "key.private" \
	"p12.private" "p8.private" "p7b.issued"
	do
		FILE_EXT="${i%%.*}"
		DEST_DIR="${i##*.}"
		if ls "$KEY_DIR/"*".$FILE_EXT" > /dev/null 2>&1; then
			cp "$KEY_DIR/"*".$FILE_EXT" "$EASYRSA_PKI/$DEST_DIR" \
			|| up23_fail_upgrade "Failed to copy .$FILE_EXT"
		else
			up23_verbose "   Note: No .$FILE_EXT files found"
		fi
	done
	up23_verbose ">> OK"
	up23_verbose "> OK"

	# Todo: CRL - Or generate a new CRL on completion
	up23_verbose "  New PKI created in: $EASYRSA_PKI"
} #=> up23_create_new_pki ()

up23_upgrade_ca ()
{
	[ -d "$EASYRSA_PKI" ] || return 0
	up23_verbose "> Confirm that index.txt.attr exists and 'unique_subject = no'"
	if [ -f "$EASYRSA_PKI/index.txt.attr" ]
	then
		if grep -q 'unique_subject = no' "$EASYRSA_PKI/index.txt.attr"
		then
			# If index.txt.attr exists and "unique_suject = no" then do nothing
			return 0
		fi
	else
		# If index.txt.attr does not exists then do nothing
		return 0
	fi

	# Otherwise this is required for all easyrsa v3
	#confirm "Set 'unique_subject = no' in index.txt.attr for your current CA: " \
	#"yes" "This version of easyrsa requires that 'unique_subject = no' is set correctly"

	printf "%s\n" "unique_subject = no" > "$EASYRSA_PKI/index.txt.attr"
	up23_verbose "> OK"
	up23_verbose "  Upgraded index.txt.attr to v306+"
} #=> up23_upgrade_index_txt_attr ()

up23_create_openssl_cnf ()
{
	up23_verbose "> OpenSSL config .."
	EASYRSA_PKI_SSL_CNFFILE="$EASYRSA_PKI/openssl-easyrsa.cnf"
	EASYRSA_PKI_SAFE_CNFFILE="$EASYRSA_PKI/safessl-easyrsa.cnf"
	cp "$EASYRSA_SSL_CNFFILE" "$EASYRSA_PKI_SSL_CNFFILE" \
	|| up23_fail_upgrade "create $EASYRSA_PKI_SSL_CNFFILE"
	up23_verbose "> OK"
	up23_verbose "  New OpenSSL config file created in: $EASYRSA_PKI_SSL_CNFFILE"

	# Create secure session
	# Because the upgrade runs twice, once as a test and then for real
	# secured_session must be cleared to avoid overload error
	#[ "$secured_session" ] && unset -v secured_session
	#up23_verbose "> Create secure session"
	#secure_session || die "up23_create_openssl_cnf - secure_session failed."
	#up23_verbose "> OK"
	#up23_verbose "  secure session: $secured_session"

	# Create $EASYRSA_PKI/safessl-easyrsa.cnf
	easyrsa_openssl makesafeconf
	if [ -f "$EASYRSA_PKI_SAFE_CNFFILE" ]
	then
		up23_verbose "  New SafeSSL config file created in: $EASYRSA_PKI_SAFE_CNFFILE"
	else
		up23_verbose "  FAILED to create New SafeSSL config file in: $EASYRSA_PKI_SAFE_CNFFILE"
	fi
} #=> up23_create_openssl_cnf ()

up23_move_easyrsa2_programs ()
{
	# These files may not exist here
	up23_verbose "> Move easyrsa2 programs to SAFE PKI .."
	for i in build-ca build-dh build-inter build-key build-key-pass \
	build-key-pkcs12 build-key-server build-req build-req-pass \
	clean-all inherit-inter list-crl pkitool revoke-full sign-req \
	whichopensslcnf build-ca-pass build-key-server-pass init-config \
	make-crl revoke-crt openssl-0.9.6.cnf openssl-0.9.8.cnf \
	openssl-1.0.0.cnf openssl.cnf README.txt index.txt.start \
	vars.bat.sample serial.start
	do
		# Although unlikely, both files could exist
		# EG: ./build-ca and ./build-ca.bat
		NIX_FILE="$EASYRSA/$i"
		WIN_FILE="$EASYRSA/$i.bat"
		if [ -f "$NIX_FILE" ]
		then
			cp "$NIX_FILE" "$EASYRSA_SAFE_PKI" \
			|| up23_fail_upgrade "copy $NIX_FILE $EASYRSA_SAFE_PKI"
		fi

		if [ -f "$WIN_FILE" ]
		then
			cp "$WIN_FILE" "$EASYRSA_SAFE_PKI" \
			|| up23_fail_upgrade "copy $WIN_FILE $EASYRSA_SAFE_PKI"
		fi

		if [ ! -f "$NIX_FILE" ] && [ ! -f "$WIN_FILE" ]
		then
			up23_verbose "File does not exist, ignoring: $i(.bat)"
		fi

	# These files are not removed on TEST run
	[ "$NOSAVE" -eq 1  ] && rm -f "$NIX_FILE" "$WIN_FILE"
	done

	up23_verbose "> OK"
	up23_verbose "  Easyrsa2 programs successfully moved to: $EASYRSA_SAFE_PKI"
} #=> up23_move_easyrsa2_programs ()

# shellcheck disable=SC2154
up23_build_v3_vars ()
{
	up23_verbose "> Build v3 vars file .."

	EASYRSA_EXT="easyrsa-upgrade-23"
	EASYRSA_VARSV2_TMP="$EASYRSA/vars-v2.tmp.$EASYRSA_EXT"
	rm -f "$EASYRSA_VARSV2_TMP"
	EASYRSA_VARSV3_TMP="$EASYRSA/vars-v3.tmp.$EASYRSA_EXT"
	rm -f "$EASYRSA_VARSV3_TMP"
	EASYRSA_VARSV3_NEW="$EASYRSA/vars-v3.new.$EASYRSA_EXT"
	rm -f "$EASYRSA_VARSV3_NEW"
	EASYRSA_VARSV3_WRN="$EASYRSA/vars-v3.wrn.$EASYRSA_EXT"
	rm -f "$EASYRSA_VARSV3_WRN"

	printf "%s\n" "\
########################++++++++++#########################
###                                                     ###
###  WARNING: THIS FILE WAS AUTOMATICALLY GENERATED     ###
###           ALL SETTINGS ARE AT THE END OF THE FILE   ###
###                                                     ###
########################++++++++++#########################

" > "$EASYRSA_VARSV3_WRN" || up23_fail_upgrade "Failed to create $EASYRSA_VARSV3_WRN"

	# Create vars v3 temp file from sourced vars v2 key variables
	{
		printf "%s\n" "set_var EASYRSA_KEY_SIZE $KEY_SIZE"
		printf "%s\n" "set_var EASYRSA_REQ_COUNTRY \"$KEY_COUNTRY\""
		printf "%s\n" "set_var EASYRSA_REQ_PROVINCE \"$KEY_PROVINCE\""
		printf "%s\n" "set_var EASYRSA_REQ_CITY \"$KEY_CITY\""
		printf "%s\n" "set_var EASYRSA_REQ_ORG \"$KEY_ORG\""
		printf "%s\n" "set_var EASYRSA_REQ_EMAIL \"$KEY_EMAIL\""
		printf "%s\n" "set_var EASYRSA_REQ_OU \"$KEY_OU\""
		printf "%s\n" 'set_var EASYRSA_NS_SUPPORT "yes"'
		printf "%s\n" 'set_var EASYRSA_DN "org"'
		printf "%s\n" 'set_var EASYRSA_RAND_SN "no"'
		printf "%s\n" ""
	} > "$EASYRSA_VARSV3_TMP" \
	|| up23_fail_upgrade "Failed to create $EASYRSA_VARSV3_TMP"

	# cat temp files into new v3 vars
	cat "$EASYRSA_VARSV3_WRN" "$EASYRSA_VARSV3_EXMP" "$EASYRSA_VARSV3_TMP" \
	> "$EASYRSA_VARSV3_NEW" \
	|| up23_fail_upgrade "Failed to create $EASYRSA_VARSV3_NEW"

	# This file must be created and restored at the end of TEST
	# for the REAL update to to succeed
	EASYRSA_VARS_LIVEBKP="$EASYRSA_TARGET_VARSFILE.livebackup"
	cp "$EASYRSA_VER2_VARSFILE" "$EASYRSA_VARS_LIVEBKP" \
	|| up23_fail_upgrade "Failed to create $EASYRSA_VARS_LIVEBKP"
	rm -f "$EASYRSA_VER2_VARSFILE"

	# "$EASYRSA_TARGET_VARSFILE" is always $EASYRSA/vars
	cp "$EASYRSA_VARSV3_NEW" "$EASYRSA_TARGET_VARSFILE" \
	|| up23_fail_upgrade "copy $EASYRSA_VARSV3_NEW to $EASYRSA_TARGET_VARSFILE"

	# Delete temp files
	rm -f "$EASYRSA_VARSV2_TMP" "$EASYRSA_VARSV3_TMP" \
	"$EASYRSA_VARSV3_NEW" "$EASYRSA_VARSV3_WRN"

	up23_verbose "> OK"
	up23_verbose "  New v3 vars file created in: $EASYRSA_TARGET_VARSFILE"
} #=> up23_build_v3_vars ()

# shellcheck disable=SC2154
up23_do_upgrade_23 ()
{
	up23_verbose "============================================================================"
	up23_verbose "Begin ** $1 ** upgrade process .."
	up23_verbose ""
	up23_verbose "Easyrsa upgrade version: $EASYRSA_UPGRADE_23"
	up23_verbose ""

	up23_verify_new_pki
	up23_create_new_pki
	up23_create_openssl_cnf
	up23_verify_current_pki
	up23_verify_current_ca
	up23_backup_current_pki
	up23_upgrade_ca
	up23_move_easyrsa2_programs
	up23_build_v3_vars

	if [ "$NOSAVE" -eq 0 ]
	then
		# Must stay in this order
		# New created dirs: EASYRSA_NEW_PKI and EASYRSA_SAFE_PKI
		rm -rf "$EASYRSA_NEW_PKI"
		rm -rf "$EASYRSA_SAFE_PKI"
		# EASYRSA_TARGET_VARSFILE is always the new created v3 vars
		# Need to know if this fails
		rm "$EASYRSA_TARGET_VARSFILE" \
		|| up23_fail_upgrade "remove new vars file: $EASYRSA_TARGET_VARSFILE"
		# EASYRSA_VER2_VARSFILE is either v2 *nix ./vars or Win vars.bat
		# Need this dance because v2 vars is same name as v3 vars above
		cp "$EASYRSA_VARS_LIVEBKP" "$EASYRSA_VER2_VARSFILE"
	fi
	rm -f "$EASYRSA_VARS_LIVEBKP"
} #= up23_do_upgrade_23 ()

up23_manage_upgrade_23 ()
{
	EASYRSA_UPGRADE_VERSION="v1.0a (2020/01/08)"
	EASYRSA_UPGRADE_TYPE="$1"
	EASYRSA_FOUND_VARS=0

	# Verify all existing versions of vars/vars.bat
	if [ -f "$vars" ]
	then
		if grep -q 'Complain if a user tries to do this:' "$vars"
		then
			EASYRSA_FOUND_VARS=1
			EASYRSA_VARS_IS_VER3=1
		fi

		# Easyrsa v3 does not use NOR allow use of `export`.
		if grep -q 'export' "$vars"
		then
			EASYRSA_FOUND_VARS=1
			EASYRSA_VARS_IS_VER2=1
			EASYRSA_VER2_VARSFILE="$vars"
			EASYRSA_TARGET_VARSFILE="$vars"
		fi
	fi

	if [ -f "$EASYRSA/vars.bat" ]
	then
		EASYRSA_FOUND_VARS=1
		EASYRSA_VARS_IS_WIN2=1
		EASYRSA_VER2_VARSFILE="$EASYRSA/vars.bat"
		EASYRSA_TARGET_VARSFILE="$EASYRSA/vars"
	fi

	if [ $EASYRSA_FOUND_VARS -ne 1 ];
	then
		die "vars file not found"
	fi

	# Only allow specific vars/vars.bat to exist
	if [ "$EASYRSA_VARS_IS_VER3" ] && [ "$EASYRSA_VARS_IS_VER2" ]
	then
		die "Verify your current vars file, v3 cannot use 'export'."
	fi

	if [ "$EASYRSA_VARS_IS_VER3" ] && [ "$EASYRSA_VARS_IS_WIN2" ]
	then
		die "Verify your current vars/vars.bat file, cannot have both."
	fi

	if [ "$EASYRSA_VARS_IS_VER2" ] && [ "$EASYRSA_VARS_IS_WIN2" ]
	then
		die "Verify your current vars/vars.bat file, cannot have both."
	fi

	# Die on invalid upgrade type or environment
	if [ "$EASYRSA_UPGRADE_TYPE" = "ca" ]
	then
		if [ "$EASYRSA_VARS_IS_VER3" ]
		then
			# v3 ensure index.txt.attr "unique_subject = no"
			up23_upgrade_ca
			unset -v EASYRSA_BATCH
			notice "Your CA is fully up to date."
			return 0
		else
			die "Only v3 PKI CA can be upgraded."
		fi
	fi

	if [ "$EASYRSA_UPGRADE_TYPE" = "pki" ]
	then
		if [ "$EASYRSA_VARS_IS_VER3" ]
		then
			unset -v EASYRSA_BATCH
			notice "Your PKI is fully up to date."
			return 0
		fi
	else
		user_error "upgrade type must be 'pki' or 'ca'."
	fi

	# PKI is potentially suitable for upgrade

	warn "
=========================================================================

                             * WARNING *

Found settings from EasyRSA-v2 which are not compatible with EasyRSA-v3.
Before you can continue, EasyRSA must upgrade your settings and PKI.
* Found EASYRSA and vars file:
  $EASYRSA
  $EASYRSA_VER2_VARSFILE :

Further info:
* https://community.openvpn.net/openvpn/wiki/easyrsa-upgrade

Easyrsa upgrade version: $EASYRSA_UPGRADE_VERSION
=========================================================================
"

# Test upgrade

	NOSAVE=0

	confirm "* EasyRSA **TEST** upgrade (Changes will NOT be written): " "yes" "
This upgrade will TEST that the upgrade works BEFORE making any changes."

	up23_do_upgrade_23 "TEST"

	notice "
=========================================================================

                             * NOTICE *

EasyRSA upgrade **TEST** has successfully completed.
"
# Upgrade for REAL

	NOSAVE=1

	confirm "* EasyRSA **REAL** upgrade (Changes WILL be written): " "yes" "
=========================================================================

                             * WARNING *

Run REAL upgrade: Answer yes (Once completed you will have a version 3 PKI)
Terminate upgrade: Answer no (No changes have been made to your current PKI)
"

	confirm "* Confirm **REAL** upgrade (Changes will be written): " "yes" "
=========================================================================

                          * SECOND WARNING *

This upgrade will permanently write changes to your PKI !
(With full backup backout)
"
	up23_do_upgrade_23 "REAL"

	notice "
=========================================================================

                             * NOTICE *

Your settings and PKI have been successfully upgraded to EasyRSA version3

A backup of your current PKI is here:
  $EASYRSA_SAFE_PKI

                        * IMPORTANT NOTICE *

1. YOU MUST VERIFY THAT YOUR NEW ./vars FILE IS SETUP CORRECTLY
2. IF YOU ARE USING WINDOWS YOU MUST ENSURE THAT openssl IS CORRECTLY DEFINED
   IN ./vars (example follows)

 #
 # This sample is in Windows syntax -- edit it for your path if not using PATH:
 # set_var EASYRSA_OPENSSL   \"C:/Program Files/OpenSSL-Win32/bin/openssl.exe\"
 #
 # Alternate location (Note: Forward slash '/' is correct for Windpws):
 # set_var EASYRSA_OPENSSL   \"C:/Program Files/Openvpn/bin/openssl.exe\"
 #

3. Finally, you can verify that easyrsa works by using these two commands:
    ./easyrsa show-ca (Verify that your CA is intact and correct)
    ./easyrsa gen-crl ((re)-generate a CRL file)

Further info:
* https://community.openvpn.net/openvpn/wiki/easyrsa-upgrade"
          up23_verbose "
                   * UPGRADE COMPLETED SUCCESSFULLY *
"

return 0

} # => up23_manage_upgrade_23 ()

print_version()
{
	ssl_version="$(
		OPENSSL_CONF=/dev/null \
			"${EASYRSA_OPENSSL:-openssl}" version
		)"
		cat << VERSION_TEXT
EasyRSA Version Information
Version:     $EASYRSA_version
Generated:   Fri Oct 13 17:27:51 CDT 2023
SSL Lib:     ${ssl_version:-undefined}
Git Commit:  3c233d279d43e419b0529411ee62bba7a08f0c0f
Source Repo: https://github.com/OpenVPN/easy-rsa
VERSION_TEXT
} # => print_version ()


########################################
# Invocation entry point:

EASYRSA_version="3.1.7"
NL='
'

# Be secure with a restrictive umask
[ "$EASYRSA_NO_UMASK" ] || umask "${EASYRSA_UMASK:=077}"

# Register cleanup on EXIT
trap 'cleanup $?' EXIT
# When SIGHUP, SIGINT, SIGQUIT, SIGABRT and SIGTERM,
# explicitly exit to signal EXIT (non-bash shells)
trap "exit 1" 1
trap "exit 2" 2
trap "exit 3" 3
trap "exit 6" 6
trap "exit 14" 15

# Get host details - No configurable input allowed
detect_host

# Initialisation requirements
unset -v \
	verify_ssl_lib_ok \
	secured_session \
	working_safe_ssl_conf working_safe_org_conf \
	makesafeconf \
	alias_days \
	prohibit_no_pass \
	invalid_vars \
	no_new_vars user_vars_true \
	do_build_full error_build_full_cleanup \
	internal_batch \
	easyrsa_exit_with_error error_info

	# Used by build-ca->cleanup to restore prompt
	# after user interrupt when using manual password
	prompt_restore=0

# Parse options
while :; do
	# Reset per pass flags
	unset -v opt val \
		is_empty empty_ok number_only zero_allowed

	# Separate option from value:
	opt="${1%%=*}"
	val="${1#*=}"

	# Empty values are not allowed unless expected
	# eg: '--batch'
	[ "$opt" = "$val" ] && is_empty=1
	# eg: '--pki-dir='
	[ "$val" ] || is_empty=1

	case "$opt" in
	--days)
		number_only=1
		# Set the appropriate date variable
		# when called by command later
		alias_days="$val"
		;;
	--startdate)
		export EASYRSA_START_DATE="$val"
		;;
	--enddate)
		export EASYRSA_END_DATE="$val"
		;;
	--pki-dir|--pki)
		export EASYRSA_PKI="$val"
		;;
	--tmp-dir)
		export EASYRSA_TEMP_DIR="$val"
		;;
	--ssl-conf)
		export EASYRSA_SSL_CONF="$val"
		;;
	--keep-tmp)
		export EASYRSA_KEEP_TEMP="$val"
		;;
	--use-algo)
		export EASYRSA_ALGO="$val"
		;;
	--keysize)
		number_only=1
		export EASYRSA_KEY_SIZE="$val"
		;;
	--curve)
		export EASYRSA_CURVE="$val"
		;;
	--dn-mode)
		export EASYRSA_DN="$val"
		;;
	--req-cn)
		export EASYRSA_REQ_CN="$val"
		;;
	--digest)
		export EASYRSA_DIGEST="$val"
		;;
	--req-c)
		empty_ok=1
		export EASYRSA_REQ_COUNTRY="$val"
		;;
	--req-st)
		empty_ok=1
		export EASYRSA_REQ_PROVINCE="$val"
		;;
	--req-city)
		empty_ok=1
		export EASYRSA_REQ_CITY="$val"
		;;
	--req-org)
		empty_ok=1
		export EASYRSA_REQ_ORG="$val"
		;;
	--req-email)
		empty_ok=1
		export EASYRSA_REQ_EMAIL="$val"
		;;
	--req-ou)
		empty_ok=1
		export EASYRSA_REQ_OU="$val"
		;;
	--req-serial)
		empty_ok=1
		export EASYRSA_REQ_SERIAL="$val"
		;;
	--ns-cert)
		empty_ok=1
		[ "$is_empty" ] && unset -v val
		export EASYRSA_NS_SUPPORT="${val:-yes}"
		;;
	--ns-comment)
		empty_ok=1
		export EASYRSA_NS_COMMENT="$val"
		;;
	--batch)
		empty_ok=1
		export EASYRSA_BATCH=1
		;;
	-s|--silent)
		empty_ok=1
		export EASYRSA_SILENT=1
		;;
	--sbatch|--silent-batch)
		empty_ok=1
		export EASYRSA_SILENT=1
		export EASYRSA_BATCH=1
		;;
	--verbose)
		empty_ok=1
		export EASYRSA_VERBOSE=1
		;;
	--days-margin)
		# ONLY ALLOWED use by status reports
		number_only=1
		export EASYRSA_iso_8601_MARGIN="$val"
		;;
	-S|--silent-ssl)
		empty_ok=1
		export EASYRSA_SILENT_SSL=1
		# This will probably be need
		#save_EASYRSA_SILENT_SSL=1
		;;
	--force-safe-ssl)
		empty_ok=1
		export EASYRSA_FORCE_SAFE_SSL=1
		;;
	--nopass|--no-pass)
		empty_ok=1
		export EASYRSA_NO_PASS=1
		;;
	--passin)
		export EASYRSA_PASSIN="$val"
		;;
	--passout)
		export EASYRSA_PASSOUT="$val"
		;;
	--raw-ca)
		empty_ok=1
		export EASYRSA_RAW_CA=1
		;;
	--notext|--no-text)
		empty_ok=1
		export EASYRSA_NO_TEXT=1
		;;
	--subca-len)
		number_only=1
		zero_allowed=1
		export EASYRSA_SUBCA_LEN="$val"
		;;
	--vars)
		user_vars_true=1
		export EASYRSA_VARS_FILE="$val"
		;;
	--copy-ext)
		empty_ok=1
		export EASYRSA_CP_EXT=1
		;;
	--subject-alt-name|--san)
		export EASYRSA_EXTRA_EXTS="\
$EASYRSA_EXTRA_EXTS
subjectAltName = $val"
		;;
	--version)
		shift "$#"
		set -- "$@" "version"
		break
		;;
	# Unsupported options
	--fix-offset)
		user_error "Option $opt is not supported.
Use options --startdate and --enddate for fixed dates."
		;;
	-*)
		user_error "\
Unknown option '$opt'.
Run 'easyrsa help options' for option help."
		;;
	*)
		break
	esac

	# fatal error when no value was provided
	if [ "$is_empty" ]; then
		[ "$empty_ok" ] || \
			user_error "Missing value to option: $opt"
	fi

	# fatal error when a number is expected but not provided
	if [ "$number_only" ]; then
		case "$val" in
			(0)
				# Allow zero only
				[ "$zero_allowed" ] || \
					user_error "$opt - Number expected: '$val'"
			;;
			(*[!1234567890]*|0*)
				user_error "$opt - Number expected: '$val'"
		esac
	fi

	shift
done

# Set cmd now
# vars_setup needs to know if this is init-pki
cmd="$1"
[ "$1" ] && shift # scrape off command

# Establish PKI and CA initialisation requirements
# This avoids unnecessary warnings and notices
case "$cmd" in
	''|help|-h|--help|--usage|version|upgrade|show-host)
		unset -v require_pki require_ca
		ignore_vars=1
	;;
	init-pki|clean-all)
		unset -v require_pki require_ca
	;;
	*)
		require_pki=1
		case "$cmd" in
			gen-req|gen-dh|build-ca|show-req| \
			make-safe-ssl|export-p*|inline)
				unset -v require_ca
			;;
			*)
				require_ca=1
		esac
esac

# Run these commands with NO setup
case "$cmd" in
	make-vars)
		create_vars_example
		cleanup ok
	;;
esac

# Intelligent env-var detection and auto-loading:
# Select vars file as EASYRSA_VARS_FILE
select_vars

# source the vars file
source_vars "$EASYRSA_VARS_FILE"

# then set defaults
default_vars

# Check for unexpected changes to EASYRSA or EASYRSA_PKI
# This will be resolved in v3.2.0
# https://github.com/OpenVPN/easy-rsa/issues/1006
validate_default_vars

# Check for conflicting input options
mutual_exclusions

# Hand off to the function responsible
# ONLY verify_working_env() for valid commands
case "$cmd" in
	init-pki|clean-all)
		verify_working_env
		init_pki "$@"
		;;
	build-ca)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CA_EXPIRE="$alias_days"
		build_ca "$@"
		;;
	gen-dh)
		verify_working_env
		gen_dh
		;;
	gen-req)
		verify_working_env
		gen_req "$@"
		;;
	sign|sign-req)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CERT_EXPIRE="$alias_days"
		sign_req "$@"
		;;
	build-client-full)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CERT_EXPIRE="$alias_days"
		build_full client "$@"
		;;
	build-server-full)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CERT_EXPIRE="$alias_days"
		build_full server "$@"
		;;
	build-serverClient-full)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CERT_EXPIRE="$alias_days"
		build_full serverClient "$@"
		;;
	gen-crl)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CRL_DAYS="$alias_days"
		gen_crl
		;;
	revoke)
		verify_working_env
		revoke "$@"
		;;
	revoke-renewed)
		verify_working_env
		revoke_renewed "$@"
		;;
	renew)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CERT_EXPIRE="$alias_days"
		renew "$@"
		;;
	rewind-renew)
		verify_working_env
		rewind_renew "$@"
		;;
	rebuild)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CERT_EXPIRE="$alias_days"
		rebuild "$@"
		;;
	import-req)
		verify_working_env
		import_req "$@"
		;;
	inline)
		verify_working_env
		inline_creds "$@" || \
			easyrsa_exit_with_error=1
		;;
	export-p12)
		verify_working_env
		export_pkcs p12 "$@"
		;;
	export-p7)
		verify_working_env
		export_pkcs p7 "$@"
		;;
	export-p8)
		verify_working_env
		export_pkcs p8 "$@"
		;;
	export-p1)
		verify_working_env
		export_pkcs p1 "$@"
		;;
	set-rsa-pass)
		verify_working_env
		set_pass_legacy rsa "$@"
		;;
	set-ec-pass)
		verify_working_env
		set_pass_legacy ec "$@"
		;;
	set-pass|set-ed-pass)
		verify_working_env
		set_pass "$@"
		;;
	update-db)
		verify_working_env
		update_db
		;;
	show-req)
		verify_working_env
		show req "$@"
		;;
	show-cert)
		verify_working_env
		show cert "$@"
		;;
	show-crl)
		verify_working_env
		show crl crl
		;;
	show-ca)
		verify_working_env
		show_ca "$@"
		;;
	show-expire)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_PRE_EXPIRY_WINDOW="$alias_days"
		status expire "$@"
		;;
	show-revoke)
		verify_working_env
		status revoke "$@"
		;;
	show-renew)
		verify_working_env
		status renew "$@"
		;;
	show-host)
		verify_working_env
		show_host "$@"
		;;
	verify|verify-cert)
		verify_working_env
		# Called with --batch, this will return error
		# when the certificate fails verification.
		# Therefore, on error, exit with error.
		verify_cert "$@" || \
			easyrsa_exit_with_error=1
		;;
	mss|make-safe-ssl)
		verify_working_env
		make_safe_ssl "$@"
		;;
	serial|check-serial)
		verify_working_env
		# Called with --batch, this will return error
		# when the serial number is not unique.
		# Therefore, on error, exit with error.
		check_serial_unique "$@" || \
			easyrsa_exit_with_error=1
		;;
	display-dn)
		verify_working_env
		display_dn "$@"
		;;
	display-san)
		verify_working_env
		display_san "$@"
		;;
	default-san)
		verify_working_env
		default_server_san "$@"
		;;
	x509-eku)
		verify_working_env
		ssl_cert_x509v3_eku "$@"
		;;
	upgrade)
		verify_working_env
		up23_manage_upgrade_23 "$@"
		;;
	""|help|-h|--help|--usage)
		verify_working_env
		cmd_help "$1"
		;;
	version)
		print_version
		;;
	*)
		user_error "\
Unknown command '$cmd'. Run without commands for usage help."
esac

# Check for untrapped errors
# shellcheck disable=SC2181
if [ $? = 0 ]; then
	# Do 'cleanup ok' on successful completion
	#print "mktemp_counter: $mktemp_counter uses"
	cleanup ok
fi

# Otherwise, exit with error
print "Untrapped error detected!"
cleanup

# vim: ft=sh nu ai sw=8 ts=8 noet
