#!/bin/sh

# Easy-RSA 3 -- A Shell-based CA Utility
#
# Copyright (C) 2024 - 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:
	information "
Easy-RSA 3 usage and overview

$easyrsa_help_title

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 utility commands, use:
  ./easyrsa help util

A list of commands is shown below:
  init-pki [ cmd-opts ]
  self-sign-server <file_name_base> [ cmd-opts ]
  self-sign-client <file_name_base> [ 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>
  renew <file_name_base>
  revoke <file_name_base> [ cmd-opts ]
  expire <file_name_base>
  revoke-expired <file_name_base> [ cmd-opts ]
  revoke-renewed <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
  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 ]
  gen-tls-auth-key / gen-tls-crypt-key
  write <type> [ cmd-opts ]"

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

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

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

	# CA Status
	if verify_ca_init test; then
		if [ -z "$EASYRSA_SILENT" ]; then
			# Show SSL output directly, with easyrsa header
			printf '%s' "   CA status: OK${NL}${NL}    "
			"$EASYRSA_OPENSSL" x509 -in "$EASYRSA_PKI/ca.crt" \
				-noout -subject -nameopt utf8,multiline
			print "" # for a clean line
		fi
	else
		information "   CA status: CA has not been built${NL}"
	fi

	# verbose info
	verbose "ssl-cnf: ${EASYRSA_SSL_CONF:-built-in}"
	verbose "x509-types: ${EASYRSA_EXT_DIR:-built-in}"
	if [ -d "$EASYRSA_TEMP_DIR" ]; then
		verbose "temp-dir: Found: $EASYRSA_TEMP_DIR"
	else
		verbose "temp-dir: Missing: ${EASYRSA_TEMP_DIR:-undefined}"
	fi
} # => 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() {
	easyrsa_help_title="\
Usage: easyrsa [ OPTIONS.. ] <COMMAND> <TARGET> [ cmd-opts.. ]"
	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 ENTIRE PKI directory (default).
      * soft    - Keep the named PKI directory and PKI 'vars' file intact.
                  Also keep the current Request files,
                  to be signed by a new CA (Partial CA renewal)."
	;;
	self-sign*)
		text="
* self-sign-server|self-sign-client <file_name_base> [ cmd-opts ]

      Creates a new self-signed server|client key pair"

		opts="
      * nopass  - Do not encrypt the private key (Default: encrypted)
                  (Equivalent to global option '--nopass|--no-pass')"
	;;
	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="
      * newsubj  - Replace subject. See 'help subject'.
      * 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>

      Create inline file for <file_name_base>."
	;;
	revoke*)
		text="
* revoke <file_name_base> [ reason ]
* revoke-expired <file_name_base> [ reason ]
* revoke-renewed <file_name_base> [ reason ]

      Revoke a certificate specified by the <file_name_base>,
      with an optional revocation [ reason ].

      Commands 'revoke-expired' and 'revoke-renewed' are functionally
      equivalent to 'revoke', however, they are used to revoke certificates
      which have been either 'expired' or 'renewed' by EasyRSA commands."
		opts="
      * [ reason ]${NL}
      Values accepted for option [ reason ]:${NL}
         us | uns* | unspecified
         kc | key* | keyCompromise
         cc | ca*  | CACompromise
         ac | aff* | affiliationChanged
         ss | sup* | superseded
         co | ces* | cessationOfOperation
         ch | cer* | certificateHold"
	;;
	expire)
		text="
* expire <file_name_base>

      Move a certificate specified by <file_name_base>
      to the 'pki/expired' directory.

      Allows an existing request to be signed again."
	;;
	renew)
		text="
* renew <file_name_base>

      Renew a certificate specified by <file_name_base>"
	;;
	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."
	;;
	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."
	;;
	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
      * nofn    - Do not set 'friendlyName'
                  For more, see: 'easyrsa help friendly'
      * legacy  - Use legacy algorithm: RC2_CBC or 3DES_CBC + MAC: SHA1
                  (Default algorithm: AES-256-CBC + MAC: SHA256)"
	;;
	friendly)
		text_only=1
		text="
* export-p12: Internal file label 'friendlyName'

      The 'friendlyname' is always set to the file-name-base.

      An alternate friendlyName can be configured by using:
      * Global option '--usefn=<friendlyName>'

      Fallback to previous behavior can be configured by using:
      * Command option 'nofn' ('friendlyname' will not be set)"
	;;
	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"
	;;
	write)
		text="
* write <type> [<filename>] ['overwrite']

      Write <type> data to stdout or <filename>

      Types:
      * ssl-cnf  - Write EasyRSA SSL config file.
      * safe-cnf - Write expanded EasyRSA SSL config file for LibreSSL.
      * COMMON|ca|server|serverClient|client|codeSigning|email|kdc
                 - Write x509-type <type> file.
      * legacy   - Write ALL support files (above) to the PKI directory.
                   Will create '\$EASYRSA_PKI/x509-types' directory.
      * legacy-hard
                 - Same as 'legacy' plus OVER-WRITE files.
      * vars     - Write vars.example file."

		opts="
      * filename - If <filename> is specified then it is the output
                   is directed to the named file.
                   Otherwise, the data is sent to stdout
      * overwrite - Overwrite the <filename>.
                   <filename> is always preserved without 'overwrite'."
	;;
	--san|--subject-alt-name|altname|subjectaltname|san)
		text_only=1
		text="
* Global 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: --san can be specified more
      than once on the command line.

      The following two command line examples are equivalent:
      1. --san=DNS:server1,DNS:serverA,IP:10.0.0.1
      2. --san=DNS:server1 --san=DNS:serverA --san=IP:10.0.0.1

      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"
	;;
	--copy-ext|copy-ext|copyext)
		text_only=1
		text="
* Global Option: How to use --copy-ext and --san=<SAN>

    These are the only commands that support --copy-ext and/or --san.

    Command 'gen-req':
      --san: Add SAN extension to the request file.

    Command 'sign-req':
      --copy-ext: Copy all request extensions to the signed certificate.
      --san: Over write the request SAN with option SAN.

    Command 'build-*-full':
      --copy-ext: Always enabled.
      --san: Add SAN extension to the request and signed certificate.

    See 'help san' for option --san full syntax."
	;;
	--days|days)
		text_only=1
		text="
* Global 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 revocation list.
        eg: '--days=180 gen-crl'
      * Cutoff days for command: show-expire.
        eg: '--days=90 show-expire'"
	;;
	--new-subj*|new-subj*|newsubj*|subject)
		text_only=1
		text="
* Global Option: --new-subject=<SUBJECT>

      This global option is used to set the new certificate subject,
      when signing a new certificate

* REQUIRES Command option: 'newsubj', for command 'sign-req'

      Using command 'sign-req', add command option 'newsubj',
      to FORCE the --new-subject to be used.

      Example:
      --new-subject='/CN=foo' sign-req client bar newsubj

      See OpenSSL command 'ca', option -subj, for full details."
	;;
	tool*|util*|more)
		# Test features
		text_only=1
		text="
NOTE:
These commands are safe to test and will NOT effect your PKI.

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

  Display DN of request or certificate: <form> = req|x509
    display-dn <form> <DIR/FILE_NAME>

  Display EKU of certificate:
    show-eku <file_name_base>|<DIR/FILE_NAME>

  Generate random hex:
    rand <decimal_number>

These commands require easyrsa-tools.lib to be installed:

  show-expire <file_name_base> (Optional)
  show-revoke <file_name_base> (Optional)
  show-renew <file_name_base> (Optional)"
	;;
	gen-tls*)
		text_only=1
		text="
Generate TLS keys for use with OpenVPN:

  gen-tls-auth-key    : Generate OpenVPN TLS-AUTH key
  gen-tls-crypt-key   : Generate OpenVPN TLS-CRYPT-V1 key (Preferred)

Only ONE TLS key is allowed to exist. (pki/private/easyrsa-tls.key)
This TLS key will be automatically added to inline files."
	;;
	opts|options)
		opt_usage
		cleanup ok
	;;
	"")
		usage
		cleanup ok
	;;
	*)
		err_text="
  Unknown command: '$1' \
(try without commands for a list of commands)"
		easyrsa_exit_with_error=1
	esac

	if [ "$err_text" ]; then
		print "$easyrsa_help_title"
		print "${err_text}"
	else
		# display the help text
		print "$easyrsa_help_title"
		[ "$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
	print
} # => cmd_help()

# Options usage
opt_usage() {
	text_only=1
	information "
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-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)

--tools=FILE    : Declare the full easyrsa-tools.lib file-name
--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)
                  For more info, see: 'easyrsa help copyext'

--san|--subject-alt-name=SUBJECT_ALT_NAME
                : Add a subjectAltName. Can be used multiple times.
                  For more info and syntax, see: 'easyrsa help altname'
--auto-san      : Use commonName as subjectAltName: 'DNS:commonName'
                  If commonName is 'n.n.n.n' then set 'IP:commonName'

--san-crit      : Mark X509v3 subjectAltName as critical
--bc-crit       : Add X509 'basicContraints = critical' attribute.
--ku-crit       : Add X509 'keyUsage = critical' attribute.
--eku-crit      : Add X509 'extendedKeyUsage = critical' attribute.

--new-subject='SUBJECT'
                : Specify a new subject field to sign a request with.
                  For more info and syntax, see: 'easyrsa help subject'

--usefn=NAME    : export-p12, set 'friendlyName' to NAME
                  For more, see: 'easyrsa help friendly'

Distinguished Name mode:

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

--req-cn=NAME   : Request commonName

  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 # printf format - print()
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:

$*${NL}"

	# error_info is for hard-to-spot errors!
	if [ "$error_info" ]; then
		print "  * $cmd: ${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
-----
$*${NL}"

	easyrsa_exit_with_error=1
	cleanup
} # => user_error()

# verbose information
verbose() {
	[ "$EASYRSA_VERBOSE" ] || return 0
	print "  # $*"
} # => verbose()

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

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

# Helpful information
information() {
	[ "$EASYRSA_SILENT" ] && return
	print "$*"
} # => 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 - confirm()
	read input
	printf '\n'
	[ "$input" = "$value" ] && return
	easyrsa_exit_with_error=1
	unset -v EASYRSA_SILENT
	notice "Aborting without confirmation."
	cleanup
} # => confirm()

# Generate random hex
easyrsa_random() {
	case "$1" in
		*[!1234567890]*|0*|"")
			die "easyrsa_random - input"
	esac

	if rand_hex="$(
			"$EASYRSA_OPENSSL" rand -hex "$1" 2>/dev/null
		)"
	then
		if [ "$2" ]; then
			force_set_var "$2" "$rand_hex"
		else
			print "$rand_hex"
		fi
		unset -v rand_hex
		return 0
	fi

	die "easyrsa_random failed"
} # => easyrsa_random()

