Subversion Repositories configs

Rev

Blame | Last modification | View Log | RSS feed

#!/bin/sh

# Easy-RSA 3 -- A Shell-based CA Utility
#
# Copyright (C) 2018 by the Open-Source OpenVPN development community.
# A full list of contributors can be found in the ChangeLog.
#
# This code released under version 2 of the GNU GPL; see COPYING and the
# Licensing/ directory of this project for full licensing details.

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

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

A list of commands is shown below. To get detailed usage and help for a
command, run:
  ./easyrsa help COMMAND

For a listing of options that can be supplied before the command, use:
  ./easyrsa help options

Here is the list of commands available with a short syntax reminder. Use the
'help' command above to get full usage details.

  init-pki
  build-ca [ cmd-opts ]
  gen-dh
  gen-req <filename_base> [ cmd-opts ]
  sign-req <type> <filename_base>
  build-client-full <filename_base> [ cmd-opts ]
  build-server-full <filename_base> [ cmd-opts ]
  revoke <filename_base> [cmd-opts]
  renew <filename_base> [cmd-opts]
  build-serverClient-full <filename_base> [ cmd-opts ]
  gen-crl
  update-db
  show-req <filename_base> [ cmd-opts ]
  show-cert <filename_base> [ cmd-opts ]
  show-ca [ cmd-opts ]
  import-req <request_file_path> <short_basename>
  export-p7 <filename_base> [ cmd-opts ]
  export-p12 <filename_base> [ cmd-opts ]
  set-rsa-pass <filename_base> [ cmd-opts ]
  set-ec-pass <filename_base> [ cmd-opts ]
  upgrade <type>
"

        # collect/show dir status:
        err_source="Not defined: vars autodetect failed and no value provided"
        work_dir="${EASYRSA:-$err_source}"
        pki_dir="${EASYRSA_PKI:-$err_source}"
        print "\
DIRECTORY STATUS (commands would take effect on these locations)
  EASYRSA: $work_dir
      PKI: $pki_dir
"
} # => usage()

# Detailed command help
# When called with no args, calls usage(), otherwise shows help for a command
cmd_help() {
        text=""
        opts=""
        case "$1" in
                init-pki|clean-all) text="
  init-pki [ cmd-opts ]
      Removes & re-initializes the PKI dir for a clean PKI" ;;
                build-ca) text="
  build-ca [ cmd-opts ]
      Creates a new CA"
                        opts="
        nopass  - do not encrypt the CA key (default is encrypted)
        subca   - create an intermediate CA keypair and request (default is a root CA)
        intca   - alias to the above" ;;
                gen-dh) text="
  gen-dh
      Generates DH (Diffie-Hellman) parameters" ;;
                gen-req) text="
  gen-req <filename_base> [ cmd-opts ]
      Generate a standalone keypair and request (CSR)

      This request is suitable for sending to a remote CA for signing."
                        opts="
        nopass  - do not encrypt the private key (default is encrypted)" ;;
                sign|sign-req) text="
  sign-req <type> <filename_base>
      Sign a certificate request of the defined type. <type> must be a known
      type such as 'client', 'server', 'serverClient', or 'ca' (or a user-added type.)

      This request file must exist in the reqs/ dir and have a .req file
      extension. See import-req below for importing reqs from other sources." ;;
                build|build-client-full|build-server-full|build-serverClient-full) text="
  build-client-full <filename_base> [ cmd-opts ]
  build-server-full <filename_base> [ cmd-opts ]
  build-serverClient-full <filename_base> [ cmd-opts ]
      Generate a keypair and sign locally for a client and/or server

      This mode uses the <filename_base> as the X509 CN."
                        opts="
        nopass  - do not encrypt the private key (default is encrypted)
        inline  - create an inline credentials file for this node" ;;
                revoke) text="
  revoke <filename_base> [reason]
      Revoke a certificate specified by the filename_base, with an optional 
      revocation reason that is one of: 
        unspecified
        keyCompromise
        CACompromise
        affiliationChanged
        superseded
        cessationOfOperation
        certificateHold";;
                renew) text="
  renew <filename_base> [ cmd-opts ]
      Renew a certificate specified by the filename_base"
                                opts="
        nopass  - do not encrypt the private key (default is encrypted)" ;;
                gen-crl) text="
  gen-crl
      Generate a CRL" ;;
                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  <filename_base> [ cmd-opts ]
  show-cert <filename_base> [ cmd-opts ]
      Shows details of the req or cert referenced by filename_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 CA cert

      Human-readable output is shown."
                        opts="
          full   - show full cert info, including pubkey/sig data" ;;
                import-req) text="
  import-req <request_file_path> <short_basename>
      Import a certificate request from a file

      This will copy the specified file into the reqs/ dir in
      preparation for signing.
      The <short_basename> is the filename base to create.

      Example usage:
        import-req /some/where/bob_request.req bob" ;;
                export-p12) text="
  export-p12 <filename_base> [ cmd-opts ]
      Export a PKCS#12 file with the keypair specified by <filename_base>"
                        opts="
        noca  - do not include the ca.crt file in the PKCS12 output
        nokey - do not include the private key in the PKCS12 output" ;;
                export-p7) text="
  export-p7 <filename_base> [ cmd-opts ]
      Export a PKCS#7 file with the pubkey specified by <filename_base>"
                        opts="
        noca  - do not include the ca.crt file in the PKCS7 output" ;;
                set-rsa-pass|set-ec-pass) text="
  set-rsa-pass <filename_base> [ cmd-opts ]
  set-ec-pass <filename_base> [ cmd-opts ]
      Set a new passphrase on an RSA or EC key for the listed <filename_base>."
                        opts="
        nopass - use no password and leave the key unencrypted
        file   - (advanced) treat the file as a raw path, not a short-name" ;;
                upgrade) text="
  upgrade <type>
      Upgrade EasyRSA PKI and/or CA. <type> must be one of:
        pki - Upgrade EasyRSA v2.x PKI to EasyRSA v3.x PKI (includes CA below)
        ca  - Upgrade EasyRSA v3.0.5 CA or older to EasyRSA v3.0.6 CA or later." ;;
                altname|subjectaltname|san) text="
  --subject-alt-name=SAN_FORMAT_STRING
      This global option adds a subjectAltName to the request or issued
      certificate. It MUST be in a valid format accepted by openssl or
      req/cert generation will fail. Note that including multiple such names
      requires them to be comma-separated; further invocations of this
      option will REPLACE the value.

      Examples of the SAN_FORMAT_STRING shown below:
        DNS:alternate.example.net
        DNS:primary.example.net,DNS:alternate.example.net
        IP:203.0.113.29
        email:alternate@example.net" ;;
                options)
                        opt_usage ;;
                "")
                        usage ;;
                *) text="
  Unknown command: '$1' (try without commands for a list of commands)" ;;
        esac

        # display the help text
        print "$text"
        [ -n "$opts" ] && print "
      cmd-opts is an optional set of command options from this list:
$opts"
} # => cmd_help()

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

The following 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:

--batch         : set automatic (no-prompts when possible) mode
--passin=ARG    : set -passin ARG for openssl
--passout=ARG   : set -passout ARG for openssl
--pki-dir=DIR   : declares the PKI directory
--vars=FILE     : define a specific 'vars' file to use for Easy-RSA config

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

--days=#        : sets the signing validity to the specified number of days
--digest=ALG    : digest to use in the requests & certificates
--dn-mode=MODE  : DN mode to use (cn_only or org)
--keysize=#     : size in bits of keypair to generate
--req-cn=NAME   : default CN to use
--subca-len=#   : path length of signed intermediate CA certs; must be >= 0 if used
--subject-alt-name : Add a subjectAltName. For more info and syntax, see:
                     ./easyrsa help altname
--use-algo=ALG  : crypto alg to use: choose rsa (default) or ec
--curve=NAME    : for elliptic curve, sets the named curve to use
--copy-ext      : Copy included request X509 extensions (namely subjAltName

Organizational DN options: (only used with the 'org' DN mode)
  (values may be blank for org DN options)

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

Deprecated features:

--ns-cert=YESNO       : yes or no to including deprecated NS extensions
--ns-comment=COMMENT  : NS comment to include (value may be blank)
"
} # => opt_usage()

