PKI/easyrsa

From SerialHobbyists Wiki
Jump to: navigation, search

This is a lightly modified version of the easyrsa (v3) script distributed with OpenVPN. The edits are mostly focused on allowing the name of the CA's own key, certificate, and request files to be easily customized via the vars file. Another small change was made to fix an issue where variables were being polled before they were defined, resulting in the vars file not being properly detected and used.

#!/bin/sh

# Easy-RSA 3 -- A Shell-based CA Utility
#
# Copyright (C) 2013 by the Open-Source OpenVPN development community
#
# 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>
  gen-crl
  update-db
  show-req <filename_base> [ cmd-opts ]
  show-cert <filename_base> [ 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 ]
"

	# collect/show dir status:
	local err_source="Not defined: vars autodetect failed and no value provided"
	local work_dir="${EASYRSA:-$err_source}"
	local 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() {
	local 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 a sub-CA keypair and request (default is a root CA)" ;;
		gen-dh) text="
  gen-dh
      Generates DH (Diffie-Helllman) 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', 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) text="
  build-client-full <filename_base> [ cmd-opts ]
  build-server-full <filename_base> [ cmd-opts ]
      Generate a keypair and sign locally for a client or server

      This mode uses the <filename_base> as the X509 CN."
			opts="
        nopass  - do not encrypt the private key (default is encrypted)" ;;
		revoke) text="
  revoke <filename_base>
      Revoke a certificate specified by the filename_base" ;;
		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" ;;
		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" ;;
		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
--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 sub-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

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
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
	clean_temp
	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() {
	local 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
	local prompt="$1" value="$2" msg="$3" input
	print "
$msg

Type the word '$value' to continue, or any other input to abort."
	printf %s "  $prompt"
	read input
	[ "$input" = "$value" ] && return
	notice "Aborting without confirmation."
	exit 9
} # => confirm()

# remove temp files
clean_temp() {
	for f in "$EASYRSA_TEMP_FILE"
	do	[ -f "$f" ] && rm "$f" 2>/dev/null
	done
} # => clean_temp()

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

	# Verify EASYRSA_OPENSSL command gives expected output
	if [ -z "$EASYRSA_SSL_OK" ]; then
		local val="$("$EASYRSA_OPENSSL" version)"
		[ "${val%% *}" = "OpenSSL" ] || die "\
Missing or invalid OpenSSL
Expected to find openssl command at: $EASYRSA_OPENSSL"
	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"
} # => vars_source_check()

# Verify supplied curve exists and generate curve file if needed
verify_curve() {
	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
	local 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
}