# Create session directory atomically or fail
secure_session() {
	# Session must not be defined
	[ -z "$secured_session" ] || die "session overload"

	# Temporary directory must exist
	[ -d "$EASYRSA_TEMP_DIR" ] || die "\
secure_session - Missing temporary directory:
* $EASYRSA_TEMP_DIR"

	for i in 1 2 3; do
		session=
		easyrsa_random 4 session
		secured_session="${EASYRSA_TEMP_DIR}/${session}"

		# atomic:
		# ONLY effects Windows 11 "broken" mkdir.exe
		# The procedure now is a "poor man's" version
		# of an atomic directory creation call.
		# The "race condition" still exists but is minimized.
		# What remains is equivalent to 32bit hash collision.
		[ -d "$secured_session" ] && continue
		if mkdir "$secured_session"; then
			# Check mkdir.exe has created the directory
			[ -d "$secured_session" ] || \
				die "secure_session - mkdir FAILED"
			[ -f "$secured_session"/temp.0.1 ] && \
				die "secure_session - temp-file EXISTS"

			# New session requires safe-ssl conf
			unset -v session OPENSSL_CONF \
				EASYRSA_SSL_CONF safe_ssl_cnf_tmp \
				working_safe_ssl_conf working_safe_org_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() {
	[ -d "$secured_session" ] || return 0
	if rm -rf "$secured_session"; then
		verbose "\
remove_secure_session: DELETED: $secured_session"
		unset -v secured_session OPENSSL_CONF \
			EASYRSA_SSL_CONF safe_ssl_cnf_tmp \
			working_safe_ssl_conf working_safe_org_conf
		return
	fi
	die "remove_secure_session Failed: $secured_session"
} # => remove_secure_session()

# 'mkdir' wrapper, broken by win11, which fails without error
easyrsa_mkdir() {
	[ "$2" ] && die "easyrsa_mkdir - excess input"
	[ "$1" ] || die "easyrsa_mkdir - input"
	[ -d "$1" ] && return
	mkdir "$1" 2>/dev/null
	[ -d "$1" ] && return
	die "easyrsa_mkdir - FAIL: $1"
} # => easyrsa_mkdir()

# 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() {
	if [ -z "$1" ] || [ "$2" ]; then
		die "easyrsa_mktemp - input error"
	fi

	# session directory must exist
	[ -d "$secured_session" ] || die "\
easyrsa_mktemp - Temporary session undefined (--tmp-dir)"

	# Force noclobber
	if [ "$easyrsa_host_os" = win ]; then
		set -o noclobber
	else
		set -C
	fi

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

	# Create shotfile
	for shot_try in x y z; do
		shotfile="${tmp_fname}.${shot_try}"
		if [ -f "$shotfile" ]; then
			verbose "\
easyrsa_mktemp: shotfile 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 1 attempt
			for ext_try in 1 2 3 4 5 6 7 8 9; do
				want_tmp_file="${tmp_fname}.${ext_try}"

				# Warn to error log file for max reached
				if [ "$EASYRSA_MAX_TEMP" -lt "$ext_try" ]; then
					print "\
Max temp-file limit $ext_try, hit for: $1" > "$easyrsa_err_log"
					die "EASYRSA_MAX_TEMP exceeded"
				fi

				if [ -f "$want_tmp_file" ]; then
					verbose "\
easyrsa_mktemp: temp-file EXISTS: $want_tmp_file"
					continue
				else
					# atomic:
					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"

							# unset noclobber
							if [ "$easyrsa_host_os" = win ]; then
								set +o noclobber
							else
								set +C
							fi

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

							unset -v tmp_fname \
								shotfile shot_try \
								want_tmp_file ext_try
							return
						else
							die "\
easyrsa_mktemp - force_set_var $1 failed"
						fi
					fi
				fi
			done
		fi
	done

	# unset noclobber
	if [ "$easyrsa_host_os" = win ]; then
		set +o noclobber
	else
		set +C
	fi

	# In case of subshell abuse, report to error log
	err_msg="\
easyrsa_mktemp - failed for: $1 @ attempt=$ext_try
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

	# Remove redundant file: index.txt.attr
	if [ -f "$EASYRSA_PKI"/index.txt.attr ]; then
		rm -f "$EASYRSA_PKI"/index.txt.attr
		verbose "cleanup: DELETED $EASYRSA_PKI/index.txt.attr"
	fi

	# undo changes BEFORE delete temp-dir
	# 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

	# Remove temp-session or create temp-snapshot
	if [ -d "$secured_session" ]; then
		if [ "$EASYRSA_KEEP_TEMP" ]; then
			# skip on black-listed directory names, with a warning
			# Use '-e' for directory or file name
			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"
				easyrsa_mkdir "$EASYRSA_TEMP_DIR"/tmp
				easyrsa_mkdir "$keep_tmp"
				rm -rf "$keep_tmp"
				mv -f "$secured_session" "$keep_tmp"
				information "Temp session preserved: $keep_tmp"
				unset -v secured_session
			fi
		fi

		# remove temp-session
		remove_secure_session
		verbose "mktemp_counter: $mktemp_counter uses"
	fi

	# When prompt is disabled then restore prompt
	case "$prompt_restore" in
		0) : ;; # Not required
		1)
			[ -t 1 ] && stty echo
			[ "$EASYRSA_SILENT" ] || print
		;;
		2)
			# shellcheck disable=SC3040 # POSIX set -o
			set -o echo
			[ "$EASYRSA_SILENT" ] || print
		;;
		*) warn "Unknown prompt_restore: '$prompt_restore'"
	esac

	# 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
	elif [ "$1" = 2 ]; then
		verbose "exit SIGINT = true"
		kill -2 "$$" # Exit: SIGINT
	elif [ "$1" = ok ]; then
		verbose "Exit: Final Success = true"
		exit 0 # Exit: Final Success
	fi

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

# 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" ]; 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"
		working_safe_org_conf=1 # Set run once
	fi

	# 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=1090 # Non-constant source
	. "$escape_hazard_tmp"
	verbose "escape_hazard: COMPLETED"
} # => 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" ]; 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 # LibreSSL Always run
		verbose "expand_ssl_config: REQUIRED"
	elif [ "$ssl_lib" = openssl ]; then # OpenSSL not required
		verbose "expand_ssl_config: IGNORED"
		return
	else
		die "expand_ssl_config: EXCEPTION" # do NOT Run
	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 expand ''
	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: via 'sed' COMPLETED"
		else
			return 1
		fi

	export EASYRSA_SSL_CONF="$safe_ssl_cnf_tmp"
	verbose \
		"expand_ssl_config: EASYRSA_SSL_CONF = $EASYRSA_SSL_CONF"
} # => expand_ssl_config()

# Easy-RSA meta-wrapper for SSL
# WARNING: Running easyrsa_openssl in a subshell
# will hide error message and verbose messages
easyrsa_openssl() {
	openssl_command="$1"; shift

	if [ "$EASYRSA_DEBUG" ]; then
		verbose "= easyrsa_openssl - BEGIN $openssl_command $*"
	else
		verbose "= easyrsa_openssl - BEGIN $openssl_command"
	fi

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

	# Use $EASYRSA_SSL_CONF (local) or $OPENSSL_CONF (global)
	if [ -f "$EASYRSA_SSL_CONF" ]; then
		export OPENSSL_CONF="$EASYRSA_SSL_CONF"
	else
		[ -f "$OPENSSL_CONF" ] || \
			die "easyrsa_openssl - OPENSSL_CONF undefined"
	fi
	verbose "= easyrsa_openssl: OPENSSL_CONF = $OPENSSL_CONF"

	# Exec SSL
	if [ "$EASYRSA_SILENT_SSL" ] && [ "$EASYRSA_BATCH" ]
	then
		if "$EASYRSA_OPENSSL" "$openssl_command" "$@" \
			2>/dev/null
		then
			verbose "= easyrsa_openssl - END $openssl_command"
			return
		fi
	else
		if "$EASYRSA_OPENSSL" "$openssl_command" "$@"
		then
			verbose "= easyrsa_openssl - END $openssl_command"
			return
		fi
	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="$(
			"$EASYRSA_OPENSSL" version 2>/dev/null
		)"
	ssl_version="$val"

	# SSL lib name
	case "${val%% *}" in
		OpenSSL)
			ssl_lib=openssl
			# Honor EASYRSA_FORCE_SAFE_SSL
			if [ "$EASYRSA_FORCE_SAFE_SSL" ]; then
				ssl_cnf_type=safe-cnf
			else
				ssl_cnf_type=ssl-cnf
			fi
			;;
		LibreSSL)
			ssl_lib=libressl
			ssl_cnf_type=safe-cnf
			;;
		*)
			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|4)
			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

	# Message
	verbose "verify_ssl_lib(): $ssl_version ($EASYRSA_OPENSSL)"
} # => 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() {
	verify_ca_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 serial; do
		if [ ! -f "$EASYRSA_PKI/$i" ] && \
			[ ! -p "$EASYRSA_PKI/$i" ]
		then
			# Used by usage() and export-p12/p7
			[ "$1" = "test" ] && return 1

			user_error "\
Missing expected CA file: $i

(perhaps you need to run build-ca?)

$verify_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?)

$verify_ca_help_note"
	done
} # => verify_ca_init()

# init-pki backend:
init_pki() {
	# Process command options
	reset="hard"
	while [ "$1" ]; do
		case "$1" in
			hard-reset|hard)
				reset="hard"
				confirm_msg=
			;;
			soft-reset|soft)
				reset="soft"
				confirm_msg='PARTIALLY '
			;;
			*) 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 [ -d "$EASYRSA_PKI" ]; then
		confirm "Confirm removal: " "yes" "
WARNING!!!

You are about to ${confirm_msg}remove the EASYRSA_PKI at:
* $EASYRSA_PKI

and initialize a fresh PKI here."

		# now remove it:
		case "$reset" in
		hard)
			# Promote use of 'init-pki soft':
			confirm "
  WARNING: COMPLETELY DESTROY current PKI (NOT recommended) ?

    [yes/NO]: " yes "\
         ******************************************
         * SECOND WARNING - STOP - SECOND WARNING *
         ******************************************

  To keep your current 'pki/vars' settings use 'init-pki soft'.
  To keep your current Request files use 'init-pki soft'
  The Requests can then be signed by a new CA (Partial CA renewal)
  To keep your current Easy-RSA TLS Key use 'init-pki soft'
  This private key file is in use by your current VPN.

       ** USE OF   'init-pki soft'   IS RECOMMENDED **${NL}"

			# # # 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
		# Save existing TLS key
			tls_key_file="$EASYRSA_PKI"/private/easyrsa-tls.key
			old_tls_key_file="$EASYRSA_PKI"/easyrsa-keepsafe-tls.key

			# If both keys exist then they must be the same
			if [ -f "$old_tls_key_file" ]; then
				if [ -f "$tls_key_file" ]; then
					# Match by hash
					tls_key_hash="$(
						"$EASYRSA_OPENSSL" dgst -sha256 \
							"$tls_key_file")"
					tls_key_hash="${tls_key_hash##* }"
					old_tls_key_hash="$(
						"$EASYRSA_OPENSSL" dgst -sha256 \
							"$old_tls_key_file")"
					old_tls_key_hash="${old_tls_key_hash##* }"
					[ "$tls_key_hash" = "$old_tls_key_hash" ] || \
						user_error "\
Easy-RSA TLS Keys do not match, only ONE of these files is valid:
* $tls_key_file
* $old_tls_key_file

Please delete the key above that is no longer in use."
				fi
			fi

			# Save existing TLS key
			if [ -f "$tls_key_file" ]; then
				tls_key_data="$(cat "$tls_key_file")"
			else
				tls_key_data=
			fi

			# Do NOT remove pki/reqs sub-dir, for "renew ca"
			for i in ca.crt crl.pem \
				issued private inline revoked renewed expired \
				serial serial.old index.txt index.txt.old \
				index.txt.attr index.txt.attr.old 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:
	easyrsa_mkdir "$EASYRSA_PKI"
	for i in issued private reqs; do
		easyrsa_mkdir "${EASYRSA_PKI}/$i"
	done

	# If one existed then recreate old TLS key backup file
	if [ "$tls_key_data" ]; then
		header="# Easy-RSA TLS Key: $(date)${NL}# DO NOT DELETE"
		printf '%s\n\n%s\n' "$header" "$tls_key_data" \
			> "$old_tls_key_file"
		tls_msg="${NL}
Previous Easy-RSA TLS key saved to:
* $old_tls_key_file${NL}"
	else
		# if an OLD TLS key still exists then notify user
		if [ -f "$old_tls_key_file" ]; then
			tls_msg="${NL}
Existing Easy-RSA TLS key preserved:
* $old_tls_key_file${NL}"
		else
			tls_msg=
		fi
	fi

	# write pki/vars.example - no temp-file because no session
	write_legacy_file_v2 \
		vars "$EASYRSA_PKI"/vars.example overwrite || \
			warn "init-pki - Failed to create vars.example"

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

Your newly created PKI dir is:
* $EASYRSA_PKI"

	# Select and show vars file
	unset -v EASYRSA_VARS_FILE
	select_vars
	information "\
Using Easy-RSA configuration:
* ${EASYRSA_VARS_FILE:-undefined}${tls_msg}"
} # => init_pki()

# Find support files from various sources
# Declare in preferred order, first wins
# beaten by command line.
# If these files are not found here then they
# will be built on-demand by the selected command.
locate_support_files() {
	# Set required sources
	ssl_cnf_file='openssl-easyrsa.cnf'
	x509_types_dir='x509-types'
	easyrsa_tools='easyrsa-tools.lib'

	# Find data-files
	for area in \
		"$EASYRSA_PKI" \
		"$EASYRSA" \
		"$PWD" \
		"${0%/*}" \
		'/usr/local/share/easy-rsa' \
		'/usr/share/easy-rsa' \
		'/etc/easy-rsa' \
		# EOL
	do
		# Find x509-types
		if [ -d "${area}/${x509_types_dir}" ]; then
			set_var EASYRSA_EXT_DIR "${area}/${x509_types_dir}"
		fi

		# Find openssl-easyrsa.cnf
		if [ -f "${area}/${ssl_cnf_file}" ]; then
			set_var EASYRSA_SSL_CONF "${area}/${ssl_cnf_file}"
		fi

		# Find easyrsa-tools.lib
		if [ -f "${area}/${easyrsa_tools}" ]; then
			set_var EASYRSA_TOOLS_LIB "${area}/${easyrsa_tools}"
		fi
	done

	verbose ": EASYRSA_EXT_DIR: ${EASYRSA_EXT_DIR:-built-in}"
	verbose ": EASYRSA_SSL_CONF: ${EASYRSA_SSL_CONF:-built-in}"
	verbose ": EASYRSA_TOOLS_LIB: ${EASYRSA_TOOLS_LIB:-undefined}"
	verbose "locate_support_files: COMPLETED"
} # => locate_support_files()

# 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
	# 3061 - In POSIX sh, read without a variable is undefined.
	# shellcheck disable=SC3040,SC3045,SC3061
	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
} # => 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
			# shellcheck disable=SC2229 # does not read 't'
			read -r "$t" <<- SECRET
				$r
				SECRET

			unset -v r t
			print
			return 0
		fi
	done
} # => get_passphrase()