# Wrapper around printf - clobber print since it's not POSIX anyway
# shellcheck disable=SC1117
print() { printf "%s\n" "$*"; }

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

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

# non-fatal warning output
warn() {
        [ ! "$EASYRSA_BATCH" ] && \
                print "
$1" 1>&2
} # => warn()

# informational notices to stdout
notice() {
        [ ! "$EASYRSA_BATCH" ] && \
                print "
$1"
} # => notice()

# yes/no case-insensitive match (operates on stdin pipe)
# Returns 0 when input contains yes, 1 for no, 2 for no match
# If both strings are present, returns 1; first matching line returns.
awk_yesno() {
        #shellcheck disable=SC2016
        awkscript='
BEGIN {IGNORECASE=1; r=2}
{       if(match($0,"no")) {r=1; exit}
        if(match($0,"yes")) {r=0; exit}
} END {exit r}'
        awk "$awkscript"
} # => awk_yesno()

# 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 input
        [ "$input" = "$value" ] && return
        notice "Aborting without confirmation."
        exit 9
} # => confirm()

# mktemp wrapper
easyrsa_mktemp() {
        [ -n "$EASYRSA_TEMP_DIR_session" ] || die "EASYRSA_TEMP_DIR_session not initialized!"
        [ -d "$EASYRSA_TEMP_DIR_session" ] || mkdir -p "$EASYRSA_TEMP_DIR_session" ||
               die "Could not create temporary directory '$EASYRSA_TEMP_DIR_session'. Permission or concurrency problem?"
        [ -d "$EASYRSA_TEMP_DIR_session" ] || die "Temporary directory '$EASYRSA_TEMP_DIR_session' does not exist"

        template="$EASYRSA_TEMP_DIR_session/tmp.XXXXXX"
        tempfile=$(mktemp "$template") || return

        # win32 mktemp shipped by easyrsa returns template as file!
        if [ "$template" = "$tempfile" ]; then
                # but win32 mktemp -d does work
                # but win32 mktemp -u does not work
                tempfile=$(mktemp -du "$tempfile") || return
                printf "" > "$tempfile" || return
        fi
        echo "$tempfile"
} # => easyrsa_mktemp

# remove temp files and do terminal cleanups
cleanup() {
        [ -z "$EASYRSA_TEMP_DIR_session" ] || rm -rf "$EASYRSA_TEMP_DIR_session"
        # shellcheck disable=SC2039
        (stty echo 2>/dev/null) || { (set -o echo 2>/dev/null) && set -o echo; }
        echo "" # just to get a clean line
} # => cleanup()

easyrsa_openssl() {
        openssl_command=$1; shift

        case $openssl_command in
                makesafeconf) has_config=true;;
                ca|req|srp|ts) has_config=true;;
                *) has_config=false;;
        esac

        if ! $has_config; then
                "$EASYRSA_OPENSSL" "$openssl_command" "$@"
                return
        fi

        easyrsa_openssl_conf=$(easyrsa_mktemp) || die "Failed to create temporary file"
        easyrsa_extra_exts=
        if [ -n "$EASYRSA_EXTRA_EXTS" ]; then
                easyrsa_extra_exts=$(easyrsa_mktemp) || die "Failed to create temporary file"
                cat >"$easyrsa_extra_exts" <<-EOF
                        req_extensions = req_extra
                        [ req_extra ]
                        $EASYRSA_EXTRA_EXTS
                EOF
        fi

        # Make LibreSSL safe config file from OpenSSL config file
        sed \
                -e "s\`ENV::EASYRSA\`EASYRSA\`g" \
                -e "s\`\$dir\`$EASYRSA_PKI\`g" \
                -e "s\`\$EASYRSA_PKI\`$EASYRSA_PKI\`g" \
                -e "s\`\$EASYRSA_CERT_EXPIRE\`$EASYRSA_CERT_EXPIRE\`g" \
                -e "s\`\$EASYRSA_CRL_DAYS\`$EASYRSA_CRL_DAYS\`g" \
                -e "s\`\$EASYRSA_DIGEST\`$EASYRSA_DIGEST\`g" \
                -e "s\`\$EASYRSA_KEY_SIZE\`$EASYRSA_KEY_SIZE\`g" \
                -e "s\`\$EASYRSA_DIGEST\`$EASYRSA_DIGEST\`g" \
                -e "s\`\$EASYRSA_DN\`$EASYRSA_DN\`g" \
                -e "s\`\$EASYRSA_REQ_COUNTRY\`$EASYRSA_REQ_COUNTRY\`g" \
                -e "s\`\$EASYRSA_REQ_PROVINCE\`$EASYRSA_REQ_PROVINCE\`g" \
                -e "s\`\$EASYRSA_REQ_CITY\`$EASYRSA_REQ_CITY\`g" \
                -e "s\`\$EASYRSA_REQ_ORG\`$EASYRSA_REQ_ORG\`g" \
                -e "s\`\$EASYRSA_REQ_OU\`$EASYRSA_REQ_OU\`g" \
                -e "s\`\$EASYRSA_REQ_CN\`$EASYRSA_REQ_CN\`g" \
                -e "s\`\$EASYRSA_REQ_EMAIL\`$EASYRSA_REQ_EMAIL\`g" \
                ${EASYRSA_EXTRA_EXTS:+-e "/^#%EXTRA_EXTS%/r $easyrsa_extra_exts"} \
                "$EASYRSA_SSL_CONF" > "$easyrsa_openssl_conf" ||
                die "Failed to update $easyrsa_openssl_conf"

        if [ "$openssl_command" = "makesafeconf" ]; then
                cp "$easyrsa_openssl_conf" "$EASYRSA_SAFE_CONF"
                err=$?
        else
                "$EASYRSA_OPENSSL" "$openssl_command" -config "$easyrsa_openssl_conf" "$@"
                err=$?
        fi

        rm -f "$easyrsa_openssl_conf"
        rm -f "$easyrsa_extra_exts"
        return $err
} # => easyrsa_openssl

vars_source_check() {
        # Check for defined EASYRSA_PKI
        [ -n "$EASYRSA_PKI" ] || die "\
EASYRSA_PKI env-var undefined"
} # => vars_source_check()

# Verify supplied curve exists and generate curve file if needed
verify_curve_ec() {
        if ! "$EASYRSA_OPENSSL" ecparam -name "$EASYRSA_CURVE" > /dev/null; then
                die "\
Curve $EASYRSA_CURVE not found. Run openssl ecparam -list_curves to show a
list of supported curves."
        fi

        # Check that the ecparams dir exists
        [ -d "$EASYRSA_EC_DIR" ] || mkdir "$EASYRSA_EC_DIR" || die "\
Failed creating ecparams dir (permissions?) at:
$EASYRSA_EC_DIR"

        # Check that the required ecparams file exists
        out="$EASYRSA_EC_DIR/${EASYRSA_CURVE}.pem"
        [ -f "$out" ] && return 0
        "$EASYRSA_OPENSSL" ecparam -name "$EASYRSA_CURVE" -out "$out" || die "\
Failed to generate ecparam file (permissions?) when writing to:
$out"

        # Explicitly return success for caller
        return 0
}

# Verify if Edward Curve exists
verify_curve_ed() {
        if [ "ed25519" = "$EASYRSA_CURVE" ] && "$EASYRSA_OPENSSL" genpkey -algorithm ED25519 > /dev/null; then
                return 0
        elif [ "ed448" = "$EASYRSA_CURVE" ] && "$EASYRSA_OPENSSL" genpkey -algorithm ED448 > /dev/null; then
                return 0
        fi
                die "Curve $EASYRSA_CURVE not found."
}