# Basic sanity-check of PKI init and complain if missing
verify_pki_init() {
	local 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_pki_init()

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

	# First check the PKI has been initialized
	verify_pki_init

	# verify expected files present:
	for i in serial index.txt "$CA_CRT" "private/$CA_KEY"; do
		if [ ! -f "$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() {
	vars_source_check

	# 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

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

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

	verify_pki_init
	[ "$EASYRSA_ALGO" = "ec" ] && verify_curve

	# setup for the simpler sub-CA situation and overwrite with root-CA if needed:
	local out_file="$EASYRSA_PKI/reqs/$CA_REQ"
	local 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 sub-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:
	local err_file="Unable to create necessary PKI files (permissions?)"
	for i in issued certs_by_serial; do
		mkdir -p "$EASYRSA_PKI/$i" || die "$err_file"
	done
	printf "" > "$EASYRSA_PKI/index.txt" || die "$err_file"
	print "01" > "$EASYRSA_PKI/serial" || die "$err_file"

	# Default CN only when not in global EASYRSA_BATCH mode:
	[ $EASYRSA_BATCH ] && opts="$opts -batch" || export EASYRSA_REQ_CN="Easy-RSA CA"
	# create the CA keypair:
	"$EASYRSA_OPENSSL" req -new -newkey $EASYRSA_ALGO:"$EASYRSA_ALGO_PARAMS" \
		-config "$EASYRSA_SSL_CONF" -keyout "$out_key" -out "$out_file" $opts || \
		die "Failed to build the CA"

	# Success messages
	if [ $sub_ca ]; then
		notice "\
NOTE: Your sub-CA request is at $out_file
and now must be sent to you 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

	local 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."
	local key_out="$EASYRSA_PKI/private/$1.key"
	local req_out="$EASYRSA_PKI/reqs/$1.req"
	[ ! $EASYRSA_BATCH ] && EASYRSA_REQ_CN="$1"
	shift

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

	verify_pki_init
	[ "$EASYRSA_ALGO" = "ec" ] && verify_curve

	# 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
		EASYRSA_EXTRA_EXTS="
req_extensions = req_extra
[ req_extra ]
$EASYRSA_EXTRA_EXTS"
		local awkscript='
{if ( match($0, "^#%EXTRA_EXTS%") )
	{ while ( getline<"/dev/stdin" ) {print} next }
 {print}
}'
		print "$EASYRSA_EXTRA_EXTS" | \
			awk "$awkscript" "$EASYRSA_SSL_CONF" \
			> "$EASYRSA_TEMP_FILE" \
			|| die "Copying SSL config to temp file failed"
		# Use this new SSL config for the rest of this function
		local EASYRSA_SSL_CONF="$EASYRSA_TEMP_FILE"
	fi

	# generate request
	[ $EASYRSA_BATCH ] && opts="$opts -batch"
	"$EASYRSA_OPENSSL" req -new -newkey $EASYRSA_ALGO:"$EASYRSA_ALGO_PARAMS" \
		-config "$EASYRSA_SSL_CONF" -keyout "$key_out" -out "$req_out" $opts \
		|| die "Failed to generate request"
	notice "\
Keypair and certificate request completed. Your files are:
req: $req_out
key: $key_out
"
	clean_temp
	return 0
} # => gen_req()

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

	# Support batch by internal caller:
	[ "$3" = "batch" ] && local 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:
	{
		# Append first any COMMON file (if present) then the cert-type extensions
		cat "$EASYRSA_EXT_DIR/COMMON"
		cat "$EASYRSA_EXT_DIR/$crt_type"

		# 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
				server)	print "nsCertType = server" ;;
				client)	print "nsCertType = client" ;;
				ca)	print "nsCertType = sslCA" ;;
			esac
		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
	} > "$EASYRSA_TEMP_FILE" || die "\
Failed to create temp extension file (bad permissions?) at:
$EASYRSA_TEMP_FILE"

	# sign request
	"$EASYRSA_OPENSSL" ca -in "$req_in" -out "$crt_out" -config "$EASYRSA_SSL_CONF" \
		-extfile "$EASYRSA_TEMP_FILE" -days $EASYRSA_CERT_EXPIRE -batch $opts \
		|| die "signing failed (openssl output above may have more detail)"
	notice "\
Certificate created at: $crt_out
"
	clean_temp
	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."
	local crt_type="$1" name="$2"
	local req_out="$EASYRSA_PKI/reqs/$2.req"
	local key_out="$EASYRSA_PKI/private/$2.key"
	local crt_out="$EASYRSA_PKI/issued/$2.crt"
	shift 2

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

	# abort on existing req/key/crt files
	local 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"
	gen_req "$name" batch $req_opts

	# Sign it
	sign_req "$crt_type" "$name" batch

} # => build_full()

# 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."
	local crt_in="$EASYRSA_PKI/issued/$1.crt"

	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"

	"$EASYRSA_OPENSSL" ca -revoke "$crt_in" -config "$EASYRSA_SSL_CONF" || die "\
Failed to revoke certificate: revocation command failed."

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