# build-ca backend:
build_ca() {
	cipher="-aes256"
	unset -v sub_ca date_stamp x509 error_info \
		ca_password_via_cmdline

	while [ "$1" ]; do
		case "$1" in
			intca|subca)
				sub_ca=1
				;;
			nopass)
				[ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1
				;;
			raw*)
				EASYRSA_RAW_CA=1
				;;
			*) user_error "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?)"

	easyrsa_mkdir "${EASYRSA_PKI}"/certs_by_serial
	easyrsa_mkdir "${EASYRSA_PKI}"/revoked
	easyrsa_mkdir "${EASYRSA_PKI}"/revoked/certs_by_serial
	easyrsa_mkdir "${EASYRSA_PKI}"/revoked/private_by_serial

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

	# If one exists then recreate TLS Key
	tls_key_file="$EASYRSA_PKI"/private/easyrsa-tls.key
	old_tls_key_file="$EASYRSA_PKI"/easyrsa-keepsafe-tls.key
	if [ -f "$old_tls_key_file" ]; then
		confirm "Re-install existing Easy-RSA TLS Key ? " yes "
An Easy-RSA TLS Key, saved by 'init-pki soft', has been found.
This TLS Key is in use by your VPN, it is recommended that you
re-install this TLS Key.

Note:
This is a private key and will NOT be added to new inline files.

To create a new Easy-RSA TLS Key, delete this old TLS Key above."
		cp "$old_tls_key_file" "$tls_key_file" || \
			warn "Failed to install TLS Key!"
		tls_key_msg="${NL}
NOTICE: The previous Easy-RSA TLS Key has been installed:
* $tls_key_file

This TLS Key will NOT be added to new inline files. These new
inline files can then be easily distributed to your servers and
clients. The TLS Key that your servers and clients have previously
received, can be added to the inline file manually.

To re-enable automatically adding this TLS Key to inline files,
simply delete the backup TLS Key at:
* $old_tls_key_file

To create a new Easy-RSA TLS Key, delete both TLS Keys above."
	else
		tls_key_msg="${NL}
Create an OpenVPN TLS-AUTH|TLS-CRYPT-V1 key now: See 'help gen-tls'"
	fi

	# Set ssl batch mode, as required
	[ "$EASYRSA_BATCH" ] && ssl_batch=1

	# 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

	# create local SSL cnf
	write_easyrsa_ssl_cnf_tmp

	# 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 on command line
		# Both --passout and --passin
		# must be defined for a CA with a password
		ca_password_via_cmdline=1

	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

	# Find or create x509 CA file
	if [ -f "$EASYRSA_EXT_DIR/ca" ]; then
		# Use the x509-types/ca file
		x509_type_file="$EASYRSA_EXT_DIR/ca"
	else
		# Use a temp file
		write_x509_type_tmp ca
		x509_type_file="$write_x509_file_tmp"
	fi

	# keyUsage critical
	if [ "$EASYRSA_KU_CRIT" ]; then
		crit_tmp=
		easyrsa_mktemp crit_tmp || \
			die "build-ca - easyrsa_mktemp KU crit_tmp"

		add_critical_attrib keyUsage "$x509_type_file" \
			"$crit_tmp" || die "build-ca - KU add_critical_attrib"

		# Use the new tmp-file with critical attribute
		x509_type_file="$crit_tmp"
		verbose "build_ca: keyUsage critical OK"
	fi

	# basicConstraints critical
	if [ "$EASYRSA_BC_CRIT" ]; then
		crit_tmp=
		easyrsa_mktemp crit_tmp || \
			die "build-ca - easyrsa_mktemp BC crit_tmp"

		add_critical_attrib basicConstraints "$x509_type_file" \
			"$crit_tmp" || die "build-ca - BC add_critical_attrib"

		# Use the new tmp-file with critical attribute
		x509_type_file="$crit_tmp"
		verbose "build_ca: basicConstraints critical OK"
	fi

	# Find or create x509 COMMON file
	if [ -f "$EASYRSA_EXT_DIR/COMMON" ]; then
		# Use the x509-types/COMMON file
		x509_COMMON_file="$EASYRSA_EXT_DIR/COMMON"
	else
		# Use a temp file
		write_x509_type_tmp COMMON
		x509_COMMON_file="$write_x509_file_tmp"
	fi

	# Check for insert-marker in ssl config file
	if ! grep -q '^#%CA_X509_TYPES_EXTRA_EXTS%' \
		"$EASYRSA_SSL_CONF"
	then
		die "\
This openssl config file does not support X509-type 'ca'.
* $EASYRSA_SSL_CONF

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

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

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

	# Insert x509-types COMMON and 'ca' and EASYRSA_EXTRA_EXTS
	{
		# X509 files
		cat "$x509_type_file" "$x509_COMMON_file"

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

	} | awk "$awkscript" "$EASYRSA_SSL_CONF" \
			> "$adjusted_ssl_cnf_tmp" || \
				die "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="$adjusted_ssl_cnf_tmp"

	# Generate CA Key
	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

	# verbose notice
	if [ "$EASYRSA_RAW_CA" ]; then
		verbose "\
build_ca: CA key password created via RAW"
	else
		if [ "$ca_password_via_cmdline" ]; then
			verbose "\
build_ca: CA key password created via command options"
		else
			if [ "$EASYRSA_NO_PASS" ]; then
				verbose "\
build_ca: CA key has no password"
			else
				verbose "\
build_ca: CA key password created via temp-files"
			fi
		fi
	fi

	# Generate the CA keypair:
	easyrsa_openssl req -utf8 -new \
		-key "$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"

	# Move temp-files to target-files
	mv "$out_key_tmp" "$out_key" || mv_temp_error=1
	mv "$out_file_tmp" "$out_file" || mv_temp_error=1
	if [ "$mv_temp_error" ]; then
		rm -f "$out_key" "$out_file"
		die "Failed to move new CA files."
	fi

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

Prior to signing operations, place your resulting Sub-CA cert at:
* $EASYRSA_PKI/ca.crt"
	else
		notice "\
CA creation complete. Your new CA certificate is at:
* $out_file${tls_key_msg}

Build-ca completed successfully."
	fi
} # => build_ca()

# Build self signed key pair
self_sign() {
	# Define x509 type
	case "$1" in
		server)
			selfsign_eku=serverAuth
			crt_type=self-signed-server
			;;
		client)
			selfsign_eku=clientAuth
			crt_type=self-signed-client
			;;
		*)
			die "self_sign: Unknown EKU '$1'"
	esac
	shift

	# pull $file_name_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."

	file_name_base="$1"
	shift

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

	# Enforce commonName
	export EASYRSA_REQ_CN="$file_name_base"

	# create local SSL cnf
	write_easyrsa_ssl_cnf_tmp

	# Refuse option as name
	case "$file_name_base" in
		nopass)
			user_error "Refusing '$file_name_base' as name."
	esac

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

	# Assign output files
	key_out="$EASYRSA_PKI/private/${file_name_base}.key"
	crt_out="$EASYRSA_PKI/issued/${file_name_base}.crt"
	inline_out="$EASYRSA_PKI/inline/${file_name_base}.inline"

	# key file must NOT exist
	[ -f "$key_out" ] && user_error "\
Cannot self-sign this request for '$file_name_base'.
Conflicting key exists at:
* $key_out"

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

	# Check algo and curve
	case "$EASYRSA_ALGO" in
		rsa|ec)
			# Silently use ec instead of rsa
			export EASYRSA_ALGO=ec
			# Selectively set --curve=secp384r1
			set_var EASYRSA_CURVE secp384r1

			# temp-file for params-file
			selfsign_params_file=""
			easyrsa_mktemp selfsign_params_file || \
				die "self_sign - easyrsa_mktemp selfsign_params_file"

			# params-file
			"$EASYRSA_OPENSSL" ecparam \
				-name "$EASYRSA_CURVE" \
				-out "$selfsign_params_file" || \
					die "self_sign - params-file failed"

			newkey_params="$EASYRSA_ALGO":"$selfsign_params_file"
			;;
		ed)
			# Selectively set --curve=ed25519
			set_var EASYRSA_CURVE ed25519
			newkey_params="$EASYRSA_CURVE"
			;;
		*)
			user_error "Unrecognised algorithm: '$EASYRSA_ALGO'"
	esac

	verbose "\
self-sign: Use ALGO:'$EASYRSA_ALGO' / CURVE:'$EASYRSA_CURVE'"

	# Assign tmp-file for config
	adjusted_ssl_cnf_tmp=""
	easyrsa_mktemp adjusted_ssl_cnf_tmp || \
		die "self_sign - easyrsa_mktemp adjusted_ssl_cnf_tmp"

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

	# Find or create x509 selfsign file
	if [ -f "$EASYRSA_EXT_DIR/selfsign" ]; then
		# Use the x509-types/selfsign file
		x509_selfsign_file="$EASYRSA_EXT_DIR/selfsign"
	else
		# Use a temp file
		write_x509_type_tmp selfsign
		x509_selfsign_file="$write_x509_file_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
		write_x509_type_tmp COMMON
		x509_COMMON_file="$write_x509_file_tmp"
	fi

	# Insert x509-types COMMON and 'selfsign' and EASYRSA_EXTRA_EXTS
	{
		# X509 files
		cat "$x509_selfsign_file" "$x509_COMMON_file"

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

	} | awk "$awkscript" "$EASYRSA_SSL_CONF" \
			> "$adjusted_ssl_cnf_tmp" || \
				die "Copying X509_TYPES to config file failed"
	verbose "self_sign: insert x509 and extensions OK"

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

	# Create temp-files for output
	tmp_key_out=""
	easyrsa_mktemp tmp_key_out || \
		die "self_sign - easyrsa_mktemp tmp_key_out"

	tmp_crt_out=""
	easyrsa_mktemp tmp_crt_out || \
		die "self_sign - easyrsa_mktemp tmp_crt_out"

	# create self-signed key pair
	easyrsa_openssl req -x509 -utf8 -sha256 -text \
		-newkey "$newkey_params" \
		-keyout "$tmp_key_out" \
		-out "$tmp_crt_out" \
		-subj "/CN=$file_name_base" \
		${EASYRSA_NO_PASS:+ "$no_password"} \
		${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
		${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \
		${EASYRSA_CERT_EXPIRE:+ -days "$EASYRSA_CERT_EXPIRE"} \
		${EASYRSA_START_DATE:+ -startdate "$EASYRSA_START_DATE"} \
		${EASYRSA_END_DATE:+ -enddate "$EASYRSA_END_DATE"}

	# Move temp-files to target-files
	mv "$tmp_key_out" "$key_out" || mv_temp_error=1
	mv "$tmp_crt_out" "$crt_out" || mv_temp_error=1
	if [ "$mv_temp_error" ]; then
		rm -f "$key_out" "$crt_out"
		die "Failed to move new key/cert files."
	fi

	# inline key/cert/fingerprint
	inline_file "$file_name_base"

	# User info
	notice "\
Self-signed '$EASYRSA_ALGO/$EASYRSA_CURVE' \
key and certificate created:
* $key_out
* $crt_out"
} # => self_sign()

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

	# check to see if we already have a dh parameters file
	if [ -f "$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
	easyrsa_openssl dhparam -out "$tmp_dh_file" \
		"$EASYRSA_KEY_SIZE" || die "Failed to generate DH params"

	# Validate dh.pem
	easyrsa_openssl dhparam -in "$tmp_dh_file" \
		-check -noout || die "Failed to validate DH params"

	# Move temp-files to target-files
	mv "$tmp_dh_file" "$out_file" || mv_temp_error=1
	if [ "$mv_temp_error" ]; then
		rm -f "$out_file"
		die "Failed to move temp DH file."
	fi

	notice "
DH parameters of size $EASYRSA_KEY_SIZE created at:
* $out_file"
} # => 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

	# Set ssl batch mode as required
	[ "$EASYRSA_BATCH" ] && ssl_batch=1

	# Set commonName
	if [ "$EASYRSA_REQ_CN" = ChangeMe ]; then
		export EASYRSA_REQ_CN="$file_name_base"
	fi

	# create local SSL cnf
	write_easyrsa_ssl_cnf_tmp

	# 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
				;;
			*) user_error "Unknown command option: '$1'"
		esac
		shift
	done

	# don't wipe out an existing request without confirmation
	[ -f "$req_out" ] && confirm "Confirm request overwrite: " "yes" "\

WARNING!!!

An existing request file was found at
* $req_out

Continuing with key generation will replace this request."

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

WARNING!!!

An existing private key was found at
* $key_out

Continuing with key generation will replace this key."

	# 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"
		# shellcheck disable=SC2016 # No expand '' - gen_req()
		awkscript='
{if ( match($0, "^#%EXTRA_EXTS%") )
	{ while ( getline<"/dev/stdin" ) {print} next }
 {print}
}'
		# Assign temp-file for config
		adjusted_ssl_cnf_tmp=""
		easyrsa_mktemp adjusted_ssl_cnf_tmp || \
			die "gen_req - easyrsa_mktemp adjusted_ssl_cnf_tmp"

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

		[ "${EASYRSA_SAN_CRIT}" ] && \
			verbose "gen-req: SAN critical OK"

		# Use this SSL config for the rest of this function
		EASYRSA_SSL_CONF="$adjusted_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" || mv_temp_error=1
	mv "$req_out_tmp" "$req_out" || mv_temp_error=1
	if [ "$mv_temp_error" ]; then
		rm -f "$key_out" "$req_out"
		die "Failed to move temp key/req file."
	fi

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

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

	# Verify $crt_type is valid
	case "$crt_type" in
		ca|server|serverClient|client|codeSigning|email|kdc)
			: # All known types plus CA for sub-ca
		;;
		*)
			warn "\
Unrecognised x509-type: '$crt_type'