verify_ssl_lib () {
        # Verify EASYRSA_OPENSSL command gives expected output
        if [ -z "$EASYRSA_SSL_OK" ]; then
                val="$("$EASYRSA_OPENSSL" version)"
                case "${val%% *}" in
                        OpenSSL|LibreSSL)
                                print "\
Using SSL: $EASYRSA_OPENSSL $("$EASYRSA_OPENSSL" version)" ;;
                        *) die "\
Missing or invalid OpenSSL
Expected to find openssl command at: $EASYRSA_OPENSSL" ;;
                esac
        fi
        EASYRSA_SSL_OK=1

        # Verify EASYRSA_SSL_CONF file exists
        [ -f "$EASYRSA_SSL_CONF" ] || die "\
The OpenSSL config file cannot be found.
Expected location: $EASYRSA_SSL_CONF"
} # => 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 that the pki dir exists
        vars_source_check
        [ -d "$EASYRSA_PKI" ] || die "\
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" ] || die "\
Missing expected directory: $i (perhaps you need to run init-pki?)
$help_note"
        done

        # verify ssl lib
        verify_ssl_lib
} # => verify_pki_init()

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

        # First check the PKI has been initialized
        verify_pki_init

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

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

        # verify expected CA-specific dirs:
        for i in issued certs_by_serial
        do
                [ -d "$EASYRSA_PKI/$i" ] || die "\
Missing expected CA dir: $i (perhaps you need to run build-ca?)
$help_note"
        done

        # explicitly return success for callers
        return 0

} # => verify_ca_init()

# init-pki backend:
init_pki() {

        # If EASYRSA_PKI exists, confirm before we rm -rf (skiped with EASYRSA_BATCH)
        if [ -e "$EASYRSA_PKI" ]; then
                confirm "Confirm removal: " "yes" "
WARNING!!!

You are about to remove the EASYRSA_PKI at: $EASYRSA_PKI
and initialize a fresh PKI here."
                # now remove it:
                rm -rf "$EASYRSA_PKI" || die "Removal of PKI dir failed. Check/correct errors above"
        fi

        # new dirs:
        for i in private reqs; do
                mkdir -p "$EASYRSA_PKI/$i" || die "Failed to create PKI file structure (permissions?)"
        done
        
        # Create $EASYRSA_SAFE_CONF ($OPENSSL_CONF) prevents bogus warnings (especially useful on win32)
        if [ ! -f "$EASYRSA_SSL_CONF" ] && [ -f "$EASYRSA/openssl-easyrsa.cnf" ];
        then
                cp "$EASYRSA/openssl-easyrsa.cnf" "$EASYRSA_SSL_CONF"
                easyrsa_openssl makesafeconf
        fi

        notice "\
init-pki complete; you may now create a CA or requests.
Your newly created PKI dir is: $EASYRSA_PKI
"
        return 0
} # => init_pki()

hide_read_pass()
{
        # shellcheck disable=SC2039
        if stty -echo 2>/dev/null; then
                read -r "$@"
                stty echo
        elif (set +o echo 2>/dev/null); then
                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
} # => hide_read_pass()

# build-ca backend:
build_ca() {
        opts=""
        sub_ca=""
        nopass=""
        crypto="-aes256"
        while [ -n "$1" ]; do
                case "$1" in
                        intca) sub_ca=1 ;;
                        subca) sub_ca=1 ;;
                        nopass) nopass=1 ;;
                        *) warn "Ignoring unknown command option: '$1'" ;;
                esac
                shift
        done

        verify_pki_init
        [ "$EASYRSA_ALGO" = "ec" ] && verify_curve_ec
        [ "$EASYRSA_ALGO" = "ed" ] && verify_curve_ed

        # setup for the simpler intermediate CA situation and overwrite with root-CA if needed:
        out_file="$EASYRSA_PKI/reqs/ca.req"
        out_key="$EASYRSA_PKI/private/ca.key"
        if [ ! $sub_ca ]; then
                out_file="$EASYRSA_PKI/ca.crt"
                opts="$opts -x509 -days $EASYRSA_CA_EXPIRE "
        fi

        # Test for existing CA, and complain if already present
        if verify_ca_init test; then
                die "\
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 here, a intermediate ca was created but not signed.
        # Notify the user and require a signed ca.crt or a init-pki:
        [ -f "$out_key" ] && \
                die "\
A CA private key exists but no ca.crt is found in your PKI dir of:
$EASYRSA_PKI
Refusing to create a new CA keypair as this operation would overwrite your
current CA keypair. If you intended to start a new CA, run init-pki first."

        # create necessary files and dirs:
        err_file="Unable to create necessary PKI files (permissions?)"
        for i in issued certs_by_serial \
                 revoked/certs_by_serial revoked/private_by_serial revoked/reqs_by_serial \
                 renewed/certs_by_serial renewed/private_by_serial renewed/reqs_by_serial;
        do
                mkdir -p "$EASYRSA_PKI/$i" || die "$err_file"
        done
        printf "" > "$EASYRSA_PKI/index.txt" || die "$err_file"
        printf "" > "$EASYRSA_PKI/index.txt.attr" || die "$err_file"
        print "01" > "$EASYRSA_PKI/serial" || die "$err_file"

        # Default CN only when not in global EASYRSA_BATCH mode:
        # shellcheck disable=SC2015
        [ "$EASYRSA_BATCH" ] && opts="$opts -batch" || export EASYRSA_REQ_CN="Easy-RSA CA"

        out_key_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
        out_file_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
        # Get password from user if necessary
        if [ ! $nopass ] && ( [ -z "$EASYRSA_PASSOUT" ] || [ -z "$EASYRSA_PASSIN" ] ); then
                out_key_pass_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
                echo
                printf "Enter New CA Key Passphrase: "
                hide_read_pass kpass
                echo
                printf "Re-Enter New CA Key Passphrase: "
                hide_read_pass kpass2
                echo
                # shellcheck disable=2154
                if [ "$kpass" = "$kpass2" ];
                then
                        printf "%s" "$kpass" > "$out_key_pass_tmp"
                else
                        die "Passphrases do not match."
                fi
        fi

        # create the CA key using AES256
        crypto_opts=""
        if [ ! $nopass ]; then
                crypto_opts="$crypto"
                [ -z "$EASYRSA_PASSOUT" ] && crypto_opts="$crypto_opts -passout file:$out_key_pass_tmp"
        fi
        if [ "$EASYRSA_ALGO" = "rsa" ]; then
                #shellcheck disable=SC2086
                "$EASYRSA_OPENSSL" genrsa -out "$out_key_tmp" $crypto_opts ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} "$EASYRSA_ALGO_PARAMS" || \
                        die "Failed create CA private key"
        elif [ "$EASYRSA_ALGO" = "ec" ]; then
                #shellcheck disable=SC2086
                "$EASYRSA_OPENSSL" ecparam -in "$EASYRSA_ALGO_PARAMS" -genkey | \
                        "$EASYRSA_OPENSSL" ec -out "$out_key_tmp" $crypto_opts ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} || \
                        die "Failed create CA private key"
        elif [ "ed" = "$EASYRSA_ALGO" ]; then
                if [ "ed25519" = "$EASYRSA_CURVE" ]; then
                        "$EASYRSA_OPENSSL" genpkey -algorithm ED25519  -out $out_key_tmp || \
                        die "Failed create CA private key"
                elif [ "ed448" = "$EASYRSA_CURVE" ]; then
                        "$EASYRSA_OPENSSL" genpkey -algorithm ED448 -out $out_key_tmp || \
                        die "Failed create CA private key"
                fi
        fi

        # create the CA keypair:
        crypto_opts=""
        [ ! $nopass ] && [ -z "$EASYRSA_PASSIN" ] && crypto_opts="-passin file:$out_key_pass_tmp"

        #shellcheck disable=SC2086
        easyrsa_openssl req -utf8 -new -key "$out_key_tmp" \
                -keyout "$out_key_tmp" -out "$out_file_tmp" $crypto_opts $opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} || \
                die "Failed to build the CA"

        mv "$out_key_tmp" "$out_key"
        mv "$out_file_tmp" "$out_file"
        [ -f "$out_key_pass_tmp" ] && rm "$out_key_pass_tmp"

        # Success messages
        if [ $sub_ca ]; then
                notice "\
NOTE: Your intermediate CA request is at $out_file
and now must be sent to your parent CA for signing. Place your resulting cert
at $EASYRSA_PKI/ca.crt prior to signing operations.
"
        else    notice "\
CA creation complete and you may now import and sign cert requests.
Your new CA certificate file for publishing is at:
$out_file
"
        fi
        return 0
} # => build_ca()