# gen-crl backend
gen_crl() {
	verify_ca_init

	local out_file="$EASYRSA_PKI/crl.pem"
	"$EASYRSA_OPENSSL" ca -gencrl -out "$out_file" -config "$EASYRSA_SSL_CONF" || die "\
CRL Generation failed.
"

	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
	local in_req="$1" short_name="$2"
	local 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() {
	local pkcs_type="$1"
	shift

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

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

	verify_pki_init

	# opts support
	local want_ca=1
	local 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

	local 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)
		local 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:
		"$EASYRSA_OPENSSL" pkcs12 -in "$crt_in" -inkey "$key_in" -export \
			-out "$pkcs_out" $pkcs_opts || die "\
Export of p12 failed: see above for related openssl errors."
	;;
	p7)
		local pkcs_out="$EASYRSA_PKI/issued/$short_name.p7b"

		# export the p7:
		"$EASYRSA_OPENSSL" crl2pkcs7 -nocrl -certfile "$crt_in" \
			-out "$pkcs_out" $pkcs_opts || 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)
	local key_type="$1"

	# values supplied by the user:
	local raw_file="$2"
	local 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
	local crypto="-des3"
	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}"

	"$EASYRSA_OPENSSL" $key_type -in "$file" -out "$file" $crypto || die "\
Failed to change the private key passphrase. See above for possible openssl
error messages."

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

# update-db backend
update_db() {
	verify_ca_init

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

# display cert DN info on a req/X509, passed by full pathname
display_dn() {
	local format="$1" path="$2"
	print "$("$EASYRSA_OPENSSL" $format -in "$path" -noout -subject -nameopt multiline)"
} # => display_dn()

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

# show-* command backend
# Prints req/cert details in a readable format
show() {
	local 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
	local 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()

# 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() {
        # Bootstrap a couple of needed defaults
	set_var EASYRSA		"$PWD"
	set_var EASYRSA_PKI	"$EASYRSA/pki"

	# Try to locate a 'vars' file in order of location preference.
	# If one is found, source it
	local vars=

	# set up program path
	local prog_vars="${0%/*}/vars"

	# command-line path:
	if [ -f "$EASYRSA_VARS_FILE" ]; then
		vars="$EASYRSA_VARS_FILE"
	# EASYRSA_PKI, if defined:
	elif [ -n "$EASYRSA_PKI" ] && [ -f "$EASYRSA_PKI/vars" ]; then
		vars="$EASYRSA_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
		EASYRSA_CALLER=1 . "$vars"
		notice "\
Note: using Easy-RSA configuration from: $vars"
	fi
	
	# Set defaults, preferring existing env-vars if present
	set_var EASYRSA_OPENSSL	openssl
	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	3650
	set_var EASYRSA_CRL_DAYS	180
	set_var EASYRSA_NS_SUPPORT	no
	set_var EASYRSA_NS_COMMENT	"Easy-RSA Generated Certificate"
	set_var EASYRSA_TEMP_FILE	"$EASYRSA_PKI/extensions.temp"
	set_var EASYRSA_REQ_CN		ChangeMe
	set_var EASYRSA_DIGEST		sha256
	set_var CA_KEY			"ca.key"
	set_var	CA_CRT			"ca.crt"
	set_var CA_REQ			"ca.req"

	# Detect openssl config, preferring EASYRSA_PKI over EASYRSA
	if [ -f "$EASYRSA_PKI/openssl-1.0.cnf" ]; then
		set_var EASYRSA_SSL_CONF	"$EASYRSA_PKI/openssl-1.0.cnf"
	else	set_var EASYRSA_SSL_CONF	"$EASYRSA/openssl-1.0.cnf"
	fi

	# 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	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}"
	else
		die "Alg '$EASYRSA_ALGO' is invalid: must be 'rsa' or 'ec'"
	fi

	# Setting OPENSSL_CONF prevents bogus warnings (especially useful on win32)
	export OPENSSL_CONF="$EASYRSA_SSL_CONF"
} # 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() {
	local var=$1
	shift
	local value="$*"
	eval "export $var=\"\${$var-$value}\""
} #=> set_var()

########################################
# 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 ;;
	--subca-len)
		export EASYRSA_SUBCA_LEN="$val" ;;
	--vars)
		export EASYRSA_VARS_FILE="$val" ;;
	--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

# 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 "$@"
		;;
	gen-crl)
		gen_crl
		;;
	revoke)
		revoke "$@"
		;;
	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 "$@"
		;;
	""|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