In order to sign a custom X509 Type certificate, there must be a
corresponding SSL configuration file in the 'x509-type' folder."
	esac

	# 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

	# create local SSL cnf
	write_easyrsa_ssl_cnf_tmp

	# Check optional subject
	force_subj=
	while [ "$1" ]; do
		case "$1" in
			nopass)
				warn "Ignoring option '$1'"
				;;
			newsubj*)
				# verify force_subj opts are used correctly
				[ "$EASYRSA_NEW_SUBJECT" ] || user_error "\
To force a new certificate subject, global option --new-subject
must also be specified."
				force_subj="$EASYRSA_NEW_SUBJECT"
				;;
			preserve*)
				export EASYRSA_PRESERVE_DN=1
				;;
			*)
				user_error "Unknown option '$1'"
		esac
		shift
	done

	# verify force_subj opts are used correctly
	if [ "$EASYRSA_NEW_SUBJECT" ]; then
		[ "$force_subj" ] || user_error "\
To force a new certificate subject, command option 'newsubj'
must also be specified."
	fi

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

	# Request file must exist
	[ -f "$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
	[ -f "$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
			easyrsa_random 16 serial

			# Require 128bit serial number
			[ "$serial" = "${serial#00}" ] || continue

			# 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 # No expand '' - sign_req()
		awkscript='
{if ( match($0, "^#%COPY_EXTS%") )
	{ while ( getline<"/dev/stdin" ) {print} next }
 {print}
}'
		# Assign temp-file for config
		adjusted_ssl_cnf_tmp=""
		easyrsa_mktemp adjusted_ssl_cnf_tmp || \
			die "sign_req - easyrsa_mktemp adjusted_ssl_cnf_tmp"

		print "$copy_exts" | \
			awk "$awkscript" "$EASYRSA_SSL_CONF" \
				> "$adjusted_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="$adjusted_ssl_cnf_tmp"
		verbose "sign_req: Using '$copy_exts'"
		verbose "sign_req: EASYRSA_SSL_CONF = $EASYRSA_SSL_CONF"
	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
		write_x509_type_tmp "$crt_type"
		x509_type_file="$write_x509_file_tmp"
	fi

	# keyUsage critical
	confirm_ku_crit=
	if [ "$EASYRSA_KU_CRIT" ]; then
		crit_tmp=
		easyrsa_mktemp crit_tmp || \
			die "sign-req - easyrsa_mktemp KU crit_tmp"

		add_critical_attrib keyUsage "$x509_type_file" \
			"$crit_tmp" || die "sign-req - KU add_critical_attrib"

		# Use the new tmp-file with critical attribute
		x509_type_file="$crit_tmp"
		confirm_ku_crit="  keyUsage:         'critical'${NL}"
		verbose "sign_req: keyUsage critical OK"
	fi

	# basicConstraints critical
	confirm_bc_crit=
	if [ "$EASYRSA_BC_CRIT" ]; then
		crit_tmp=
		easyrsa_mktemp crit_tmp || \
			die "sign-req - easyrsa_mktemp BC crit_tmp"

		add_critical_attrib basicConstraints "$x509_type_file" \
			"$crit_tmp" || die "sign-req - BC add_critical_attrib"

		# Use the new tmp-file with critical attribute
		x509_type_file="$crit_tmp"
		confirm_bc_crit="  basicConstraints: 'critical'${NL}"
		verbose "sign_req: basicConstraints critical OK"
	fi

	# extendedKeyUsage critical
	confirm_eku_crit=
	if [ "$EASYRSA_EKU_CRIT" ]; then
		crit_tmp=
		easyrsa_mktemp crit_tmp || \
			die "sign-req - easyrsa_mktemp EKU crit_tmp"

		add_critical_attrib extendedKeyUsage "$x509_type_file" \
			"$crit_tmp" || die "sign-req - EKU add_critical_attrib"

		# Use the new tmp-file with critical attribute
		x509_type_file="$crit_tmp"
		confirm_eku_crit="  extendedKeyUsage: 'critical'${NL}"
		verbose "sign_req: extendedKeyUsage critical OK"
	fi

	# Find or create x509 COMMON file
	if [ -f "$EASYRSA_EXT_DIR/COMMON" ]; then
		# Use the x509-types/COMMON file
		x509_COMMON_file="$EASYRSA_EXT_DIR/COMMON"
	else
		# Use a temp file
		write_x509_type_tmp COMMON
		x509_COMMON_file="$write_x509_file_tmp"
	fi

	# Support a dynamic CA path length when present:
	unset -v basicConstraints confirm_bc_len
	if [ "$crt_type" = "ca" ] && [ "$EASYRSA_SUBCA_LEN" ]
	then
		# Print the last occurrence of basicConstraints in
		# x509-types/ca
		# If basicConstraints is not defined then bail
		# shellcheck disable=SC2016 # No expand '' - sign_req()
		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'"
		confirm_pathlen="
  Path length:      '$EASYRSA_SUBCA_LEN'${NL}"
		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

	# Get request CN
	# EASYRSA_REQ_CN MUST always be set to the CSR CN
	EASYRSA_REQ_CN="$(
		"$EASYRSA_OPENSSL" req -utf8 -in "$req_in" -noout \
			-subject -nameopt multiline | grep 'commonName'
	)" || warn "sign-req - EASYRSA_REQ_CN FAILED"
	EASYRSA_REQ_CN="${EASYRSA_REQ_CN##*= }"

	# Add auto SAN, if EASYRSA_AUTO_SAN is enabled
	if [ -z "$EASYRSA_SAN" ] && [ "$EASYRSA_AUTO_SAN" ]; then
		# Choose DNS:san or IP:san
		if print "$EASYRSA_REQ_CN" | grep -q \
			'^[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+$'
		then
			EASYRSA_SAN="IP:${EASYRSA_REQ_CN}"
		else
			EASYRSA_SAN="DNS:${EASYRSA_REQ_CN}"
		fi

		# Add auto SAN to EASYRSA_EXTRA_EXTS
		EASYRSA_EXTRA_EXTS="\
$EASYRSA_EXTRA_EXTS
subjectAltName = ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}"
		verbose "sign-req: Auto SAN: ${EASYRSA_SAN}"
	fi

	# confirm SAN critical
	confirm_san_crit=
	if [ "${EASYRSA_SAN_CRIT}" ]; then
		confirm_san_crit="  subjectAltName:   'critical'${NL}"
		verbose "sign-req: SAN critical OK"
	fi

	# 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 supplied extra extensions
		# and/or SAN extension
		if [ "$EASYRSA_EXTRA_EXTS" ]; then
			print "$EASYRSA_EXTRA_EXTS"
		fi
	} > "$ext_tmp" || die "\
Failed to create temp extension file (bad permissions?) at:
* $ext_tmp"
	verbose "sign_req: Generated extensions file OK"

	# Set confirm CN
	confirm_CN="  Requested CN:     '$EASYRSA_REQ_CN'"

	# Set confirm type
	confirm_type="  Requested type:   '$crt_type'"

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

	# Set confirm DN
	if [ "$force_subj" ]; then
		confirm_dn="${NL}* Forced Subject:   '$force_subj'${NL}"
	else
		confirm_dn="${NL}$(display_dn req "$req_in")" || \
			die "sign-req: display_dn"
	fi

	# Set confirm SAN
	# SAN from .req
	if [ "$EASYRSA_CP_EXT" ]; then
		# capture complete CSR
		req_text="$(
			"$EASYRSA_OPENSSL" req -utf8 -in "$req_in" -noout -text
			)" || die "sign-req: openssl: req_text"

		# Check CSR for any requested SAN
		if echo "$req_text" | \
			grep -q 'X509v3 Subject Alternative Name'
		then
			# extract requested SAN
			# 'grep -A' may not be strictly POSIX, die on error
			req_x509_san="$(
				echo "$req_text" | \
					grep -A 1 'X509v3 Subject Alternative Name'
			)" || die "sign-req: req_x509_san: grep -A 1 (POSIX)"
		else
			# No requested SAN
			req_x509_san=
		fi
	fi

	# Set confirm details
	confirm_critical_attribs="
${confirm_bc_crit}${confirm_ku_crit}\
${confirm_eku_crit}${confirm_san_crit}"

	confirm_details="\
${confirm_CN}
${confirm_type}${confirm_pathlen}
${confirm_period}
${confirm_critical_attribs}${confirm_dn}"

	# --san takes priority over req SAN and --copy-ext
	if [ "$EASYRSA_SAN" ]; then
		confirm_san="\
            X509v3 Subject Alternative Name:
                ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}"
	else
		confirm_san="$req_x509_san"
	fi

	# Set confirm SAN
	if [ "$EASYRSA_SAN" ] || [ "$req_x509_san" ]; then
		confirm_details="$confirm_details${NL}${NL}$confirm_san"
	fi

	# Display the request subject in an easy-to-read format
	# Confirm the user wishes to sign this request
	# The foreign_request confirmation is not required
	# for build_full:
	if [ "$local_request" ]; then
		unset -v foreign_request
	else
		foreign_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 requested details: " "yes" "\
${foreign_request}You are about to sign the following certificate:

$confirm_details" # => 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} \
		${force_subj:+ -subj "$force_subj"} \
		${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"

	# Move temp-files to target-files
	mv "$crt_out_tmp" "$crt_out" || mv_temp_error=1
	if [ "$mv_temp_error" ]; then
		rm -f "$crt_out"
		die "Failed to move temp certificate file."
	fi

	# inline file
	inline_file "$file_name_base"

	# Success messages
	notice "\
Certificate created at:
* $crt_out"
} # => sign_req()

# Add 'critical' attribute to X509-type file
add_critical_attrib() {
	case "$1" in
		basicConstraints|keyUsage|extendedKeyUsage) : ;; # ok
		*) die "add_critical_attrib - usage: '$1'"
	esac

	[ -f "$2" ] || die "add_critical_attrib - file-2: '$2'"
	[ -f "$3" ] || die "add_critical_attrib - file-3: '$3'"

	sed s/"$1 = "/"$1 = critical,"/g "$2" > "$3"
} # => add_critical_attrib()

# Check serial in db
check_serial_unique() {
	[ "$1" ] || user_error "Serial number required!"
	case "$1" in
		(*[!1234567890abcdef]*)
			user_error "Invalid serial number: '$1'"
	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 capture all output
	# Do NOT unset check_serial for sign-req error msg
	check_serial="$(
			"$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
				;;
			*) user_error "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:
*"
	[ -f "$req_out" ] && \
		user_error "Request $err_exists $req_out"
	[ -f "$key_out" ] && \
		user_error "Key $err_exists $key_out"
	[ -f "$crt_out" ] && \
		user_error "Certificate $err_exists $crt_out"
	unset -v err_exists

	# create request
	verbose "build_full: BEGIN gen_req"
	gen_req "$name" batch
	verbose "build_full: END gen_req"

	# Set to modify sign-req confirmation message
	local_request=1

	# Recreate temp-session and
	# drop edits to SSL Conf file
	remove_secure_session
	locate_support_files
	secure_session
	write_global_safe_ssl_cnf_tmp

	# Require --copy-ext
	export EASYRSA_CP_EXT=1

	# Sign it
	verbose "build_full: BEGIN sign_req"
	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
	verbose "build_full: END sign_req"
} # => build_full()