# gen-dh backend:
gen_dh() {
        verify_pki_init

        out_file="$EASYRSA_PKI/dh.pem"
        "$EASYRSA_OPENSSL" dhparam -out "$out_file" "$EASYRSA_KEY_SIZE" || \
                die "Failed to build DH params"
        notice "\
DH parameters of size $EASYRSA_KEY_SIZE created at $out_file
"
        return 0
} # => gen_dh()

# gen-req backend:
gen_req() {
        # pull filename base and use as default interactive CommonName:
        [ -n "$1" ] || die "\
Error: gen-req must have a file base as the first argument.
Run easyrsa without commands for usage and commands."
        key_out="$EASYRSA_PKI/private/$1.key"
        req_out="$EASYRSA_PKI/reqs/$1.req"
        [ ! "$EASYRSA_BATCH" ] && EASYRSA_REQ_CN="$1"
        shift

        # function opts support
        opts=
        while [ -n "$1" ]; do
                case "$1" in
                        nopass) opts="$opts -nodes" ;;
                        # batch flag supports internal callers needing silent operation
                        batch) EASYRSA_BATCH=1 ;;
                        *) warn "Ignoring unknown command option: '$1'" ;;
                esac
                shift
        done

        verify_pki_init
        [ "$EASYRSA_ALGO" = "ec" ] && verify_curve_ec
        [ "$EASYRSA_ALGO" = "ed" ] && verify_curve_ed

        # 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 [ -n "$EASYRSA_EXTRA_EXTS" ]; then
                # Setup & insert the extra ext data keyed by a magic line
                extra_exts="
req_extensions = req_extra
[ req_extra ]
$EASYRSA_EXTRA_EXTS"
                #shellcheck disable=SC2016
                awkscript='
{if ( match($0, "^#%EXTRA_EXTS%") )
        { while ( getline<"/dev/stdin" ) {print} next }
 {print}
}'
                conf_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
                print "$extra_exts" | \
                        awk "$awkscript" "$EASYRSA_SSL_CONF" \
                        > "$conf_tmp" \
                        || die "Copying SSL config to temp file failed"
                # Use this new SSL config for the rest of this function
                EASYRSA_SSL_CONF="$conf_tmp"
        fi

        key_out_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
        req_out_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
        # generate request
        [ $EASYRSA_BATCH ] && opts="$opts -batch"
        # shellcheck disable=2086,2148
        algo_opts=""
        if [ "ed" != $EASYRSA_ALGO ];then
                algo_opts=" -newkey $EASYRSA_ALGO:$EASYRSA_ALGO_PARAMS "
        fi
        easyrsa_openssl req -utf8 -new $algo_opts \
                -keyout "$key_out_tmp" -out "$req_out_tmp" $opts ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} \
                || die "Failed to generate request"
        mv "$key_out_tmp" "$key_out"
        mv "$req_out_tmp" "$req_out"
        notice "\
Keypair and certificate request completed. Your files are:
req: $req_out
key: $key_out
"
        return 0
} # => gen_req()

# common signing backend
sign_req() {
        crt_type="$1"
        opts=""
        req_in="$EASYRSA_PKI/reqs/$2.req"
        crt_out="$EASYRSA_PKI/issued/$2.crt"

        # Randomize Serial number
        if [ "$EASYRSA_RAND_SN" != "no" ];
        then
                i=""
                serial=""
                check_serial=""
                for i in 1 2 3 4 5; do
                        "$EASYRSA_OPENSSL" rand -hex -out "$EASYRSA_PKI/serial" 16
                        serial="$(cat "$EASYRSA_PKI/serial")"
                        check_serial="$("$EASYRSA_OPENSSL" ca -config "$EASYRSA_SSL_CONF" -status "$serial" 2>&1)"
                        case "$check_serial" in
                                *"not present in db"*) break ;;
                                *) continue ;;
                        esac
                done
        fi

        # Support batch by internal caller:
        [ "$3" = "batch" ] && EASYRSA_BATCH=1

        verify_ca_init

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

        # Cert type must exist under the EASYRSA_EXT_DIR
        [ -r "$EASYRSA_EXT_DIR/$crt_type" ] || die "\
Unknown cert type '$crt_type'"

        # Request file must exist
        [ -f "$req_in" ] || die "\
No request found for the input: '$2'
Expected to find the request at: $req_in"

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

        # Display the request subject in an easy-to-read format
        # Confirm the user wishes to sign this request
        confirm "Confirm request details: " "yes" "
You are about to sign the following certificate.
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.

Request subject, to be signed as a $crt_type certificate for $EASYRSA_CERT_EXPIRE days:

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

        # Generate the extensions file for this cert:
        ext_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
        {
                # Append first any COMMON file (if present) then the cert-type extensions
                cat "$EASYRSA_EXT_DIR/COMMON"
                cat "$EASYRSA_EXT_DIR/$crt_type"
                # copy req extensions
                [ "$EASYRSA_CP_EXT" ] && print "copy_extensions = copy"

                # Support a dynamic CA path length when present:
                [ "$crt_type" = "ca" ] && [ -n "$EASYRSA_SUBCA_LEN" ] && \
                        print "basicConstraints = CA:TRUE, pathlen:$EASYRSA_SUBCA_LEN"

                # Deprecated Netscape extension support, if enabled
                if print "$EASYRSA_NS_SUPPORT" | awk_yesno; then
                        [ -n "$EASYRSA_NS_COMMENT" ] && \
                                print "nsComment = \"$EASYRSA_NS_COMMENT\""
                        case "$crt_type" in
                                serverClient)   print "nsCertType = serverClient" ;;
                                server)         print "nsCertType = server" ;;
                                client)         print "nsCertType = client" ;;
                                ca)             print "nsCertType = sslCA" ;;
                        esac
                fi

                # If type is server and no subjectAltName was requested,
                # add one to the extensions file
                if [ "$crt_type" = 'server' ] || [ "$crt_type" = 'serverClient' ];
                then
                        echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName
                        if [ $? -ne 0 ];
                        then
                                san=$(display_san req "$req_in")

                                if [ -n "$san" ];
                                then
                                        print "subjectAltName = $san"
                                else
                                        default_server_san "$req_in"
                                fi
                        fi
                fi

                # Add any advanced extensions supplied by env-var:
                [ -n "$EASYRSA_EXTRA_EXTS" ] && print "$EASYRSA_EXTRA_EXTS"

                : # needed to keep die from inherting the above test
        } > "$ext_tmp" || die "\
Failed to create temp extension file (bad permissions?) at:
$ext_tmp"

        # sign request
        crt_out_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
        easyrsa_openssl ca -utf8 -in "$req_in" -out "$crt_out_tmp" \
                -extfile "$ext_tmp" -days "$EASYRSA_CERT_EXPIRE" -batch $opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} \
                || die "signing failed (openssl output above may have more detail)"
        mv "$crt_out_tmp" "$crt_out"
        rm -f "$ext_tmp"
        notice "\
Certificate created at: $crt_out
"
        return 0
} # => sign_req()

# common build backend
# used to generate+sign in 1 step
build_full() {
        verify_ca_init

        # pull filename base:
        [ -n "$2" ] || die "\
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"
        req_out="$EASYRSA_PKI/reqs/$2.req"
        key_out="$EASYRSA_PKI/private/$2.key"
        crt_out="$EASYRSA_PKI/issued/$2.crt"
        shift 2

        # function opts support
        req_opts=
        while [ -n "$1" ]; do
                case "$1" in
                        nopass) req_opts="$req_opts nopass" ;;
                        inline) EASYRSA_INLINE=1 ;;
                        *) warn "Ignoring unknown command option: '$1'" ;;
                esac
                shift
        done

        # abort on existing req/key/crt files
        err_exists="\
file already exists. Aborting build to avoid overwriting this file.
If you wish to continue, please use a different name or remove the file.
Matching file found at: "
        [ -f "$req_out" ] && die "Request $err_exists $req_out"
        [ -f "$key_out" ] && die "Key $err_exists $key_out"
        [ -f "$crt_out" ] && die "Certificate $err_exists $crt_out"

        # create request
        EASYRSA_REQ_CN="$name"
        #shellcheck disable=SC2086
        gen_req "$name" batch $req_opts

        # Sign it
        ( sign_req "$crt_type" "$name" batch ) || {
                rm -f "$req_out" "$key_out"
                die "Failed to sign '$name'"
        }

        # inline it
        if [ $EASYRSA_INLINE ]; then
                inline_creds
        fi
} # => build_full()

# Create inline credentials file for this node
inline_creds ()
{
        [ -f "$EASYRSA_PKI/$EASYRSA_REQ_CN.creds" ] \
        && die "Inline file exists: $EASYRSA_PKI/$EASYRSA_REQ_CN.creds"
        {
                printf "%s\n" "# $crt_type: $EASYRSA_REQ_CN"
                printf "%s\n" ""
                printf "%s\n" "<ca>"
                cat "$EASYRSA_PKI/ca.crt"
                printf "%s\n" "</ca>"
                printf "%s\n" ""
                printf "%s\n" "<cert>"
                cat "$crt_out"
                printf "%s\n" "</cert>"
                printf "%s\n" ""
                printf "%s\n" "<key>"
                cat "$key_out"
                printf "%s\n" "</key>"
                printf "%s\n" ""
        } > "$EASYRSA_PKI/$EASYRSA_REQ_CN.creds"
} # => inline_creds ()

# revoke backend
revoke() {
        verify_ca_init

        # pull filename base:
        [ -n "$1" ] || die "\
Error: didn't find a file base name as the first argument.
Run easyrsa without commands for usage and command help."
        crt_in="$EASYRSA_PKI/issued/$1.crt"

        opts=""
        if [ "$2" ]; then
                opts="$opts -crl_reason $2"
        fi
        
        verify_file x509 "$crt_in" || die "\
Unable to revoke as the input file is not a valid certificate. Unexpected
input in file: $crt_in"

        # confirm operation by displaying DN:
        confirm "Continue with revocation: " "yes" "
Please confirm you wish to revoke the certificate with the following subject:

$(display_dn x509 "$crt_in")
"       # => confirm end

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

        # shellcheck disable=SC2086
        easyrsa_openssl ca -utf8 -revoke "$crt_in" ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} $opts || die "\
Failed to revoke certificate: revocation command failed."

        # move revoked files so we can reissue certificates with the same name
        move_revoked "$1"

        notice "\
IMPORTANT!!!

Revocation was successful. You must run gen-crl and upload a CRL to your
infrastructure in order to prevent the revoked cert from being accepted.
"       # => notice end
        return 0
} #= revoke()

# move-revoked
# moves revoked certificates to an alternative folder
# allows reissuing certificates with the same name
move_revoked() {
        verify_ca_init

        [ -n "$1" ] || die "\
Error: didn't find a file base name as the first argument.
Run easyrsa without commands for usage and command help."

        crt_in="$EASYRSA_PKI/issued/$1.crt"
        key_in="$EASYRSA_PKI/private/$1.key"
        req_in="$EASYRSA_PKI/reqs/$1.req"

        verify_file x509 "$crt_in" || die "\
Unable to move revoked input file. The file is not a valid certificate. Unexpected
input in file: $crt_in"

        if [ -e "$req_in" ]
        then
                verify_file req "$req_in" || die "\
Unable to move request. The file is not a valid request. Unexpected
input in file: $req_in"
        fi

        # get the serial number of the certificate -> serial=XXXX
        cert_serial="$(easyrsa_openssl x509 -in "$crt_in" -noout -serial)"
        # remove the serial= part -> we only need the XXXX part
        cert_serial=${cert_serial##*=}

        crt_by_serial="$EASYRSA_PKI/certs_by_serial/$cert_serial.pem"
        crt_by_serial_revoked="$EASYRSA_PKI/revoked/certs_by_serial/$cert_serial.crt"
        key_by_serial_revoked="$EASYRSA_PKI/revoked/private_by_serial/$cert_serial.key"
        req_by_serial_revoked="$EASYRSA_PKI/revoked/reqs_by_serial/$cert_serial.req"

        # make sure revoked dirs exist
        [ -d "$EASYRSA_PKI/revoked" ] || mkdir "$EASYRSA_PKI/revoked"
        [ -d "$EASYRSA_PKI/revoked/certs_by_serial" ] || mkdir "$EASYRSA_PKI/revoked/certs_by_serial"
        [ -d "$EASYRSA_PKI/revoked/private_by_serial" ] || mkdir "$EASYRSA_PKI/revoked/private_by_serial"
        [ -d "$EASYRSA_PKI/revoked/reqs_by_serial" ] || mkdir "$EASYRSA_PKI/revoked/reqs_by_serial"

        # move crt, key and req file to revoked folders
        mv "$crt_in" "$crt_by_serial_revoked"

        # only move the req if we have it
        [ -e "$req_in" ] && mv "$req_in" "$req_by_serial_revoked"

        # only move the key if we have it
        [ -e "$key_in" ] && mv "$key_in" "$key_by_serial_revoked"

        # move the rest of the files (p12, p7, ...)
        # shellcheck disable=SC2231
        for file in $EASYRSA_PKI/private/$1\.???
        do
                # get file extension
                file_ext="${file##*.}"

                [ -f "$file" ] && mv "$file" "$EASYRSA_PKI/revoked/private_by_serial/$cert_serial.$file_ext"
        done

        # remove the dublicate certificate in the certs_by_serial folder
        rm "$crt_by_serial"

        return 0

} #= move_revoked()

# renew backend
renew() {
        verify_ca_init

        # pull filename base:
        [ -n "$1" ] || die "\
Error: didn't find a file base name as the first argument.
Run easyrsa without commands for usage and command help."
        crt_in="$EASYRSA_PKI/issued/$1.crt"

        opts=""
        if [ "$2" ]; then
                opts="$2"
        fi

        verify_file x509 "$crt_in" || die "\
Unable to renew as the input file is not a valid certificate. Unexpected
input in file: $crt_in"

        # confirm operation by displaying DN:
        confirm "Continue with renew: " "yes" "
Please confirm you wish to renew the certificate with the following subject:

$(display_dn x509 "$crt_in")
"       # => confirm end

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

        # Check if old cert is expired or expires within 30 days
        expire_date=$(
                easyrsa_openssl x509 -in "$crt_in" -noout -enddate |
                sed 's/^notAfter=//'
                )
        case $(uname 2>/dev/null) in
                "Darwin"|*"BSD")
                        expire_date=$(date -j -f '%b %d %T %Y %Z' "$expire_date" +%s)
                        allow_renew_date=$(date -j -v"+${EASYRSA_CERT_RENEW}d" +%s)
                        ;;
                *)
                        # This works on Windows, too, since uname doesn't exist and this is catch-all
                        expire_date=$(date -d "$expire_date" +%s)
                        allow_renew_date=$(date -d "+${EASYRSA_CERT_RENEW}day" +%s)
                        ;;
        esac

        [ "$expire_date" -lt "$allow_renew_date" ] || die "\
Certificate expires in more than $EASYRSA_CERT_RENEW days.
Renewal not allowed."

        # Extract certificate usage from old cert
        cert_ext_key_usage=$(
                easyrsa_openssl x509 -in "$crt_in" -noout -text |
                sed -n "/X509v3 Extended Key Usage:/{n;s/^ *//g;p;}"
                )
        case $cert_ext_key_usage in
                "TLS Web Client Authentication")
                        cert_type=client
                        ;;
                "TLS Web Server Authentication")
                        cert_type=server
                        ;;
                "TLS Web Server Authentication, TLS Web Client Authentication")
                        cert_type=serverClient
                        ;;
        esac

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

        # move renewed files so we can reissue certificate with the same name
        # FIXME: Modify revoke() to also work on the renewed certs subdir
        move_renewed "$1"

        # renew certificate
        # shellcheck disable=SC2086
        build_full $cert_type $1 $opts || die "\
Failed to renew certificate: renew command failed."

        notice "\
IMPORTANT!!!