# Generate inline file V2
inline_file() {
	# Allow complete disable
	[ "$EASYRSA_DISABLE_INLINE" ] && return

	# definitive source
	[ "$1" ] || die "inline_file - Missing file_name_base"

	# make inline dirs
	easyrsa_mkdir "$EASYRSA_PKI"/inline
	easyrsa_mkdir "$EASYRSA_PKI"/inline/private

	# Source files
	crt_source="${EASYRSA_PKI}/issued/${1}.crt"
	key_source="${EASYRSA_PKI}/private/${1}.key"
	ca_source="$EASYRSA_PKI"/ca.crt
	tls_source="$EASYRSA_PKI"/private/easyrsa-tls.key
	old_tls_key_file="$EASYRSA_PKI"/easyrsa-keepsafe-tls.key

	# output
	inline_out="${EASYRSA_PKI}/inline/${1}.inline"
	print "\
# Inline files in the 'private' directory contain security keys which
# MUST only be transmitted over a secure connection, such as 'scp'." \
		> "$EASYRSA_PKI"/inline/private/README.inline.private
	inline_incomplete=
	inline_private=

	# Generate Inline data
	# Certificate
	if [ -f "$crt_source" ]; then
		crt_data="\
<cert>
$(cat "$crt_source")
</cert>"

		# Calculate decimal value for serial number
		# because openvpn uses decimal serial ?!?
		# for '--crl-verify /path/to/dir dir'
		# For reasons unknown..
		if which bc >/dev/null; then
			crt_serial="$(
				"$EASYRSA_OPENSSL" x509 -in "$crt_source" \
					-noout -serial
				)" || die "inline_file - SSL -serial failed"
			crt_serial="${crt_serial#*=}"
			crt_serial_dec="$(
					echo "ibase=16; $crt_serial" | bc
				)" || die "inline_file - HEX to DEC failed"
		else
			crt_serial_dec="Unavailable"
		fi

		# Generate fingerprint
		crt_fingerprint="$(
			"$EASYRSA_OPENSSL" x509 -in "$crt_source" \
				-noout -sha256 -fingerprint
			)" || die "inline_file - Failed -fingerprint"
		# strip prefix
		crt_fingerprint="${crt_fingerprint#*=}"

		# Certificate type
		if [ -z "$crt_type" ]; then
			ssl_cert_x509v3_eku "$crt_source" crt_type || \
				die "inline_file: Failed to set crt_type"
		fi

		# commonName
		crt_CN="$(
			display_dn x509 "$crt_source" | grep 'commonName'
		)" || die "inline_file: Failed to set crt_CN"
		# strip prefix
		crt_CN="${crt_CN#*= }"
	else
		inline_incomplete=1
		crt_data="\
# When you recieve your signed certificate place it in the
# 'pki/issued' sub-dir of your PKI and use command 'inline'
# to rebuild this inline file with your certificate.
# <cert>
# * Paste your user certificate here *
# </cert>"

		crt_fingerprint=unknown
		crt_type=unknown
		crt_CN=unknown
	fi

	# Private key
	if [ -f "$key_source" ]; then
		inline_private=1
		key_data="\
<key>
$(cat "$key_source")
</key>"
	else
		inline_incomplete=1
		key_data="\
# When you recieve your key place it in the
# 'pki/private' sub-dir of your PKI and use command 'inline'
# to rebuild this inline file with your key.
# <key>
# * Paste your private key here *
# </key>"
	fi

	# CA certificate
	if [ -f "$ca_source" ]; then
		ca_data="\
<ca>
$(cat "$ca_source")
</ca>"
	else
		inline_incomplete=1
		ca_data="\
# When you recieve your CA certificate place it in the
# 'pki' sub-dir of your PKI and use command 'inline'
# to rebuild this inline file with your CA certificate.
# <ca>
# * Paste your CA certificate here *
# </ca>"
	fi

	# TLS KEY - Set TLS auth|crypt key inline label
	if [ -f "$tls_source" ]; then
		tls_key_data="$(cat "$tls_source")"
		case "$tls_key_data" in
			*'TLS-AUTH'*) tls_key_label=tls-auth ;;
			*'TLS-CRYPT'*) tls_key_label=tls-crypt ;;
			*) tls_key_label=
		esac
	fi

	# Do NOT add TLS key if OLD TLS key exists
	# because this PSK has already been shared.
	if [ -f "$old_tls_key_file" ]; then
		tls_data="\
# Add the existing TLS AUTH/CRYPT-V1 Key here:
# <${tls_key_label}>
# * Paste The existing pre-shared TLS key here *
# </${tls_key_label}>"

		# Add --key-direction for TLS-AUTH
		[ "$tls_key_label" = tls-auth ] && \
			tls_data="$tls_data
#
# Add the required 'key-direction 0|1' here:
# key-direction 1"
		unset -v tls_key_data tls_key_label
	else
		# Add standard TLS key details
		if [ -f "$tls_source" ]; then
			inline_private=1
			if [ "$tls_key_label" ]; then
				tls_data="\
<${tls_key_label}>
${tls_key_data}
</${tls_key_label}>"
			else
				inline_incomplete=1
				tls_data="# Easy-RSA TLS Key not recognised!"
			fi
		else
			#inline_incomplete=1
			tls_data="# Easy-RSA TLS Key not found!"
		fi
	fi

	# Only support inline TLS keys for OpenVPN server/client use
	case "$crt_type" in
		server) key_direction="key-direction 0" ;;
		client) key_direction="key-direction 1" ;;
		*)
		verbose "inline: Unsupported certificate type: $crt_type"
		tls_key_label=
		key_direction=
		tls_data="# No TLS Key support for cert-type: $crt_type"
	esac

	# Add --key-direction for TLS-AUTH
	if [ "$tls_key_label" = tls-auth ]; then
		tls_data="${tls_data}${NL}${NL}${key_direction}"
	fi

	# If inline file has keys then redirect to 'private' dir
	[ "$inline_private" ] && \
		inline_out="${EASYRSA_PKI}/inline/private/${1}.inline"

	# Print data
	print "\
# Easy-RSA Inline file
# Certificate type: $crt_type
# commonName: $crt_CN
# SHA256 fingerprint:
# $crt_fingerprint
# Decimal serial number: $crt_serial_dec

$crt_data

$key_data

$ca_data

$tls_data
" > "$inline_out"

	# user info
	if [ "$inline_incomplete" ]; then
		warn "\
INCOMPLETE Inline file created:
* $inline_out"
	else
		notice "\
Inline file created:
* $inline_out"
	fi
} # => inline_file()

# 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

	# create local SSL cnf
	write_easyrsa_ssl_cnf_tmp

	in_dir="$EASYRSA_PKI"
	key_in="$in_dir/private/${file_name_base}.key"
	req_in="$in_dir/reqs/${file_name_base}.req"
	inline_pub="$in_dir/inline/${file_name_base}.inline"
	inline_pri="$in_dir/inline/private/${file_name_base}.inline"

	# input cert for revocation: issued, expired or renewed
	crt_in="${in_dir}/${cert_dir}/${file_name_base}.crt"

	# Assign possible "crl_reason"
	if [ "$1" ]; then
		crl_reason="$1"
		shift

		case "$crl_reason" in
			us|uns*) crl_reason=unspecified ;;
			kc|key*) crl_reason=keyCompromise ;;
			cc|[Cc][Aa]*) crl_reason=CACompromise ;;
			ac|aff*) crl_reason=affiliationChanged ;;
			ss|sup*) crl_reason=superseded ;;
			co|ces*) crl_reason=cessationOfOperation ;;
			ch|cer*) crl_reason=certificateHold ;;
			*) user_error "\
Unexpected reason: '$crl_reason'. See 'help revoke' for valid reasons."
		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 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"

	# Check for misuse of revoke when revoke-* is intended
	case "$cert_dir" in
	issued)
		# expired cert
		exp_exist="${in_dir}/expired/${file_name_base}.crt"
		if [ -f "$exp_exist" ]; then
			exp_endd="$(
				"$EASYRSA_OPENSSL" x509 -in "$exp_exist" -noout \
					-enddate -serial)" || die "revoke - expire -enddate"
			# shellcheck disable=SC2295 # Expansions inside ${..}
			exp_confirm="
Expired certificate:
* $exp_exist
  Expiry: ${exp_endd%%${NL}serial=*}
  Serial: ${exp_endd##*serial=}
  Use command 'revoke-expired' to revoke this certificate."
		else
			unset -v exp_exist exp_endd exp_confirm
		fi

		# renewed cert
		ren_exist="${in_dir}/renewed/${file_name_base}.crt"
		if [ -f "$ren_exist" ]; then
			ren_endd="$(
				"$EASYRSA_OPENSSL" x509 -in "$ren_exist" -noout \
					-enddate -serial)" || die "revoke - renew -enddate"
			# shellcheck disable=SC2295 # Expansions inside ${..}
			ren_confirm="
Renewed certificate:
* $ren_exist
  Expiry: ${ren_endd%%${NL}serial=*}
  Serial: ${ren_endd##*serial=}
  Use command 'revoke-renewed' to revoke this certificate."
		else
			unset -v ren_exist ren_endd ren_confirm
		fi

		# issued cert
		crt_endd="$(
			"$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout \
				-enddate -serial)" || die "revoke - expire -enddate"

		# Confirm intended use of 'revoke'
		if [ "$exp_exist" ] || [ "$ren_exist" ]; then
			warn "The following certificate(s) exist:
${exp_exist:+${exp_confirm}${NL}}${ren_exist:+${ren_confirm}${NL}}"
			# shellcheck disable=SC2295 # Expansions inside ${..}
			confirm "  Confirm intended use of 'revoke' ? " yes "\
Please confirm your intended use of 'revoke' for the following
issued certificate:${NL}
* $crt_in
  Expiry: ${crt_endd%%${NL}serial=*}
  Serial: ${crt_endd##*serial=}"
		fi

		# Revoking an issued cert forces req/key to be moved
		move_req_and_key=1
	;;
	expired|renewed/issued)
		# Revoke-expired/renewed cert means req/key can remain
		move_req_and_key=
	;;
	*)
		die "Invalid cert_dir: '$cert_dir'"
	esac

	# Verify request
	if [ -f "$req_in" ]; then
		verify_file req "$req_in" || user_error "\
Unable to verify request. The file is not a valid request.
Request was expected at:
* $req_in"
	fi

	# get the serial number of the certificate
	cert_serial=
	ssl_cert_serial "$crt_in" cert_serial || \
		die "$cmd: Failed to get cert serial number!"

	# 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.
*"
	[ -f "$crt_out" ] && \
		user_error "$deny_msg certificate: $crt_out"
	[ -f "$key_out" ] && \
		user_error "$deny_msg private key: $key_out"
	[ -f "$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
	if [ "$move_req_and_key" ] && [ -f "$key_in" ]; then
		if_exist_key_in="
* $key_in"
	fi

	if [ "$move_req_and_key" ] && [ -f "$req_in" ]; then
		if_exist_req_in="
* $req_in"
	fi

	# Set confirm DN and serial
	confirm_dn="$(display_dn x509 "$crt_in")" || \
		die "revoke: display_dn"
	confirm_sn="    serial-number             = $cert_serial"

	# 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:
* $inline_pub
* $inline_pri"

	confirm "  Continue with revocation: " "yes" "
Please confirm that you wish to revoke the certificate
with the following subject:

$confirm_dn
$confirm_sn

    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."
} # => revoke()

# revoke_move
# moves revoked certificates to the 'revoked' folder
# allows reissuing certificates with the same name
revoke_move() {
	easyrsa_mkdir "$EASYRSA_PKI"/revoked
	easyrsa_mkdir "$EASYRSA_PKI"/revoked/reqs_by_serial
	easyrsa_mkdir "$EASYRSA_PKI"/revoked/certs_by_serial
	easyrsa_mkdir "$EASYRSA_PKI"/revoked/private_by_serial

	# only move the req when revoking an issued cert
	# and if we have the req
	if [ "$move_req_and_key" ] && [ -f "$req_in" ]; then
		mv "$req_in" "$req_out" || warn "Failed to move: $req_in"
	fi

	# move crt to revoked folder
	mv "$crt_in" "$crt_out" || die "Failed to move: $crt_in"

	# only move the key when revoking an issued cert
	# and if we have the key
	if [ "$move_req_and_key" ] && [ -f "$key_in" ]; then
		mv "$key_in" "$key_out" || warn "Failed to move: $key_in"
	fi

	# remove any pkcs files
	for pkcs in p12 p7b p8 p1; do
		if [ -f "$in_dir/issued/$file_name_base.$pkcs" ]; then
			# issued
			rm "$in_dir/issued/$file_name_base.$pkcs" ||
				warn "Failed to remove: $file_name_base.$pkcs"
		fi

		if [ -f "$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"
		fi
	done

	# remove inline files
	rm -f "$inline_pub" "$inline_pri" || warn \
		"revoke_move - Error trying to remove inline files."
} # => revoke_move()

# Move expired cert out of pki/issued to pki/expired
# to allow renewal
expire_cert() {
	# pull filename base:
	[ "$1" ] || user_error "\
Error: didn't find a file base name as the first argument.
Run easyrsa without commands for usage and command help."

	# Assign file_name_base and dust off!
	file_name_base="$1"
	shift

	# input
	in_dir="$EASYRSA_PKI/issued"
	crt_in="$in_dir/$file_name_base.crt"
	#key_in="$in_dir/private/$file_name_base.key"
	#req_in="$in_dir/reqs/$file_name_base.req"

	# output
	out_dir="$EASYRSA_PKI/expired"
	crt_out="$out_dir/$file_name_base.crt"

	# make output folder
	easyrsa_mkdir "$EASYRSA_PKI"/expired

	# Do not over write existing cert
	if [ -f "$crt_out" ]; then
		user_error "\
Existing file must be revoked:
* $crt_out"
	fi

	# deprecate ALL options
	while [ "$1" ]; do
		case "$1" in
			nopass)
				warn "\
Option 'nopass' is not supported by command '$cmd'."
				;;
			*) 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

	# get the serial number of the certificate
	cert_serial=
	ssl_cert_serial "$crt_in" cert_serial || \
		die "$cmd: Failed to get cert serial number!"

	# Set confirm DN and serial
	confirm_dn="$(display_dn x509 "$crt_in")" || \
		die "expire: display_dn"
	confirm_sn="    serial-number             = $cert_serial"

	# date of expiry
	# Equal to: easyrsa-tools.lib - ssl_cert_not_after_date()
	# This is left as a reminder that easyrsa does not handle
	# dates well and they should be avoided, at all cost.
	# This is for confirmation purposes ONLY.
	crt_expire="$(
		"$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -enddate
		)" || die "expire: enddate"
	confirm_ex="    notAfter date             = ${crt_expire#*=}"

	# confirm
	confirm "  Continue with expiry: " yes "
Please confirm you wish to expire the certificate
with the following subject:

$confirm_dn

$confirm_sn

$confirm_ex" # => End confirm

	# move cert to expired dir
	mv "$crt_in" "$crt_out" || die "failed to move expired: $crt_in"

	# User message
	notice "\
Certificate has been successfully moved to the expired directory.
* $crt_out