Renew was successful.
You may want to revoke the old certificate once the new one has been deployed.
"       # => notice end
        return 0
} #= renew()

# move-renewed
# moves renewed certificates to an alternative folder
# allows reissuing certificates with the same name
move_renewed() {
        verify_ca_init

        [ -n "$1" ] || die "\
Error: didn't find a file base name as the first argument.
Run easyrsa without commands for usage and command help."

        crt_in="$EASYRSA_PKI/issued/$1.crt"
        key_in="$EASYRSA_PKI/private/$1.key"
        req_in="$EASYRSA_PKI/reqs/$1.req"

        verify_file x509 "$crt_in" || die "\
Unable to move renewed input file. The file is not a valid certificate. Unexpected
input in file: $crt_in"

        if [ -e "$req_in" ]
        then
                verify_file req "$req_in" || die "\
Unable to move request. The file is not a valid request. Unexpected
input in file: $req_in"
        fi

        # get the serial number of the certificate -> serial=XXXX
        cert_serial="$(easyrsa_openssl x509 -in "$crt_in" -noout -serial)"
        # remove the serial= part -> we only need the XXXX part
        cert_serial=${cert_serial##*=}

        crt_by_serial="$EASYRSA_PKI/certs_by_serial/$cert_serial.pem"
        crt_by_serial_renewed="$EASYRSA_PKI/renewed/certs_by_serial/$cert_serial.crt"
        key_by_serial_renewed="$EASYRSA_PKI/renewed/private_by_serial/$cert_serial.key"
        req_by_serial_renewed="$EASYRSA_PKI/renewed/reqs_by_serial/$cert_serial.req"

        # make sure renewed dirs exist
        [ -d "$EASYRSA_PKI/renewed" ] || mkdir "$EASYRSA_PKI/renewed"
        [ -d "$EASYRSA_PKI/renewed/certs_by_serial" ] || mkdir "$EASYRSA_PKI/renewed/certs_by_serial"
        [ -d "$EASYRSA_PKI/renewed/private_by_serial" ] || mkdir "$EASYRSA_PKI/renewed/private_by_serial"
        [ -d "$EASYRSA_PKI/renewed/reqs_by_serial" ] || mkdir "$EASYRSA_PKI/renewed/reqs_by_serial"

        # move crt, key and req file to renewed folders
        mv "$crt_in" "$crt_by_serial_renewed"

        # only move the req if we have it
        [ -e "$req_in" ] && mv "$req_in" "$req_by_serial_renewed"

        # only move the key if we have it
        [ -e "$key_in" ] && mv "$key_in" "$key_by_serial_renewed"

        # move the rest of the files (p12, p7, ...)
        # shellcheck disable=SC2231
        for file in $EASYRSA_PKI/private/$1\.???
        do
                # get file extension
                file_ext="${file##*.}"

                [ -f "$file" ] && mv "$file" "$EASYRSA_PKI/renewed/private_by_serial/$cert_serial.$file_ext"
        done

        # remove the duplicate certificate in the certs_by_serial folder
        rm "$crt_by_serial"

        return 0

} #= move_renewed()

# gen-crl backend
gen_crl() {
        verify_ca_init

        out_file="$EASYRSA_PKI/crl.pem"
        out_file_tmp="$(easyrsa_mktemp)" || die "Failed to create temporary file"
        easyrsa_openssl ca -utf8 -gencrl -out "$out_file_tmp" ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} || die "\
CRL Generation failed.
"
        mv "$out_file_tmp" "$out_file"

        notice "\
An updated CRL has been created.
CRL file: $out_file
"
        return 0
} # => gen_crl()

# import-req backend
import_req() {
        verify_pki_init

        # pull passed paths
        in_req="$1" short_name="$2"
        out_req="$EASYRSA_PKI/reqs/$2.req" 

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

        verify_file req "$in_req" || die "\
The input file does not appear to be a certificate request. Aborting import.
Offending file: $in_req"

        # destination must not exist
        [ -f "$out_req" ] && die "\
Unable to import the request as the destination file already exists.
Please choose a different name for your imported request file.
Existing file at: $out_req"
        
        # now import it
        cp "$in_req" "$out_req"

        notice "\
The request has been successfully imported with a short name of: $short_name
You may now use this name to perform signing operations on this request.
"
        return 0
} # => import_req()

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

        [ -n "$1" ] || die "\
Unable to export p12: incorrect command syntax.
Run easyrsa without commands for usage and command help."

        short_name="$1"
        crt_in="$EASYRSA_PKI/issued/$1.crt"
        key_in="$EASYRSA_PKI/private/$1.key"
        crt_ca="$EASYRSA_PKI/ca.crt"
        shift

        verify_pki_init

        # opts support
        want_ca=1
        want_key=1
        while [ -n "$1" ]; do
                case "$1" in
                        noca) want_ca="" ;;
                        nokey) want_key="" ;;
                        *) warn "Ignoring unknown command option: '$1'" ;;
                esac
                shift
        done

        pkcs_opts=
        if [ $want_ca ]; then
                verify_file x509 "$crt_ca" || die "\
Unable to include CA cert in the $pkcs_type output (missing file, or use noca option.)
Missing file expected at: $crt_ca"
                pkcs_opts="$pkcs_opts -certfile $crt_ca"
        fi

        # input files must exist
        verify_file x509 "$crt_in" || die "\
Unable to export $pkcs_type for short name '$short_name' without the certificate.
Missing cert expected at: $crt_in"

        case "$pkcs_type" in
        p12)
                pkcs_out="$EASYRSA_PKI/private/$short_name.p12"

                if [ $want_key ]; then
                        [ -f "$key_in" ] || die "\
Unable to export p12 for short name '$short_name' without the key
(if you want a p12 without the private key, use nokey option.)
Missing key expected at: $key_in"
                else
                        pkcs_opts="$pkcs_opts -nokeys"
                fi

                # export the p12:
                # shellcheck disable=SC2086
                easyrsa_openssl pkcs12 -in "$crt_in" -inkey "$key_in" -export \
                        -out "$pkcs_out" $pkcs_opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} || die "\
Export of p12 failed: see above for related openssl errors."
        ;;
        p7)
                pkcs_out="$EASYRSA_PKI/issued/$short_name.p7b"

                # export the p7:
                # shellcheck disable=SC2086
                easyrsa_openssl crl2pkcs7 -nocrl -certfile "$crt_in" \
                        -out "$pkcs_out" $pkcs_opts ${EASYRSA_PASSIN:+-passin "$EASYRSA_PASSIN"} ${EASYRSA_PASSOUT:+-passout "$EASYRSA_PASSOUT"} || die "\
Export of p7 failed: see above for related openssl errors."
        ;;
esac

        notice "\
Successful export of $pkcs_type file. Your exported file is at the following
location: $pkcs_out
"
        return 0
} # => export_pkcs()

# set-pass backend
set_pass() {
        verify_pki_init

        # key type, supplied internally from frontend command call (rsa/ec)
        key_type="$1"

        # values supplied by the user:
        raw_file="$2"
        file="$EASYRSA_PKI/private/$raw_file.key"
        [ -n "$raw_file" ] || die "\
Missing argument to 'set-$key_type-pass' command: no name/file supplied.
See help output for usage details."

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

        [ -f "$file" ] || die "\
Missing private key: expected to find the private key component at:
$file"

        notice "\
If the key is currently encrypted you must supply the decryption passphrase.
${crypto:+You will then enter a new PEM passphrase for this key.$NL}"

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

        mv "$out_key_tmp" "$file" || die "\
Failed to change the private key passphrase. See above for error messages."

        notice "Key passphrase successfully changed"
        
} # => set_pass()

# update-db backend
update_db() {
        verify_ca_init

        easyrsa_openssl ca -utf8 -updatedb || die "\
Failed to perform update-db: see above for related openssl errors."
        return 0
} # => update_db()

display_san() {
        format="$1" path="$2"

        echo "$EASYRSA_EXTRA_EXTS" | grep -q subjectAltName

        if [ $? -eq 0 ]; then
                print "$(echo "$EASYRSA_EXTRA_EXTS" | grep subjectAltName | sed 's/^\s*subjectAltName\s*=\s*//')"
        else
                san=$(
                        "$EASYRSA_OPENSSL" "$format" -in "$path" -noout -text |
                        sed -n "/X509v3 Subject Alternative Name:/{n;s/ //g;s/IPAddress:/IP:/g;s/RegisteredID/RID/;p;}"
                        )

                [ -n "$san" ] && print "$san"
        fi
}

# display cert DN info on a req/X509, passed by full pathname
display_dn() {
        format="$1" path="$2"
        print "$("$EASYRSA_OPENSSL" "$format" -in "$path" -noout -subject -nameopt multiline)"
        san=$(display_san "$1" "$2")
        if [ -n "$san" ]; then
                print ""
                print "X509v3 Subject Alternative Name:"
                print "    $san"
        fi

} # => display_dn()

# generate default SAN from req/X509, passed by full pathname
default_server_san() {
        path="$1"
        cn=$(
                easyrsa_openssl req -in "$path" -noout -subject -nameopt sep_multiline |
                awk -F'=' '/^  *CN=/{print $2}'
                )
        echo "$cn" | grep -E -q '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'
        #shellcheck disable=SC2181
        if [ $? -eq 0 ]; then
                print "subjectAltName = IP:$cn"
        else
                print "subjectAltName = DNS:$cn"
        fi
} # => default_server_san()

# 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 || return 1
        return 0
} # => verify_file()

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

        # opts support
        opts="-${type}opt no_pubkey,no_sigdump"
        while [ -n "$1" ]; do
                case "$1" in
                        full) 
                                opts=""
                                ;;
                        *) 
                                warn "Ignoring unknown command option: '$1'" 
                                ;;
                esac
                shift
        done

        # Determine cert/req type
        if [ "$type" = "cert" ]; then
                verify_ca_init
                in_file="$EASYRSA_PKI/issued/${name}.crt"
                format="x509"
        else
                verify_pki_init
                in_file="$EASYRSA_PKI/reqs/${name}.req"
                format="req"
        fi

        # Verify file exists and is of the correct type
        [ -f "$in_file" ] || die "\
No such $type file with a basename of '$name' is present.
Expected to find this file at:
$in_file"
        verify_file $format "$in_file" || die "\
This file is not a valid $type file:
$in_file"

        notice "\
Showing $type details for '$name'.
This file is stored at:
$in_file
"
        easyrsa_openssl $format -in "$in_file" -noout -text\
                -nameopt multiline $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
        opts="-certopt no_pubkey,no_sigdump"
        while [ -n "$1" ]; do
                case "$1" in
                        full) opts= ;;
                        *) warn "Ignoring unknown command option: '$1'" ;;
                esac
                shift
        done

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

        # Verify file exists and is of the correct type
        [ -f "$in_file" ] || die "\
No such $type file with a basename of '$name' is present.
Expected to find this file at:
$in_file"
        verify_file $format "$in_file" || die "\
This file is not a valid $type file:
$in_file"

        notice "\
Showing $type details for 'ca'.
This file is stored at:
$in_file
"
        easyrsa_openssl $format -in "$in_file" -noout -text\
                -nameopt multiline $opts || die "\
OpenSSL failure to process the input"
} # => show_ca()

# vars setup
# Here sourcing of 'vars' if present occurs. If not present, defaults are used
# to support running without a sourced config format
vars_setup() {
        # Try to locate a 'vars' file in order of location preference.
        # If one is found, source it
        vars=

        # set up program path
        prog_file="$0"
        prog_file2="$(which -- "$prog_file" 2>/dev/null)" && prog_file="$prog_file2"
        prog_file2="$(readlink -f "$prog_file" 2>/dev/null)" && prog_file="$prog_file2"
        prog_dir="${prog_file%/*}"
        prog_vars="${prog_dir}/vars"
        # set up PKI path
        pki_vars="${EASYRSA_PKI:-$PWD/pki}/vars"

        # command-line path:
        if [ ! -z "$EASYRSA_VARS_FILE" ]; then
          if [ ! -f "$EASYRSA_VARS_FILE" ]; then
                  # If the --vars option does not point to a file, show helpful error.
                        die "The file '$EASYRSA_VARS_FILE' was not found."
                fi
                vars="$EASYRSA_VARS_FILE"
        # PKI location, if present:
        elif [ -f "$pki_vars" ]; then
                vars="$pki_vars"
        # EASYRSA, if defined:
        elif [ -n "$EASYRSA" ] && [ -f "$EASYRSA/vars" ]; then
                vars="$EASYRSA/vars"
        # program location:
        elif [ -f "$prog_vars" ]; then
                vars="$prog_vars"
        fi
        
        # If a vars file was located, source it
        # If $EASYRSA_NO_VARS is defined (not blank) this is skipped
        if [ -z "$EASYRSA_NO_VARS" ] && [ -n "$vars" ]; then
                if grep -Eq 'EASYRSA_PASSIN|EASYRSA_PASSOUT' "$vars"; then
                        die "\
Variable EASYRSA_PASSIN or EASYRSA_PASSOUT has been found in the configuration \
file. Storing sensitive information in the configuration file is not \
recommended - please remove it from there before continuing."
                fi
                #shellcheck disable=SC2034
                EASYRSA_CALLER=1 
                # shellcheck disable=SC1090
                . "$vars"
                notice "\
Note: using Easy-RSA configuration from: $vars"
        fi
        
        # Set defaults, preferring existing env-vars if present
        set_var EASYRSA         "$prog_dir"
        set_var EASYRSA_OPENSSL openssl
        set_var EASYRSA_PKI     "$PWD/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_ALGO            rsa
        set_var EASYRSA_KEY_SIZE        2048
        set_var EASYRSA_CURVE           secp384r1
        set_var EASYRSA_EC_DIR          "$EASYRSA_PKI/ecparams"
        set_var EASYRSA_CA_EXPIRE       3650
        set_var EASYRSA_CERT_EXPIRE     825 # new default of 36 months  
        set_var EASYRSA_CERT_RENEW      30
        set_var EASYRSA_CRL_DAYS        180
        set_var EASYRSA_NS_SUPPORT      no
        set_var EASYRSA_NS_COMMENT      "Easy-RSA (3.0.7) Generated Certificate"
        set_var EASYRSA_TEMP_DIR        "$EASYRSA_PKI"
        set_var EASYRSA_REQ_CN          ChangeMe
        set_var EASYRSA_DIGEST          sha256
        set_var EASYRSA_SSL_CONF        "$EASYRSA_PKI/openssl-easyrsa.cnf"
        set_var EASYRSA_SAFE_CONF       "$EASYRSA_PKI/safessl-easyrsa.cnf"
        set_var EASYRSA_KDC_REALM       "CHANGEME.EXAMPLE.COM"

        # Same as above for the x509-types extensions dir
        if [ -d "$EASYRSA_PKI/x509-types" ]; then
                set_var EASYRSA_EXT_DIR         "$EASYRSA_PKI/x509-types"
        else    
                #TODO: This should be removed.  Not really suitable for packaging.
                set_var EASYRSA_EXT_DIR         "$EASYRSA/x509-types"
        fi

        # EASYRSA_ALGO_PARAMS must be set depending on selected algo
        if [ "ec" = "$EASYRSA_ALGO" ]; then
                EASYRSA_ALGO_PARAMS="$EASYRSA_EC_DIR/${EASYRSA_CURVE}.pem"
        elif [ "rsa" = "$EASYRSA_ALGO" ]; then
                EASYRSA_ALGO_PARAMS="${EASYRSA_KEY_SIZE}"
        elif [ "ed" != "$EASYRSA_ALGO" ]; then
                die "Alg '$EASYRSA_ALGO' is invalid: must be 'rsa', 'ec' or 'ed' "
        fi

        # Assign value to $EASYRSA_TEMP_DIR_session and work around Windows mktemp bug when parent dir is missing
        if [ -z "$EASYRSA_TEMP_DIR_session" ]; then
                if [ -d "$EASYRSA_TEMP_DIR" ]; then
                        EASYRSA_TEMP_DIR_session="$(mktemp -du "$EASYRSA_TEMP_DIR/easy-rsa-$$.XXXXXX")"
                else
                        # If the directory does not exist then we have not run init-pki
                        mkdir -p "$EASYRSA_TEMP_DIR" || die "Cannot create $EASYRSA_TEMP_DIR (permission?)"
                        EASYRSA_TEMP_DIR_session="$(mktemp -du "$EASYRSA_TEMP_DIR/easy-rsa-$$.XXXXXX")"
                        rm -rf "$EASYRSA_TEMP_DIR"
                fi
        fi

        # Setting OPENSSL_CONF prevents bogus warnings (especially useful on win32)
        export OPENSSL_CONF="$EASYRSA_SAFE_CONF"

        # Upgrade to 306: Create $EASYRSA_SSL_CONF if it does not exist but only if $EASYRSA_PKI exists.
        if [ ! -f "$EASYRSA_SSL_CONF" ] && [ -f "$EASYRSA/openssl-easyrsa.cnf" ] && [ -d "$EASYRSA_PKI" ];
        then
                cp "$EASYRSA/openssl-easyrsa.cnf" "$EASYRSA_SSL_CONF"
                easyrsa_openssl makesafeconf
        fi

} # vars_setup()