This certificate is still valid, until it expires.
It can be revoked with command 'revoke-expired'.

It is now possible to sign a new certificate for '$file_name_base'"
} # => expire_cert()

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

	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:+ -crldays "$EASYRSA_CRL_DAYS"} \
		${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} || \
			die "CRL Generation failed."

	# Move temp-files to target-files
	mv "$out_file_tmp" "$out_file" || mv_temp_error=1
	if [ "$mv_temp_error" ]; then
		#rm -f "$out_file"
		die "Failed to move temp CRL file."
	fi

	# Copy to DER - As published by OpenSSL
	if "$EASYRSA_OPENSSL" crl -in "$out_file" -out "$out_der" \
		-outform DER
	then
		crl_der_note="An updated CRL DER copy has been created:
* $out_der"
	else
		crl_der_note="Failed to create CRL DER copy!"
	fi

	notice "\
$crl_der_note

An updated CRL has been created:
* $out_file"
} # => 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
	[ -f "$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:
* $in_req"

	# destination must not exist
	[ -f "$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."
} # => 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"

	# Always set a friendly_name
	set_var EASYRSA_P12_FR_NAME "$file_name_base"
	friendly_name="$EASYRSA_P12_FR_NAME"

	# opts support
	cipher=-aes256
	want_ca=1
	want_key=1
	unset -v nokeys legacy

	# Under OpenSSL 1.1, use the PBE/MAC algorithms OpenSSL 3.0 uses,
	# unless "legacy" is set.  This makes the .p12 files readable by
	# OpenSSL 3.0 without needing '-legacy'.
	if [ "$openssl_v3" ]; then
		# No cipher opts required
		p12_cipher_opts=""
	else
		# Upgrade PBE & MAC opts - Reset by option 'legacy'
		p12_cipher_opts="-keypbe AES-256-CBC -certpbe AES-256-CBC"
		p12_cipher_opts="${p12_cipher_opts} -macalg sha256"
	fi

	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
				;;
			nofn)
				friendly_name=""
				;;
			legacy)
				if [ "$openssl_v3" ]; then
					legacy=-legacy
				else
					# Downgrade PBE & MAC opts
					p12_cipher_opts=""
				fi
				;;
			*)
				user_error "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 [ -f "$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 [ -f "$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
	inline_out=
	inline_msg=
	case "$pkcs_type" in
	p12)
		pkcs_out="$EASYRSA_PKI/private/$file_name_base.p12"
		inline_out="$EASYRSA_PKI/inline/$file_name_base-p12.inline"

		[ "$legacy" ] && \
			error_info="SSL library may not support -legacy mode"

		# export the p12:
		# shellcheck disable=2086 # Double quote p12_cipher_opts
		easyrsa_openssl pkcs12 -export \
			-in "$crt_in" \
			-out "$pkcs_out" \
			-inkey "$key_in" \
			${nokeys} \
			${legacy} \
			${p12_cipher_opts} \
			${friendly_name:+ -name "$friendly_name"} \
			${want_ca:+ -certfile "$crt_ca"} \
			${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \
			${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \
				|| die "Failed to export PKCS#12"

		# Inline .p12 only
		# Get cert CN
		inline_CN="$(
			"$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -subject \
				-nameopt multiline,-esc_msb | grep 'commonName'
		)" || die "export_pkcs - inline_CN FAILED"
		inline_CN="${inline_CN##*= }"

		# BASE64 encode pkcs12
		inline_tmp=
		easyrsa_mktemp inline_tmp || die "export_pkcs - inline_tmp"
		if "$EASYRSA_OPENSSL" enc -a -in "$pkcs_out" > "$inline_tmp"
		then
			# make inline file
			{
				print "\
# Easy-RSA inline file: pkcs12
# commonName: ${inline_CN}${NL}"
				print "<pkcs12>"
				cat "$inline_tmp"
				print "</pkcs12>"
			} > "$inline_out" || die "export_pkcs - make inline"

			inline_msg="\
A BASE64 encoded inline file has also been created at:
* ${inline_out}${NL}"
		else
			inline_msg="\
Failed to create a BASE64 encoded inline file${NL}"
		fi
		;;
	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

	# User messages
	notice "\
Successful export of $pkcs_type file. Your exported file is at:
* $pkcs_out"
	[ "$inline_msg" ] && print "$inline_msg"

	return 0
} # => export_pkcs()

# 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"
				;;
			*) user_error "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

	[ -f "$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 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
	"$EASYRSA_OPENSSL" "$format" -in "$path" -noout -subject \
	-nameopt utf8,sep_multiline,space_eq,lname,align
} # => display_dn()

# 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
	[ -f "$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
	[ -f "$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 [ -f "$1" ]; then
		__crt="$1"
	else
		__crt="${EASYRSA_PKI}/issued/${1}.crt"
		[ -f "$__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}"
	__codeSign="Code Signing"
	unset -v __known

	# Extract certificate Extended Key Usage
	if [ "$ssl_lib" = libressl ]; then
		__eku="$(
			"$EASYRSA_OPENSSL" x509 -in "${__crt}" -noout -text | \
				sed -n "/${__pattern}/{n;s/^ *//g;p;}"
			)"
	else
		__eku="$(
			"$EASYRSA_OPENSSL" x509 -in "${__crt}" -noout \
				-ext extendedKeyUsage | \
					sed -e /"${__pattern}"/d -e s/^\ *//
			)"
	fi

	# Match EKU with supported usage
	case "$__eku" in
		"$__srv_cli")
			__known=1
			__type=serverClient
			;;
		"$__cli")
			__known=1
			__type=client
			;;
		"$__srv")
			__known=1
			__type=server
			;;
		"$__codeSign")
			__known=1
			__type=codeSign
			;;
		'')
			__type=undefined
			;;
		*)
			__type="'$__eku'"
	esac

	# Check for self-sign
	if "$EASYRSA_OPENSSL" x509 -in "$__crt" -noout -text | \
		grep -q 'CA:TRUE'
	then
		__type="self-signed-$__type"
	fi

	# Set variable to return
	if [ "$__var" ]; then
		verbose "ssl_cert_x509v3_eku - EKU: $__type"
		force_set_var "$__var" "$__type"
	elif [ "$__known" ]; then
		information "
* Known X509v3 Extended Key Usage: $__type"
	else
		information "
* Unknown X509v3 Extended Key Usage: $__type"
	fi

	unset -v __crt __var __pattern __srv_cli __cli __srv \
		__codeSign __eku __type

	if [ "$__known" ]; then
		unset -v __known
		return
	fi

	# Also, catch errors from SSL x509 command
	# for '__eku' subshell+pipe
	return 1
} # => 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()