# variable assignment by indirection when undefined; merely exports
# the variable when it is already defined (even if currently null)
# Sets $1 as the value contained in $2 and exports (may be blank)
set_var() {
        var=$1
        shift
        value="$*"
        eval "export $var=\"\${$var-$value}\""
} #=> set_var()


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

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

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

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

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

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

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

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

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

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

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

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

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

up23_verify_current_pki ()
{
        up23_verbose "> Verify CURRENT PKI vars .."

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

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

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

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

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

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

        # Current CA is unverified
        # Extract the current CA details
        CA_SUBJECT="$(easyrsa_openssl $format -in "$in_file" -subject -noout -nameopt multiline)"

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

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

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

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

up23_show_current_ca ()
{
        printf "%s\n" "-------------------------------------------------------------------------"
        # $opts is always set here
        # shellcheck disable=SC2086
        easyrsa_openssl $format -in "$in_file" -noout -text\
        -nameopt multiline $opts || die "\
        OpenSSL failure to process the input CA certificate: $in_file"
        printf "%s\n" "-------------------------------------------------------------------------"
} #=> up23_show_current_ca ()

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

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

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

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

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

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

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

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

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

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

        # Otherwise this is required for all easyrsa v3
        #confirm "Set 'unique_subject = no' in index.txt.attr for your current CA: " \
        #"yes" "This version of easyrsa requires that 'unique_subject = no' is set correctly"
        
        printf "%s\n" "unique_subject = no" > "$EASYRSA_PKI/index.txt.attr"
        up23_verbose "> OK"
        up23_verbose "  Upgraded index.txt.attr to v306+"
} #=> up23_upgrade_index_txt_attr ()

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

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

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

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

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

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

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

up23_build_v3_vars ()
{
        up23_verbose "> Build v3 vars file .."

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

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

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

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

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

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

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

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

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

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

        up23_verify_new_pki
        up23_verify_current_pki
        up23_verify_current_ca
        up23_backup_current_pki
        up23_create_new_pki
        up23_upgrade_ca
        up23_move_easyrsa2_programs
        up23_build_v3_vars
        up23_create_openssl_cnf

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

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

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

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

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

        [ "$EASYRSA_FOUND_VARS" ] || return 0

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

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

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

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

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

        # PKI is potentially suitable for upgrade

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

                           * WARNING *

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

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

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

# Test upgrade

        NOSAVE=0

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

        up23_do_upgrade_23 "TEST"

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

                             * NOTICE *

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

        NOSAVE=1

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

                             * WARNING *

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

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

                          * SECOND WARNING *

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

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

                             * NOTICE *

Your settings and PKI have been successfully upgraded to EasyRSA version3

A backup of your current PKI is here:
  $EASYRSA_SAFE_PKI

                        * IMPORTANT NOTICE *

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

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

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

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

return 0

} # => up23_manage_upgrade_23 ()



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

NL='
'

# Be secure with a restrictive umask
[ -z "$EASYRSA_NO_UMASK" ] && umask 077

# Parse options
while :; do
        # Separate option from value:
        opt="${1%%=*}"
        val="${1#*=}"
        empty_ok="" # Empty values are not allowed unless excepted

        case "$opt" in
        --days)
                export EASYRSA_CERT_EXPIRE="$val"
                export EASYRSA_CA_EXPIRE="$val"
                export EASYRSA_CRL_DAYS="$val"
                ;;
        --pki-dir)
                export EASYRSA_PKI="$val" ;;
        --use-algo)
                export EASYRSA_ALGO="$val" ;;
        --keysize)
                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" ;;
        --ns-cert)
                export EASYRSA_NS_SUPPORT="$val" ;;
        --ns-comment)
                empty_ok=1
                export EASYRSA_NS_COMMENT="$val" ;;
        --batch)
                empty_ok=1
                export EASYRSA_BATCH=1 ;;
        --passin)
                export EASYRSA_PASSIN="$val";;
        --passout)
                export EASYRSA_PASSOUT="$val";;
        --subca-len)
                export EASYRSA_SUBCA_LEN="$val" ;;
        --vars)
                export EASYRSA_VARS_FILE="$val" ;;
        --copy-ext)
                empty_ok=1
                export EASYRSA_CP_EXT=1 ;;
        --subject-alt-name)
                export EASYRSA_EXTRA_EXTS="\
$EASYRSA_EXTRA_EXTS
subjectAltName = $val" ;;
        *)
                break ;;
        esac

        # fatal error when no value was provided
        if [ ! $empty_ok ] && { [ "$val" = "$1" ] || [ -z "$val" ]; }; then
                die "Missing value to option: $opt"
        fi

        shift
done

# Intelligent env-var detection and auto-loading:
vars_setup

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

# Upgrade: EasyRSA v2.x to EasyRSA v3.x
# Upgrade: EasyRSA < v3.0.6 to v3.0.6+
#up23_manage_upgrade_23

# determine how we were called, then hand off to the function responsible
cmd="$1"
[ -n "$1" ] && shift # scrape off command
case "$cmd" in
        init-pki|clean-all)
                init_pki "$@"
                ;;
        build-ca)
                build_ca "$@"
                ;;
        gen-dh)
                gen_dh
                ;;
        gen-req)
                gen_req "$@"
                ;;
        sign|sign-req)
                sign_req "$@"
                ;;
        build-client-full)
                build_full client "$@"
                ;;
        build-server-full)
                build_full server "$@"
                ;;
        build-serverClient-full)
                build_full serverClient "$@"
                ;;
        gen-crl)
                gen_crl
                ;;
        revoke)
                revoke "$@"
                ;;
        renew)
                renew "$@"
                ;;
        import-req)
                import_req "$@"
                ;;
        export-p12)
                export_pkcs p12 "$@"
                ;;
        export-p7)
                export_pkcs p7 "$@"
                ;;
        set-rsa-pass)
                set_pass rsa "$@"
                ;;
        set-ec-pass)
                set_pass ec "$@"
                ;;
        update-db)
                update_db
                ;;
        show-req)
                show req "$@"
                ;;
        show-cert)
                show cert "$@"
                ;;
        show-ca)
                show_ca "$@"
                ;;
        upgrade)
                up23_manage_upgrade_23 "$@"
                ;;
        ""|help|-h|--help|--usage)
                cmd_help "$1"
                exit 0
                ;;
        *)
                die "Unknown command '$cmd'. Run without commands for usage help."
                ;;
esac

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