# 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 # No expand '' - detect_host()
	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, temp-file
		# call openssl directly because error is expected
			"$EASYRSA_OPENSSL" ecparam \
				-name "$EASYRSA_CURVE" \
				-out "$EASYRSA_ALGO_PARAMS" \
				>/dev/null 2>&1 || user_error "\
Failed to generate ecparam file for curve '$EASYRSA_CURVE'"
		;;
	ed)
		# Verify Edwards curve
		# call openssl directly because error is expected
			"$EASYRSA_OPENSSL" genpkey \
				-algorithm "$EASYRSA_CURVE" \
				>/dev/null 2>&1 || user_error "\
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' OK"
} # => 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)
				# 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() {
	# User specified vars file will be used ONLY
	if [ "$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 [ -f "$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 [ -f "$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 [ -f "$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 [ -f "$PWD/vars" ]; then
			verbose "select_vars: PWD/vars"
			set_var EASYRSA_VARS_FILE "$PWD/vars"
		fi
	fi

	# if select_vars failed to find a vars file
	if [ -z "$EASYRSA_VARS_FILE" ]; then
		verbose "select_vars: No vars"
		return 1
	fi
} # => select_vars()

# Source a vars file
source_vars() {
	# 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
	[ -f "$target_file" ] || user_error "\
Missing vars file:
* $target_file"

	# Sanitize vars
	if grep -q \
		-e 'EASYRSA_PASSIN' -e 'EASYRSA_PASSOUT' \
		-e '[^(]`[^)]' \
		-e 'export ' \
		-e 'unset ' \
			"$target_file"
	then
		# here we go ..
		err_msg="\
These problems have been found in your 'vars' settings:${NL}"

		# No passwords!
		if grep -q \
			-e 'EASYRSA_PASSIN' -e 'EASYRSA_PASSOUT' \
			"$target_file"
		then
			err_msg="${err_msg}
  Use of 'EASYRSA_PASSIN' or 'EASYRSA_PASSOUT':
  Storing password information in the 'vars' file is not permitted."
		fi

		# No backticks
		if grep -q \
			-e '[^(]`[^)]' \
			"$target_file"
		then
			err_msg="${err_msg}
  Use of unsupported characters:
  These characters are not supported: \` backtick"
		fi

		# No export
		if grep -q \
			-e 'export ' \
			"$target_file"
		then
			err_msg="${err_msg}
  Use of 'export':
  Remove 'export' or replace it with 'set_var'."
		fi

		# No unset
		if grep -q \
			-e 'unset ' \
			"$target_file"
		then
			err_msg="${err_msg}
  Use of 'unset':
  Remove 'unset' ('force_set_var' may also work)."
		fi

		# Fatal error
		user_error "${err_msg}${NL}
Please, correct these errors and try again."

	else
		verbose "source_vars: CLEAN '$target_file'"
	fi

	# Enable sourcing 'vars'
	# shellcheck disable=SC2034 # appears unused - source_vars()
	EASYRSA_CALLER=1
	easyrsa_path="$PATH"
	# shellcheck disable=SC2123 # PATH is - source_vars()
	PATH=./

	# Test sourcing 'vars' in a subshell
	# shellcheck disable=1090 # can't follow - source_vars()
	if ( . "$target_file" ); then
		# Source 'vars' now
		# shellcheck disable=1090 # can't follow - source_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 (~VER~) Generated Certificate"

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

	set_var EASYRSA_KDC_REALM		"CHANGEME.EXAMPLE.COM"

	set_var EASYRSA_MAX_TEMP		1
} # => 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
	[ "$quiet_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() {
	verbose "verify_working_env: BEGIN"
	# For commands which 'require a PKI' and PKI exists
	if [ "$require_pki" ]; then
		# Verify PKI is initialised
		verify_pki_init

		# Temp dir session
		secure_session

		# global safe ssl cnf temp
		write_global_safe_ssl_cnf_tmp

		# Verify selected algorithm and parameters
		verify_algo_params

		# Verify CA is initialised
		if [ "$require_ca" ]; then
			verify_ca_init
		fi
	else
		# For commands that do not require a PKI
		# but do require a temp-dir, eg. 'write'
		# If there is a valid temp-dir:
		# Create temp-session and openssl-easyrsa.cnf (Temp) now
		if [ -d "$EASYRSA_TEMP_DIR" ]; then
			# Temp dir session
			secure_session

			# global safe ssl cnf temp
			write_global_safe_ssl_cnf_tmp
		fi
	fi
	verbose "verify_working_env: COMPLETED Handover-to: $cmd"
} # => 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()

# sanitize 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
	# force unsetting $1; Guard unset with '|| die', just in case
	unset -v "$1" || die "force_set_var - unset '$1'"
	# Allow empty value to unset variable by returning
	[ "$2" ] || return 0
	set_var "$1" "$2" && return
	die "force_set_var - set_var '$*'"
} # => force_set_var()

# global Safe SSL conf file, for use by any SSL lib
write_global_safe_ssl_cnf_tmp() {
	global_safe_ssl_cnf_tmp=
	easyrsa_mktemp global_safe_ssl_cnf_tmp || die "\
verify_working_env - easyrsa_mktemp global_safe_ssl_cnf_tmp"

	write_legacy_file_v2 safe-cnf "$global_safe_ssl_cnf_tmp" || \
		die "verify_working_env - write safe-cnf"

	export OPENSSL_CONF="$global_safe_ssl_cnf_tmp"
	verbose "GLOBAL - OPENSSL_CONF = $OPENSSL_CONF"
} # => write_global_safe_ssl_cnf_tmp()

# Create as needed: $EASYRSA_SSL_CONF pki/openssl-easyrsa.cnf
# If the existing file has a known hash then use temp-file.
# Otherwise, use the file in place.
write_easyrsa_ssl_cnf_tmp() {
	if [ -f "$EASYRSA_SSL_CONF" ]; then
		verbose "write_easyrsa_ssl_cnf_tmp: SSL config EXISTS"

		# Set known hashes
		# 3.1.7 -> Current
		known_file_317="\
13ca05f031d58c5e2912652b33099ce9\
ac05f49595e5d5fe96367229e3ce070c"

		# 3.1.5 -> 3.1.6
		known_file_315="\
87d51ca0db1cc0ac3cc2634792fc5576\
e0034ebf9d546de11674b897514f3afb"

		# 3.1.0 -> 3.1.4
		known_file_310="\
5455947df40f01f845bf79c1e89f102c\
628faaa65d71a6512d0e17bdd183feb0"

		# 3.0.8 -> 3.0.9
		known_file_308="\
1cc6a1de93ca357b5c364aa0fa2c4bea\
f97425686fa1976d436fa31f550641aa"

		# Built-in here-doc 3.2.0
		known_heredoc_320="\
82439f1860838e28f6270d5d06b17717\
56db777861e19bf9edc21222f86a310d"

		# Get file hash
		file_hash="$(
			"$EASYRSA_OPENSSL" dgst -sha256 -r \
				"$EASYRSA_SSL_CONF" 2>/dev/null
		)" || die "write_easyrsa_ssl_cnf_tmp - hash malfunction!"

		# Strip excess SSL info
		file_hash="${file_hash%% *}"

		# Compare SSL output
		case "$file_hash" in
		*[!1234567890abcdef]*|'')
			die "write_easyrsa_ssl_cnf_tmp - hash failure!"
		esac

		# Check file hash against known hash
		hash_is_unknown=""

		case "$file_hash" in
			"$known_file_317") ;;
			"$known_file_315") ;;
			"$known_file_310") ;;
			"$known_file_308") ;;
			"$known_heredoc_320") ;;

			*)
				# File is unknown or has been changed
				# leave in place
				hash_is_unknown=1
		esac

		# Cleanup
		unset -v file_hash known_heredoc_320 \
				known_file_317 \
				known_file_315 \
				known_file_310 \
				known_file_308

		# Use the existing file ONLY
		if [ "$hash_is_unknown" ] || [ "$EASYRSA_FORCE_SAFE_SSL" ]
		then
			unset -v hash_is_unknown
			verbose "write_easyrsa_ssl_cnf_tmp: SSL config UNKNOWN!"

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

			return 0
		fi

		# Ignore existing file, prefer to use a temp-file
		verbose "write_easyrsa_ssl_cnf_tmp: SSL config KNOWN"
	fi

	# SET and USE temp-file from here-doc Now
	# Create temp-file
	ssl_cnf_tmp=
	easyrsa_mktemp ssl_cnf_tmp || die "\
write_easyrsa_ssl_cnf_tmp - easyrsa_mktemp"

	# Write SSL cnf to temp-file
	write_legacy_file_v2 "$ssl_cnf_type" "$ssl_cnf_tmp" || die "\
write_easyrsa_ssl_cnf_tmp - write $ssl_cnf_type: $ssl_cnf_tmp"

	# export SSL cnf tmp
	export EASYRSA_SSL_CONF="$ssl_cnf_tmp"
	verbose "\
write_easyrsa_ssl_cnf_tmp: $ssl_cnf_type \
- EASYRSA_SSL_CONF = $EASYRSA_SSL_CONF"

	export OPENSSL_CONF="$EASYRSA_SSL_CONF"
	verbose "LOCAL - OPENSSL_CONF = $OPENSSL_CONF"
} # => write_easyrsa_ssl_cnf_tmp()

# Write x509 type file to a temp file
write_x509_type_tmp() {
	# Verify x509-type before redirect
	case "$1" in
		COMMON|ca|server|serverClient|client|email| \
		codeSigning|kdc|selfsign)
			: # ok
		;;
	*)
		die "write_x509_type_tmp - unknown type '$1'"
	esac

	write_x509_file_tmp=""
	easyrsa_mktemp write_x509_file_tmp || \
		die "write_x509_type_tmp - easyrsa_mktemp"

	write_legacy_file_v2 "$1" "$write_x509_file_tmp" || \
		die "write_x509_type_tmp - write $1"


	verbose ": write_x509_type_tmp: $1 COMPLETE"
} # => write_x509_type_tmp()

############################################################################
#
# Create legacy files
#

# Write ALL legacy files to $1 or default
all_legacy_files_v2() {
	# Confirm over write
	if [ "$legacy_file_over_write" ]; then
		confirm "${NL}  Confirm OVER-WRITE files ? " yes "
Warning:
'legacy-hard' will OVER-WRITE all legacy files to default settings.

Legacy files:
* File: ${EASYRSA_PKI}/openssl-easyrsa.cnf
* File: ${EASYRSA_PKI}/vars.example
* Dir:  ${EASYRSA_PKI}/x509-types/*"

		verbose "all_legacy_files_v2 - over-write ENABLED"
	fi

	# Output directories
	legacy_out_d="$EASYRSA_PKI"
		easyrsa_mkdir "$legacy_out_d"
	x509_types_d="$legacy_out_d"/x509-types
		easyrsa_mkdir "$x509_types_d"

	# Create x509-types
	for legacy_type in COMMON ca server serverClient client \
		email codeSigning kdc
	do
		legacy_target="${x509_types_d}/${legacy_type}"
		write_legacy_file_v2 "$legacy_type" "$legacy_target" \
			"$legacy_file_over_write"
	done

	# vars.example
	legacy_type=vars
	legacy_target="$legacy_out_d"/vars.example
	write_legacy_file_v2 "$legacy_type" "$legacy_target" \
		"$legacy_file_over_write"

	# openssl-easyrsa.cnf
	legacy_type=ssl-cnf
	legacy_target="$legacy_out_d"/openssl-easyrsa.cnf
	write_legacy_file_v2 "$legacy_type" "$legacy_target" \
		"$legacy_file_over_write"

	# User notice
	if [ "$legacy_file_over_write" ]; then
		notice "legacy-hard has updated all files."
	else
		notice "legacy has updated missing files."
	fi
} # => all_legacy_files_v2()

# write legacy files to stdout or user defined file
write_legacy_file_v2() {
	# recursion check
	write_recursion="$(( write_recursion + 1 ))"
	if [ "$write_recursion" -gt 1 ]; then
		print "write recursion" > "$easyrsa_err_log"
		die "write recursion"
	fi

	write_type="$1"
	write_file="$2"
	write_over=
	[ "$3" = overwrite ] && write_over="$3"

	# Select by type
	case "$write_type" in
	ssl-cnf)
		set_openssl_easyrsa_cnf_vars unexpanded
		;;
	safe-cnf)
		set_openssl_easyrsa_cnf_vars expanded
		;;
	vars)
		;;
	# This correctly renames 'code-signing' to 'codeSigning'
	COMMON|ca|server|serverClient|client|codeSigning|email|kdc)
		;;
	selfsign)
		;;
	*)
		user_error "write - unknown type '$write_type'"
	esac

	# If $write_file is given then establish overwrite rules
	if [ "$write_file" ]; then

		# $write_file must not be a directory
		[ -d "$write_file" ] && user_error \
			"write: Target is a directory: '$write_file'"

		# If $write_file exists then check for temp-file
		if [ -f "$write_file" ]; then
			# if this is a temp file then enable auto-overwrite
			path="${write_file%%/temp.*}"
			if [ "${secured_session}" = "$path" ]; then
				verbose ": write_legacy_file_v2 - temp-file ACCEPTED"
				write_over=overwrite
			else
				# target is not a temp-file, overwrite not changed
				verbose ": Target is not a temp-file: $write_file"
			fi
		else
			# enable overwrite, "there is no file" to over write
			verbose ": Missing input file: $write_file"
			write_over=overwrite
		fi
	fi

	# write legacy data stream to stdout or file
	if [ "$write_file" ]; then
		if [ "$write_over" ]; then
			verbose ": write_legacy_file_v2 - over-write ENABLED"
			create_legacy_stream "$write_type" > "$write_file" || \
				die "write failed"
		else
			user_error "write: Over-write refused for existing file!"
		fi
	else
		# write stream to stdout ONLY
		create_legacy_stream "$write_type"
	fi

	write_recursion="$(( write_recursion - 1 ))"
} # => write_legacy_file_v2()

# set heredoc variables for openssl-easyrsa.cnf
# shellcheck disable=SC2016 # (info): $ don't expand in ''
set_openssl_easyrsa_cnf_vars(){
	case "$1" in
	expanded)
		# fully expand ssl-cnf for safe-cnf
		conf_EASYRSA_dir="$EASYRSA_PKI"
		conf_EASYRSA_PKI="$EASYRSA_PKI"
		conf_EASYRSA_DIGEST="$EASYRSA_DIGEST"
		conf_EASYRSA_KEY_SIZE="$EASYRSA_KEY_SIZE"
		conf_EASYRSA_DN="$EASYRSA_DN"
		conf_EASYRSA_REQ_CN="$EASYRSA_REQ_CN"
		conf_EASYRSA_REQ_COUNTRY="$EASYRSA_REQ_COUNTRY"
		conf_EASYRSA_REQ_PROVINCE="$EASYRSA_REQ_PROVINCE"
		conf_EASYRSA_REQ_CITY="$EASYRSA_REQ_CITY"
		conf_EASYRSA_REQ_ORG="$EASYRSA_REQ_ORG"
		conf_EASYRSA_REQ_OU="$EASYRSA_REQ_OU"
		conf_EASYRSA_REQ_EMAIL="$EASYRSA_REQ_EMAIL"
		conf_EASYRSA_REQ_SERIAL="$EASYRSA_REQ_SERIAL"
		;;
	unexpanded)
		# write standard ssl-cnf
		conf_EASYRSA_dir='$dir'
		conf_EASYRSA_PKI='$ENV::EASYRSA_PKI'
		conf_EASYRSA_DIGEST='$ENV::EASYRSA_DIGEST'
		conf_EASYRSA_KEY_SIZE='$ENV::EASYRSA_KEY_SIZE'
		conf_EASYRSA_DN='$ENV::EASYRSA_DN'
		conf_EASYRSA_REQ_CN='$ENV::EASYRSA_REQ_CN'
		conf_EASYRSA_REQ_COUNTRY='$ENV::EASYRSA_REQ_COUNTRY'
		conf_EASYRSA_REQ_PROVINCE='$ENV::EASYRSA_REQ_PROVINCE'
		conf_EASYRSA_REQ_CITY='$ENV::EASYRSA_REQ_CITY'
		conf_EASYRSA_REQ_ORG='$ENV::EASYRSA_REQ_ORG'
		conf_EASYRSA_REQ_OU='$ENV::EASYRSA_REQ_OU'
		conf_EASYRSA_REQ_EMAIL='$ENV::EASYRSA_REQ_EMAIL'
		conf_EASYRSA_REQ_SERIAL='$ENV::EASYRSA_REQ_SERIAL'
		;;
	*)
		die "set_openssl_easyrsa_cnf_vars - input"
	esac
} # => set_openssl_easyrsa_cnf_vars()

# Create x509 type
create_legacy_stream() {
	case "$1" in
	COMMON)
	# COMMON is not very useful
		cat <<- "CREATE_X509_TYPE_COMMON"
		CREATE_X509_TYPE_COMMON
		;;
	easyrsa)
	# This could be COMMON but not is not suitable for a CA
		cat <<- "CREATE_X509_TYPE_EASYRSA"
		basicConstraints = CA:FALSE
		subjectKeyIdentifier = hash
		authorityKeyIdentifier = keyid,issuer:always
		keyUsage = digitalSignature,keyEncipherment
		CREATE_X509_TYPE_EASYRSA
		;;
	serverClient)
	# serverClient
		create_legacy_stream easyrsa
		cat <<- "CREATE_X509_TYPE_SERV_CLI"
		extendedKeyUsage = serverAuth,clientAuth
		CREATE_X509_TYPE_SERV_CLI
		;;
	server)
	# server
		create_legacy_stream easyrsa
		cat <<- "CREATE_X509_TYPE_SERV"
		extendedKeyUsage = serverAuth
		CREATE_X509_TYPE_SERV
		;;
	client)
	# client
		create_legacy_stream easyrsa
		cat <<- "CREATE_X509_TYPE_CLI"
		extendedKeyUsage = clientAuth
		CREATE_X509_TYPE_CLI
		;;
	ca)
	# ca
		cat <<- "CREATE_X509_TYPE_CA"
		basicConstraints = CA:TRUE
		subjectKeyIdentifier = hash
		authorityKeyIdentifier = keyid:always,issuer:always
		keyUsage = cRLSign, keyCertSign
		CREATE_X509_TYPE_CA
		;;
	selfsign)
	# selfsign
		cat <<- "CREATE_X509_TYPE_SELFSIGN"
		subjectKeyIdentifier = hash
		authorityKeyIdentifier = keyid:always,issuer:always
		basicConstraints = CA:TRUE
		keyUsage = digitalSignature,keyEncipherment
		CREATE_X509_TYPE_SELFSIGN

		print "extendedKeyUsage = $selfsign_eku"
		;;
	codeSigning)
	# codeSigning
		cat <<- "CREATE_X509_CODE_SIGNING"
		basicConstraints = CA:FALSE
		subjectKeyIdentifier = hash
		authorityKeyIdentifier = keyid,issuer:always
		extendedKeyUsage = codeSigning
		keyUsage = digitalSignature
		CREATE_X509_CODE_SIGNING
		;;
	email)
	# email
		cat <<- "CREATE_X509_TYPE_EMAIL"
		basicConstraints = CA:FALSE
		subjectKeyIdentifier = hash
		authorityKeyIdentifier = keyid,issuer:always
		extendedKeyUsage = emailProtection
		keyUsage = digitalSignature,keyEncipherment,nonRepudiation
		CREATE_X509_TYPE_EMAIL
		;;
	kdc)
	# kdc
		cat <<- "CREATE_X509_TYPE_KDC"
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
extendedKeyUsage = 1.3.6.1.5.2.3.5
keyUsage = nonRepudiation,digitalSignature,keyEncipherment,keyAgreement
issuerAltName = issuer:copy
subjectAltName = otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name

[kdc_princ_name]
realm = EXP:0,GeneralString:${ENV::EASYRSA_KDC_REALM}
principal_name = EXP:1,SEQUENCE:kdc_principal_seq

[kdc_principal_seq]
name_type = EXP:0,INTEGER:1
name_string = EXP:1,SEQUENCE:kdc_principals

[kdc_principals]
princ1 = GeneralString:krbtgt
princ2 = GeneralString:${ENV::EASYRSA_KDC_REALM}
CREATE_X509_TYPE_KDC
		;;
	vars)
	# vars
		cat << "CREATE_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"

# Windows users, to generate OpenVPN TLS Keys the Openvpn binary must be
# defined here.
#
#set_var EASYRSA_OPENVPN "C:\\Program Files\\Openvpn\\bin\\openvpn.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

# Generate automatic subjectAltName for certificates
#
#set_var	EASYRSA_AUTO_SAN	1

# Add critical attribute to X509 fields: basicConstraints (BC),
# keyUsage (KU), extendedKeyUsage (EKU) or SAN
#
#set_var	EASYRSA_BC_CRIT		1
#set_var	EASYRSA_KU_CRIT		1
#set_var	EASYRSA_EKU_CRIT	1
#set_var	EASYRSA_SAN_CRIT	1

# Disable automatic inline files
#
#set_var	EASYRSA_DISABLE_INLINE	1
CREATE_VARS_EXAMPLE
		;;
	ssl-cnf|safe-cnf)
	# SSL config v3.2.0-1
	cat << CREATE_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		= $conf_EASYRSA_PKI	# Where everything is kept
certs		= $conf_EASYRSA_dir			# Where the issued certs are kept
crl_dir		= $conf_EASYRSA_dir			# Where the issued crl are kept
database	= $conf_EASYRSA_dir/index.txt	# database index file.
new_certs_dir	= $conf_EASYRSA_dir/certs_by_serial	# default place for new certs.

certificate	= $conf_EASYRSA_dir/ca.crt		# The CA certificate
serial		= $conf_EASYRSA_dir/serial		# The current serial number
crl		= $conf_EASYRSA_dir/crl.pem		# The current CRL
private_key	= $conf_EASYRSA_dir/private/ca.key	# The private key
RANDFILE	= $conf_EASYRSA_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

# These fields are always configured via the command line.
# These fields are removed from this here-doc but retained
# in 'openssl-easyrsa.cnf' file, in case something breaks.
# default_days is no longer required by Easy-RSA
#default_days	= \$ENV::EASYRSA_CERT_EXPIRE	# how long to certify for
# default_crl_days is no longer required by Easy-RSA
#default_crl_days	= \$ENV::EASYRSA_CRL_DAYS	# how long before next CRL

default_md	= $conf_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		= $conf_EASYRSA_KEY_SIZE
default_keyfile	= privkey.pem
default_md		= $conf_EASYRSA_DIGEST
distinguished_name	= $conf_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	= $conf_EASYRSA_REQ_CN

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

stateOrProvinceName		= State or Province Name (full name)
stateOrProvinceName_default	= $conf_EASYRSA_REQ_PROVINCE

localityName			= Locality Name (eg, city)
localityName_default		= $conf_EASYRSA_REQ_CITY

0.organizationName		= Organization Name (eg, company)
0.organizationName_default	= $conf_EASYRSA_REQ_ORG

organizationalUnitName		= Organizational Unit Name (eg, section)
organizationalUnitName_default	= $conf_EASYRSA_REQ_OU

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

emailAddress			= Email Address
emailAddress_default		= $conf_EASYRSA_REQ_EMAIL
emailAddress_max		= 64

serialNumber		= Serial-number (eg, device serial-number)
serialNumber_default	= $conf_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
CREATE_SSL_CONFIG
		;;
	*)
		die "create_legacy_stream: unknown type '$1'"
	esac
} # => create_legacy_stream()

# Load easyrsa-tools.lib
source_easyrsa_tools_lib() {
	if [ -f "$EASYRSA_TOOLS_LIB" ]; then
		export EASYRSA_TOOLS_CALLER=1
		# shellcheck disable=SC1090 # can't follow non-constant..
		. "$EASYRSA_TOOLS_LIB" || \
			die "Source failed: $EASYRSA_TOOLS_LIB"
		unset -v EASYRSA_TOOLS_CALLER tools_error

		verbose "EASYRSA_TOOLS_LIB: $EASYRSA_TOOLS_LIB"
		verbose "EASYRSA_TOOLS_VERSION: $EASYRSA_TOOLS_VERSION"

		# Verify tools version
		if [ "$EASYRSA_TOOLS_VERSION" -lt 321 ]; then
			tools_error_txt="\
EasyRSA Tools version is out of date:
* EASYRSA_TOOLS_VERSION: $EASYRSA_TOOLS_VERSION"
			return 1
		fi
	else
		verbose "Missing: easyrsa-tools.lib"
		tools_error_txt="Missing: easyrsa-tools.lib

Use of command '$cmd' requires Easy-RSA tools library.
Source: https://github.com/OpenVPN/easy-rsa/dev/easyrsa-tools.lib
Download: https://raw.githubusercontent.com/OpenVPN/easy-rsa/refs/heads/master/dev/easyrsa-tools.lib

Place a copy of easyrsa-tools.lib in a standard system location."
		return 1
	fi
} # => source_easyrsa_tools_lib()

# Version information
print_version() {
	ssl_version="$(
			"${EASYRSA_OPENSSL:-openssl}" version 2>/dev/null
		)"
		cat << VERSION_TEXT
EasyRSA Version Information
Version:     $EASYRSA_version
Generated:   ~DATE~
SSL Lib:     ${ssl_version:-undefined}
Git Commit:  ~GITHEAD~
Source Repo: https://github.com/OpenVPN/easy-rsa
VERSION_TEXT
} # => print_version()


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

EASYRSA_version="~VER~"
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 15" 15

# Get host details - No configurable input allowed
detect_host

# Initialisation requirements
unset -v \
	OPENSSL_CONF \
	verify_ssl_lib_ok ssl_batch \
	secured_session \
	working_safe_ssl_conf working_safe_org_conf \
	alias_days text \
	prohibit_no_pass \
	invalid_vars \
	local_request error_build_full_cleanup \
	selfsign_eku \
	internal_batch mv_temp_error \
	easyrsa_exit_with_error error_info \
	write_recursion tools_error tools_error_txt

	# Used by build-ca->cleanup to restore prompt
	# after user interrupt when using manual password
	prompt_restore=0
	# Sequential temp-file counter
	mktemp_counter=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
			;;
		--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)
			export EASYRSA_VARS_FILE="$val"
			;;
		--copy-ext)
			empty_ok=1
			export EASYRSA_CP_EXT=1
			;;
		--subject-alt-name|--san)
			# This allows --san to be used multiple times
			if [ "$EASYRSA_SAN" ]; then
				EASYRSA_SAN="$EASYRSA_SAN, $val"
			else
				EASYRSA_SAN="$val"
			fi
			;;
		--auto-san)
			empty_ok=1
			export EASYRSA_AUTO_SAN=1
			;;
		--san-crit*)
			empty_ok=1
			export EASYRSA_SAN_CRIT='critical,'
			;;
		--bc-crit*)
			empty_ok=1
			export EASYRSA_BC_CRIT=1
			;;
		--ku-crit*)
			empty_ok=1
			export EASYRSA_KU_CRIT=1
			;;
		--eku-crit*)
			empty_ok=1
			export EASYRSA_EKU_CRIT=1
			;;
		--new-subj*)
			export EASYRSA_NEW_SUBJECT="$val"
			;;
		--usefn)
			export EASYRSA_P12_FR_NAME="$val"
			;;
		--tools)
			export EASYRSA_TOOLS_LIB="$val"
			;;
		--version)
			shift "$#"
			set -- "$@" "version"
			break
			;;
		-*)
			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

# option dependencies
# Add full --san to extra extensions
if [ "$EASYRSA_SAN" ]; then
	EASYRSA_EXTRA_EXTS="\
$EASYRSA_EXTRA_EXTS
subjectAltName = ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}"
fi

# 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
unset -v require_pki require_ca quiet_vars

case "$cmd" in
	''|help|-h|--help|--usage| \
	version|show-host|rand|random)
		quiet_vars=1
		;;
	init-pki|clean-all)
		: # ok
		;;
	*)
		require_pki=1
		case "$cmd" in
			gen-req|gen-dh|build-ca|show-req|export-p*| \
			inline|self-sign-*|write)
				: ;; # ok
			*) require_ca=1
		esac
esac

# Intelligent env-var detection and auto-loading:
# Select vars file as EASYRSA_VARS_FILE
# then source the vars file, if found
# otherwise, ignore no vars file
if select_vars; then
	[ "$quiet_vars" ] || information "\
Using Easy-RSA 'vars' configuration:
* $EASYRSA_VARS_FILE"
	source_vars "$EASYRSA_VARS_FILE"
else
	verbose "\
No Easy-RSA 'vars' configuration file exists!"
fi

# then set defaults
default_vars

# Check for unexpected changes to EASYRSA or EASYRSA_PKI
# https://github.com/OpenVPN/easy-rsa/issues/1006
validate_default_vars

# Check for conflicting input options
mutual_exclusions

# Find x509-types, openssl-easyrsa.cnf and easyrsa-tools.lib
locate_support_files

# Verify SSL Lib - One time ONLY
verify_ssl_lib

# 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

# 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 "$@"
		;;
	self-sign-server)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CERT_EXPIRE="$alias_days"
		self_sign server "$@"
		;;
	self-sign-client)
		verify_working_env
		[ -z "$alias_days" ] || \
			export EASYRSA_CERT_EXPIRE="$alias_days"
		self_sign client "$@"
		;;
	self*)
		user_error "Self-sign syntax example: 'self-sign-server foo'"
		;;
	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
		cert_dir=issued
		revoke "$@"
		;;
	revoke-expired)
		verify_working_env
		cert_dir=expired
		revoke "$@"
		;;
	revoke-renewed)
		verify_working_env
		cert_dir=renewed/issued
		revoke "$@"
		;;
	import-req)
		verify_working_env
		import_req "$@"
		;;
	expire)
		verify_working_env
		expire_cert "$@"
		;;
	inline)
		verify_working_env
		inline_file "$@"
		;;
	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-pass|set-rsa-pass|set-ec-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-host)
		verify_working_env
		show_host "$@"
		;;
	renew|show-expire|show-revoke|show-renew|verify-cert)
		verify_working_env

		# easyrsa-tools.lib is required
		source_easyrsa_tools_lib || tools_error=1

		case "$cmd" in
			renew)
				[ "$tools_error" ] && user_error "$tools_error_txt

A certificate can be renewed without EasyRSA Tools. Expire the certificate
using command 'expire' and sign the original request with 'sign-req'."
				[ -z "$alias_days" ] || \
					export EASYRSA_CERT_EXPIRE="$alias_days"
				renew "$@"
				;;
			show-expire)
				[ "$tools_error" ] && user_error "$tools_error_txt"
				[ -z "$alias_days" ] || \
					export EASYRSA_PRE_EXPIRY_WINDOW="$alias_days"
				status expire "$@"
				;;
			show-revoke)
				[ "$tools_error" ] && user_error "$tools_error_txt"
				status revoke "$@"
				;;
			show-renew)
				[ "$tools_error" ] && user_error "$tools_error_txt"
				status renew "$@"
				;;
			verify-cert)
				[ "$tools_error" ] && user_error "$tools_error_txt"
				# 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
				;;
			*)
				die "Unknown command: '$cmd'"
		esac
		;;
	gen-tls-*)
		verify_working_env

		# easyrsa-tools.lib is required
		source_easyrsa_tools_lib || tools_error=1
		[ "$tools_error" ] && user_error "$tools_error_txt"

		case "$cmd" in
			gen-tls-auth|gen-tls-auth-*)
				tls_key_gen tls-auth "$@"
			;;
			gen-tls-crypt|gen-tls-crypt-*)
				tls_key_gen tls-crypt "$@"
			;;
			gen-tls-cryptv2|gen-tls-cryptv2-*)
				tls_key_gen tls-crypt-v2 "$@"
			;;
			*)
				die "Command '$cmd' not currently implemented."
		esac
		;;
	write)
		verify_working_env

		# Write legacy files to write_dir
		# or EASYRSA_PKI or EASYRSA
		case "$1" in
		legacy)
			# over-write NO
			shift
			legacy_file_over_write=
			all_legacy_files_v2 "$@"
			;;
		legacy-hard)
			# over-write YES
			shift
			legacy_file_over_write=overwrite
			all_legacy_files_v2 "$@"
			;;
		*)
			write_legacy_file_v2 "$@"
		esac
		;;
	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 "$@"
		;;
	x509-eku|show-eku)
		verify_working_env
		ssl_cert_x509v3_eku "$@" || \
			easyrsa_exit_with_error=1
		;;
	rand|random)
		easyrsa_random "$1"
		;;
	""|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 # Quote expand - pre-cleanup $?
if [ $? = 0 ]; then
	# Do 'cleanup ok' on successful completion
	cleanup ok
fi

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

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