#!/bin/sh # Easy-RSA 3 -- A Shell-based CA Utility # # Copyright (C) 2025 - The Open-Source OpenVPN development community. # A full list of contributors can be found on Github at: # https://github.com/OpenVPN/easy-rsa/graphs/contributors # # This code released under version 2 of the GNU GPL; see COPYING # and the Licensing/ directory of this project for full licensing # details. # Help/usage output to stdout usage() { # command help: print " Easy-RSA 3 global option and command overview. Global options: --version : Prints EasyRSA version and build information --batch : Set automatic (no-prompts when possible) mode --silent|-s : Disable all warnings, notices and information --sbatch : Combined --silent and --batch operating mode --silent-ssl|-S : Silence SSL output (Requires batch mode) --nopass|no-pass: Do not use passwords Can NOT be used with --passin or --passout --passin=ARG : Set -passin ARG for openssl (eg: pass:xEasyRSAy) --passout=ARG : Set -passout ARG for openssl (eg: pass:xEasyRSAy) --raw-ca : Build CA with password via RAW SSL input --vars=FILE : Define a specific 'vars' file to use for Easy-RSA config (Default vars file is in the current working directory) --pki=DIR : Declare the PKI directory (Default PKI directory is sub-directory 'pki') See Advanced.md for in depth usage. --umask=ARG : Define a UMASK (Default 077) --no-umask : Do not use a UMASK, fall back to file system default. --ssl-cnf=FILE : Define a specific OpenSSL config file for Easy-RSA to use (Default config file is in the EasyRSA PKI directory) --force-safe-ssl: Always generate a safe SSL config file (Default: Generate Safe SSL config once per instance) --no-lockfile : Disable lock-file (Useful for read-only PKI) --tmp-dir=DIR : Declare the temporary directory (Default temporary directory is the EasyRSA PKI directory) --keep-tmp=NAME : Keep the original temporary session by name: NAME NAME is a sub-directory of the dir declared by --tmp-dir This option ALWAYS over-writes a sub-dir of the same name. Certificate & Request options: (these impact cert/req field values) --text : Create certificate requests with human readable text --notext|no-text: Create certificates without human readable text --days=# : Sets the signing validity to the specified number of days Applies to other commands. For details, see: 'help days' --startdate=DATE: Sets the SSL option '-startdate' (Format '[YY]YYMMDDhhmmssZ') --enddate=DATE : Sets the SSL option '-enddate' (Format '[YY]YYMMDDhhmmssZ') --digest=ALG : Digest to use in the requests & certificates --keysize=# : Size in bits of keypair to generate (RSA Only) --use-algo=ALG : Crypto alg to use: choose rsa (default), ec or ed --curve=NAME : For elliptic curve, sets the named curve (Default: algo ec: secp384r1, algo ed: ed25519) --subca-len=# : Path length of signed intermediate CA certificates --copy-ext : Copy included request X509 extensions (namely subjAltName) For more info, see: 'easyrsa help copyext' --san|--subject-alt-name=SUBJECT_ALT_NAME : Add a subjectAltName. Can be used multiple times. For more info and syntax, see: 'easyrsa help altname' --auto-san : Use commonName as subjectAltName: 'DNS:commonName' If commonName is 'n.n.n.n' then set 'IP:commonName' --san-crit : Mark X509v3 subjectAltName as critical --bc-crit : Add X509 'basicContraints = critical' attribute. --ku-crit : Add X509 'keyUsage = critical' attribute. --eku-crit : Add X509 'extendedKeyUsage = critical' attribute. --new-subject='SUBJECT' : Specify a new subject field to sign a request with. For more info and syntax, see: 'easyrsa help subject' --usefn=NAME : export-p12, set 'friendlyName' to NAME For more, see: 'easyrsa help friendly' Distinguished Name mode: --dn-mode=MODE : Distinguished Name mode to use 'cn_only' (Default) or 'org' --req-cn=NAME : Request commonName Distinguished Name Organizational options: (only used with '--dn-mode=org') --req-c=CC : Country code (2-letters) --req-st=NAME : State/Province --req-city=NAME : City/Locality --req-org=NAME : Organization --req-email=NAME : Email addresses --req-ou=NAME : Organizational Unit --req-serial=VALUE : Entity serial number (Only used when declared) Deprecated features: --ns-cert : Include deprecated Netscape extensions --ns-comment=COMMENT : Include deprecated Netscape comment (may be blank) Command list: init-pki self-sign-server [ cmd-opts ] self-sign-client [ cmd-opts ] build-ca [ cmd-opts ] gen-dh gen-req [ cmd-opts ] sign-req [ cmd-opts ] build-client-full [ cmd-opts ] build-server-full [ cmd-opts ] build-serverClient-full [ cmd-opts ] inline expire renew-ca renew revoke [ cmd-opts ] #(DEPRECATED) revoke-issued [ cmd-opts ] #(REPLACEMENT) revoke-expired [ cmd-opts ] revoke-renewed [ cmd-opts ] gen-crl update-db show-req [ cmd-opts ] show-cert [ cmd-opts ] show-ca [ cmd-opts ] show-crl show-expire (Optional) show-revoke (Optional) show-renew (Optional) verify-cert import-req export-p1 [ cmd-opts ] export-p7 [ cmd-opts ] export-p8 [ cmd-opts ] export-p12 [ cmd-opts ] set-pass [ cmd-opts ] gen-tls-auth-key / gen-tls-crypt-key write serial|check-serial display-dn
show-eku | rand " } # => usage() # Detailed command help # When called with no args, calls usage(), # otherwise shows help for a command # Please maintain strict indentation rules. # Commands are TAB indented, while text is SPACE indented. # 'case' indentation is minimalistic. cmd_help() { easyrsa_help_title="\ Usage: easyrsa [ OPTIONS.. ] [ cmd-opts.. ]" unset -v text err_text opts text_only case "$1" in init-pki|clean-all) text=" * init-pki [ cmd-opts ] Removes & re-initializes the PKI directory for a new PKI" ;; self-sign*) text=" * self-sign-server|self-sign-client [ cmd-opts ] Creates a new self-signed server|client key pair" opts=" * nopass - Do not encrypt the private key (Default: encrypted) (Equivalent to global option '--nopass|--no-pass')" ;; build-ca) text=" * build-ca [ cmd-opts ] Creates a new CA" opts=" * rawca - ONLY use SSL binary to input CA password (Equivalent to global option '--rawca|--raw-ca') * nopass - Do not encrypt the private key (Default: encrypted) (Equivalent to global option '--nopass|--no-pass') * subca - Create an intermediate CA keypair and request intca (default is a root CA)" ;; gen-dh) text=" * gen-dh Generates DH (Diffie-Hellman) parameters file" ;; gen-req) text=" * gen-req [ cmd-opts ] Generate a standalone-private-key and certificate-signing-request This request is suitable for sending to a remote CA for signing." opts=" * nopass - Do not encrypt the private key (Default: encrypted) (Equivalent to global option '--nopass|--no-pass') * text - Include certificate text in request" ;; sign|sign-req) text=" * sign-req [ cmd-opts ] Sign a certificate request of the defined type. must be a known type. eg: 'client', 'server', 'serverClient', 'ca' or a user-added type. All supported types are listed in the x509-types directory. This request file must exist in the reqs/ dir and have a .req file extension. See 'import-req' for importing from other sources." opts=" * newsubj - Replace subject. See 'help subject'. * preserve - Use the DN-field order of the CSR not the CA." ;; build|build-client-full|build-server-full|build-serverClient-full) text=" * build-client-full [ cmd-opts ] * build-server-full [ cmd-opts ] * build-serverClient-full [ cmd-opts ] Generate a keypair and sign locally. This mode uses the as the X509 commonName." opts=" * nopass - Do not encrypt the private key (Default: encrypted) (Equivalent to global option '--nopass|--no-pass')" ;; inline) text=" * inline Create inline file for ." ;; revoke*) text=" * revoke [ reason ] Revoke a certificate specified by the , with an optional revocation [ reason ]. Values accepted for option [ reason ]: us | uns* | unspecified kc | key* | keyCompromise cc | ca* | CACompromise ac | aff* | affiliationChanged ss | sup* | superseded co | ces* | cessationOfOperation ch | cer* | certificateHold Commands 'revoke-expired' and 'revoke-renewed' are functionally equivalent to 'revoke-issued', however, they are used to revoke certificates which have been either 'expired' or 'renewed' by Easy-RSA commands 'expire' or 'renew'. Commmand 'revoke' is DEPRECATED and can ONLY be used in batch mode. Commmand 'revoke-issued' REPLACES command 'revoke'. REQUIRED COMMANDS: * 'revoke-issued' [ reason ] Revoke a current, issued certificate. Archives the original request and private key files. * 'revoke-expired' [ reason ] Revoke an old, expired certificate. Preserves the original request and private key files. * 'revoke-renewed' [ reason ] Revoke an old, renewed certificate. Preserves the original request and private key files. All 'revoke' commands archive the specified certificate by serial number." opts=" * [ reason ]${NL} Values accepted for option [ reason ]: Details above." ;; expire) text=" * expire Move a certificate specified by to the 'pki/expired' directory. Allows an existing request to be signed again." ;; renew-ca) text=" * renew-ca Renew CA certificate. This will build a new CA certificate and archive the old one. Before changes are made to the current PKI, user confirmation is required." ;; renew) text=" * renew Renew a certificate specified by " ;; gen-crl) text=" * gen-crl Generate a certificate revocation list [CRL]" ;; update-db) text=" * update-db Update the index.txt database This command will use the system time to update the status of issued certificates." ;; show-req|show-cert) text=" * show-req [ cmd-opts ] * show-cert [ cmd-opts ] Shows details of the req or cert referenced by Human-readable output is shown, including any requested cert options when showing a request." opts=" * full - show full req/cert info, including pubkey/sig data" ;; show-ca) text=" * show-ca [ cmd-opts ] Shows details of the Certificate Authority [CA] certificate Human-readable output is shown." opts=" * full - show full CA info, including pubkey/sig data" ;; show-crl) text=" * show-crl Shows details of the current certificate revocation list (CRL) Human-readable output is shown." ;; verify|verify-cert) text=" * verify-cert [ cmd-opts ] Verify certificate against CA Returns the current validity of the certificate." opts=" * batch - On failure to verify, return error (1) to caller" ;; import-req) text=" * import-req Import a certificate request from a file This will copy the specified file into the reqs/ dir in preparation for signing. The is the to create. Example usage: import-req /some/where/bob_request.req bob" ;; export-p12) text=" * export-p12 [ cmd-opts ] Export a PKCS#12 file with the keypair, specified by " opts=" * nopass - Do not encrypt the private key (Default: encrypted) (Equivalent to global option '--nopass|--no-pass') * noca - Do not include the ca.crt file in the PKCS12 output * nokey - Do not include the private key in the PKCS12 output * nofn - Do not set 'friendlyName' For more, see: 'easyrsa help friendly' * legacy - Use legacy algorithm: RC2_CBC or 3DES_CBC + MAC: SHA1 (Default algorithm: AES-256-CBC + MAC: SHA256)" ;; friendly) text_only=1 text=" * export-p12: Internal file label 'friendlyName' The 'friendlyname' is always set to the file-name-base. An alternate friendlyName can be configured by using: * Global option '--usefn=' Fallback to previous behavior can be configured by using: * Command option 'nofn' ('friendlyname' will not be set)" ;; export-p7) text=" * export-p7 [ cmd-opts ] Export a PKCS#7 file with the pubkey, specified by " opts=" * noca - Do not include the ca.crt file in the PKCS7 output" ;; export-p8) text=" * export-p8 [ cmd-opts ] Export a PKCS#8 file with the private key, specified by " opts=" * nopass - Do not encrypt the private key (Default: encrypted) (Equivalent to global option '--nopass|--no-pass')" ;; export-p1) text=" * export-p1 [ cmd-opts ] Export a PKCS#1 (RSA format) file with the pubkey, specified by " opts=" * nopass - Do not encrypt the private key (Default: encrypted) (Equivalent to global option '--nopass|--no-pass')" ;; set-pass|set-ed-pass|set-rsa-pass|set-ec-pass) text=" * set-pass [ cmd-opts ] Set a new passphrase for the private key specified by DEPRECATED: 'set-rsa-pass' and 'set-ec-pass'" opts=" * nopass - Do not encrypt the private key (Default: encrypted) (Equivalent to global option '--nopass|--no-pass') * file - (Advanced) Treat the file as a raw path, not a short-name" ;; write) text=" * write Write data to stdout Types: * vars - Write vars.example file. * ssl-cnf - Write EasyRSA SSL config file. * safe-cnf - Write expanded EasyRSA SSL config file for LibreSSL. * COMMON|ca|server|serverClient|client|codeSigning|email|kdc - Write x509-type file. * legacy - Write ALL support files (above) to the PKI directory. Will create '\$EASYRSA_PKI/x509-types' directory. * legacy-hard - Same as 'legacy' plus OVER-WRITE files." ;; --san|--subject-alt-name|altname|subjectaltname|san) text_only=1 text=" * Global Option: --subject-alt-name=SAN_FORMAT_STRING This global option adds a subjectAltName to the request or issued certificate. It MUST be in a valid format accepted by openssl or req/cert generation will fail. NOTE: --san can be specified more than once on the command line. The following two command line examples are equivalent: 1. --san=DNS:server1,DNS:serverA,IP:10.0.0.1 2. --san=DNS:server1 --san=DNS:serverA --san=IP:10.0.0.1 Examples of the SAN_FORMAT_STRING shown below: * DNS:alternate.example.net * DNS:primary.example.net,DNS:alternate.example.net * IP:203.0.113.29 * email:alternate@example.net" ;; --copy-ext|copy-ext|copyext) text_only=1 text=" * Global Option: How to use --copy-ext and --san= These are the only commands that support --copy-ext and/or --san. Command 'gen-req': --san: Add SAN extension to the request file. Command 'sign-req': --copy-ext: Copy all request extensions to the signed certificate. --san: Over write the request SAN with option SAN. Command 'build-*-full': --copy-ext: Always enabled. --san: Add SAN extension to the request and signed certificate. See 'help san' for option --san full syntax." ;; --days|days) text_only=1 text=" * Global Option: --days=DAYS This global option is an alias for one of the following: * Expiry days for a new CA. eg: '--days=3650 build-ca' * Expiry days for new/renewed certificate. eg: '--days=1095 renew server' * Expiry days for certificate revocation list. eg: '--days=180 gen-crl' * Cutoff days for command: show-expire. eg: '--days=90 show-expire'" ;; --new-subj*|new-subj*|newsubj*|subject) text_only=1 text=" * Global Option: --new-subject= This global option is used to set the new certificate subject, when signing a new certificate * REQUIRES Command option: 'newsubj', for command 'sign-req' Using command 'sign-req', add command option 'newsubj', to FORCE the --new-subject to be used. Example: --new-subject='/CN=foo' sign-req client bar newsubj See OpenSSL command 'ca', option -subj, for full details." ;; serial|check-serial) text_only=1 text=" * serial|check-serial Check certificate number is unique." ;; display-dn) text_only=1 text=" * display-dn Display DN of request or certificate: = req|x509" ;; show-eku) text_only=1 text=" * show-eku | Display Extended Key Usage of certificate." ;; rand|random) text_only=1 text=" * rand Generate random hex." ;; show-expire|show-revoke|show-renew) text_only=1 text=" * show-expire (Optional) Show a list of certificates which will expire in the number of --days. (Default: 90 days) * show-revoke (Optional) Show a list of revoked certificates. * show-renew (Optional) Show a list of renewed certificates." ;; gen-tls*) text=" Generate TLS keys for use with OpenVPN: gen-tls-auth-key : Generate OpenVPN TLS-AUTH key gen-tls-crypt-key : Generate OpenVPN TLS-CRYPT-V1 key (Preferred) Only ONE TLS key is allowed to exist. (pki/private/easyrsa-tls.key) This TLS key will be automatically added to inline files." ;; "") usage cleanup ok ;; *) err_text=" Unknown command: '$1' \ (try without commands for a list of commands)" easyrsa_exit_with_error=1 esac if [ "$err_text" ]; then print "$easyrsa_help_title" print "${err_text}" else # display the help text print "$easyrsa_help_title" [ "$text" ] && print "$text" if [ "$text_only" ]; then : # ok - No opts message required else print " Available command options [ cmd-opts ]: ${opts:- * No supported command options}" fi fi print } # => cmd_help() # default help default_overview() { print " Easy-RSA 3 Overview: To get a list of available options and commands, use: ./easyrsa help To get detailed usage and help for commands, use: ./easyrsa help " # collect/show dir status: text_only=1 work_dir="${EASYRSA:-undefined}" pki_dir="${EASYRSA_PKI:-undefined}" # check for vars changing PKI unexpectedly! if [ "$invalid_vars" ]; then ivmsg=" *WARNING*: \ Invalid vars setting for EASYRSA and/or EASYRSA_PKI${NL}" else unset -v ivmsg fi # Print details print " DIRECTORY STATUS (commands would take effect on these locations) EASYRSA: $work_dir PKI: $pki_dir vars-file: ${EASYRSA_VARS_FILE:-Missing or undefined}${ivmsg}" # Print algo details print " Algorithm: $EASYRSA_ALGO" case "$EASYRSA_ALGO" in rsa) print " Key size: $EASYRSA_KEY_SIZE" ;; ec|ed) print " Curve: $EASYRSA_CURVE" ;; *) print " Algorithm: UNKNOWN!" esac # CA Status if verify_ca_init test; then if [ -z "$EASYRSA_SILENT" ]; then # Show SSL output directly, with easyrsa header printf '%s' " CA status: OK${NL}${NL} " "$EASYRSA_OPENSSL" x509 -in "$EASYRSA_PKI/ca.crt" \ -noout -subject -nameopt utf8,multiline print "" # for a clean line fi else if [ -f "$EASYRSA_PKI"/peer-fp.mode ]; then print "\ CA status: No CA, Peer-Fingerprint only PKI enabled${NL}" else print "\ CA status: CA has not been built${NL}" fi fi # verbose info verbose "ssl-cnf: ${EASYRSA_SSL_CONF:-built-in}" verbose "x509-types: ${EASYRSA_EXT_DIR:-built-in}" if [ -d "$EASYRSA_TEMP_DIR" ]; then verbose "temp-dir: Found: $EASYRSA_TEMP_DIR" else verbose "temp-dir: Missing: ${EASYRSA_TEMP_DIR:-undefined}" fi } # => default_overview() # Wrapper around printf - clobber print since it's not POSIX anyway # print() is used internally, so MUST NOT be silenced. # shellcheck disable=SC1117 # printf format - print() print() { printf '%s\n' "$*" } # => print() # Exit fatally with a message to stderr # present even with EASYRSA_BATCH as these are fatal problems die() { print " Easy-RSA error: $*${NL}" # error_info is for hard-to-spot errors! if [ "$error_info" ]; then print " * $cmd: ${error_info}${NL}" fi # show host info show_host # exit to cleanup() exit "${2:-1}" } # => die() # User errors, less noise than die() user_error() { print " EasyRSA version $EASYRSA_version Error ----- $*${NL}" easyrsa_exit_with_error=1 cleanup } # => user_error() # verbose information verbose() { [ "$EASYRSA_VERBOSE" ] || return 0 print " # $fn_name; $*" } # => verbose() # non-fatal warning output warn() { [ "$EASYRSA_SILENT" ] && return print " WARNING ======= $*${NL}" } # => warn() # informational notices to stdout notice() { [ "$EASYRSA_SILENT" ] && return print " Notice ------ $*${NL}" } # => notice() # Helpful information information() { [ "$EASYRSA_SILENT" ] && return print "$*" } # => information() # intent confirmation helper func # returns without prompting in EASYRSA_BATCH confirm() { [ "$EASYRSA_BATCH" ] && return prompt="$1" value="$2" msg="$3" input="" print "\ $msg Type the word '$value' to continue, or any other input to abort." printf %s " $prompt" # shellcheck disable=SC2162 # read without -r - confirm() read input printf '\n' [ "$input" = "$value" ] && return easyrsa_exit_with_error=1 unset -v EASYRSA_SILENT notice "Aborting without confirmation." cleanup } # => confirm() # Generate random hex easyrsa_random() { case "$1" in *[!1234567890]*|0*|"") die "easyrsa_random - input" esac if rand_hex="$( "$EASYRSA_OPENSSL" rand -hex "$1" 2>/dev/null )" then if [ "$2" ]; then force_set_var "$2" "$rand_hex" else print "$rand_hex" fi unset -v rand_hex return 0 fi die "easyrsa_random failed" } # => easyrsa_random() # Set clobber on|off set_no_clobber() { case "$1" in on) if [ "$easyrsa_host_os" = win ]; then set -o noclobber && return else set -C && return fi ;; off) if [ "$easyrsa_host_os" = win ]; then set +o noclobber && return else set +C && return fi ;; *) : # drop to error esac die "set_no_clobber() $1, Failed" } # => set_no_clobber() # Create lock-file create_lock_file() { # Force noclobber set_no_clobber on # Create lock-file from PID [ "$1" ] || die "create_lock_file - input" print "$$" 2>/dev/null 1>"$1" || return 1 # unset noclobber set_no_clobber off } # => create_lock_file() # Remove lock-file, if lock_data matches PID remove_lock_file() { if [ "$2" = "$$" ]; then rm "$1" 2>/dev/null || return 1 elif [ "$2" = FORCE ]; then rm "$1" 2>/dev/null || return 1 else return 1 fi } # => remove_lock_file() # Create session directory atomically or fail secure_session() { # Session must not be defined [ "$secured_session" ] && die "session overload" # Temporary directory must exist [ -d "$EASYRSA_TEMP_DIR" ] || die "\ secure_session - Missing temporary directory: * $EASYRSA_TEMP_DIR" for i in 1 2 3; do session= easyrsa_random 4 session secured_session="${EASYRSA_TEMP_DIR}/${session}" # atomic: if mkdir "$secured_session"; then # New session requires safe-ssl conf unset -v session OPENSSL_CONF \ working_safe_ssl_conf working_safe_org_conf easyrsa_err_log="$secured_session/error.log" verbose "secure_session; CREATED $secured_session" return fi done die "secure_session failed" } # => secure_session() # Remove secure session remove_secure_session() { [ -d "$secured_session" ] || return 0 if rm -rf "$secured_session"; then verbose "remove_secure_session; DELETED $secured_session" # Restore original EASYRSA_SSL_CONF export EASYRSA_SSL_CONF="$original_ssl_cnf" unset -v secured_session OPENSSL_CONF \ working_safe_ssl_conf working_safe_org_conf return fi die "remove_secure_session Failed: $secured_session" } # => remove_secure_session() # Create temp-file atomically or fail # WARNING: Running easyrsa_openssl in a subshell # will hide error message and verbose messages # from easyrsa_mktemp() easyrsa_mktemp() { if [ -z "$1" ] || [ "$2" ]; then die "easyrsa_mktemp - input error" fi # session directory must exist [ -d "$secured_session" ] || die "\ easyrsa_mktemp - Temporary session undefined (--tmp-dir)" # Create shotfile for high in 0 1; do for low in 0 1 2 3 4 5 6 7 8 9; do shotfile="${secured_session}/temp.${high}${low}" # Force noclobber set_no_clobber on # atomic: printf "" 2>/dev/null 1>"$shotfile" || continue # unset noclobber set_no_clobber off # Assign external temp-file name if force_set_var "$1" "$shotfile"; then verbose "easyrsa_mktemp; $1 $shotfile" # Update counter mktemp_counter="$((mktemp_counter+1))" return else die "easyrsa_mktemp - force_set_var $1 failed" fi done done # In case of subshell abuse, report to error log err_msg="easyrsa_mktemp - failed for: $1" print "$err_msg" > "$easyrsa_err_log" die "$err_msg" } # => easyrsa_mktemp() # remove temp files and do terminal cleanups cleanup() { # In case of subshell abuse, display error log file if [ -f "$easyrsa_err_log" ]; then print; cat "$easyrsa_err_log"; print fi # undo changes BEFORE delete temp-dir # Remove files when build_full()->sign_req() is interrupted [ "$error_build_full_cleanup" ] && \ rm -f "$crt_out" "$req_out" "$key_out" # Restore files when renew is interrupted [ "$error_undo_renew_move" ] && renew_restore_move # Remove temp-session or create temp-snapshot if [ -d "$secured_session" ]; then if [ "$EASYRSA_KEEP_TEMP" ]; then # skip on black-listed directory names, with a warning # Use '-e' for directory or file name if [ -e "$EASYRSA_TEMP_DIR/$EASYRSA_KEEP_TEMP" ] then warn "\ Prohibited value for --keep-tmp: '$EASYRSA_KEEP_TEMP' Temporary session not preserved." else # create temp-snapshot keep_tmp="$EASYRSA_TEMP_DIR/tmp/$EASYRSA_KEEP_TEMP" mkdir -p "${EASYRSA_TEMP_DIR}/tmp/${keep_tmp}" || die \ "cleanup() - Failed to create '${keep_tmp}' directory." rm -rf "$keep_tmp" mv -f "$secured_session" "$keep_tmp" information "Temp session preserved: $keep_tmp" unset -v secured_session fi fi # remove temp-session remove_secure_session verbose "mktemp_counter: $mktemp_counter uses" fi # When prompt is disabled then restore prompt case "$prompt_restore" in 0) : ;; # Not required 1) [ -t 1 ] && stty echo [ "$EASYRSA_SILENT" ] || print ;; 2) # shellcheck disable=SC3040 # POSIX set -o set -o echo [ "$EASYRSA_SILENT" ] || print ;; *) warn "Unknown prompt_restore: '$prompt_restore'" esac # Clear traps trap - 0 1 2 3 6 15 # Remove lock-file if [ -f "$lock_file" ] || [ "$create_lock_file_error" ] then # Too test this, create a lock-file # and uncomment the following line #read -r -p "Continue.." keypress lock_data="$(cat "$lock_file" 2>/dev/null)" || \ lock_data=error if remove_lock_file "$lock_file" "$lock_data"; then verbose "cleanup: lock-file REMOVED OK" else if [ "$lock_data" = error ]; then error_description=READ else error_description=REMOVE fi # Print error message and set error code print "\ cleanup: Failed to ${error_description} lock-file! Please check that easyrsa is not being used by another process and then try running the easyrsa command again." # Reserve exit-code 17 for lock-file error easyrsa_exit_with_error=17 fi else verbose "cleanup: lock-file does not exist." fi # Exit: Known errors # -> confirm(): aborted # -> verify_cert(): verify failed --batch mode # -> check_serial_unique(): not unique --batch mode # -> user_error(): User errors but not die() if [ "$easyrsa_exit_with_error" ]; then verbose "Exit: Known errors = true ($easyrsa_exit_with_error)" exit "$easyrsa_exit_with_error" elif [ "$1" = 2 ]; then verbose "exit SIGINT = true" kill -2 "$$" # Exit: SIGINT elif [ "$1" = ok ]; then verbose "Exit: Final Success = true" exit 0 # Exit: Final Success fi # if 'cleanup' is called without 'ok' then an error occurred verbose "Exit: Final Fail = true" exit 1 # Exit: Final Fail, unknown error } # => cleanup() # Escape hazardous characters # Auto-escape hazardous characters: # '&' - Workaround 'sed' behavior # '$' - Workaround 'easyrsa' based limitation # This is required for all SSL libs, otherwise, # there are unacceptable differences in behavior escape_hazard() { if [ "$EASYRSA_FORCE_SAFE_SSL" ]; then # Always run verbose "escape_hazard: FORCED" elif [ "$working_safe_org_conf" ]; then # Has run once verbose "escape_hazard: BYPASSED" return else # Run once verbose "escape_hazard: RUN-ONCE" working_safe_org_conf=1 # Set run once fi # Assign temp-file escape_hazard_tmp="" easyrsa_mktemp escape_hazard_tmp # write org fields to org temp-file and escape '&' and '$' print "\ export EASYRSA_REQ_COUNTRY=\"$EASYRSA_REQ_COUNTRY\" export EASYRSA_REQ_PROVINCE=\"$EASYRSA_REQ_PROVINCE\" export EASYRSA_REQ_CITY=\"$EASYRSA_REQ_CITY\" export EASYRSA_REQ_ORG=\"$EASYRSA_REQ_ORG\" export EASYRSA_REQ_OU=\"$EASYRSA_REQ_OU\" export EASYRSA_REQ_EMAIL=\"$EASYRSA_REQ_EMAIL\" export EASYRSA_REQ_SERIAL=\"$EASYRSA_REQ_SERIAL\"\ " | sed -e s\`'\&'\`'\\\&'\`g \ -e s\`'\$'\`'\\\$'\`g > "$escape_hazard_tmp" || \ die "escape_hazard - Failed to write temp-file" # Reload fields from fully escaped temp-file # shellcheck disable=1090 # Non-constant source . "$escape_hazard_tmp" verbose "escape_hazard: COMPLETED" } # => escape_hazard() # Replace environment variable names with current value # and write to temp-file or return error from sed expand_ssl_config() { if [ "$EASYRSA_FORCE_SAFE_SSL" ]; then # Always run verbose "expand_ssl_config: FORCED" elif [ "$working_safe_ssl_conf" ]; then # Has run once verbose "expand_ssl_config: BYPASSED" return elif [ "$ssl_lib" = libressl ]; then # LibreSSL Always run verbose "expand_ssl_config: REQUIRED" elif [ "$ssl_lib" = openssl ]; then # OpenSSL not required verbose "expand_ssl_config: IGNORED" return else die "expand_ssl_config: EXCEPTION" # do NOT Run fi # Set run once working_safe_ssl_conf=1 verbose "expand_ssl_config: RUN-ONCE" # Assign temp-file safe_ssl_cnf_tmp="" easyrsa_mktemp safe_ssl_cnf_tmp # Rewrite # shellcheck disable=SC2016 # No expand '' if sed \ \ -e s\`'$dir'\`\ \""$EASYRSA_PKI"\"\`g \ \ -e s\`'$ENV::EASYRSA_PKI'\`\ \""$EASYRSA_PKI"\"\`g \ \ -e s\`'$ENV::EASYRSA_CERT_EXPIRE'\`\ \""$EASYRSA_CERT_EXPIRE"\"\`g \ \ -e s\`'$ENV::EASYRSA_CRL_DAYS'\`\ \""$EASYRSA_CRL_DAYS"\"\`g \ \ -e s\`'$ENV::EASYRSA_DIGEST'\`\ \""$EASYRSA_DIGEST"\"\`g \ \ -e s\`'$ENV::EASYRSA_KEY_SIZE'\`\ \""$EASYRSA_KEY_SIZE"\"\`g \ \ -e s\`'$ENV::EASYRSA_DN'\`\ \""$EASYRSA_DN"\"\`g \ \ -e s\`'$ENV::EASYRSA_REQ_CN'\`\ \""$EASYRSA_REQ_CN"\"\`g \ \ -e s\`'$ENV::EASYRSA_REQ_COUNTRY'\`\ \""$EASYRSA_REQ_COUNTRY"\"\`g \ \ -e s\`'$ENV::EASYRSA_REQ_PROVINCE'\`\ \""$EASYRSA_REQ_PROVINCE"\"\`g \ \ -e s\`'$ENV::EASYRSA_REQ_CITY'\`\ \""$EASYRSA_REQ_CITY"\"\`g \ \ -e s\`'$ENV::EASYRSA_REQ_ORG'\`\ \""$EASYRSA_REQ_ORG"\"\`g \ \ -e s\`'$ENV::EASYRSA_REQ_OU'\`\ \""$EASYRSA_REQ_OU"\"\`g \ \ -e s\`'$ENV::EASYRSA_REQ_EMAIL'\`\ \""$EASYRSA_REQ_EMAIL"\"\`g \ \ -e s\`'$ENV::EASYRSA_REQ_SERIAL'\`\ \""$EASYRSA_REQ_SERIAL"\"\`g \ \ "$EASYRSA_SSL_CONF" > "$safe_ssl_cnf_tmp" then verbose "expand_ssl_config: via 'sed' COMPLETED" else return 1 fi export EASYRSA_SSL_CONF="$safe_ssl_cnf_tmp" unset -v safe_ssl_cnf_tmp verbose \ "expand_ssl_config: EASYRSA_SSL_CONF = $EASYRSA_SSL_CONF" } # => expand_ssl_config() # Easy-RSA meta-wrapper for SSL # WARNING: Running easyrsa_openssl in a subshell # will hide error message and verbose messages easyrsa_openssl() { openssl_command="$1"; shift if [ "$EASYRSA_DEBUG" ]; then verbose "easyrsa_openssl; BEGIN $openssl_command $*" else verbose "easyrsa_openssl; BEGIN $openssl_command" fi # Do not allow 'rand' here, see easyrsa_random() case "$openssl_command" in rand) die "easyrsa_openssl: Illegal SSL command: rand" esac # Use $EASYRSA_SSL_CONF (local) or $OPENSSL_CONF (global) if [ -f "$EASYRSA_SSL_CONF" ]; then export OPENSSL_CONF="$EASYRSA_SSL_CONF" elif [ -f "$OPENSSL_CONF" ]; then export OPENSSL_CONF else die "easyrsa_openssl - OPENSSL_CONF undefined" fi verbose "easyrsa_openssl; OPENSSL_CONF = $OPENSSL_CONF" # Exec SSL if [ "$EASYRSA_SILENT_SSL" ] && [ "$EASYRSA_BATCH" ] then if "$EASYRSA_OPENSSL" "$openssl_command" "$@" 2>/dev/null then verbose "easyrsa_openssl; END $openssl_command" return fi else if "$EASYRSA_OPENSSL" "$openssl_command" "$@" then verbose "easyrsa_openssl; END $openssl_command" return fi fi # Always fail here die "\ easyrsa_openssl - Command has failed: * $EASYRSA_OPENSSL $openssl_command $*" } # => easyrsa_openssl() # Verify the SSL library is functional # and establish version dependencies verify_ssl_lib() { # Run once only [ "$verify_ssl_lib_ok" ] && return verify_ssl_lib_ok=1 unset -v openssl_v3 # redirect std-err, ignore missing ssl/openssl.cnf val="$( "$EASYRSA_OPENSSL" version 2>/dev/null )" ssl_version="$val" # SSL lib name case "${val%% *}" in OpenSSL) ssl_lib=openssl # Honor EASYRSA_FORCE_SAFE_SSL if [ "$EASYRSA_FORCE_SAFE_SSL" ]; then ssl_cnf_type=safe-cnf else ssl_cnf_type=ssl-cnf fi ;; LibreSSL) ssl_lib=libressl ssl_cnf_type=safe-cnf ;; *) error_msg="$("$EASYRSA_OPENSSL" version 2>&1)" user_error "\ * OpenSSL must either exist in your PATH or be defined in your vars file. Invalid SSL output for 'version': $error_msg" esac # Set SSL version dependent $no_password option osslv_major="${val#* }" osslv_major="${osslv_major%%.*}" case "$osslv_major" in 1) no_password='-nodes' ;; 2) no_password='-nodes' ;; 3|4) case "$ssl_lib" in openssl) openssl_v3=1 no_password='-noenc' ;; libressl) no_password='-nodes' ;; *) die "Unexpected SSL library: $ssl_lib" esac ;; *) die "Unexpected SSL version: $osslv_major" esac # Message verbose "verify_ssl_lib; $ssl_version ($EASYRSA_OPENSSL)" } # => verify_ssl_lib() # Basic sanity-check of PKI init and complain if missing verify_pki_init() { help_note="\ Run easyrsa without commands for usage and command help." # Check for defined EASYRSA_PKI [ "$EASYRSA_PKI" ] || die "\ EASYRSA_PKI env-var undefined" # check that the pki dir exists [ -d "$EASYRSA_PKI" ] || user_error "\ EASYRSA_PKI does not exist (perhaps you need to run init-pki)? Expected to find the EASYRSA_PKI at: * $EASYRSA_PKI $help_note" # verify expected dirs present: for i in private reqs; do [ -d "$EASYRSA_PKI/$i" ] || user_error "\ Missing expected directory: $i (perhaps you need to run init-pki?) $help_note" done unset -v help_note } # => verify_pki_init() # Verify core CA files present verify_ca_init() { verify_ca_help_note="\ Run easyrsa without commands for usage and command help." # Verify expected files are present. # Allow files to be regular files (or symlinks), # but also pipes, for flexibility with ca.key for i in ca.crt private/ca.key index.txt serial; do if [ ! -f "$EASYRSA_PKI/$i" ] && \ [ ! -p "$EASYRSA_PKI/$i" ] then # Used by usage() and export-p12/p7 [ "$1" = "test" ] && return 1 user_error "\ Missing expected CA file: $i (perhaps you need to run build-ca?) $verify_ca_help_note" fi done # When operating in 'test' mode, return success. # test callers don't care about CA-specific dir structure [ "$1" = "test" ] && return 0 # verify expected CA-specific dirs: for i in issued certs_by_serial; do [ -d "$EASYRSA_PKI/$i" ] || user_error "\ Missing expected CA dir: $i (perhaps you need to run build-ca?) $verify_ca_help_note" done } # => verify_ca_init() # init-pki backend: init_pki() { # EasyRSA will NOT do 'rm -rf /' case "$EASYRSA_PKI" in .|..|./|../|.//*|..//*|/|//*|\\|?:|'') user_error "Invalid PKI: $EASYRSA_PKI" esac # If EASYRSA_PKI exists, confirm before deletion if [ -d "$EASYRSA_PKI" ]; then confirm "Confirm removal: " "yes" " WARNING!!! You are about to remove the EASYRSA_PKI at: * $EASYRSA_PKI and initialize a fresh PKI here." fi # # # shellcheck disable=SC2115 # Use "${var:?}" rm -rf "$EASYRSA_PKI" || \ die "init-pki hard reset failed." # new dirs: for i in issued private reqs; do mkdir -p "${EASYRSA_PKI}/${i}" || \ die "init_pki() - Failed to create '$i' directory." done # write pki/vars.example - no temp-file because no session write_legacy_file_v2 \ vars "$EASYRSA_PKI"/vars.example overwrite || \ warn "init_pki() - Failed to create vars.example" # User notice notice "\ 'init-pki' complete; you may now create a CA or requests. Your newly created PKI dir is: * $EASYRSA_PKI" # Select and show vars file unset -v EASYRSA_VARS_FILE select_vars information "\ Using Easy-RSA configuration: * ${EASYRSA_VARS_FILE:-undefined}" } # => init_pki() # Find support files from various sources # Declare in preferred order, first wins # beaten by command line. # If these files are not found here then they # will be built on-demand by the selected command. locate_support_files() { # Set required sources ssl_cnf_file='openssl-easyrsa.cnf' x509_types_dir='x509-types' # Find data-files for area in \ "$EASYRSA_PKI" \ "$EASYRSA" \ "$PWD" \ "${0%/*}" \ '/usr/local/share/easy-rsa' \ '/usr/share/easy-rsa' \ '/etc/easy-rsa' \ # EOL do # Find x509-types if [ -d "${area}/${x509_types_dir}" ]; then set_var EASYRSA_EXT_DIR "${area}/${x509_types_dir}" fi # Find openssl-easyrsa.cnf if [ -f "${area}/${ssl_cnf_file}" ]; then set_var EASYRSA_SSL_CONF "${area}/${ssl_cnf_file}" fi done verbose "\ locate_support_files; EASYRSA_EXT_DIR: ${EASYRSA_EXT_DIR:-built-in}" verbose "\ locate_support_files; EASYRSA_SSL_CONF: ${EASYRSA_SSL_CONF:-built-in}" } # => locate_support_files() # Disable terminal echo, if possible, otherwise warn hide_read_pass() { # 3040 - In POSIX sh, set option [name] is undefined # 3045 - In POSIX sh, some-command-with-flag is undefined # 3061 - In POSIX sh, read without a variable is undefined. # shellcheck disable=SC3040,SC3045,SC3061 if stty -echo 2>/dev/null; then prompt_restore=1 read -r "$@" stty echo elif (set +o echo 2>/dev/null); then prompt_restore=2 set +o echo read -r "$@" set -o echo elif (echo | read -r -s 2>/dev/null) ; then read -r -s "$@" else warn "\ Could not disable echo. Password will be shown on screen!" read -r "$@" fi prompt_restore=0 } # => hide_read_pass() # Get passphrase get_passphrase() { while :; do r="" printf '\n%s' "$2" hide_read_pass r if [ "${#r}" -lt 4 ]; then printf '\n%s\n' \ "Passphrase must be at least 4 characters!" else printf '%s' "$r" > "$1" || die "get_passphrase() malfunction" print return 0 fi done return 1 } # => get_passphrase() # build-ca backend: build_ca() { # Only allow if peer-fingerprint mode file does not exist if [ -f "$EASYRSA_PKI"/peer-fp.mode ]; then user_error "Cannot create CA in a peer-fingerprint PKI" fi cipher="-aes256" unset -v sub_ca date_stamp x509 error_info \ ca_password_via_cmdline while [ "$1" ]; do case "$1" in intca|subca) sub_ca=1 ;; nopass) [ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1 ;; rawca) # option --raw-ca demands user interaction # which forbids --batch [ "$EASYRSA_BATCH" ] && user_error \ "--raw-ca is incompatible with --batch" EASYRSA_RAW_CA=1 ;; *) user_error "Unknown command option: '$1'" esac shift done out_key="$EASYRSA_PKI/private/ca.key" # setup for an intermediate CA if [ "$sub_ca" ]; then # Generate a CSR out_file="$EASYRSA_PKI/reqs/ca.req" else # Generate a certificate out_file="$EASYRSA_PKI/ca.crt" date_stamp=1 x509=1 fi # RAW mode must take priority if [ "$EASYRSA_RAW_CA" ]; then unset -v EASYRSA_NO_PASS EASYRSA_PASSOUT EASYRSA_PASSIN verbose "CA password RAW method" else # If encrypted then create the CA key with AES256 cipher if [ "$EASYRSA_NO_PASS" ]; then unset -v cipher else unset -v no_password fi fi # Test for existing CA, and complain if already present if verify_ca_init test; then user_error "\ Unable to create a CA as you already seem to have one set up. If you intended to start a new CA, run init-pki first." fi # If a private key exists, an intermediate ca was created # but not signed. # Notify user and require a signed ca.crt or a init-pki: if [ -f "$out_key" ]; then user_error "\ A CA private key exists but no ca.crt is found in your PKI: * $EASYRSA_PKI Refusing to create a new CA as this would overwrite your current CA. To start a new CA, run init-pki first." fi # create necessary dirs: mkdir -p \ "${EASYRSA_PKI}"/certs_by_serial \ "${EASYRSA_PKI}"/revoked/certs_by_serial \ "${EASYRSA_PKI}"/revoked/private_by_serial || \ die "build_ca() - Failed to create PKI sub-directories." # create necessary files: err_msg="\ Unable to create necessary PKI files (permissions?)" printf "" > \ "$EASYRSA_PKI/index.txt" || die "$err_msg" printf '%s\n' "01" \ > "$EASYRSA_PKI/serial" || die "$err_msg" unset -v err_msg # Set ssl batch mode, as required [ "$EASYRSA_BATCH" ] && ssl_batch=1 # Default CA commonName if [ "$EASYRSA_REQ_CN" = ChangeMe ]; then if [ "$sub_ca" ]; then export EASYRSA_REQ_CN="Easy-RSA Sub-CA" else export EASYRSA_REQ_CN="Easy-RSA CA" fi fi # create local SSL cnf write_easyrsa_ssl_cnf_tmp # Assign cert and key temp files out_key_tmp="" easyrsa_mktemp out_key_tmp out_file_tmp="" easyrsa_mktemp out_file_tmp # Get passphrase from user if necessary if [ "$EASYRSA_RAW_CA" ] then # Passphrase will be provided confirm " Accept ? " yes "\ Raw CA mode =========== CA password must be input THREE times: 1. Set the password. 2. Confirm the password. 3. Use the password. (Create the Root CA)" elif [ "$EASYRSA_NO_PASS" ] then : # No passphrase required elif [ "$EASYRSA_PASSOUT" ] && [ "$EASYRSA_PASSIN" ] then # passphrase defined on command line # Both --passout and --passin # must be defined for a CA with a password ca_password_via_cmdline=1 else # Get passphrase p in_key_pass_tmp= easyrsa_mktemp in_key_pass_tmp get_passphrase "$in_key_pass_tmp" \ "Enter New CA Key Passphrase: " || \ die "get_passphrase in failed." # Confirm passphrase q out_key_pass_tmp= easyrsa_mktemp out_key_pass_tmp get_passphrase "$out_key_pass_tmp" \ "Confirm New CA Key Passphrase: " || \ die "get_passphrase out failed." # Validate passphrase p="$(cat "$in_key_pass_tmp")" || die "passphrase in malfunction" q="$(cat "$out_key_pass_tmp")" || die "passphrase out malfunction" if [ "$p" = "$q" ]; then # Clear possible conflicts and use temp-files not vars unset -v EASYRSA_PASSOUT EASYRSA_PASSIN p q r else user_error "Passphrase mismatch!" fi fi # Find or create x509 CA file if [ -f "$EASYRSA_EXT_DIR/ca" ]; then # Use the x509-types/ca file x509_type_file="$EASYRSA_EXT_DIR/ca" else # Use a temp file write_x509_type_tmp ca x509_type_file="$write_x509_file_tmp" fi # keyUsage critical if [ "$EASYRSA_KU_CRIT" ]; then add_critical_attrib keyUsage \ "$x509_type_file" x509_type_file || \ die "build-ca - add_critical_attrib kU" verbose "keyUsage critical OK" fi # basicConstraints critical if [ "$EASYRSA_BC_CRIT" ]; then add_critical_attrib basicConstraints \ "$x509_type_file" x509_type_file || \ die "build-ca - add_critical_attrib bC" verbose "basicConstraints critical OK" fi # Find or create x509 COMMON file if [ -f "$EASYRSA_EXT_DIR/COMMON" ]; then # Use the x509-types/COMMON file x509_COMMON_file="$EASYRSA_EXT_DIR/COMMON" else # Use a temp file write_x509_type_tmp COMMON x509_COMMON_file="$write_x509_file_tmp" fi # Check for insert-marker in ssl config file if ! grep -q '^#%CA_X509_TYPES_EXTRA_EXTS%' \ "$EASYRSA_SSL_CONF" then die "\ This openssl config file does not support X509-type 'ca'. * $EASYRSA_SSL_CONF Please update 'openssl-easyrsa.cnf' to the latest Easy-RSA release." fi # Assign awkscript to insert EASYRSA_EXTRA_EXTS # shellcheck disable=SC2016 # No expand '' - build_ca() awkscript='\ {if ( match($0, "^#%CA_X509_TYPES_EXTRA_EXTS%") ) { while ( getline<"/dev/stdin" ) {print} next } {print} }' # Assign tmp-file for config adjusted_ssl_cnf_tmp="" easyrsa_mktemp adjusted_ssl_cnf_tmp # Insert x509-types COMMON and 'ca' and EASYRSA_EXTRA_EXTS { # X509 files cat "$x509_type_file" "$x509_COMMON_file" # User extensions [ "$EASYRSA_EXTRA_EXTS" ] && \ print "$EASYRSA_EXTRA_EXTS" } | awk "$awkscript" "$EASYRSA_SSL_CONF" \ > "$adjusted_ssl_cnf_tmp" || \ die "Copying X509_TYPES to config file failed" verbose "Insert x509 and extensions OK" # Use this new SSL config for the rest of this function export EASYRSA_SSL_CONF="$adjusted_ssl_cnf_tmp" # Generate CA Key case "$EASYRSA_ALGO" in rsa) easyrsa_openssl genpkey \ -algorithm "$EASYRSA_ALGO" \ -pkeyopt rsa_keygen_bits:"$EASYRSA_ALGO_PARAMS" \ -out "$out_key_tmp" \ ${cipher:+ "$cipher"} \ ${EASYRSA_PASSOUT:+ -pass "$EASYRSA_PASSOUT"} \ ${out_key_pass_tmp:+ -pass file:"$out_key_pass_tmp"} \ || die "Failed create CA private key" ;; ec) easyrsa_openssl genpkey \ -paramfile "$EASYRSA_ALGO_PARAMS" \ -out "$out_key_tmp" \ ${cipher:+ "$cipher"} \ ${EASYRSA_PASSOUT:+ -pass "$EASYRSA_PASSOUT"} \ ${out_key_pass_tmp:+ -pass file:"$out_key_pass_tmp"} \ || die "Failed create CA private key" ;; ed) easyrsa_openssl genpkey \ -algorithm "$EASYRSA_CURVE" \ -out "$out_key_tmp" \ ${cipher:+ "$cipher"} \ ${EASYRSA_PASSOUT:+ -pass "$EASYRSA_PASSOUT"} \ ${out_key_pass_tmp:+ -pass file:"$out_key_pass_tmp"} \ || die "Failed create CA private key" ;; *) die "Unknown algorithm: $EASYRSA_ALGO" esac # verbose notice if [ "$EASYRSA_RAW_CA" ]; then verbose "CA key password created via RAW" elif [ "$ca_password_via_cmdline" ]; then verbose "CA key password created via command options" elif [ "$EASYRSA_NO_PASS" ]; then verbose "CA key has no password" else verbose "CA key pass created via temp-files" fi # Generate the CA keypair: easyrsa_openssl req -utf8 -new \ -key "$out_key_tmp" \ -out "$out_file_tmp" \ ${ssl_batch:+ -batch} \ ${x509:+ -x509} \ ${EASYRSA_TEXT_ON:+ -text} \ ${date_stamp:+ -days "$EASYRSA_CA_EXPIRE"} \ ${EASYRSA_DIGEST:+ -"$EASYRSA_DIGEST"} \ ${EASYRSA_NO_PASS:+ "$no_password"} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ ${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \ ${in_key_pass_tmp:+ -passin file:"$in_key_pass_tmp"} \ ${out_key_pass_tmp:+ -passout file:"$out_key_pass_tmp"} \ || die "Failed to build the CA keypair" # Move temp-files to target-files mv "$out_key_tmp" "$out_key" || mv_temp_error=1 mv "$out_file_tmp" "$out_file" || mv_temp_error=1 if [ "$mv_temp_error" ]; then rm -f "$out_key" "$out_file" die "Failed to move new CA files." fi # Success messages if [ "$sub_ca" ]; then notice "\ Your intermediate CA request is at: * $out_file and now must be sent to your parent CA for signing. Prior to signing operations, place your resulting Sub-CA cert at: * $EASYRSA_PKI/ca.crt" else notice "\ CA creation complete. Your new CA certificate is at: * $out_file Build-ca completed successfully." fi } # => build_ca() # Build self signed key pair self_sign() { # Only allow if CA does not exist if [ -f "$EASYRSA_PKI"/ca.crt ] || \ [ -f "$EASYRSA_PKI"/private/ca.key ] then user_error "Cannot create self-signed certificate in a CA." fi # Define x509 type case "$1" in server) selfsign_eku=serverAuth crt_type=self-signed-server ;; client) selfsign_eku=clientAuth crt_type=self-signed-client ;; *) die "self_sign: Unknown EKU '$1'" esac shift # pull $file_name_base [ "$1" ] || user_error "\ Error: didn't find a file base name as the first argument. Run easyrsa without commands for usage and command help." file_name_base="$1" shift # Prohibit --req-cn [ "$EASYRSA_REQ_CN" = ChangeMe ] || user_error "\ Option conflict --req-cn: * '$cmd' does not support setting an external commonName" # Enforce commonName export EASYRSA_REQ_CN="$file_name_base" # create local SSL cnf write_easyrsa_ssl_cnf_tmp # Refuse option as name case "$file_name_base" in nopass) user_error "Refusing '$file_name_base' as name." esac # function opts support while [ "$1" ]; do case "$1" in nopass) [ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1 ;; *) user_error "Unknown command option: '$1'" esac shift done # Assign output files key_out="$EASYRSA_PKI/private/${file_name_base}.key" crt_out="$EASYRSA_PKI/issued/${file_name_base}.crt" inline_out="$EASYRSA_PKI/inline/${file_name_base}.inline" # key file must NOT exist [ -f "$key_out" ] && user_error "\ Cannot self-sign this request for '$file_name_base'. Conflicting key exists at: * $key_out" # Certificate file must NOT exist [ -f "$crt_out" ] && user_error "\ Cannot self-sign this request for '$file_name_base'. Conflicting certificate exists at: * $crt_out" # Check algo and curve case "$EASYRSA_ALGO" in rsa|ec) # Silently use ec instead of rsa export EASYRSA_ALGO=ec # Selectively set --curve=secp384r1 set_var EASYRSA_CURVE secp384r1 # temp-file for params-file selfsign_params_file="" easyrsa_mktemp selfsign_params_file # params-file "$EASYRSA_OPENSSL" ecparam \ -name "$EASYRSA_CURVE" \ -out "$selfsign_params_file" || \ die "self_sign - params-file failed" newkey_params="$EASYRSA_ALGO":"$selfsign_params_file" ;; ed) # Selectively set --curve=ed25519 set_var EASYRSA_CURVE ed25519 newkey_params="$EASYRSA_CURVE" ;; *) user_error "Unrecognised algorithm: '$EASYRSA_ALGO'" esac verbose "Use ALGO:'$EASYRSA_ALGO' / CURVE:'$EASYRSA_CURVE'" # Assign tmp-file for config adjusted_ssl_cnf_tmp="" easyrsa_mktemp adjusted_ssl_cnf_tmp # Assign awkscript to insert EASYRSA_EXTRA_EXTS # shellcheck disable=SC2016 # No expand '' - build_ca() awkscript='\ {if ( match($0, "^#%CA_X509_TYPES_EXTRA_EXTS%") ) { while ( getline<"/dev/stdin" ) {print} next } {print} }' # create X509 'selfsign' temp-file, Only allowed internally write_x509_type_tmp selfsign batch x509_selfsign_file="$write_x509_file_tmp" # Insert x509-type 'selfsign' and EASYRSA_EXTRA_EXTS { cat "$x509_selfsign_file" [ "$EASYRSA_EXTRA_EXTS" ] && print "$EASYRSA_EXTRA_EXTS" } | awk "$awkscript" "$EASYRSA_SSL_CONF" \ > "$adjusted_ssl_cnf_tmp" || \ die "Copying X509_TYPES to config file failed" verbose "Insert x509 and extensions OK" # Use this new SSL config for the rest of this function export EASYRSA_SSL_CONF="$adjusted_ssl_cnf_tmp" # Create temp-files for output tmp_key_out="" easyrsa_mktemp tmp_key_out tmp_crt_out="" easyrsa_mktemp tmp_crt_out # create self-signed key pair easyrsa_openssl req -x509 -utf8 -sha256 -text \ -newkey "$newkey_params" \ -keyout "$tmp_key_out" \ -out "$tmp_crt_out" \ -subj "/CN=$file_name_base" \ ${EASYRSA_TEXT_ON:+ -text} \ ${EASYRSA_NO_PASS:+ "$no_password"} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ ${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \ ${EASYRSA_CERT_EXPIRE:+ -days "$EASYRSA_CERT_EXPIRE"} \ ${EASYRSA_START_DATE:+ -startdate "$EASYRSA_START_DATE"} \ ${EASYRSA_END_DATE:+ -enddate "$EASYRSA_END_DATE"} # Move temp-files to target-files mv "$tmp_key_out" "$key_out" || mv_temp_error=1 mv "$tmp_crt_out" "$crt_out" || mv_temp_error=1 if [ "$mv_temp_error" ]; then rm -f "$key_out" "$crt_out" die "Failed to move new key/cert files." fi # inline key/cert/fingerprint inline_file "$file_name_base" # Mark PKI as self-signed only pfp_data='peer-fp-mode - Please DO NOT DELETE this file' print "$pfp_data" > "$EASYRSA_PKI"/peer-fp.mode || \ die "Failed to setup peer-fingerprint mode." # User info notice "\ Self-signed '$EASYRSA_ALGO/$EASYRSA_CURVE' \ key and certificate created: * $key_out * $crt_out" } # => self_sign() # gen-dh backend: gen_dh() { out_file="$EASYRSA_PKI/dh.pem" # check to see if we already have a dh parameters file if [ -f "$out_file" ]; then if [ "$EASYRSA_BATCH" ]; then # if batch is enabled, die user_error "\ DH parameters file already exists at: $out_file" else # warn the user, allow to force overwrite confirm "Overwrite? " "yes" "\ DH parameters file already exists at: $out_file" fi fi # Create a temp file # otherwise user abort leaves an incomplete dh.pem tmp_dh_file="" easyrsa_mktemp tmp_dh_file # Generate dh.pem easyrsa_openssl dhparam -out "$tmp_dh_file" \ "$EASYRSA_KEY_SIZE" || die "Failed to generate DH params" # Validate dh.pem easyrsa_openssl dhparam -in "$tmp_dh_file" \ -check -noout || die "Failed to validate DH params" # Move temp-files to target-files mv "$tmp_dh_file" "$out_file" || mv_temp_error=1 if [ "$mv_temp_error" ]; then rm -f "$out_file" die "Failed to move temp DH file." fi notice " DH parameters of size $EASYRSA_KEY_SIZE created at: * $out_file" } # => gen_dh() # gen-req and key backend: gen_req() { # pull filename, use as default interactive CommonName [ "$1" ] || user_error "\ Error: gen-req must have a file-name-base as the first argument. Run easyrsa without commands for usage and commands." file_name_base="$1" shift # scrape off file-name-base # Set ssl batch mode as required [ "$EASYRSA_BATCH" ] && ssl_batch=1 # Set commonName if [ "$EASYRSA_REQ_CN" = ChangeMe ]; then export EASYRSA_REQ_CN="$file_name_base" fi # create local SSL cnf write_easyrsa_ssl_cnf_tmp # Output files key_out="$EASYRSA_PKI/private/${file_name_base}.key" req_out="$EASYRSA_PKI/reqs/${file_name_base}.req" # function opts support while [ "$1" ]; do case "$1" in text) text=1 ;; nopass) [ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1 ;; # batch flag supports internal caller build_full() batch) ssl_batch=1 ;; *) user_error "Unknown command option: '$1'" esac shift done # don't wipe out an existing request without confirmation [ -f "$req_out" ] && confirm "Confirm request overwrite: " "yes" "\ WARNING!!! An existing request file was found at * $req_out Continuing with key generation will replace this request." # don't wipe out an existing private key without confirmation [ -f "$key_out" ] && confirm "Confirm key overwrite: " "yes" "\ WARNING!!! An existing private key was found at * $key_out Continuing with key generation will replace this key." # When EASYRSA_EXTRA_EXTS is defined, # append it to openssl's [req] section: if [ "$EASYRSA_EXTRA_EXTS" ]; then # Check for insert-marker in ssl config file if ! grep -q '^#%EXTRA_EXTS%' "$EASYRSA_SSL_CONF" then die "\ This openssl config file does \ does not support EASYRSA_EXTRA_EXTS. * $EASYRSA_SSL_CONF Please update 'openssl-easyrsa.cnf' \ to the latest Easy-RSA release." fi # Setup & insert the extra ext data keyed by magic line extra_exts=" req_extensions = req_extra [ req_extra ] $EASYRSA_EXTRA_EXTS" # shellcheck disable=SC2016 # No expand '' - gen_req() awkscript=' {if ( match($0, "^#%EXTRA_EXTS%") ) { while ( getline<"/dev/stdin" ) {print} next } {print} }' # Assign temp-file for config adjusted_ssl_cnf_tmp="" easyrsa_mktemp adjusted_ssl_cnf_tmp # Insert $extra_exts @ %EXTRA_EXTS% in SSL Config print "$extra_exts" | \ awk "$awkscript" "$EASYRSA_SSL_CONF" \ > "$adjusted_ssl_cnf_tmp" || \ die "Writing SSL config to temp file failed" [ "${EASYRSA_SAN_CRIT}" ] && \ verbose "gen-req: SAN critical OK" # Use this SSL config for the rest of this function export EASYRSA_SSL_CONF="$adjusted_ssl_cnf_tmp" fi # Name temp files key_out_tmp="" easyrsa_mktemp key_out_tmp req_out_tmp="" easyrsa_mktemp req_out_tmp # Set algorithm options algo_opts="" case "$EASYRSA_ALGO" in rsa|ec) # Set elliptic curve parameters-file # or RSA bit-length algo_opts="$EASYRSA_ALGO:$EASYRSA_ALGO_PARAMS" ;; ed) # Set Edwards curve name algo_opts="$EASYRSA_CURVE" ;; *) die "gen_req - Unknown algorithm: $EASYRSA_ALGO" esac # Generate request if easyrsa_openssl req -utf8 -new -newkey "$algo_opts" \ -keyout "$key_out_tmp" \ -out "$req_out_tmp" \ ${ssl_batch:+ -batch} \ ${EASYRSA_TEXT_ON:+ -text} \ ${text:+ -text} \ ${EASYRSA_NO_PASS:+ "$no_password"} \ ${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} then : # ok else die "Failed to generate request" fi # Move temp-files to target-files mv "$key_out_tmp" "$key_out" || mv_temp_error=1 mv "$req_out_tmp" "$req_out" || mv_temp_error=1 if [ "$mv_temp_error" ]; then rm -f "$key_out" "$req_out" die "Failed to move temp key/req file." fi # Success messages notice "\ Private-Key and Public-Certificate-Request files created. Your files are: * req: $req_out * key: $key_out${do_build_full:+ $NL}" } # => gen_req() # common signing backend sign_req() { crt_type="$1" file_name_base="$2" # Verify $crt_type is valid case "$crt_type" in ca) # Inline file not required for signing a sub CA EASYRSA_DISABLE_INLINE=1 ;; server|serverClient|client|codeSigning|email|kdc) : # All known types ;; *) warn "\ Unrecognised x509-type: '$crt_type' In order to sign a custom X509 Type certificate, there must be a corresponding SSL configuration file in the 'x509-types' folder." esac # Check argument sanity: [ "$file_name_base" ] || user_error "\ Incorrect number of arguments provided to sign-req: expected 2, got $# (see command help for usage)" req_in="$EASYRSA_PKI/reqs/$file_name_base.req" crt_out="$EASYRSA_PKI/issued/$file_name_base.crt" shift 2 # create local SSL cnf write_easyrsa_ssl_cnf_tmp # Check optional subject force_subj= while [ "$1" ]; do case "$1" in nopass) warn "Ignoring option '$1'" ;; newsubj*) # verify force_subj opts are used correctly [ "$EASYRSA_NEW_SUBJECT" ] || user_error "\ To force a new certificate subject, global option --new-subject must also be specified." force_subj="$EASYRSA_NEW_SUBJECT" ;; preserve*) export EASYRSA_PRESERVE_DN=1 ;; *) user_error "Unknown option '$1'" esac shift done # verify force_subj opts are used correctly if [ "$EASYRSA_NEW_SUBJECT" ]; then [ "$force_subj" ] || user_error "\ To force a new certificate subject, command option 'newsubj' must also be specified." fi # Cert type must NOT be COMMON [ "$crt_type" = COMMON ] && user_error "\ Invalid certificate type: '$crt_type'" # Request file must exist [ -f "$req_in" ] || user_error "\ No request found for the input: '$file_name_base' Expected to find the request at: * $req_in" # Certificate file must NOT exist [ -f "$crt_out" ] && user_error "\ Cannot sign this request for '$file_name_base'. Conflicting certificate exists at: * $crt_out" # Confirm input is a cert req verify_file req "$req_in" || user_error "\ The certificate request file is not in a valid X509 format: * $req_in" # Randomize Serial number if [ "$EASYRSA_RAND_SN" != no ]; then serial="" check_serial="" unset -v serial_is_unique for i in 1 2 3 4 5; do easyrsa_random 16 serial # Require 128bit serial number [ "$serial" = "${serial#00}" ] || continue # Check for duplicate serial in CA db if check_serial_unique "$serial" batch; then serial_is_unique=1 break fi done # Check for unique_serial [ "$serial_is_unique" ] || die "\ sign_req - Randomize Serial number failed: $check_serial" # Print random $serial to pki/serial file # for use by SSL config print "$serial" > "$EASYRSA_PKI/serial" || \ die "sign_req - write serial to file" unset -v serial check_serial serial_is_unique fi # When EASYRSA_CP_EXT is defined, # adjust openssl's [default_ca] section: if [ "$EASYRSA_CP_EXT" ]; then # Check for insert-marker in ssl config file if ! grep -q '^#%COPY_EXTS%' "$EASYRSA_SSL_CONF" then die "\ This openssl config file does \ not support option '--copy-ext'. * $EASYRSA_SSL_CONF Please update 'openssl-easyrsa.cnf' \ to the latest Easy-RSA release." fi # Setup & insert the copy_extensions data # keyed by a magic line copy_exts="copy_extensions = copy" # shellcheck disable=SC2016 # No expand '' - sign_req() awkscript=' {if ( match($0, "^#%COPY_EXTS%") ) { while ( getline<"/dev/stdin" ) {print} next } {print} }' # Assign temp-file for config adjusted_ssl_cnf_tmp="" easyrsa_mktemp adjusted_ssl_cnf_tmp print "$copy_exts" | \ awk "$awkscript" "$EASYRSA_SSL_CONF" \ > "$adjusted_ssl_cnf_tmp" || die "\ Writing 'copy_exts' to SSL config temp-file failed" # Use this SSL config for the rest of this function export EASYRSA_SSL_CONF="$adjusted_ssl_cnf_tmp" verbose "Using '$copy_exts'" verbose "EASYRSA_SSL_CONF = $EASYRSA_SSL_CONF" fi # Find or create x509-type file if [ -f "$EASYRSA_EXT_DIR/$crt_type" ]; then # Use the x509-types/$crt_type file x509_type_file="$EASYRSA_EXT_DIR/$crt_type" else # Use a temp file write_x509_type_tmp "$crt_type" x509_type_file="$write_x509_file_tmp" fi # keyUsage critical confirm_ku_crit= if [ "$EASYRSA_KU_CRIT" ]; then add_critical_attrib keyUsage \ "$x509_type_file" x509_type_file || \ die "sign-req - add_critical_attrib kU" confirm_ku_crit=" keyUsage: 'critical'${NL}" verbose "keyUsage critical OK" fi # basicConstraints critical confirm_bc_crit= if [ "$EASYRSA_BC_CRIT" ]; then add_critical_attrib basicConstraints \ "$x509_type_file" x509_type_file || \ die "sign-req - add_critical_attrib bC" confirm_bc_crit=" basicConstraints: 'critical'${NL}" verbose "basicConstraints critical OK" fi # extendedKeyUsage critical confirm_eku_crit= if [ "$EASYRSA_EKU_CRIT" ]; then add_critical_attrib extendedKeyUsage \ "$x509_type_file" x509_type_file || \ die "sign-req - add_critical_attrib eKU" confirm_eku_crit=" extendedKeyUsage: 'critical'${NL}" verbose "extendedKeyUsage critical OK" fi # Find or create x509 COMMON file if [ -f "$EASYRSA_EXT_DIR/COMMON" ]; then # Use the x509-types/COMMON file x509_COMMON_file="$EASYRSA_EXT_DIR/COMMON" else # Use a temp file write_x509_type_tmp COMMON x509_COMMON_file="$write_x509_file_tmp" fi # Support a dynamic CA path length when present: unset -v basicConstraints confirm_bc_len if [ "$crt_type" = "ca" ] && [ "$EASYRSA_SUBCA_LEN" ] then # Print the last occurrence of basicConstraints in # x509-types/ca # If basicConstraints is not defined then bail # shellcheck disable=SC2016 # No expand '' - sign_req() awkscript='\ /^[[:blank:]]*basicConstraints[[:blank:]]*=/ { bC=$0 } END { if (length(bC) == 0 ) exit 1; print bC }' basicConstraints="$( awk "$awkscript" "$x509_type_file" )" || die "\ basicConstraints is not defined, cannot use 'pathlen'" confirm_pathlen=" Path length: '$EASYRSA_SUBCA_LEN'${NL}" verbose "Using basicConstraints pathlen" fi # Deprecated Netscape extension support case "$EASYRSA_NS_SUPPORT" in [yY][eE][sS]) confirm "Confirm use of Netscape extensions: " yes \ "WARNING: Netscape extensions are DEPRECATED!" # Netscape extension case "$crt_type" in serverClient) ns_cert_type="nsCertType = serverClient" ;; server) ns_cert_type="nsCertType = server" ;; client) ns_cert_type="nsCertType = client" ;; ca) ns_cert_type="nsCertType = sslCA" ;; *) ns_cert_type="nsCertType = $crt_type" esac verbose "Using $ns_cert_type" ;; *) # ok No NS support required unset -v ns_cert_type esac # Get request CN # EASYRSA_REQ_CN MUST always be set to the CSR CN # or use --new-subect EASYRSA_REQ_CN="$( "$EASYRSA_OPENSSL" req -utf8 -in "$req_in" -noout \ -subject -nameopt multiline | grep 'commonName' )" || warn "sign-req - EASYRSA_REQ_CN FAILED" EASYRSA_REQ_CN="${EASYRSA_REQ_CN##*= }" # Add auto SAN, if EASYRSA_AUTO_SAN is enabled if [ -z "$EASYRSA_SAN" ] && [ "$EASYRSA_AUTO_SAN" ]; then # Choose DNS:san or IP:san if print "$EASYRSA_REQ_CN" | grep -q \ '^[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+$' then EASYRSA_SAN="IP:${EASYRSA_REQ_CN}" else EASYRSA_SAN="DNS:${EASYRSA_REQ_CN}" fi # Add auto SAN to EASYRSA_EXTRA_EXTS EASYRSA_EXTRA_EXTS="\ $EASYRSA_EXTRA_EXTS subjectAltName = ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}" verbose "Auto SAN: ${EASYRSA_SAN}" fi # confirm SAN critical confirm_san_crit= if [ "${EASYRSA_SAN_CRIT}" ]; then confirm_san_crit=" subjectAltName: 'critical'${NL}" verbose "SAN critical OK" fi # Generate the extensions file for this cert: ext_tmp="" easyrsa_mktemp ext_tmp # Begin output redirect { # Append $cert-type extensions cat "$x509_COMMON_file" "$x509_type_file" # Support a dynamic CA path length when present: if [ "$basicConstraints" ]; then print "$basicConstraints, pathlen:$EASYRSA_SUBCA_LEN" fi # Deprecated Netscape extension support if [ "$ns_cert_type" ]; then print "$ns_cert_type" print "nsComment = \"$EASYRSA_NS_COMMENT\"" fi # Add user supplied extra extensions # and/or SAN extension if [ "$EASYRSA_EXTRA_EXTS" ]; then print "$EASYRSA_EXTRA_EXTS" fi } > "$ext_tmp" || die "\ Failed to create temp extension file (bad permissions?) at: * $ext_tmp" verbose "Generated extensions file OK" # Set confirm CN confirm_CN=" Requested CN: '$EASYRSA_REQ_CN'" # Set confirm type confirm_type=" Requested type: '$crt_type'" # Set confirm valid_period message if [ "$EASYRSA_END_DATE" ]; then confirm_period=" Valid until: '$EASYRSA_END_DATE'" else confirm_period=" Valid for: '$EASYRSA_CERT_EXPIRE' days" fi # Set confirm DN if [ "$force_subj" ]; then confirm_dn="${NL}* Forced Subject: '$force_subj'${NL}" else confirm_dn="${NL}$(display_dn req "$req_in")" || \ die "sign-req: display_dn" fi # Set confirm SAN # SAN from .req if [ "$EASYRSA_CP_EXT" ]; then # capture complete CSR req_text="$( "$EASYRSA_OPENSSL" req -utf8 -in "$req_in" -noout -text )" || die "sign-req: openssl: req_text" # Check CSR for any requested SAN if echo "$req_text" | \ grep -q 'X509v3 Subject Alternative Name' then # extract requested SAN # 'grep -A' may not be strictly POSIX, die on error req_x509_san="$( echo "$req_text" | \ grep -A 1 'X509v3 Subject Alternative Name' )" || die "sign-req: req_x509_san: grep -A 1 (POSIX)" else # No requested SAN req_x509_san= fi fi # Set confirm details confirm_critical_attribs=" ${confirm_bc_crit}${confirm_ku_crit}\ ${confirm_eku_crit}${confirm_san_crit}" confirm_details="\ ${confirm_CN} ${confirm_type}${confirm_pathlen} ${confirm_period} ${confirm_critical_attribs}${confirm_dn}" # --san takes priority over req SAN and --copy-ext if [ "$EASYRSA_SAN" ]; then confirm_san="\ X509v3 Subject Alternative Name: ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}" else confirm_san="$req_x509_san" fi # Set confirm SAN if [ "$EASYRSA_SAN" ] || [ "$req_x509_san" ]; then confirm_details="$confirm_details${NL}${NL}$confirm_san" fi # Display the request subject in an easy-to-read format # Confirm the user wishes to sign this request # The foreign_request confirmation is not required # for build_full: if [ "$local_request" ]; then unset -v foreign_request else foreign_request="\ Please check over the details shown below for accuracy. \ Note that this request has not been cryptographically verified. Please be sure \ it came from a trusted source or that you have verified the request checksum \ with the sender.$NL" fi # Request FINAL user confirmation confirm "Confirm requested details: " "yes" "\ ${foreign_request}You are about to sign the following certificate: $confirm_details" # => confirm end # Assign temp cert file crt_out_tmp="" easyrsa_mktemp crt_out_tmp # sign request easyrsa_openssl ca -utf8 -batch \ -in "$req_in" -out "$crt_out_tmp" \ -extfile "$ext_tmp" \ ${EASYRSA_PRESERVE_DN:+ -preserveDN} \ ${force_subj:+ -subj "$force_subj"} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ ${EASYRSA_TEXT_OFF:+ -notext} \ ${EASYRSA_CERT_EXPIRE:+ -days "$EASYRSA_CERT_EXPIRE"} \ ${EASYRSA_START_DATE:+ -startdate "$EASYRSA_START_DATE"} \ ${EASYRSA_END_DATE:+ -enddate "$EASYRSA_END_DATE"} \ || die "\ Signing failed (openssl output above may have more detail)" verbose "signed cert '$file_name_base' OK" # Move temp-files to target-files mv "$crt_out_tmp" "$crt_out" || mv_temp_error=1 if [ "$mv_temp_error" ]; then rm -f "$crt_out" die "Failed to move temp certificate file." fi # inline file inline_file "$file_name_base" # Success messages notice "\ Certificate created at: * $crt_out" } # => sign_req() # Add 'critical' attribute to X509-type file add_critical_attrib() { case "$1" in basicConstraints|keyUsage|extendedKeyUsage) : ;; # ok *) die "$fn_name - usage: '$1'" esac [ -f "$2" ] || die "$fn_name - missing input file" [ "$3" ] || die "$fn_name - missing variable" crit_tmp= easyrsa_mktemp crit_tmp # Insert 'critical,' attrib, ONLY if NOT present srch="${1}[[:blank:]]*=[[:blank:]]*critical" repl="${1}[[:blank:]]*=[[:blank:]]*" with="${1} = critical," sed /"$srch"/!s/"$repl"/"$with"/g \ "$2" > "$crit_tmp" || return 1 # Use the new tmp-file:$crit_tmp with critical attribute force_set_var "$3" "$crit_tmp" || return 1 verbose "add_critical_attrib; ${1}: force_set_var: ${3} = ${crit_tmp}" unset -v srch repl with crit_tmp } # => add_critical_attrib() # Check serial in db check_serial_unique() { [ "$1" ] || user_error "Serial number required!" case "$1" in (*[!1234567890abcdef]*) user_error "Invalid serial number: '$1'" esac # Check for openssl -status of serial number # Always errors out - Do not capture error check_serial="$( "$EASYRSA_OPENSSL" ca -status "$1" 2>&1 )" || : # Check for duplicate serial in CA db case "$check_serial" in (*"not present in db"*) unique_serial_true=1 verbose "check_serial_unique; unique_serial=true" ;; # This is caused by file:index.txt.attr being set to # 'unique_subject = yes' AND a duplicate cert subject (*"Error creating name index"*) die "check_serial_unique(): Duplicate Subject conflict" ;; *) unique_serial_true= verbose "check_serial_unique; unique_serial=false" esac # In batch mode return result only if [ "$2" = batch ] || [ "$EASYRSA_BATCH" ]; then if [ "$unique_serial_true" ]; then return 0 else return 1 fi fi # Otherwise, show result to user # and do not return any error code print " check_serial_status RESULT: ======================================== $check_serial ======================================== COMPLETE" } # => check_serial_unique() # common build backend # used to generate+sign in 1 step build_full() { # pull filename base: [ "$2" ] || user_error "\ Error: didn't find a file base name as the first argument. Run easyrsa without commands for usage and commands." crt_type="$1" name="$2" shift 2 req_out="$EASYRSA_PKI/reqs/$name.req" key_out="$EASYRSA_PKI/private/$name.key" crt_out="$EASYRSA_PKI/issued/$name.crt" # function opts support while [ "$1" ]; do case "$1" in nopass) [ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1 ;; *) user_error "Unknown command option: '$1'" esac shift done # abort on existing req/key/crt files err_exists="\ file already exists. Aborting build to avoid overwriting this file. If you wish to continue, please use a different name. Conflicting file found at: *" [ -f "$req_out" ] && \ user_error "Request $err_exists $req_out" [ -f "$key_out" ] && \ user_error "Key $err_exists $key_out" [ -f "$crt_out" ] && \ user_error "Certificate $err_exists $crt_out" unset -v err_exists # create request verbose "build_full: BEGIN gen_req" fn_name="$fn_name; gen_req" gen_req "$name" batch fn_name="${fn_name%; gen_req}" verbose "build_full: END gen_req" # Set to modify sign-req confirmation message local_request=1 # Recreate temp-session and # drop edits to SSL Conf file remove_secure_session secure_session locate_support_files write_global_safe_ssl_cnf_tmp # Require --copy-ext export EASYRSA_CP_EXT=1 # Sign it verbose "build_full: BEGIN sign_req" fn_name="$fn_name; sign_req" error_build_full_cleanup=1 if sign_req "$crt_type" "$name"; then unset -v error_build_full_cleanup do_build_full else die "\ Failed to sign '$name' - \ See error messages above for details." fi fn_name="${fn_name%; sign_req}" verbose "build_full: END sign_req" } # => build_full() # Generate inline file V2 inline_file() { # Allow complete disable [ "$EASYRSA_DISABLE_INLINE" ] && return # definitive source [ "$1" ] || die "inline_file - Missing file_name_base" # make inline dirs mkdir -p "$EASYRSA_PKI"/inline/private || \ die "inline_file() - failed to create 'inline' directory." # Source files crt_source="${EASYRSA_PKI}/issued/${1}.crt" key_source="${EASYRSA_PKI}/private/${1}.key" ca_source="$EASYRSA_PKI"/ca.crt tls_source="$EASYRSA_PKI"/private/easyrsa-tls.key old_tls_key_file="$EASYRSA_PKI"/easyrsa-keepsafe-tls.key dh_params_source="$EASYRSA_PKI"/dh.pem # output inline_out="${EASYRSA_PKI}/inline/${1}.inline" readme="$EASYRSA_PKI"/inline/private/README.inline.private if [ ! -f "$readme" ]; then print "\ # Inline files in the 'private' directory contain security keys # which MUST ONLY be transmitted over a SECURE connection. # eg. 'https' or 'scp'." > "$readme" || \ warn "inline_file - Failed to create README" fi unset -v readme # flags inline_incomplete= inline_private= # Generate Inline data # Certificate if [ -f "$crt_source" ]; then crt_data="\ $(cat "$crt_source") " # Calculate decimal value for serial number # because openvpn uses decimal serial # for '--crl-verify /path/to/dir dir' if which bc 1>/dev/null 2>&1; then inline_crt_serial= ssl_cert_serial "$crt_source" inline_crt_serial || \ die "inline_file - ssl_cert_serial" crt_serial_dec="$( echo "ibase=16; $inline_crt_serial" | bc )" || die "inline_file - HEX to DEC failed" else crt_serial_dec="Unavailable" fi # Generate fingerprint crt_fingerprint="$( "$EASYRSA_OPENSSL" x509 -in "$crt_source" \ -noout -sha256 -fingerprint )" || die "inline_file - Failed -fingerprint" # strip prefix crt_fingerprint="${crt_fingerprint#*=}" # Certificate type inline_crt_type= ssl_cert_x509v3_eku "$crt_source" inline_crt_type || \ die "inline_file: Failed to set inline_crt_type" # commonName inline_crt_CN="$( display_dn x509 "$crt_source" | grep 'commonName' )" || die "inline_file: Failed to set inline_crt_CN" # strip prefix inline_crt_CN="${inline_crt_CN#*= }" else inline_incomplete=1 crt_data="\ # When you recieve your signed certificate place it in the # 'pki/issued' sub-dir of your PKI and use command 'inline' # to rebuild this inline file with your certificate. # # * Paste your user certificate here * # " crt_fingerprint=unknown inline_crt_type=unknown inline_crt_CN=unknown fi # Private key if [ -f "$key_source" ]; then inline_private=1 key_data="\ $(cat "$key_source") " else inline_incomplete=1 key_data="\ # When you recieve your key place it in the # 'pki/private' sub-dir of your PKI and use command 'inline' # to rebuild this inline file with your key. # # * Paste your private key here * # " fi # CA certificate ca_data= case "$inline_crt_type" in # OpenVPN 2.5 or older does not support --peer-fingerprint # Easy-RSA does not support old OpenVPN because it is not # possible to determine which self-siged-server cert to use. self-signed-client) ca_data="\ # Since OpenVPN 2.6 --peer-fingerprint --client does not require a CA # OpenVPN 2.5 or older requires the self-siged-server certificate # placed here." ;; self-signed-server) ca_data="\ # CA ceriticate not required for --peer-fingerprint server" ;; *) if [ -f "$ca_source" ]; then ca_data="\ $(cat "$ca_source") " else inline_incomplete=1 ca_data="\ # When you recieve your CA certificate place it in the # 'pki' sub-dir of your PKI and use command 'inline' # to rebuild this inline file with your CA certificate. # # * Paste your CA certificate here * # " fi esac # Diffie-Hellman parameters file dh_params_data= case "$inline_crt_type" in server|serverClient) # Collect DH data if [ "$EASYRSA_ALGO" = rsa ]; then if [ -f "$dh_params_source" ]; then dh_params_data="${NL} $(cat "$dh_params_source") " else inline_incomplete=1 dh_params_data="${NL} # # * Paste your Diffie-Hellman parameters file here * # " fi else # ok, not RSA dh_params_data="${NL} # Diffie-Hellman parameters file not required dh none" fi ;; self-signed-server) # ok, self-sign dh_params_data="${NL} # Diffie-Hellman parameters file not required dh none" ;; *) : # ok, not server esac # Append DH data to CA data ca_data="${ca_data}${dh_params_data}" # TLS KEY - Set TLS auth|crypt key inline label tls_key_data= if [ -f "$tls_source" ]; then tls_key_data="$(cat "$tls_source")" case "$tls_key_data" in *'TLS-AUTH'*) tls_key_label=tls-auth ;; *'TLS-CRYPT'*) tls_key_label=tls-crypt ;; *) tls_key_label= esac fi # Do NOT add TLS key if OLD TLS key exists # because this PSK has already been shared. if [ -f "$old_tls_key_file" ]; then tls_data="\ # Add the existing TLS AUTH/CRYPT-V1 Key here: # <${tls_key_label}> # * Paste The existing pre-shared TLS key here * # " # Add --key-direction for TLS-AUTH [ "$tls_key_label" = tls-auth ] && \ tls_data="$tls_data # # Add the required 'key-direction 0|1' here: # key-direction 1" unset -v tls_key_data tls_key_label else # Add standard TLS key details if [ -f "$tls_source" ]; then inline_private=1 if [ "$tls_key_label" ]; then tls_data="\ <${tls_key_label}> ${tls_key_data} " else inline_incomplete=1 tls_data="# Easy-RSA TLS Key not recognised!" fi else inline_incomplete=1 tls_data="# Easy-RSA TLS Key not found!" fi fi # Only support inline TLS keys for OpenVPN server/client use case "$inline_crt_type" in server|self-signed-server) key_direction="key-direction 0" ;; client|self-signed-client) key_direction="key-direction 1" ;; *) verbose "inline: Unsupported cert-type: $inline_crt_type" tls_key_label= key_direction= tls_data="# No TLS Key support for cert-type: $inline_crt_type" esac # Add --key-direction for TLS-AUTH if [ "$tls_key_label" = tls-auth ]; then tls_data="${tls_data}${NL}${NL}${key_direction}" fi # If inline file has keys then redirect to 'private' dir [ "$inline_private" ] && \ inline_out="${EASYRSA_PKI}/inline/private/${1}.inline" # Print data print "\ # Easy-RSA Inline file # Certificate type: $inline_crt_type # commonName: $inline_crt_CN # SHA256 fingerprint: # $crt_fingerprint # Decimal serial number: $crt_serial_dec $crt_data $key_data $ca_data $tls_data " > "$inline_out" # user info if [ "$inline_incomplete" ]; then warn "\ INCOMPLETE Inline file created: * $inline_out" else notice "\ Inline file created: * $inline_out" fi } # => inline_file() # revoke backend revoke() { # Set cert directory (IE. type) to revoke cert_dir="$1" shift # pull filename base: [ "$1" ] || user_error "\ Error: didn't find a file base name as the first argument. Run easyrsa without commands for usage and command help." # Assign file_name_base and dust off! file_name_base="$1" shift # create local SSL cnf write_easyrsa_ssl_cnf_tmp in_dir="$EASYRSA_PKI" key_in="$in_dir/private/${file_name_base}.key" req_in="$in_dir/reqs/${file_name_base}.req" inline_pub="$in_dir/inline/${file_name_base}.inline" inline_pri="$in_dir/inline/private/${file_name_base}.inline" # input cert for revocation: issued, expired or renewed crt_in="${in_dir}/${cert_dir}/${file_name_base}.crt" # Assign possible "crl_reason" if [ "$1" ]; then crl_reason="$1" shift case "$crl_reason" in us|uns*) crl_reason=unspecified ;; kc|key*) crl_reason=keyCompromise ;; cc|[Cc][Aa]*) crl_reason=CACompromise ;; ac|aff*) crl_reason=affiliationChanged ;; ss|sup*) crl_reason=superseded ;; co|ces*) crl_reason=cessationOfOperation ;; ch|cer*) crl_reason=certificateHold ;; *) user_error "\ Unexpected reason: '$crl_reason'. See 'help revoke' for valid reasons." esac else unset -v crl_reason fi # Enforce syntax if [ "$1" ]; then user_error "Syntax error: $1" fi # referenced cert must exist: [ -f "$crt_in" ] || user_error "\ Unable to revoke as no certificate was found. Certificate was expected at: * $crt_in" # Set conflicting cert files: issued/ VS expired/ renewed/ crt_iss="$EASYRSA_PKI/issued/${file_name_base}.crt" crt_exp="$EASYRSA_PKI/expired/${file_name_base}.crt" crt_ren="$EASYRSA_PKI/renewed/issued/${file_name_base}.crt" # If the command is 'revoke' then # if an issued cert exists then check that the others do not # To ensure that 'revoke' is not called accidentally if [ "$cmd" = revoke ] && [ -f "$crt_iss" ]; then if [ -f "$crt_exp" ] || [ -f "$crt_ren" ]; then msg= [ -f "$crt_exp" ] && msg="${NL}[Expired] $crt_exp" [ -f "$crt_ren" ] && msg="${msg}${NL}[Renewed] $crt_ren" # Force user to select revoke type [ "$EASYRSA_BATCH" ] || user_error "\ Conflicting file(s) found:${msg} Please select which type of 'revoke' command is required: * 'revoke-issued' will revoke a current certificate. * 'revoke-expired' will revoke an old cert, which has been expired. * 'revoke-renewed' will revoke an old cert, which has been renewed. Please see 'help revoke' for full details." fi fi # Clear variables no longer in use unset -v crt_iss crt_exp crt_ren # Verify certificate verify_file x509 "$crt_in" || user_error "\ Unable to revoke as the input-file is not a valid certificate. Certificate was expected at: * $crt_in" # Forbid self-signed cert from being expired/renewed/revoked if forbid_selfsign "$crt_in"; then user_error "Cannot $cmd a self-signed certificate." fi # Verify request if [ -f "$req_in" ]; then verify_file req "$req_in" || user_error "\ Unable to verify request. The file is not a valid request. Request was expected at: * $req_in" fi # get the serial number of the certificate cert_serial= ssl_cert_serial "$crt_in" cert_serial || \ die "$cmd: Failed to get cert serial number!" # Set out_dir out_dir="$EASYRSA_PKI/revoked" crt_out="$out_dir/certs_by_serial/${cert_serial}.crt" key_out="$out_dir/private_by_serial/${cert_serial}.key" req_out="$out_dir/reqs_by_serial/${cert_serial}.req" # NEVER over-write a revoked cert, serial must be unique deny_msg="\ Cannot revoke this certificate, a conflicting file exists. *" [ -f "$crt_out" ] && \ user_error "$deny_msg certificate: $crt_out" [ -f "$key_out" ] && \ user_error "$deny_msg private key: $key_out" [ -f "$req_out" ] && \ user_error "$deny_msg request : $req_out" unset -v deny_msg # Check for key and request files unset -v if_exist_key_in if_exist_req_in if [ "$revoke_move_req_and_key" ] && [ -f "$key_in" ]; then if_exist_key_in=" * $key_in" fi if [ "$revoke_move_req_and_key" ] && [ -f "$req_in" ]; then if_exist_req_in=" * $req_in" fi # Set confirm DN and serial confirm_dn="$(display_dn x509 "$crt_in")" || \ die "revoke: display_dn" confirm_sn=" serial-number = $cert_serial" # confirm operation by displaying DN: warn "\ This process is destructive! These files will be MOVED to the 'revoked' sub-directory: * $crt_in${if_exist_key_in}${if_exist_req_in} These files will be DELETED: All PKCS files for commonName: $file_name_base The inline credentials files: * $inline_pub * $inline_pri" # now confirm confirm " Continue with revocation: " "yes" " Please confirm that you wish to revoke the certificate with the following subject: $confirm_dn $confirm_sn Reason: ${crl_reason:-None given}" # Revoke certificate easyrsa_openssl ca -utf8 -revoke "$crt_in" \ ${crl_reason:+ -crl_reason "$crl_reason"} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ || die "\ Failed to revoke certificate: revocation command failed." # move revoked files # so we can reissue certificates with the same name revoke_move notice "\ * IMPORTANT * Revocation was successful. You must run 'gen-crl' and upload a new CRL to your infrastructure in order to prevent the revoked certificate from being accepted." } # => revoke() # revoke_move # moves revoked certificates to the 'revoked' folder # allows reissuing certificates with the same name revoke_move() { mkdir -p \ "$EASYRSA_PKI"/revoked/reqs_by_serial \ "$EASYRSA_PKI"/revoked/certs_by_serial \ "$EASYRSA_PKI"/revoked/private_by_serial || \ die "revoke_move() - Failed to create 'revoked' directory." # only move the req when revoking an issued cert # and if we have the req if [ "$revoke_move_req_and_key" ] && [ -f "$req_in" ]; then mv "$req_in" "$req_out" || warn "Failed to move: $req_in" fi # move crt to revoked folder mv "$crt_in" "$crt_out" || die "Failed to move: $crt_in" # only move the key when revoking an issued cert # and if we have the key if [ "$revoke_move_req_and_key" ] && [ -f "$key_in" ]; then mv "$key_in" "$key_out" || warn "Failed to move: $key_in" fi # remove any pkcs files for pkcs in p12 p7b p8 p1; do if [ -f "$in_dir/issued/$file_name_base.$pkcs" ]; then # issued rm "$in_dir/issued/$file_name_base.$pkcs" || warn "Failed to remove: $file_name_base.$pkcs" fi if [ -f "$in_dir/private/$file_name_base.$pkcs" ]; then # private rm "$in_dir/private/$file_name_base.$pkcs" || warn "Failed to remove: $file_name_base.$pkcs" fi done # remove inline files rm -f "$inline_pub" "$inline_pri" || warn \ "revoke_move - Error trying to remove inline files." } # => revoke_move() # Move expired cert out of pki/issued to pki/expired # to allow renewal expire_cert() { # pull filename base: [ "$1" ] || user_error "\ Error: didn't find a file base name as the first argument. Run easyrsa without commands for usage and command help." # Assign file_name_base and dust off! file_name_base="$1" shift # input in_dir="$EASYRSA_PKI/issued" crt_in="$in_dir/$file_name_base.crt" # output out_dir="$EASYRSA_PKI/expired" crt_out="$out_dir/$file_name_base.crt" # make output folder mkdir -p "$EASYRSA_PKI"/expired || \ die "expire_cert() - Failed to create 'expired' directory." # Do not over write existing cert if [ -f "$crt_out" ]; then user_error "\ Cannot expire this certificate, a conflicting file exists: * certificate: $crt_out Use command 'revoke-exired' to revoke this certificate." fi # deprecate ALL options while [ "$1" ]; do case "$1" in nopass) warn "\ Option 'nopass' is not supported by command '$cmd'." ;; *) user_error "Unknown option: $1" esac shift done # Verify certificate if [ -f "$crt_in" ]; then verify_file x509 "$crt_in" || user_error "\ Input file is not a valid certificate: * $crt_in" else user_error "\ Missing certificate file: * $crt_in" fi # Forbid self-signed cert from being expired/renewed/revoked if forbid_selfsign "$crt_in"; then user_error "Cannot $cmd a self-signed certificate." fi # get the serial number of the certificate cert_serial= ssl_cert_serial "$crt_in" cert_serial || \ die "$cmd: Failed to get cert serial number!" # Set confirm DN and serial confirm_dn="$(display_dn x509 "$crt_in")" || \ die "expire: display_dn" confirm_sn=" serial-number = $cert_serial" # date of expiry # Equal to: easyrsa-tools.lib - ssl_cert_not_after_date() # This is left as a reminder that easyrsa does not handle # dates well and they should be avoided, at all cost. # This is for confirmation purposes ONLY. crt_expire="$( "$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -enddate )" || die "expire: enddate" confirm_ex=" notAfter date = ${crt_expire#*=}" # confirm confirm " Continue with expiry: " yes " Please confirm you wish to expire the certificate with the following subject: $confirm_dn $confirm_sn $confirm_ex" # => End confirm # move cert to expired dir mv "$crt_in" "$crt_out" || die "failed to move expired: $crt_in" # User message notice "\ Certificate has been successfully moved to the expired directory. * $crt_out This certificate is still valid, until it expires. It can be revoked with command 'revoke-expired'. It is now possible to sign a new certificate for '$file_name_base'" } # => expire_cert() # Forbid an EasyRSA self-signed cert from being expired/renewed/revoked # by a CA that has nothing to do with the cert # EasyRSA built self signed certs are forced to always specify # the issuer cert serial, using built-in X509-type file 'selfsign'. # All other certs pass this test, even without a signing serial. forbid_selfsign() { # cert temp-file forbid_ss_tmp= easyrsa_mktemp forbid_ss_tmp forbid_serial= ssl_cert_serial "$1" forbid_serial || \ die "$fn_name - ssl_cert_serial" # SSL text "$EASYRSA_OPENSSL" x509 -in "$1" -noout -text > "$forbid_ss_tmp" || \ die "$fn_name - ssl text" # Extract signing cert serial signing_serial="$( grep "^[[:blank:]]*serial:.*$" "$forbid_ss_tmp" | \ sed -e 's/^[[:blank:]]*serial//' -e 's/://g' )" || die "$fn_name - signing_serial subshell" verbose "forbid_selfsign; $forbid_serial = $signing_serial" # Compare $ssl_cert_serial to $signing_serial [ "$forbid_serial" = "$signing_serial" ] } # => forbid_selfsign() # gen-crl backend gen_crl() { out_file="$EASYRSA_PKI/crl.pem" out_der="$EASYRSA_PKI/crl.der" out_file_tmp="" easyrsa_mktemp out_file_tmp if [ -r "$out_file" ]; then cp -p "$out_file" "$out_file_tmp" || \ warn "Failed to preserve CRL file permissions." fi easyrsa_openssl ca -utf8 -gencrl -out "$out_file_tmp" \ ${EASYRSA_CRL_DAYS:+ -crldays "$EASYRSA_CRL_DAYS"} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} || \ die "CRL Generation failed." # Move temp-files to target-files cp -p "$out_file_tmp" "$out_file" || die "Failed to move temp CRL file." # Copy to DER - As published by OpenSSL if "$EASYRSA_OPENSSL" crl -in "$out_file" -out "$out_der" \ -outform DER then crl_der_note="An updated CRL DER copy has been created: * $out_der" else crl_der_note="Failed to create CRL DER copy!" fi notice "\ $crl_der_note An updated CRL has been created: * $out_file IMPORTANT: When the CRL expires, an OpenVPN Server which uses a CRL will reject ALL new connections, until the CRL is replaced." } # => gen_crl() # import-req backend import_req() { # pull passed paths in_req="$1" short_name="$2" out_req="$EASYRSA_PKI/reqs/$2.req" [ "$short_name" ] || user_error "\ Unable to import: incorrect command syntax. Run easyrsa without commands for usage and command help." # Request file must exist [ -f "$in_req" ] || user_error "\ No request found for the input: '$2' Expected to find the request at: * $in_req" verify_file req "$in_req" || user_error "\ The certificate request file is not in a valid X509 format: * $in_req" # destination must not exist [ -f "$out_req" ] && user_error "\ Please choose a different name for your imported request file. Conflicting file already exists at: * $out_req" # now import it cp "$in_req" "$out_req" notice "\ Request successfully imported with short-name: $short_name This request is now ready to be signed." } # => import_req() # export pkcs#12, pkcs#7, pkcs#8 or pkcs#1 export_pkcs() { pkcs_type="$1" shift [ "$1" ] || user_error "\ Unable to export '$pkcs_type': incorrect command syntax. Run easyrsa without commands for usage and command help." file_name_base="$1" shift crt_in="$EASYRSA_PKI/issued/$file_name_base.crt" key_in="$EASYRSA_PKI/private/$file_name_base.key" crt_ca="$EASYRSA_PKI/ca.crt" # Always set a friendly_name set_var EASYRSA_P12_FR_NAME "$file_name_base" friendly_name="$EASYRSA_P12_FR_NAME" # opts support cipher=-aes256 want_ca=1 want_key=1 unset -v nokeys legacy # Under OpenSSL 1.1, use the PBE/MAC algorithms OpenSSL 3.0 uses, # unless "legacy" is set. This makes the .p12 files readable by # OpenSSL 3.0 without needing '-legacy'. if [ "$openssl_v3" ]; then # No cipher opts required unset -v p12_keypbe p12_certpbe p12_macalg else # Upgrade PBE & MAC opts - Reset by option 'legacy' p12_keypbe=AES-256-CBC p12_certpbe=AES-256-CBC p12_macalg=sha256 fi while [ "$1" ]; do case "$1" in noca) want_ca="" ;; nokey) want_key="" # Undocumented OpenSSL feature: option # -nokeys will ignore missing -inkey file # No doubt, the reason for the extra -inkey nokeys=-nokeys ;; nopass) [ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1 ;; nofn) friendly_name="" ;; legacy) if [ "$openssl_v3" ]; then # OpenSSL v3 requires providers/legacy.so # EasyRSA can use OPENSSL_MODULES=/path-to/providers legacy=-legacy else # Downgrade PBE & MAC opts unset -v p12_keypbe p12_certpbe p12_macalg fi ;; *) user_error "Unknown command option: '$1'" esac shift done # Required options - PKCS, rhymes with mess case "$pkcs_type" in p12|p7) : # ok ;; p8|p1) want_key=1 ;; *) die "Unknown PKCS type: $pkcs_type" esac # Check for CA, if required if [ "$want_ca" ]; then case "$pkcs_type" in p12|p7) # verify_ca_init() here, otherwise not required if verify_ca_init test; then : # ok else warn "\ Missing CA Certificate, expected at: * $crt_ca" confirm " Continue without CA Certificate (EG: option 'noca') ? " yes " Your PKI does not include a CA Certificate. You can export your User Certificate to a $pkcs_type file but the CA Certificate will not be included." # --batch mode does not allow # on-the-fly command changes if [ "$EASYRSA_BATCH" ]; then die "export-$pkcs_type: Missing CA" fi want_ca="" fi ;; p8|p1) : # Not required ;; *) die "Unknown PKCS type: $pkcs_type" esac fi # Check for key, if required if [ "$want_key" ]; then if [ -f "$key_in" ]; then : #ok else case "$pkcs_type" in p12) warn "\ Missing Private Key, expected at: * $key_in" confirm " Continue without Private Key (EG: option 'nokey') ? " yes " Your PKI does not include a Private Key for '$file_name_base'. You can export your User Certificate to a '$pkcs_type' file but the Private Key will not be included." # --batch mode does not allow # on-the-fly command changes if [ "$EASYRSA_BATCH" ]; then die "export-$pkcs_type: Missing key" fi nokeys=-nokeys ;; p8|p1) user_error "\ Missing Private Key, expected at: * $key_in" ;; p7) : # Not required ;; *) die "Unknown PKCS type: $pkcs_type" esac fi fi # Check for certificate, if required if [ -f "$crt_in" ]; then : # ok else case "$pkcs_type" in p12|p7) user_error "\ Missing User Certificate, expected at: * $crt_in" ;; p8|p1) : # Not required ;; *) die "Unknown PKCS type: $pkcs_type" esac fi # For 'nopass' PKCS requires an explicit empty password if [ "$EASYRSA_NO_PASS" ]; then EASYRSA_PASSIN=pass: EASYRSA_PASSOUT=pass: unset -v cipher # pkcs#1 only fi # Complete export inline_msg= case "$pkcs_type" in p12) pkcs_out="${EASYRSA_PKI}/private/${file_name_base}.p12" # Only PKCS12 can be inlined for OpenVPN inline_dir="$EASYRSA_PKI"/inline/private inline_out="${inline_dir}/${file_name_base}".inline-p12 [ "$legacy" ] && \ error_info="SSL library may not support -legacy mode" verbose "export-p12: cipher opts: \ -keypbe=$p12_keypbe | -certpbe=$p12_certpbe | -macalg=$p12_macalg" # export the p12: easyrsa_openssl pkcs12 -export \ -in "$crt_in" \ -out "$pkcs_out" \ -inkey "$key_in" \ ${nokeys} \ ${legacy} \ ${p12_keypbe:+ -keypbe "$p12_keypbe"} \ ${p12_certpbe:+ -certpbe "$p12_certpbe"} \ ${p12_macalg:+ -macalg "$p12_macalg"} \ ${friendly_name:+ -name "$friendly_name"} \ ${want_ca:+ -certfile "$crt_ca"} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ ${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \ || die "Failed to export PKCS#12" # Inline .p12 only # Get cert CN inline_CN="$( "$EASYRSA_OPENSSL" x509 -in "$crt_in" -noout -subject \ -nameopt multiline,-esc_msb | grep 'commonName' )" || die "export_pkcs - inline_CN FAILED" inline_CN="${inline_CN##*= }" # BASE64 encode pkcs12 inline_tmp= easyrsa_mktemp inline_tmp if "$EASYRSA_OPENSSL" enc -a -in "$pkcs_out" > "$inline_tmp" then # make inline file { print "\ # Easy-RSA inline file: pkcs12 # commonName: ${inline_CN}${NL}" print "" cat "$inline_tmp" print "" } > "$inline_out" || die "export_pkcs - make inline" inline_msg="\ A BASE64 encoded inline file has also been created at: * ${inline_out}${NL}" else inline_msg="\ Failed to create a BASE64 encoded inline file${NL}" fi ;; p7) pkcs_out="$EASYRSA_PKI/issued/$file_name_base.p7b" # export the p7: easyrsa_openssl crl2pkcs7 -nocrl \ -certfile "$crt_in" \ -out "$pkcs_out" \ ${want_ca:+ -certfile "$crt_ca"} \ || die "Failed to export PKCS#7" ;; p8) pkcs_out="$EASYRSA_PKI/private/$file_name_base.p8" # export the p8: easyrsa_openssl pkcs8 -topk8 \ -in "$key_in" \ -out "$pkcs_out" \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ ${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \ || die "Failed to export PKCS#8" ;; p1) pkcs_out="$EASYRSA_PKI/private/$file_name_base.p1" # OpenSSLv3 requires -traditional for PKCS#1 # Otherwise, OpenSSLv3 outputs PKCS#8 [ "$verify_ssl_lib_ok" ] || \ die "export_pkcs.p1: verify_ssl_lib_ok FAIL" if [ "$openssl_v3" ]; then traditional=-traditional else unset -v traditional fi # export the p1: easyrsa_openssl rsa \ -in "$key_in" \ -out "$pkcs_out" \ ${traditional} \ ${cipher} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ ${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} \ || die "Failed to export PKCS#1" ;; *) die "Unknown PKCS type: $pkcs_type" esac # User messages notice "\ Successful export of $pkcs_type file. Your exported file is at: * $pkcs_out" [ "$inline_msg" ] && print "$inline_msg" return 0 } # => export_pkcs() # set-pass backend set_pass() { # values supplied by the user: raw_file="$1" file="$EASYRSA_PKI/private/$raw_file.key" if [ "$raw_file" ]; then shift else user_error "\ Missing argument: no name/file supplied." fi # parse command options cipher="-aes256" while [ "$1" ]; do case "$1" in nopass) [ "$prohibit_no_pass" ] || EASYRSA_NO_PASS=1 ;; file) file="$raw_file" ;; *) user_error "Unknown command option: '$1'" esac shift done # If nopass then do not encrypt else encrypt with password. if [ "$EASYRSA_NO_PASS" ]; then unset -v cipher fi [ -f "$file" ] || user_error "\ Missing private key: expected to find the private key file at: * $file" notice "\ If the key is encrypted then you must supply the current password. ${cipher:+You will then enter a new password for this key.$NL}" # Set password out_key_tmp="" easyrsa_mktemp out_key_tmp easyrsa_openssl pkey -in "$file" -out "$out_key_tmp" \ ${cipher:+ "$cipher"} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ ${EASYRSA_PASSOUT:+ -passout "$EASYRSA_PASSOUT"} || \ die "Failed to change the private key passphrase." # Move old key-file out of the way mv "$file" "${file}.tmp" || \ die "Failed to move the old-key file." # Move new key-file into place if mv "$out_key_tmp" "$file"; then rm -f "${file}.tmp" else mv -f "${file}.tmp" "$file" die "Failed to update the private key file." fi key_update=changed [ "$EASYRSA_NO_PASS" ] && key_update=removed notice "Key passphrase successfully $key_update" } # => set_pass() # Verify OpenVPN binary verify_openvpn() { if [ -f "$EASYRSA_OPENVPN" ]; then verbose "verify_openvpn; Preset EASYRSA_OPENVPN='$EASYRSA_OPENVPN'" return fi # Try to find openvpn *nix if which openvpn >/dev/null 2>&1; then set_var EASYRSA_OPENVPN "$(which openvpn)" else # Try to find openvpn.exe, specifically for Windows # Assign temp-file for Windows path name ovpn_path_tmp="" easyrsa_mktemp ovpn_path_tmp if which openvpn.exe > "$ovpn_path_tmp"; then # shellcheck disable=SC1003 # (info): Want to escape a single quote? ovpn_path="$(sed s/'\\'/'\/'/ "$ovpn_path_tmp")" || \ die "verify_openvpn - Failed to convert openvpn path." set_var EASYRSA_OPENVPN "$ovpn_path" else user_error "\ An 'openvpn' binary is not in your system PATH. EasyRSA can not generate OpenVPN TLS keys." fi fi verbose "verify_openvpn; Set EASYRSA_OPENVPN='$EASYRSA_OPENVPN'" } # => verify_openvpn() # OpenVPN TLS Auth/Crypt Key tls_key_gen() { case "$1" in tls-crypt-v2) print "Unavailable." cleanup ;; tls-crypt) tls_key_type=TLS-CRYPT ;; tls-auth) tls_key_type=TLS-AUTH ;; *) die "Unknown key type: '$1'" esac # Over write error message tls_key_error_msg=" If this file is changed then it MUST be redistributed to ALL servers AND clients, to be in effect. Do NOT change this existing file." # Assign possible TLS key sources tls_key_file="$EASYRSA_PKI"/private/easyrsa-tls.key old_tls_key_file="$EASYRSA_PKI"/easyrsa-keepsafe-tls.key # Forbid overwrite - default TLS key if [ -f "$tls_key_file" ]; then tls_key_data="$(cat "$tls_key_file")" case "$tls_key_data" in *'TLS-CRYPT'*) tls_key_type=TLS-CRYPT ;; *'TLS-AUTH'*) tls_key_type=TLS-AUTH ;; *) tls_key_type=UNKNOWN esac user_error "\ Cannot overwrite existing $tls_key_type Key: * $tls_key_file $tls_key_error_msg" fi # Forbid overwrite - Old TLS key if [ -f "$old_tls_key_file" ]; then old_tls_key_data="$(cat "$old_tls_key_file")" case "$old_tls_key_data" in *'TLS-CRYPT'*) tls_key_type=TLS-CRYPT ;; *'TLS-AUTH'*) tls_key_type=TLS-AUTH ;; *) tls_key_type=UNKNOWN esac user_error "\ Cannot overwrite existing $tls_key_type Key: * $old_tls_key_file $tls_key_error_msg" fi verify_openvpn tls_key_tmp= easyrsa_mktemp tls_key_tmp # Generate TLS Key "$EASYRSA_OPENVPN" --genkey "$1" "$tls_key_tmp" || \ die "tls_key_gen - --genkey $tls_key_type FAIL" # Insert type label { print "# Easy-RSA $tls_key_type Key" cat "$tls_key_tmp" } > "$tls_key_file" || \ die "tls_key_gen - Insert type label FAIL" notice "\ $tls_key_type Key generated at: * $tls_key_file $tls_key_error_msg" verbose "tls_key_gen: openvpn --genkey $tls_key_type OK" } # => tls_key_gen() # Get certificate start date # shellcheck disable=2317 # Unreach - ssl_cert_not_before_date() ssl_cert_not_before_date() { verbose "ssl_cert_not_before_date; DEPRECATED" [ "$#" = 2 ] || die "\ ssl_cert_not_before_date - input error" [ -f "$1" ] || die "\ ssl_cert_not_before_date - missing cert" fn_ssl_out="$( "$EASYRSA_OPENSSL" x509 -in "$1" -noout -startdate )" || die "\ ssl_cert_not_before_date - failed: -startdate" fn_ssl_out="${fn_ssl_out#*=}" force_set_var "$2" "$fn_ssl_out" || die "\ ssl_cert_not_before_date - failed to set var '$*'" unset -v fn_ssl_out } # => ssl_cert_not_before_date() # Get certificate end date ssl_cert_not_after_date() { verbose "ssl_cert_not_after_date; DEPRECATED" [ "$#" = 2 ] || die "\ ssl_cert_not_after_date - input error" [ -f "$1" ] || die "\ ssl_cert_not_after_date - missing cert" fn_ssl_out="$( "$EASYRSA_OPENSSL" x509 -in "$1" -noout -enddate )" || die "\ ssl_cert_not_after_date - failed: -enddate" fn_ssl_out="${fn_ssl_out#*=}" force_set_var "$2" "$fn_ssl_out" || die "\ ssl_cert_not_after_date - failed to set var '$*'" unset -v fn_ssl_out } # => ssl_cert_not_after_date() # SSL -- v3 -- startdate iso_8601 # shellcheck disable=2317 # Unreach - iso_8601_cert_startdate() iso_8601_cert_startdate() { verbose "iso_8601_cert_startdate; NEW" [ "$#" = 2 ] || die "\ iso_8601_cert_startdate: input error" [ -f "$1" ] || die "\ iso_8601_cert_startdate: missing cert" # On error return, let the caller decide what to do if fn_ssl_out="$( "$EASYRSA_OPENSSL" x509 -in "$1" -noout \ -startdate -dateopt iso_8601 )" then : # ok else # The caller MUST assess this error verbose "iso_8601_cert_startdate; GENERATED ERROR" return 1 fi fn_ssl_out="${fn_ssl_out#*=}" force_set_var "$2" "$fn_ssl_out" || die "\ iso_8601_cert_startdate: failed to set var '$*'" unset -v fn_ssl_out } # => iso_8601_cert_startdate() # SSL -- v3 -- enddate iso_8601 iso_8601_cert_enddate() { verbose "iso_8601_cert_enddate; NEW" [ "$#" = 2 ] || die "\ iso_8601_cert_enddate: input error" [ -f "$1" ] || die "\ iso_8601_cert_enddate: missing cert" # On error return, let the caller decide what to do if fn_ssl_out="$( "$EASYRSA_OPENSSL" x509 -in "$1" -noout \ -enddate -dateopt iso_8601 2>/dev/null )" then : # ok else # The caller MUST assess this error verbose "iso_8601_cert_enddate; GENERATED ERROR" return 1 fi fn_ssl_out="${fn_ssl_out#*=}" force_set_var "$2" "$fn_ssl_out" || die "\ iso_8601_cert_enddate: failed to set var '$*'" unset -v fn_ssl_out } # => iso_8601_cert_enddate() # Build an iso_8601 date from database data db_date_to_iso_8601() { # check input [ "$#" = 2 ] || \ die "db_date_to_iso_8601: input error" # Expected format: '230612235959Z' in_date="$1" verbose "db_date_to_iso_8601; in_date=$in_date" # Check length [ "${#in_date}" = 13 ] || \ die "db_date_to_iso_8601: input length" # yyyy is expected to be only 'yy' yyyy="${in_date%???????????}" in_date="${in_date#??}" # Prepend century based on value - Valid until 2070 yyyy="${yyyy#0}" # drop a leading zero if [ "$yyyy" -lt 70 ]; then # 20th century if [ "${#yyyy}" = 2 ]; then yyyy=20"${yyyy}" else yyyy=200"${yyyy}" fi else # 19th century if [ "${#yyyy}" = 2 ]; then yyyy=19"${yyyy}" else yyyy=190"${yyyy}" fi fi # Consume remaining two digit strings and Timezone mm="${in_date%?????????}" in_date="${in_date#??}" dd="${in_date%???????}" in_date="${in_date#??}" HH="${in_date%?????}" in_date="${in_date#??}" MM="${in_date%???}" in_date="${in_date#??}" SS="${in_date%?}" in_date="${in_date#??}" TZ="$in_date" # Assign iso_8601 date out_date="${yyyy}-${mm}-${dd} ${HH}:${MM}:${SS}${TZ}" verbose "db_date_to_iso_8601; out_date=$out_date" # Return out_date force_set_var "$2" "$out_date" || \ die "db_date_to_iso_8601: force_set_var $2 $out_date" unset -v in_date out_date yyyy mm dd HH MM SS TZ } # => db_date_to_iso_8601() # Certificate expiry will_cert_be_valid() { # Verify file exists and is a valid cert [ -f "$1" ] || \ die "will_cert_be_valid - Missing file: $1" verify_file x509 "$1" || \ die "will_cert_be_valid - Invalid file: $1" # Verify --days case "$2" in 0) : ;; # ok ''|*[!1234567890]*|0*) die "will_cert_be_valid - Non-decimal value: $2" esac # is the cert still valid at this future date ssl_out="$( "$EASYRSA_OPENSSL" x509 -in "$1" -checkend "$2" )" # analyse SSL output case "$ssl_out" in 'Certificate will not expire') return 0 ;; 'Certificate will expire') return 1 ;; *) die "will_cert_be_valid - Failure: '$ssl_out'" esac } # => will_cert_be_valid() # Read db # shellcheck disable=SC2295 # nested expand - read_db() read_db() { TCT=' ' # tab character db_in="$EASYRSA_PKI/index.txt" pki_r_issued="$EASYRSA_PKI/renewed/issued" pki_r_by_sno="$EASYRSA_PKI/renewed/certs_by_serial" unset -v target_found while read -r db_status db_notAfter db_record; do # Quiet schellcheck # SC2034 (warning): db_notAfter appears unused unused="$db_notAfter" db_notAfter="$unused" unset -v db_notAfter unused verbose "***** Read next record *****" # Recreate temp-session and # drop edits to SSL Conf file remove_secure_session secure_session locate_support_files write_global_safe_ssl_cnf_tmp # Interpret the db/certificate record unset -v db_serial db_cn db_revoke_date db_reason case "$db_status" in V|E) # Valid db_serial="${db_record%%${TCT}*}" db_record="${db_record#*${TCT}}" db_cn="${db_record#*/CN=}"; db_cn="${db_cn%%/*}" cert_issued="$EASYRSA_PKI/issued/$db_cn.crt" cert_r_issued="$pki_r_issued/$db_cn.crt" cert_r_by_sno="$pki_r_by_sno/$db_serial.crt" ;; R) # Revoked db_revoke_date="${db_record%%${TCT}*}" db_reason="${db_revoke_date#*,}" if [ "$db_reason" = "$db_revoke_date" ]; then db_reason="None given" else db_revoke_date="${db_revoke_date%,*}" fi db_record="${db_record#*${TCT}}" db_serial="${db_record%%${TCT}*}" db_record="${db_record#*${TCT}}" db_cn="${db_record#*/CN=}"; db_cn="${db_cn%%/*}" ;; *) die "Unexpected status: $db_status" esac # Output selected status report for this record case "$report" in expire) # Certs which expire before EASYRSA_PRE_EXPIRY_WINDOW days case "$db_status" in V|E) case "$target" in '') expire_status_v2 "$cert_issued" ;; *) if [ "$target" = "$db_cn" ]; then expire_status_v2 "$cert_issued" fi esac ;; *) : # Ignore ok esac ;; revoke) # Certs which have been revoked case "$db_status" in R) case "$target" in '') revoke_status ;; *) if [ "$target" = "$db_cn" ]; then revoke_status fi esac ;; *) : # Ignore ok esac ;; renew) # Certs which have been renewed but not revoked case "$db_status" in V|E) case "$target" in '') renew_status ;; *) if [ "$target" = "$db_cn" ]; then renew_status fi esac ;; *) : # Ignore ok esac ;; *) die "Unrecognised report: $report" esac # Is db record for target found if [ "$target" = "$db_cn" ]; then target_found=1 fi done < "$db_in" # Add CA to show-expire case "$report" in expire) # Extract -endate ca_enddate="$( "$EASYRSA_OPENSSL" x509 -in "$EASYRSA_PKI"/ca.crt \ -noout -enddate )" ca_enddate="${ca_enddate#*=}" # Check CA for expiry if will_cert_be_valid "$EASYRSA_PKI"/ca.crt \ "$pre_expire_window_s" then : # cert will still be valid by expiry window else # Print CA expiry date printf '\n%s\n\n' \ "CA certificate will expire on $ca_enddate" fi esac # Check for target found/valid commonName, if given if [ "$target" ]; then [ "$target_found" ] || \ warn "Certificate for $target was not found" fi } # => read_db() # Expire status expire_status_v2() { # The certificate for CN should exist but may not if [ -f "$1" ]; then verbose "expire_status_v2; cert exists" # Check if cert will be valid else print details if will_cert_be_valid "$1" "$pre_expire_window_s" then verbose "expire_status_v2; cert will still be valid" else # cert expiry date cert_not_after_date= if iso_8601_cert_enddate "$1" cert_not_after_date; then : # ok else # Standard date - OpenSSL v1 ssl_cert_not_after_date "$1" cert_not_after_date fi # show expiring cert details printf '%s%s\n' \ "$db_status | Serial: $db_serial | " \ "$cert_not_after_date | CN: $db_cn" fi else verbose "expire_status_v2; issued cert does not exist" fi } # => expire_status_v2() # Revoke status revoke_status() { # Translate db date to usable date cert_revoke_date= # eg: 220517142247Z --> 2022-05-17 14:22:47Z db_date_to_iso_8601 "$db_revoke_date" cert_revoke_date printf '%s%s%s\n' \ "$db_status | Serial: $db_serial | " \ "Revoked: $cert_revoke_date | " \ "Reason: $db_reason | CN: $db_cn" } # => revoke_status() # Renewed status # renewed certs only remain in the renewed folder until revoked # Only ONE renewed cert with unique CN can exist in renewed folder renew_status() { # Does a Renewed cert exist ? # files in issued are file name, or in serial are SerialNumber unset -v \ cert_file_in cert_is_issued cert_is_serial renew_is_old # Find renewed/issued/CN if [ -f "$cert_r_issued" ]; then cert_file_in="$cert_r_issued" cert_is_issued=1 fi # Find renewed/cert_by_serial/SN if [ -f "$cert_r_by_sno" ]; then cert_file_in="$cert_r_by_sno" cert_is_serial=1 renew_is_old=1 fi # Both should not exist if [ "$cert_is_issued" ] && [ "$cert_is_serial" ]; then die "Too many certs" fi # If a renewed cert exists if [ "$cert_file_in" ]; then # get the serial number of the certificate ssl_cert_serial "$cert_file_in" cert_serial # db serial must match certificate serial, otherwise # this is an issued cert that replaces a renewed cert if [ "$db_serial" != "$cert_serial" ]; then information "\ serial mismatch: db_serial: $db_serial cert_serial: $cert_serial cert_file_in: $cert_file_in" return 0 fi # Use cert date # Assigns cert_not_after_date ssl_cert_not_after_date \ "$cert_file_in" cert_not_after_date # Highlight renewed/cert_by_serial if [ "$renew_is_old" ]; then printf '%s%s\n' \ "*** $db_status | Serial: $db_serial | " \ "Expires: $cert_not_after_date | CN: $db_cn" else printf '%s%s\n' \ "$db_status | Serial: $db_serial | " \ "Expires: $cert_not_after_date | CN: $db_cn" fi else # Cert is valid but not renewed : # ok - ignore fi } # => renew_status() # cert status reports status() { [ "$#" -gt 0 ] || die "status - input error" report="$1" target="$2" # test fix: https://github.com/OpenVPN/easy-rsa/issues/819 export LC_TIME=C.UTF-8 # expiry seconds pre_expire_window_s="$(( EASYRSA_PRE_EXPIRY_WINDOW * 60*60*24 ))" # If no target file then add Notice if [ -z "$target" ]; then # Select correct Notice case "$report" in expire) notice "\ * Showing certificates which expire in less than \ $EASYRSA_PRE_EXPIRY_WINDOW days (--days):" ;; revoke) notice "\ * Showing certificates which are revoked:" ;; renew) notice "\ * Showing certificates which have been renewed but NOT revoked: *** Marks those which require 'rewind-renew' \ before they can be revoked." ;; *) warn "Unrecognised report: $report" esac fi # Create report read_db } # => status() # renew backend renew() { # pull filename base: [ "$1" ] || user_error "\ Error: didn't find a file base name as the first argument. Run easyrsa without commands for usage and command help." # Assign file_name_base and dust off! file_name_base="$1" shift # Assign input files in_dir="$EASYRSA_PKI" crt_in="$in_dir/issued/${file_name_base}.crt" key_in="$in_dir/private/${file_name_base}.key" req_in="$in_dir/reqs/${file_name_base}.req" inline_pub="$in_dir/inline/${file_name_base}.inline" inline_pri="$in_dir/inline/private/${file_name_base}.inline" # deprecate ALL options while [ "$1" ]; do case "$1" in nopass) warn "\ Option 'nopass' is not supported by command 'renew'." ;; *) user_error "Unknown option: $1" esac shift done # Verify certificate if [ -f "$crt_in" ]; then verify_file x509 "$crt_in" || user_error "\ Input file is not a valid certificate: * $crt_in" else user_error "\ Missing certificate file: * $crt_in" fi # Forbid self-signed cert from being expired/renewed/revoked if forbid_selfsign "$crt_in"; then user_error "Cannot $cmd a self-signed certificate." fi # Verify request if [ -f "$req_in" ]; then verify_file req "$req_in" || user_error "\ Input file is not a valid request: * $req_in" else user_error "\ Missing request file: * $req_in" fi # Get cert commonName cert_CN="$( display_dn x509 "$crt_in" | grep 'commonName' )" || die "renew - display_dn of cert failed" # Get req commonName req_CN="$( display_dn req "$req_in" | grep 'commonName' )" || die "renew - display_dn of req failed" # For renew, cert_CN must match req_CN [ "$cert_CN" = "$req_CN" ] || user_error \ "Certificate cannot be renewed due to commonName mismatch" verbose "cert_CN MATCH req_CN" # get the serial number of the certificate ssl_cert_serial "$crt_in" cert_serial || \ die "$cmd: Failed to get cert serial number!" # Set out_dir out_dir="$EASYRSA_PKI/renewed" crt_out="$out_dir/issued/${file_name_base}.crt" # NEVER over-write a renewed cert, revoke it first if [ -f "$crt_out" ]; then user_error "\ Cannot renew this certificate, a conflicting file exists: * certificate: $crt_out Use command 'revoke-renewed' to revoke this certificate." fi # Extract certificate usage from old cert cert_type= ssl_cert_x509v3_eku "$crt_in" cert_type # create temp-file for full cert text full_crt_tmp= easyrsa_mktemp full_crt_tmp # write full cert text tempfile data "$EASYRSA_OPENSSL" x509 -in "$crt_in" \ -noout -text > "$full_crt_tmp" || \ die "write full cert text" # Use SAN from old cert ONLY if grep -q 'X509v3 Subject Alternative Name' \ "$full_crt_tmp" then EASYRSA_SAN="$( grep -A 1 'X509v3 Subject Alternative Name' \ "$full_crt_tmp" | \ sed -e s/'^\ *'// \ -e /'X509v3 Subject Alternative Name'/d \ -e s/'IP Address:'/'IP:'/g )" || die "renew - EASYRSA_SAN: easyrsa_openssl subshell" verbose "EASYRSA_SAN: ${EASYRSA_SAN}" # --san-crit unset -v EASYRSA_SAN_CRIT if grep -q 'X509v3 Subject Alternative Name: critical' \ "$full_crt_tmp" then export EASYRSA_SAN_CRIT='critical,' verbose "--san-crit ENABLED" fi export EASYRSA_EXTRA_EXTS="\ $EASYRSA_EXTRA_EXTS subjectAltName = ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}" verbose "EASYRSA_EXTRA_EXTS: ${EASYRSA_EXTRA_EXTS}" fi # --bc-crit if grep -q 'X509v3 Basic Constraints: critical' \ "$full_crt_tmp" then export EASYRSA_BC_CRIT=1 verbose "--bc-crit ENABLED" fi # --ku-crit if grep -q 'X509v3 Key Usage: critical' \ "$full_crt_tmp" then export EASYRSA_KU_CRIT=1 verbose "--ku-crit ENABLED" fi # --eku-crit if grep -q 'X509v3 Extended Key Usage: critical' \ "$full_crt_tmp" then export EASYRSA_EKU_CRIT=1 verbose "--eku-crit ENABLED" fi # Disable options not supported by renew unset -v EASYRSA_AUTO_SAN EASYRSA_NEW_SUBJECT # confirm operation by displaying Warning confirm "Continue with 'renew' ? " yes "\ WARNING: This process is destructive! These files will be MOVED to the 'renewed' sub-directory: * $crt_in These files will be DELETED: All PKCS files for commonName: $file_name_base The inline credentials files: * $inline_pub * $inline_pri The CA attibutes file will be set to 'unique_subject = no'. Required to create a new certificate with an existing name." # Set 'unique_subject = no' in index.txt.attr CA_attrib='unique_subject = no' print "$CA_attrib" > "$EASYRSA_PKI"/index.txt.attr || \ die "Failed to update index.txt.attr" # move renewed files # so we can reissue certificate with the same name renew_move error_undo_renew_move=1 # Set to modify sign-req confirmation message local_request=1 # renew certificate # EASYRSA_BATCH=1 fn_name="$fn_name; sign_req" if sign_req "$cert_type" "$file_name_base" then fn_name="${fn_name%; sign_req}" unset -v error_undo_renew_move else # If renew failed then restore cert. # Otherwise, issue a warning renew_restore_move die "Renewal has failed to build a new certificate." fi # Success messages notice "\ Renew was successful. * IMPORTANT * Renew has created a new certificate, to replace the old one. To revoke the old certificate, once the new one has been deployed, use command 'revoke-renewed $file_name_base'" } # => renew() # Restore files on failure to renew renew_restore_move() { # restore crt file to PKI folders rrm_err= if mv "$restore_crt_out" "$restore_crt_in"; then : # ok else warn "Failed to restore: $restore_crt_in" rrm_err=1 fi # messages if [ "$rrm_err" ]; then warn "Failed to restore renewed files." else notice "\ Renew FAILED but files have been successfully restored." fi } # => renew_restore_move() # renew_move # moves renewed certificates to the 'renewed' folder # allows reissuing certificates with the same name renew_move() { # make sure renewed dirs exist mkdir -p "$out_dir"/issued || \ die "renew_move() - Failed to create '$out_dir/issued' directory." # move crt to renewed folders # After this point, renew is possible! restore_crt_in="$crt_in" restore_crt_out="$crt_out" mv "$crt_in" "$crt_out" || \ die "Failed to move: $crt_in" # Remove files that can be recreated: # remove any pkcs files for pkcs in p12 p7b p8 p1; do # issued rm -f "$in_dir/issued/$file_name_base.$pkcs" # private rm -f "$in_dir/private/$file_name_base.$pkcs" done # remove inline files rm -f "$inline_pub" "$inline_pri" || warn \ "renew_move - Error trying to remove inline files." } # => renew_move() # Verify certificate against CA verify_cert() { # pull filename base: [ "$1" ] || user_error "\ Error: didn't find a as the first argument. Run easyrsa without commands for usage and command help." # Assign file_name_base and dust off! file_name_base="$1" shift # function opts support while [ "$1" ]; do case "$1" in # batch flag, return status [0/1] to calling # program. Otherwise, exit 0 on completion. batch) EASYRSA_BATCH=1 ;; *) warn "Ignoring unknown command option: '$1'" esac shift done in_dir="$EASYRSA_PKI" ca_crt="$in_dir/ca.crt" crt_in="$in_dir/issued/$file_name_base.crt" # Cert file must exist [ -f "$crt_in" ] || user_error "\ No certificate found for the input: * '$crt_in'" # Verify file is a valid cert verify_file x509 "$crt_in" || user_error "\ Input is not a valid certificate: * $crt_in" # Silent SSL or not if [ "$EASYRSA_SILENT_SSL" ]; then # Test SSL out # openssl direct call because error is expected if "$EASYRSA_OPENSSL" verify \ -CAfile "$ca_crt" "$crt_in" >/dev/null then verify_cert_ok=1 else unset -v verify_cert_ok fi else if "$EASYRSA_OPENSSL" verify \ -CAfile "$ca_crt" "$crt_in" then verify_cert_ok=1 else unset -v verify_cert_ok fi fi # Return cert status if [ "$verify_cert_ok" ]; then notice "\ Certificate name: $file_name_base Verification status: GOOD" else notice "\ Certificate name: $file_name_base Verification status: FAILED" # Exit with error (batch mode) if [ "$EASYRSA_BATCH" ]; then # exit with error at cleanup easyrsa_exit_with_error=1 # Return error for internal callers return 1 fi fi } # => verify_cert() # Renew CA certificate renew_ca_cert() { # dirs and files ca_key_file="$EASYRSA_PKI"/private/ca.key ca_cert_file="$EASYRSA_PKI"/ca.crt exp_ca_cert_list="$EASYRSA_PKI"/expired-ca.list # Set fixed variables x509=1 date_stamp=1 f_name="renew_ca_cert()" # Set default CA commonName [ "$EASYRSA_REQ_CN" = ChangeMe ] || \ warn "\ $cmd does not support setting an external commonName." # Copy Old CA commonName as default EASYRSA_REQ_CN="$( "$EASYRSA_OPENSSL" x509 -in "$ca_cert_file" \ -noout -subject -nameopt utf8,multiline | \ grep 'commonName' | sed -e \ s\`^[[:blank:]]*commonName[[:blank:]]*=[[:blank:]]\`\` )" || die "renew_ca_cert - Failed to get EASYRSA_REQ_CN" export EASYRSA_REQ_CN # Set ssl batch mode, as required [ "$EASYRSA_BATCH" ] && ssl_batch=1 # create local SSL cnf write_easyrsa_ssl_cnf_tmp # Assign new cert temp-file out_cert_tmp= easyrsa_mktemp out_cert_tmp # Assign old cert temp-file old_cert_tmp= easyrsa_mktemp old_cert_tmp # Write complete CA cert to old cert temp-file "$EASYRSA_OPENSSL" x509 -in "$ca_cert_file" \ -text > "$old_cert_tmp" || \ die "$f_name Write CA cert to temp-file" # Find or create x509 CA file if [ -f "$EASYRSA_EXT_DIR/ca" ]; then # Use the x509-types/ca file x509_type_file="$EASYRSA_EXT_DIR/ca" else # Use a temp file write_x509_type_tmp ca x509_type_file="$write_x509_file_tmp" fi # basicConstraints critical if grep -q 'Basic Constraints: critical' "$old_cert_tmp" then add_critical_attrib basicConstraints \ "$x509_type_file" x509_type_file || \ die "$f_name: add_critical_attrib bC" verbose "basicConstraints critical OK" fi # keyUsage critical if grep -q 'Key Usage: critical' "$old_cert_tmp" then add_critical_attrib keyUsage \ "$x509_type_file" x509_type_file || \ die "$f_name: add_critical_attrib kU" verbose "keyUsage critical OK" fi # Find or create x509 COMMON file if [ -f "$EASYRSA_EXT_DIR/COMMON" ]; then # Use the x509-types/COMMON file x509_COMMON_file="$EASYRSA_EXT_DIR/COMMON" else # Use a temp file write_x509_type_tmp COMMON x509_COMMON_file="$write_x509_file_tmp" fi # Check for insert-marker in ssl config file if ! grep -q '^#%CA_X509_TYPES_EXTRA_EXTS%' \ "$EASYRSA_SSL_CONF" then die "\ This openssl config file does not support X509-type 'ca'. * $EASYRSA_SSL_CONF Please update 'openssl-easyrsa.cnf' to the latest Easy-RSA release." fi # Assign awkscript to insert EASYRSA_EXTRA_EXTS # shellcheck disable=SC2016 # No expand '' - build_ca() awkscript='\ {if ( match($0, "^#%CA_X509_TYPES_EXTRA_EXTS%") ) { while ( getline<"/dev/stdin" ) {print} next } {print} }' # Assign tmp-file for config adjusted_ssl_cnf_tmp="" easyrsa_mktemp adjusted_ssl_cnf_tmp # Insert x509-types COMMON and 'ca' and EASYRSA_EXTRA_EXTS { # X509 files cat "$x509_type_file" "$x509_COMMON_file" # User extensions [ "$EASYRSA_EXTRA_EXTS" ] && \ print "$EASYRSA_EXTRA_EXTS" } | awk "$awkscript" "$EASYRSA_SSL_CONF" \ > "$adjusted_ssl_cnf_tmp" || \ die "$f_name Copy X509_TYPES to config failed" verbose "Insert x509 and extensions OK" # Use this new SSL config for the rest of this function export EASYRSA_SSL_CONF="$adjusted_ssl_cnf_tmp" # Generate new CA cert: easyrsa_openssl req -utf8 -new \ -key "$ca_key_file" \ -out "$out_cert_tmp" \ ${ssl_batch:+ -batch} \ ${x509:+ -x509} \ ${date_stamp:+ -days "$EASYRSA_CA_EXPIRE"} \ ${EASYRSA_DIGEST:+ -"$EASYRSA_DIGEST"} \ ${EASYRSA_NO_PASS:+ "$no_password"} \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} \ # EOL # Collect New CA text new_ca_text="$( "$EASYRSA_OPENSSL" x509 -in "$out_cert_tmp" -noout -text )" # Confirm renewed certificate installation confirm "Install the new CA certificate ? " yes " NEW CA CERTIFICATE: $new_ca_text WARNING !!! Your CA certificate is ready to be renewed. (Details above) This new CA certificate will completely replace the old one. The old CA will be archived to the 'expired-ca.list' file. Please check the details above are correct, before continuing." # Prepare header file for updated old CA list header_tmp= easyrsa_mktemp header_tmp # header and separator text hdr='# Easy-RSA expired CA certificate list:' spr='# =====================================' # make full header temp-file printf '%s\n%s\n\n' "$hdr" "$spr" > "$header_tmp" || \ die "$f_name printf header to header-temp" # Prepare old cert list if [ -f "$exp_ca_cert_list" ]; then # Assign old cert list temp file exp_cert_list_tmp= easyrsa_mktemp exp_cert_list_tmp # write list to temp-fie, remove header not separators sed -e s/"^${hdr}$"// \ "$exp_ca_cert_list" > "$exp_cert_list_tmp" || \ die "$f_name sed exp_ca_cert_list" fi # Add full old CA Cert to old CA Cert list file if [ -f "$exp_cert_list_tmp" ]; then cat "$header_tmp" "$old_cert_tmp" "$exp_cert_list_tmp" \ > "$exp_ca_cert_list" || \ die "$f_name cat exp_cert_list_tmp" else cat "$header_tmp" "$old_cert_tmp" \ > "$exp_ca_cert_list" || \ die "$f_name cat old_cert_tmp" fi # Install renewed CA Cert temp-file as current CA cert mv -f "$out_cert_tmp" "$ca_cert_file" || \ die "Failed to install renewed CA temp-file!" notice "\ CA certificate has been successfully renewed. Your old CA cerificate has been added to the expired CA list at: * $exp_ca_cert_list Your renewed CA cerificate is at: * $ca_cert_file" } # => renew_ca_cert() # update-db backend update_db() { easyrsa_openssl ca -utf8 -updatedb \ ${EASYRSA_PASSIN:+ -passin "$EASYRSA_PASSIN"} || \ die "Failed to perform update-db." } # => update_db() # display cert DN info on a req/X509, passed by full pathname display_dn() { [ "$#" = 2 ] || die "\ display_dn - input error" format="$1" path="$2" shift 2 # Display DN "$EASYRSA_OPENSSL" "$format" -in "$path" -noout -subject \ -nameopt utf8,sep_multiline,space_eq,lname,align } # => display_dn() # verify a file seems to be a valid req/X509 verify_file() { format="$1" path="$2" "$EASYRSA_OPENSSL" "$format" -in "$path" -noout 2>/dev/null } # => verify_file() # show-* command backend # Prints req/cert details in a readable format show() { type="$1" name="$2" in_file="" format="" [ "$name" ] || user_error "\ Missing expected argument. Run easyrsa without commands for usage help." shift 2 # opts support type_opts="-${type}opt" out_opts="no_pubkey,no_sigdump" name_opts="utf8,sep_multiline,space_eq,lname,align" while [ "$1" ]; do case "$1" in full) out_opts= ;; *) warn "Ignoring unknown command option: '$1'" esac shift done # Determine cert/req type (v2) case "$type" in cert) in_file="$EASYRSA_PKI/issued/$name.crt" format="x509" ;; req) in_file="$EASYRSA_PKI/reqs/$name.req" format="req" ;; crl) in_file="$EASYRSA_PKI/$name.pem" format="crl" unset -v type_opts out_opts name_opts ;; *) die "Unrecognised type: $type" esac # Verify file exists and is of the correct type [ -f "$in_file" ] || user_error "\ No such '$type' type file with a of '$name'. Expected to find this file at: * $in_file" verify_file "$format" "$in_file" || user_error "\ This file is not a valid $type file: * $in_file" notice "\ Showing '$type' details for: '$name' This file is stored at: * $in_file${NL}" easyrsa_openssl "$format" -in "$in_file" -noout -text \ ${type_opts:+ "$type_opts" "$out_opts"} \ ${name_opts:+ -nameopt "$name_opts"} || \ die "OpenSSL failure to process the input" } # => show() # show-ca command backend # Prints CA cert details in a readable format show_ca() { # opts support out_opts="no_pubkey,no_sigdump" name_opts="utf8,sep_multiline,space_eq,lname,align" while [ "$1" ]; do case "$1" in full) out_opts= ;; *) warn "Ignoring unknown command option: '$1'" esac shift done in_file="$EASYRSA_PKI/ca.crt" format="x509" # Verify file exists and is of the correct type [ -f "$in_file" ] || user_error "\ No such $type file with a basename of '$name' is present. Expected to find this file at: $in_file" verify_file "$format" "$in_file" || user_error "\ This file is not a valid $type file: $in_file" notice "\ Showing details for CA certificate, at: * $in_file${NL}" easyrsa_openssl "$format" -in "$in_file" -noout -text \ -nameopt "$name_opts" -certopt "$out_opts" || \ die "OpenSSL failure to process the input" } # => show_ca() # Certificate X509v3 Extended Key Usage # ''$ceku_*'' is Cert EKU, to localize function variables ssl_cert_x509v3_eku() { # check input file name if [ -f "$1" ]; then ceku_crt="$1" else ceku_crt="${EASYRSA_PKI}/issued/${1}.crt" [ -f "$ceku_crt" ] || \ die "$fn_name - Missing cert '$ceku_crt'" fi # required variables ceku_pattern='X509v3 Extended Key Usage:' ceku_cli='TLS Web Client Authentication' ceku_srv='TLS Web Server Authentication' ceku_srv_cli="${ceku_srv}, ${ceku_cli}" ceku_codeSign='Code Signing' ceku_known= # Extract certificate Extended Key Usage if [ "$ssl_lib" = libressl ]; then ceku_eku="$( "$EASYRSA_OPENSSL" x509 -in "$ceku_crt" -noout \ -text | \ sed -n "/${ceku_pattern}/{n;s/^ *//g;p;}" )" || die "$fn_name - LibreSSL error" else ceku_eku="$( "$EASYRSA_OPENSSL" x509 -in "$ceku_crt" -noout \ -ext extendedKeyUsage | \ sed -e /"${ceku_pattern}"/d -e s/^\ *// )" || die "$fn_name - OpenSSL error" fi # Match EKU with supported usage case "$ceku_eku" in "$ceku_srv_cli") ceku_known=1 ceku_type=serverClient ;; "$ceku_cli") ceku_known=1 ceku_type=client ;; "$ceku_srv") ceku_known=1 ceku_type=server ;; "$ceku_codeSign") ceku_known=1 # translate 'Code Signing' into x509-type file-name ceku_type=codeSigning ;; '') ceku_type=undefined ;; *) ceku_type="'$ceku_eku'" esac # Check for self-sign if "$EASYRSA_OPENSSL" x509 -in "$ceku_crt" \ -noout -text | grep -q 'CA:TRUE' then ceku_type="self-signed-$ceku_type" fi # Set variable to return if [ "$2" ]; then force_set_var "$2" "$ceku_type" || \ die "$fn_name - force_set_var failed" verbose "ssl_cert_x509v3_eku; EKU='$ceku_type' [$2]" elif [ "$ceku_known" ]; then information "\ * Known X509v3 Extended Key Usage: '$ceku_type'" else information "\ * UNKNOWN X509v3 Extended Key Usage: '$ceku_type'" fi fn_name="${fn_name%; ssl_cert_x509v3_eku}" # Succeed for known types only [ "$ceku_known" ] && return return 1 } # => ssl_cert_x509v3_eku() # get the serial number of the certificate -> serial=XXXX ssl_cert_serial() { [ "$#" = 2 ] || die "ssl_cert_serial - input error" [ -f "$1" ] || die "ssl_cert_serial - missing cert" fn_ssl_out="$( "$EASYRSA_OPENSSL" x509 -in "$1" -noout -serial )" || die "ssl_cert_serial - failed: -serial" # remove the serial= part -> we only need the XXXX part fn_ssl_out="${fn_ssl_out##*=}" force_set_var "$2" "$fn_ssl_out" || \ die "ssl_cert_serial - failed to set var '$*'" unset -v fn_ssl_out } # => ssl_cert_serial() # Identify host OS detect_host() { unset -v \ easyrsa_ver_test easyrsa_host_os easyrsa_host_test \ easyrsa_win_git_bash # Detect Windows [ "${OS}" ] && easyrsa_host_test="${OS}" # shellcheck disable=SC2016 # No expand '' - detect_host() easyrsa_ksh=\ '@(#)MIRBSD KSH R39-w32-beta14 $Date: 2013/06/28 21:28:57 $' [ "${KSH_VERSION}" = "${easyrsa_ksh}" ] && \ easyrsa_host_test="${easyrsa_ksh}" unset -v easyrsa_ksh # If not Windows then nix if [ "${easyrsa_host_test}" ]; then easyrsa_host_os=win easyrsa_uname="${easyrsa_host_test}" easyrsa_shell="$SHELL" # Detect Windows git/bash if [ "${EXEPATH}" ]; then easyrsa_shell="$SHELL (Git)" easyrsa_win_git_bash="${EXEPATH}" # If found then set openssl NOW! #[ -e /usr/bin/openssl ] && \ # set_var EASYRSA_OPENSSL /usr/bin/openssl fi else easyrsa_host_os=nix easyrsa_uname="$(uname 2>/dev/null)" easyrsa_shell="${SHELL:-undefined}" fi easyrsa_ver_test="${EASYRSA_version%%~*}" if [ "$easyrsa_ver_test" ]; then host_out="Host: $EASYRSA_version" else host_out="Host: dev" fi host_out="\ $host_out | $easyrsa_host_os | $easyrsa_uname | $easyrsa_shell" host_out="\ ${host_out}${easyrsa_win_git_bash+ | "$easyrsa_win_git_bash"}" unset -v easyrsa_ver_test easyrsa_host_test } # => detect_host() # Extra diagnostics show_host() { [ "$EASYRSA_SILENT" ] && return print_version print "$host_out" [ "$EASYRSA_DEBUG" ] || return 0 case "$easyrsa_host_os" in win) set ;; nix) env ;; *) print "Unknown host OS: $easyrsa_host_os" esac } # => show_host() # Verify the selected algorithm parameters verify_algo_params() { case "$EASYRSA_ALGO" in rsa) # Set RSA key size EASYRSA_ALGO_PARAMS="$EASYRSA_KEY_SIZE" ;; ec) # Verify Elliptic curve EASYRSA_ALGO_PARAMS="" easyrsa_mktemp EASYRSA_ALGO_PARAMS # Create the required ecparams file, temp-file # call openssl directly because error is expected "$EASYRSA_OPENSSL" ecparam \ -name "$EASYRSA_CURVE" \ -out "$EASYRSA_ALGO_PARAMS" \ >/dev/null 2>&1 || user_error "\ Failed to generate ecparam file for curve '$EASYRSA_CURVE'" ;; ed) # Verify Edwards curve # call openssl directly because error is expected "$EASYRSA_OPENSSL" genpkey \ -algorithm "$EASYRSA_CURVE" \ >/dev/null 2>&1 || user_error "\ Edwards Curve '$EASYRSA_CURVE' not found." ;; *) user_error "\ Unknown algorithm '$EASYRSA_ALGO': Must be 'rsa', 'ec' or 'ed'" esac verbose "verify_algo_params; OK: algo '$EASYRSA_ALGO'" } # => verify_algo_params() # Check for conflicting input options mutual_exclusions() { # --nopass cannot be used with --passout if [ "$EASYRSA_PASSOUT" ]; then # --passout MUST take priority over --nopass [ "$EASYRSA_NO_PASS" ] && warn "\ Option --passout cannot be used with --nopass|nopass." unset -v EASYRSA_NO_PASS prohibit_no_pass=1 fi # Restrict --days=0 to 'show-expire' if [ "$alias_days" = 0 ]; then case "$cmd" in show-expire) : ;; # ok *) user_error "Cannot use --days=0 for command $cmd" esac fi # --silent-ssl requires --batch if [ "$EASYRSA_SILENT_SSL" ]; then [ "$EASYRSA_BATCH" ] || warn "\ Option --silent-ssl requires batch mode --batch." fi # --startdate requires --enddate # otherwise, --days counts from now if [ "$EASYRSA_START_DATE" ]; then [ "$EASYRSA_END_DATE" ] || user_error "\ Use of --startdate requires use of --enddate." fi # Basic --startdate/--enddate check case "${#EASYRSA_START_DATE}" in 0|13|15) : ;; # ok *) user_error "\ Format of --startdate/--enddate must be [YY]YYMMDDhhmmssZ" esac case "${#EASYRSA_END_DATE}" in 0|13|15) : ;; # ok *) user_error "\ Format of --startdate/--enddate must be [YY]YYMMDDhhmmssZ" esac # --enddate may over-rule EASYRSA_CERT_EXPIRE if [ "$EASYRSA_END_DATE" ]; then case "$cmd" in sign-req|build-*-full|renew) # User specified alias_days IS over-ruled if [ "$alias_days" ]; then warn "\ Option --days is over-ruled by option --enddate." fi unset -v EASYRSA_CERT_EXPIRE alias_days ;; *) warn "\ EasyRSA '$cmd' does not support --startdate or --enddate" unset -v EASYRSA_START_DATE EASYRSA_END_DATE esac fi # Insecure Windows directory if [ "$easyrsa_host_os" = win ]; then if echo "$PWD" | grep -q '/Prog.*/OpenVPN/easy-rsa' then verbose "\ Using Windows-System-Folders for your PKI is NOT SECURE! Your Easy-RSA PKI CA Private Key is WORLD readable. To correct this problem, it is recommended that you either: * Copy Easy-RSA to your User folders and run it from there, OR * Define your PKI to be in your User folders. EG: 'easyrsa --pki-dir=\"C:/Users//easy-rsa/pki\"\ '" fi fi verbose "mutual_exclusions; COMPLETED" } # => mutual_exclusions() # Select vars in order preference: # Here sourcing of 'vars' if present occurs. # If not present, defaults are used to support # running without a sourced config format. select_vars() { # User specified vars file will be used ONLY if [ "$EASYRSA_VARS_FILE" ]; then : # Takes priority, nothing to do # This is where auto-load goes bananas else # User specified PKI; if vars exists, use it ONLY if [ "$EASYRSA_PKI" ]; then if [ -f "$EASYRSA_PKI"/vars ]; then set_var EASYRSA_VARS_FILE "$EASYRSA_PKI"/vars fi fi # User specified EASYRSA; if vars exists, use it ONLY if [ "$EASYRSA" ]; then if [ -f "$EASYRSA"/vars ]; then set_var EASYRSA_VARS_FILE "$EASYRSA"/vars fi fi # Default PKI; if vars exists, use it ONLY if [ -f "$PWD"/pki/vars ] && \ [ -z "$EASYRSA_PKI" ] && [ -z "$EASYRSA" ] then # Prevent vars from changing 'expected' PKI. # A vars in the PKI MUST always imply EASYRSA_PKI expected_EASYRSA="$PWD" expected_EASYRSA_PKI="$PWD"/pki set_var EASYRSA_VARS_FILE "$PWD"/pki/vars fi # Default working dir; if vars exists, use it ONLY if [ -f "$PWD"/vars ]; then set_var EASYRSA_VARS_FILE "$PWD"/vars fi fi # if select_vars failed to find a vars file if [ -z "$EASYRSA_VARS_FILE" ]; then verbose "select_vars; No vars" return 1 fi verbose "select_vars; selected $EASYRSA_VARS_FILE" } # => select_vars() # Source a vars file source_vars() { # File to be sourced target_file="$1" # target_file MUST exist [ -f "$target_file" ] || user_error "\ Missing vars file: * $target_file" # Sanitize target_file if grep -q \ -e 'EASYRSA_PASSIN' -e 'EASYRSA_PASSOUT' \ -e '[^(]`[^)]' \ -e 'export ' \ -e 'unset ' \ "$target_file" then # here we go .. err_msg="\ These problems have been found in your 'vars' settings:${NL}" # No passwords! if grep -q \ -e 'EASYRSA_PASSIN' -e 'EASYRSA_PASSOUT' \ "$target_file" then err_msg="${err_msg} Use of 'EASYRSA_PASSIN' or 'EASYRSA_PASSOUT': Storing password information in the 'vars' file is not permitted." fi # No backticks if grep -q \ -e '[^(]`[^)]' \ "$target_file" then err_msg="${err_msg} Use of unsupported characters: These characters are not supported: \` backtick" fi # No export if grep -q \ -e 'export ' \ "$target_file" then err_msg="${err_msg} Use of 'export': Remove 'export' or replace it with 'set_var'." fi # No unset if grep -q \ -e 'unset ' \ "$target_file" then err_msg="${err_msg} Use of 'unset': Remove 'unset' ('force_set_var' may also work)." fi # Fatal error user_error "${err_msg}${NL} Please, correct these errors and try again." fi # Enable sourcing target_file # shellcheck disable=SC2034 # EASYRSA_CALLER appears unused EASYRSA_CALLER=1 # Test sourcing target_file in a subshell # shellcheck disable=1090 # can't follow - source_vars() if ( . "$target_file" ); then # Source target_file now # shellcheck disable=1090 # can't follow - source_vars() . "$target_file" || \ die "Failed to source the '$target_file' file." else die "Failed to dry-run the '$target_file' file." fi verbose "source_vars; sourced $target_file" unset -v EASYRSA_CALLER target_file } # => source_vars() # Set defaults default_vars() { # Set defaults, preferring existing env-vars if present set_var EASYRSA "$PWD" set_var EASYRSA_OPENSSL openssl set_var EASYRSA_PKI "$EASYRSA/pki" set_var EASYRSA_DN cn_only set_var EASYRSA_REQ_COUNTRY "US" set_var EASYRSA_REQ_PROVINCE "California" set_var EASYRSA_REQ_CITY "San Francisco" set_var EASYRSA_REQ_ORG "Copyleft Certificate Co" set_var EASYRSA_REQ_EMAIL me@example.net set_var EASYRSA_REQ_OU "My Organizational Unit" set_var EASYRSA_REQ_SERIAL "" set_var EASYRSA_ALGO rsa set_var EASYRSA_KEY_SIZE 2048 case "$EASYRSA_ALGO" in rsa) : # ok # default EASYRSA_KEY_SIZE must always be set # it must NOT be set selectively because it is # present in the SSL config file ;; ec) set_var EASYRSA_CURVE secp384r1 ;; ed) set_var EASYRSA_CURVE ed25519 ;; *) user_error "\ Algorithm '$EASYRSA_ALGO' is invalid: Must be 'rsa', 'ec' or 'ed'" esac set_var EASYRSA_CA_EXPIRE 3650 set_var EASYRSA_CERT_EXPIRE 825 set_var \ EASYRSA_PRE_EXPIRY_WINDOW 90 set_var EASYRSA_CRL_DAYS 180 set_var EASYRSA_NS_SUPPORT no set_var EASYRSA_NS_COMMENT \ "Easy-RSA (~VER~) Generated Certificate" set_var EASYRSA_TEMP_DIR "$EASYRSA_PKI" set_var EASYRSA_REQ_CN ChangeMe set_var EASYRSA_DIGEST sha256 set_var EASYRSA_KDC_REALM "CHANGEME.EXAMPLE.COM" set_var EASYRSA_MAX_TEMP 1 } # => default_vars() # Validate expected values for EASYRSA and EASYRSA_PKI validate_default_vars() { unset -v unexpected_error # Keep checks separate # EASYRSA if [ "$expected_EASYRSA" ]; then [ "$expected_EASYRSA" = "$EASYRSA" ] || \ unexpected_error="\ EASYRSA: $EASYRSA Expected: $expected_EASYRSA" fi # EASYRSA_PKI if [ "$expected_EASYRSA_PKI" ]; then if [ "$expected_EASYRSA_PKI" = "$EASYRSA_PKI" ]; then : # ok else if [ "$unexpected_error" ]; then # Add a new-line Extra separator, for clarity unexpected_error="${unexpected_error}${NL}${NL}" fi unexpected_error="${unexpected_error}\ EASYRSA_PKI: $EASYRSA_PKI Expected: $expected_EASYRSA_PKI" fi fi # Return no error [ -z "$unexpected_error" ] && return # This is an almost unacceptable error invalid_vars=1 [ "$quiet_vars" ] || user_error "\ The values in the vars file have unexpectedly changed the values for EASYRSA and/or EASYRSA_PKI. The default pki/vars file is forbidden to change these values. vars-file: $EASYRSA_VARS_FILE ${unexpected_error}" } # => validate_default_vars() # Create PKI lock-file, as necessary request_lock_file() { [ "$EASYRSA_NO_LOCKFILE" ] && return # Create lock-file create_lock_file_error= if [ -d "${EASYRSA_PKI}" ]; then lock_file="${EASYRSA_PKI}"/lock.file if create_lock_file "$lock_file"; then verbose "request_lock_file; lock-file CREATED $lock_file" else easyrsa_exit_with_error=17 if [ -f "$lock_file" ]; then # Do not remove existing lock-file in batch mode if [ "$EASYRSA_BATCH" ]; then create_lock_file_error=1 cleanup fi # Allow user to remove lock-file confirm "Remove existing lock-file ? " yes " ERROR: lock-file exists! If you are certain that easyrsa is not being used by another process then you can safely delete the existing lock-file and try running the easyrsa command again." confirm " *** SECOND WARNING *** Remove existing lock-file ? " yes " ========================================${NL}" # remove_lock_file by FORCE remove_lock_file "$lock_file" FORCE || \ die "Failed to FORCE remove lock-file!" # quit now, force retry notice "\ lock-file removed - Please try running the easyrsa command again." cleanup else die "Failed to create lock-file (permissions?)" fi fi else verbose "request_lock_file; lock-file not required." fi } # => request_lock_file() # Verify working environment verify_working_env() { fn_name="$cmd: verify_working_env" # Intelligent env-var detection and auto-loading: # Select vars file as EASYRSA_VARS_FILE # then source the vars file, if found # otherwise, ignore no vars file if select_vars; then [ "$quiet_vars" ] || information "\ Using Easy-RSA 'vars' configuration: * $EASYRSA_VARS_FILE" source_vars "$EASYRSA_VARS_FILE" fi # then set defaults default_vars # Check for unexpected changes to EASYRSA or EASYRSA_PKI # https://github.com/OpenVPN/easy-rsa/issues/1006 validate_default_vars # Check for conflicting input options mutual_exclusions # Verify SSL Lib - One time ONLY verify_ssl_lib # For commands which 'require a PKI' and PKI exists if [ "$require_pki" ]; then # Verify PKI is initialised verify_pki_init # Opportunist PKI lock-file request_lock_file # Temp dir session and throw-away temp-file secure_session easyrsa_mktemp test_temp_file # global safe ssl cnf temp write_global_safe_ssl_cnf_tmp # Verify selected algorithm and parameters verify_algo_params # Verify CA is initialised if [ "$require_ca" ]; then verify_ca_init fi else # For commands that do not require a PKI # but do require a temp-dir, eg. 'write' # If there is a valid temp-dir: # Create temp-session and openssl-easyrsa.cnf (Temp) now if [ -d "$EASYRSA_TEMP_DIR" ]; then # Temp dir session and throw-away temp-file secure_session easyrsa_mktemp test_temp_file # global safe ssl cnf temp write_global_safe_ssl_cnf_tmp fi fi # Find x509-types, openssl-easyrsa.cnf # and easyrsa-tools.lib locate_support_files # Save original EASYRSA_SSL_CONF original_ssl_cnf="$EASYRSA_SSL_CONF" fn_name="$cmd" } # => verify_working_env() # variable assignment by indirection. # Sets '$1' as the value contained in '$2' # and exports (may be blank) set_var() { [ -z "$*" ] && return [ -z "$3" ] || \ user_error "set_var - excess input '$*'" case "$1" in *=*) user_error "set_var - var '$1'" esac eval "export \"$1\"=\"\${$1-$2}\"" && return die "set_var - eval '$*'" } # => set_var() # sanitize and set var # nix.sh/win.sh/busybox.sh never return error from unset # when an invalid variable name 'a=b' is used with a value # to set, eg. 'c'; This causes EasyRSA to execute: # eval "export a=b=c". 'set_var EASYRSA_PKI=pki' results in # $EASYRSA_PKI being set to 'pki=pki-', without error! # Guard against this possible user error with 'case'. force_set_var() { [ -z "$3" ] || \ user_error "force_set_var - excess input '$*'" case "$1" in *=*) user_error "force_set_var - var '$1'" esac # force unsetting $1; Guard unset with '|| die', just in case unset -v "$1" || die "force_set_var - unset '$1'" # Allow empty value to unset variable by returning [ "$2" ] || return 0 set_var "$1" "$2" && return die "force_set_var - set_var '$*'" } # => force_set_var() # global Safe SSL conf file, for use by any SSL lib write_global_safe_ssl_cnf_tmp() { global_safe_ssl_cnf_tmp= easyrsa_mktemp global_safe_ssl_cnf_tmp write_legacy_file_v2 safe-cnf "$global_safe_ssl_cnf_tmp" \ overwrite || die "verify_working_env - write safe-cnf" export OPENSSL_CONF="$global_safe_ssl_cnf_tmp" verbose "\ write_global_safe_ssl_cnf_tmp; GLOBAL OPENSSL_CONF = $OPENSSL_CONF" } # => write_global_safe_ssl_cnf_tmp() # Create as needed: $EASYRSA_SSL_CONF pki/openssl-easyrsa.cnf # If the existing file has a known hash then use temp-file. # Otherwise, use the file in place. write_easyrsa_ssl_cnf_tmp() { # If EASYRSA_SSL_CONF is undefined then use default [ "$EASYRSA_SSL_CONF" ] || set_var \ EASYRSA_SSL_CONF "$EASYRSA_PKI"/openssl-easyrsa.cnf if [ -f "$EASYRSA_SSL_CONF" ]; then verbose "write_easyrsa_ssl_cnf_tmp; SSL config EXISTS" # Set known hashes # 3.1.7 -> Current known_file_317="\ 13ca05f031d58c5e2912652b33099ce9\ ac05f49595e5d5fe96367229e3ce070c" # 3.1.5 -> 3.1.6 known_file_315="\ 87d51ca0db1cc0ac3cc2634792fc5576\ e0034ebf9d546de11674b897514f3afb" # 3.1.0 -> 3.1.4 known_file_310="\ 5455947df40f01f845bf79c1e89f102c\ 628faaa65d71a6512d0e17bdd183feb0" # 3.0.8 -> 3.0.9 known_file_308="\ 1cc6a1de93ca357b5c364aa0fa2c4bea\ f97425686fa1976d436fa31f550641aa" # Built-in here-doc 3.2.0 known_heredoc_320="\ 82439f1860838e28f6270d5d06b17717\ 56db777861e19bf9edc21222f86a310d" # Get file hash file_hash="$( "$EASYRSA_OPENSSL" dgst -sha256 -r \ "$EASYRSA_SSL_CONF" 2>/dev/null )" || die "write_easyrsa_ssl_cnf_tmp - hash malfunction!" # Strip excess SSL info file_hash="${file_hash%% *}" # Compare SSL output case "$file_hash" in *[!1234567890abcdef]*|'') die "write_easyrsa_ssl_cnf_tmp - hash failure!" esac # Check file hash against known hash hash_is_unknown="" case "$file_hash" in "$known_file_317") ;; "$known_file_315") ;; "$known_file_310") ;; "$known_file_308") ;; "$known_heredoc_320") ;; *) # File is unknown or has been changed # leave in place hash_is_unknown=1 esac # Cleanup unset -v file_hash known_heredoc_320 \ known_file_317 \ known_file_315 \ known_file_310 \ known_file_308 # Use the existing file ONLY if [ "$hash_is_unknown" ] || [ "$EASYRSA_FORCE_SAFE_SSL" ] then unset -v hash_is_unknown verbose "write_easyrsa_ssl_cnf_tmp; SSL config UNKNOWN!" # Auto-escape hazardous characters escape_hazard || \ die "easyrsa_openssl - escape_hazard failed" # Rewrite SSL config expand_ssl_config || \ die "easyrsa_openssl - expand_ssl_config failed" return 0 fi # Ignore existing file, prefer to use a temp-file verbose "write_easyrsa_ssl_cnf_tmp; SSL config KNOWN" fi # SET and USE temp-file from here-doc Now # Create temp-file ssl_cnf_tmp= easyrsa_mktemp ssl_cnf_tmp # Write SSL cnf to temp-file write_legacy_file_v2 "$ssl_cnf_type" "$ssl_cnf_tmp" \ overwrite || die "\ write_easyrsa_ssl_cnf_tmp - write $ssl_cnf_type: $ssl_cnf_tmp" # export SSL cnf tmp export EASYRSA_SSL_CONF="$ssl_cnf_tmp" verbose "\ write_easyrsa_ssl_cnf_tmp; $ssl_cnf_type \ - EASYRSA_SSL_CONF = $EASYRSA_SSL_CONF" export OPENSSL_CONF="$EASYRSA_SSL_CONF" verbose "\ write_easyrsa_ssl_cnf_tmp; LOCAL \ - OPENSSL_CONF = $OPENSSL_CONF" } # => write_easyrsa_ssl_cnf_tmp() # Write x509 type file to a temp file write_x509_type_tmp() { # Verify x509-type before redirect case "$1" in COMMON|ca|server|serverClient|client|email|codeSigning|kdc) : # ok ;; selfsign) [ "$2" = batch ] || die "X509 selfsign only allowed internally" ;; *) die "write_x509_type_tmp - unknown type '$1'" esac # $write_x509_file_tmp is returned as the new temp-file, to the caller write_x509_file_tmp= easyrsa_mktemp write_x509_file_tmp write_legacy_file_v2 "$1" "$write_x509_file_tmp" overwrite || \ die "write_x509_type_tmp - write $1" verbose "write_x509_type_tmp; '$1' COMPLETE" } # => write_x509_type_tmp() ############################################################################ # # Create legacy files # # Write ALL legacy files to default name with optional 'overwrite' all_legacy_files_v2() { # Confirm over write if [ "$1" ]; then confirm "${NL} Confirm OVER-WRITE files ? " yes " Warning: 'legacy-hard' will OVER-WRITE all legacy files to default settings. Legacy files: * File: ${EASYRSA_PKI}/openssl-easyrsa.cnf * File: ${EASYRSA_PKI}/vars.example * Dir: ${EASYRSA_PKI}/x509-types/*" verbose "all_legacy_files_v2; over-write ENABLED" else verbose "all_legacy_files_v2; over-write DISABLED" fi # Output directories x509_types_d="$EASYRSA_PKI"/x509-types mkdir -p "$x509_types_d" || die \ "all_legacy_files_v2() - Failed to create '$x509_types_d' directory." # Create x509-types, except selfsign for legacy_type in COMMON ca server serverClient client \ email codeSigning kdc do legacy_target="${x509_types_d}/${legacy_type}" write_legacy_file_v2 "$legacy_type" "$legacy_target" "$1" done # vars.example legacy_type=vars legacy_target="$EASYRSA_PKI"/vars.example write_legacy_file_v2 "$legacy_type" "$legacy_target" "$1" # openssl-easyrsa.cnf legacy_type=ssl-cnf legacy_target="$EASYRSA_PKI"/openssl-easyrsa.cnf write_legacy_file_v2 "$legacy_type" "$legacy_target" "$1" # User notice if [ "$1" ]; then notice "legacy-hard has updated all files." else notice "legacy has updated missing files." fi } # => all_legacy_files_v2() # write legacy files to stdout or user defined file write_legacy_file_v2() { # recursion check write_recursion="$(( write_recursion + 1 ))" if [ "$write_recursion" -gt 1 ]; then print "write recursion" > "$easyrsa_err_log" die "write recursion" fi # Always required 'type' write_type="$1" # Only allowed by internal callers write_file="$2" write_over= [ "$3" = overwrite ] && write_over=1 # Select by type case "$write_type" in ssl-cnf) set_openssl_easyrsa_cnf_vars unexpanded ;; safe-cnf) set_openssl_easyrsa_cnf_vars expanded ;; vars) ;; # This correctly renames 'code-signing' to 'codeSigning' COMMON|ca|server|serverClient|client|codeSigning|email|kdc) ;; selfsign) ;; *) user_error "write - unknown type '$write_type'" esac # If $write_file is given then establish overwrite rules # write legacy data stream to stdout or file if [ -f "$write_file" ]; then if [ "$write_over" ]; then verbose "write_legacy_file_v2; over-write ENABLED for $write_type" create_legacy_stream "$write_type" > "$write_file" || \ die "write failed" [ "$EASYRSA_DEBUG" ] && print \ "### write OVERWRITE: $write_type to $write_file" else # Preserve existing file and continue verbose "write_legacy_file_v2; over-write BLOCKED for $write_type" [ "$EASYRSA_DEBUG" ] && print \ "### write PRESERVE existing: $write_file" fi # $write_file does not exist, so create it elif [ "$write_file" ]; then verbose "write_legacy_file_v2; over-write IGNORED for $write_type" create_legacy_stream "$write_type" > "$write_file" || \ die "write failed" [ "$EASYRSA_DEBUG" ] && print \ "### write NEWFILE: $write_type to $write_file" else # write stream to stdout ONLY create_legacy_stream "$write_type" fi write_recursion="$(( write_recursion - 1 ))" } # => write_legacy_file_v2() # set heredoc variables for openssl-easyrsa.cnf # shellcheck disable=SC2016 # (info): $ don't expand in '' set_openssl_easyrsa_cnf_vars() { case "$1" in expanded) # fully expand ssl-cnf for safe-cnf conf_EASYRSA_dir="$EASYRSA_PKI" conf_EASYRSA_PKI="$EASYRSA_PKI" conf_EASYRSA_DIGEST="$EASYRSA_DIGEST" conf_EASYRSA_KEY_SIZE="$EASYRSA_KEY_SIZE" conf_EASYRSA_DN="$EASYRSA_DN" conf_EASYRSA_REQ_CN="$EASYRSA_REQ_CN" conf_EASYRSA_REQ_COUNTRY="$EASYRSA_REQ_COUNTRY" conf_EASYRSA_REQ_PROVINCE="$EASYRSA_REQ_PROVINCE" conf_EASYRSA_REQ_CITY="$EASYRSA_REQ_CITY" conf_EASYRSA_REQ_ORG="$EASYRSA_REQ_ORG" conf_EASYRSA_REQ_OU="$EASYRSA_REQ_OU" conf_EASYRSA_REQ_EMAIL="$EASYRSA_REQ_EMAIL" conf_EASYRSA_REQ_SERIAL="$EASYRSA_REQ_SERIAL" ;; unexpanded) # write standard ssl-cnf conf_EASYRSA_dir='$dir' conf_EASYRSA_PKI='$ENV::EASYRSA_PKI' conf_EASYRSA_DIGEST='$ENV::EASYRSA_DIGEST' conf_EASYRSA_KEY_SIZE='$ENV::EASYRSA_KEY_SIZE' conf_EASYRSA_DN='$ENV::EASYRSA_DN' conf_EASYRSA_REQ_CN='$ENV::EASYRSA_REQ_CN' conf_EASYRSA_REQ_COUNTRY='$ENV::EASYRSA_REQ_COUNTRY' conf_EASYRSA_REQ_PROVINCE='$ENV::EASYRSA_REQ_PROVINCE' conf_EASYRSA_REQ_CITY='$ENV::EASYRSA_REQ_CITY' conf_EASYRSA_REQ_ORG='$ENV::EASYRSA_REQ_ORG' conf_EASYRSA_REQ_OU='$ENV::EASYRSA_REQ_OU' conf_EASYRSA_REQ_EMAIL='$ENV::EASYRSA_REQ_EMAIL' conf_EASYRSA_REQ_SERIAL='$ENV::EASYRSA_REQ_SERIAL' ;; *) die "set_openssl_easyrsa_cnf_vars - input" esac } # => set_openssl_easyrsa_cnf_vars() # Create x509 type create_legacy_stream() { case "$1" in COMMON) # COMMON is not very useful cat <<- "CREATE_X509_TYPE_COMMON" CREATE_X509_TYPE_COMMON ;; easyrsa) # This could be COMMON but not is not suitable for a CA cat <<- "CREATE_X509_TYPE_EASYRSA" basicConstraints = CA:FALSE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always keyUsage = digitalSignature,keyEncipherment CREATE_X509_TYPE_EASYRSA ;; serverClient) # serverClient create_legacy_stream easyrsa cat <<- "CREATE_X509_TYPE_SERV_CLI" extendedKeyUsage = serverAuth,clientAuth CREATE_X509_TYPE_SERV_CLI ;; server) # server create_legacy_stream easyrsa cat <<- "CREATE_X509_TYPE_SERV" extendedKeyUsage = serverAuth CREATE_X509_TYPE_SERV ;; client) # client create_legacy_stream easyrsa cat <<- "CREATE_X509_TYPE_CLI" extendedKeyUsage = clientAuth CREATE_X509_TYPE_CLI ;; ca) # ca cat <<- "CREATE_X509_TYPE_CA" basicConstraints = CA:TRUE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always keyUsage = cRLSign, keyCertSign CREATE_X509_TYPE_CA ;; selfsign) # selfsign cat <<- "CREATE_X509_TYPE_SELFSIGN" # WARNING: 'selfsign' is NOT a user configurable X509-type file. subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always basicConstraints = CA:TRUE keyUsage = digitalSignature,keyEncipherment CREATE_X509_TYPE_SELFSIGN print "extendedKeyUsage = $selfsign_eku" ;; codeSigning) # codeSigning cat <<- "CREATE_X509_CODE_SIGNING" basicConstraints = CA:FALSE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always extendedKeyUsage = codeSigning keyUsage = digitalSignature CREATE_X509_CODE_SIGNING ;; email) # email cat <<- "CREATE_X509_TYPE_EMAIL" basicConstraints = CA:FALSE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always extendedKeyUsage = emailProtection keyUsage = digitalSignature,keyEncipherment,nonRepudiation CREATE_X509_TYPE_EMAIL ;; kdc) # kdc cat <<- "CREATE_X509_TYPE_KDC" basicConstraints = CA:FALSE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always extendedKeyUsage = 1.3.6.1.5.2.3.5 keyUsage = nonRepudiation,digitalSignature,keyEncipherment,keyAgreement issuerAltName = issuer:copy subjectAltName = otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name [kdc_princ_name] realm = EXP:0,GeneralString:${ENV::EASYRSA_KDC_REALM} principal_name = EXP:1,SEQUENCE:kdc_principal_seq [kdc_principal_seq] name_type = EXP:0,INTEGER:1 name_string = EXP:1,SEQUENCE:kdc_principals [kdc_principals] princ1 = GeneralString:krbtgt princ2 = GeneralString:${ENV::EASYRSA_KDC_REALM} CREATE_X509_TYPE_KDC ;; vars) # vars cat << "CREATE_VARS_EXAMPLE" # Easy-RSA 3 parameter settings # NOTE: If you installed Easy-RSA from your package manager, do not edit # this file in place -- instead, you should copy the entire easy-rsa directory # to another location so future upgrades do not wipe out your changes. # HOW TO USE THIS FILE # # vars.example contains built-in examples to Easy-RSA settings. You MUST name # this file "vars" if you want it to be used as a configuration file. If you # do not, it WILL NOT be automatically read when you call easyrsa commands. # # It is not necessary to use this config file unless you wish to change # operational defaults. These defaults should be fine for many uses without # the need to copy and edit the "vars" file. # # All of the editable settings are shown commented and start with the command # "set_var" -- this means any set_var command that is uncommented has been # modified by the user. If you are happy with a default, there is no need to # define the value to its default. # NOTES FOR WINDOWS USERS # # Paths for Windows *MUST* use forward slashes, or optionally double-escaped # backslashes (single forward slashes are recommended.) This means your path # to the openssl binary might look like this: # "C:/Program Files/OpenSSL-Win32/bin/openssl.exe" # A little housekeeping: DO NOT EDIT THIS SECTION # # Easy-RSA 3.x does not source into the environment directly. # Complain if a user tries to do this: if [ -z "$EASYRSA_CALLER" ]; then echo "You appear to be sourcing an Easy-RSA *vars* file. This is" >&2 echo "no longer necessary and is disallowed. See the section called" >&2 echo "*How to use this file* near the top comments for more details." >&2 return 1 fi # DO YOUR EDITS BELOW THIS POINT # If your OpenSSL command is not in the system PATH, you will need to define # the path here. Normally this means a full path to the executable, otherwise # you could have left it undefined here and the shown default would be used. # # Windows users, remember to use paths with forward-slashes (or escaped # back-slashes.) Windows users should declare the full path to the openssl # binary here if it is not in their system PATH. # #set_var EASYRSA_OPENSSL "openssl" # # This sample is in Windows syntax -- edit it for your path if not using PATH: #set_var EASYRSA_OPENSSL "C:/Program Files/OpenSSL-Win32/bin/openssl.exe" # Windows users, to generate OpenVPN TLS Keys the Openvpn binary must be # defined here. # #set_var EASYRSA_OPENVPN "C:\\Program Files\\Openvpn\\bin\\openvpn.exe" # Define X509 DN mode. # # This is used to adjust which elements are included in the Subject field # as the DN ("Distinguished Name"). Note that in 'cn_only' mode the # Organizational fields, listed further below, are not used. # # Choices are: # cn_only - Use just a commonName value. # org - Use the "traditional" format: # Country/Province/City/Org/Org.Unit/email/commonName # #set_var EASYRSA_DN "cn_only" # Organizational fields (used with "org" mode and ignored in "cn_only" mode). # These are the default values for fields which will be placed in the # certificate. Do not leave any of these fields blank, although interactively # you may omit any specific field by typing the "." symbol (not valid for # email). # # NOTE: The following characters are not supported # in these "Organizational fields" by Easy-RSA: # back-tick (`) # #set_var EASYRSA_REQ_COUNTRY "US" #set_var EASYRSA_REQ_PROVINCE "California" #set_var EASYRSA_REQ_CITY "San Francisco" #set_var EASYRSA_REQ_ORG "Copyleft Certificate Co" #set_var EASYRSA_REQ_EMAIL "me@example.net" #set_var EASYRSA_REQ_OU "My Organizational Unit" # Preserve the Distinguished Name field order # of the certificate signing request # *Only* effective in --dn-mode=org # #set_var EASYRSA_PRESERVE_DN 1 # Set no password mode - This will create the entire PKI without passwords. # This can be better managed by choosing which entity private keys should be # encrypted with the following command line options: # Global option '--no-pass' or command option 'nopass'. # #set_var EASYRSA_NO_PASS 1 # Choose a size in bits for your keypairs. The recommended value is 2048. # Using 2048-bit keys is considered more than sufficient for many years into # the future. Larger keysizes will slow down TLS negotiation and make key/DH # param generation take much longer. Values up to 4096 should be accepted by # most software. Only used when the crypto alg is rsa, see below. # #set_var EASYRSA_KEY_SIZE 2048 # The default crypto mode is rsa; ec can enable elliptic curve support. # Note that not all software supports ECC, so use care when enabling it. # Choices for crypto alg are: (each in lower-case) # * rsa # * ec # * ed # #set_var EASYRSA_ALGO rsa # Define the named curve, used in ec & ed modes: # #set_var EASYRSA_CURVE secp384r1 # In how many days should the root CA key expire? # #set_var EASYRSA_CA_EXPIRE 3650 # In how many days should certificates expire? # #set_var EASYRSA_CERT_EXPIRE 825 # How many days until the Certificate Revokation List will expire. # # IMPORTANT: When the CRL expires, an OpenVPN Server which uses a # CRL will reject ALL new connections, until the CRL is replaced. # #set_var EASYRSA_CRL_DAYS 180 # Random serial numbers by default. # Set to 'no' for the old incremental serial numbers. # #set_var EASYRSA_RAND_SN "yes" # Cut-off window for checking expiring certificates. # #set_var EASYRSA_PRE_EXPIRY_WINDOW 90 # Generate automatic subjectAltName for certificates # #set_var EASYRSA_AUTO_SAN 1 # Add critical attribute to X509 fields: basicConstraints (BC), # keyUsage (KU), extendedKeyUsage (EKU) or SAN # #set_var EASYRSA_BC_CRIT 1 #set_var EASYRSA_KU_CRIT 1 #set_var EASYRSA_EKU_CRIT 1 #set_var EASYRSA_SAN_CRIT 1 # Disable automatic inline files # #set_var EASYRSA_DISABLE_INLINE 1 CREATE_VARS_EXAMPLE ;; ssl-cnf|safe-cnf) # SSL config v3.2.0-1 cat << CREATE_SSL_CONFIG # For use with Easy-RSA 3.0+ and OpenSSL or LibreSSL #################################################################### [ ca ] default_ca = CA_default # The default ca section #################################################################### [ CA_default ] dir = $conf_EASYRSA_PKI # Where everything is kept certs = $conf_EASYRSA_dir # Where the issued certs are kept crl_dir = $conf_EASYRSA_dir # Where the issued crl are kept database = $conf_EASYRSA_dir/index.txt # database index file. new_certs_dir = $conf_EASYRSA_dir/certs_by_serial # default place for new certs. certificate = $conf_EASYRSA_dir/ca.crt # The CA certificate serial = $conf_EASYRSA_dir/serial # The current serial number crl = $conf_EASYRSA_dir/crl.pem # The current CRL private_key = $conf_EASYRSA_dir/private/ca.key # The private key RANDFILE = $conf_EASYRSA_dir/.rand # private random number file x509_extensions = basic_exts # The extensions to add to the cert # A placeholder to handle the --copy-ext feature: #%COPY_EXTS% # Do NOT remove or change this line as --copy-ext support requires it # This allows a V2 CRL. Ancient browsers don't like it, but anything Easy-RSA # is designed for will. In return, we get the Issuer attached to CRLs. crl_extensions = crl_ext # These fields are always configured via the command line. # These fields are removed from this here-doc but retained # in 'openssl-easyrsa.cnf' file, in case something breaks. # default_days is no longer required by Easy-RSA #default_days = \$ENV::EASYRSA_CERT_EXPIRE # how long to certify for # default_crl_days is no longer required by Easy-RSA #default_crl_days = \$ENV::EASYRSA_CRL_DAYS # how long before next CRL default_md = $conf_EASYRSA_DIGEST # use public key default MD preserve = no # keep passed DN ordering # This allows to renew certificates which have not been revoked unique_subject = no # A few different ways of specifying how similar the request should look # For type CA, the listed attributes must be the same, and the optional # and supplied fields are just that :-) policy = policy_anything # For the 'anything' policy, which defines allowed DN fields [ policy_anything ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional serialNumber = optional #################################################################### # Easy-RSA request handling # We key off \$DN_MODE to determine how to format the DN [ req ] default_bits = $conf_EASYRSA_KEY_SIZE default_keyfile = privkey.pem default_md = $conf_EASYRSA_DIGEST distinguished_name = $conf_EASYRSA_DN x509_extensions = easyrsa_ca # The extensions to add to the self signed cert # A placeholder to handle the \$EXTRA_EXTS feature: #%EXTRA_EXTS% # Do NOT remove or change this line as \$EXTRA_EXTS support requires it #################################################################### # Easy-RSA DN (Subject) handling # Easy-RSA DN for cn_only support: [ cn_only ] commonName = Common Name (eg: your user, host, or server name) commonName_max = 64 commonName_default = $conf_EASYRSA_REQ_CN # Easy-RSA DN for org support: [ org ] countryName = Country Name (2 letter code) countryName_default = $conf_EASYRSA_REQ_COUNTRY countryName_min = 2 countryName_max = 2 stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = $conf_EASYRSA_REQ_PROVINCE localityName = Locality Name (eg, city) localityName_default = $conf_EASYRSA_REQ_CITY 0.organizationName = Organization Name (eg, company) 0.organizationName_default = $conf_EASYRSA_REQ_ORG organizationalUnitName = Organizational Unit Name (eg, section) organizationalUnitName_default = $conf_EASYRSA_REQ_OU commonName = Common Name (eg: your user, host, or server name) commonName_max = 64 commonName_default = $conf_EASYRSA_REQ_CN emailAddress = Email Address emailAddress_default = $conf_EASYRSA_REQ_EMAIL emailAddress_max = 64 serialNumber = Serial-number (eg, device serial-number) serialNumber_default = $conf_EASYRSA_REQ_SERIAL #################################################################### # Easy-RSA cert extension handling # This section is effectively unused as the main script sets extensions # dynamically. This core section is left to support the odd usecase where # a user calls openssl directly. [ basic_exts ] basicConstraints = CA:FALSE subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer:always # The Easy-RSA CA extensions [ easyrsa_ca ] # PKIX recommendations: subjectKeyIdentifier=hash authorityKeyIdentifier=keyid:always,issuer:always # This could be marked critical, but it's nice to support reading by any # broken clients who attempt to do so. basicConstraints = CA:true # Limit key usage to CA tasks. If you really want to use the generated pair as # a self-signed cert, comment this out. keyUsage = cRLSign, keyCertSign # nsCertType omitted by default. Let's try to let the deprecated stuff die. # nsCertType = sslCA # A placeholder to handle the \$X509_TYPES and CA extra extensions \$EXTRA_EXTS: #%CA_X509_TYPES_EXTRA_EXTS% # Do NOT remove or change this line as \$X509_TYPES and EXTRA_EXTS demands it # CRL extensions. [ crl_ext ] # Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. # issuerAltName=issuer:copy authorityKeyIdentifier=keyid:always,issuer:always CREATE_SSL_CONFIG ;; *) die "create_legacy_stream: unknown type '$1'" esac } # => create_legacy_stream() # Version information print_version() { ssl_version="$( "${EASYRSA_OPENSSL:-openssl}" version 2>/dev/null )" cat << VERSION_TEXT EasyRSA Version Information Version: $EASYRSA_version Generated: ~DATE~ SSL Lib: ${ssl_version:-undefined} Git Commit: ~GITHEAD~ Source Repo: https://github.com/OpenVPN/easy-rsa VERSION_TEXT } # => print_version() ######################################## # Invocation entry point: EASYRSA_version="~VER~" NL=' ' # Register cleanup on EXIT trap 'cleanup $?' EXIT # When SIGHUP, SIGINT, SIGQUIT, SIGABRT and SIGTERM, # explicitly exit to signal EXIT (non-bash shells) trap "exit 1" 1 trap "exit 2" 2 trap "exit 3" 3 trap "exit 6" 6 trap "exit 15" 15 # Get host details - No configurable input allowed detect_host # Initialisation requirements unset -v \ OPENSSL_CONF \ verify_ssl_lib_ok ssl_batch \ secured_session \ working_safe_ssl_conf working_safe_org_conf \ alias_days text \ prohibit_no_pass \ invalid_vars \ local_request error_build_full_cleanup \ selfsign_eku \ internal_batch mv_temp_error \ easyrsa_exit_with_error error_info \ write_recursion require_pki require_ca quiet_vars # Used by build-ca->cleanup to restore prompt # after user interrupt when using manual password prompt_restore=0 # Sequential temp-file counter mktemp_counter=0 # Parse options while :; do # Reset per pass flags unset -v opt val \ is_empty empty_ok number_only zero_allowed # Separate option from value: opt="${1%%=*}" val="${1#*=}" # Empty values are not allowed unless expected # eg: '--batch' [ "$opt" = "$val" ] && is_empty=1 # eg: '--pki-dir=' [ "$val" ] || is_empty=1 case "$opt" in --days) number_only=1 zero_allowed=1 # Set the appropriate date variable # when called by command later alias_days="$val" ;; --startdate) export EASYRSA_START_DATE="$val" ;; --enddate) export EASYRSA_END_DATE="$val" ;; --pki-dir|--pki) export EASYRSA_PKI="$val" ;; --no-lockfile) empty_ok=1 export EASYRSA_NO_LOCKFILE=1 ;; --tmp-dir) export EASYRSA_TEMP_DIR="$val" ;; --umask) export EASYRSA_UMASK="$val" ;; --no-umask) empty_ok=1 export EASYRSA_NO_UMASK=1 ;; --ssl-cnf|--ssl-conf) export EASYRSA_SSL_CONF="$val" ;; --keep-tmp) export EASYRSA_KEEP_TEMP="$val" ;; --use-algo) export EASYRSA_ALGO="$val" ;; --keysize) number_only=1 export EASYRSA_KEY_SIZE="$val" ;; --curve) export EASYRSA_CURVE="$val" ;; --dn-mode) export EASYRSA_DN="$val" ;; --req-cn) export EASYRSA_REQ_CN="$val" ;; --digest) export EASYRSA_DIGEST="$val" ;; --req-c) empty_ok=1 export EASYRSA_REQ_COUNTRY="$val" ;; --req-st) empty_ok=1 export EASYRSA_REQ_PROVINCE="$val" ;; --req-city) empty_ok=1 export EASYRSA_REQ_CITY="$val" ;; --req-org) empty_ok=1 export EASYRSA_REQ_ORG="$val" ;; --req-email) empty_ok=1 export EASYRSA_REQ_EMAIL="$val" ;; --req-ou) empty_ok=1 export EASYRSA_REQ_OU="$val" ;; --req-serial) empty_ok=1 export EASYRSA_REQ_SERIAL="$val" ;; --ns-cert) empty_ok=1 [ "$is_empty" ] && unset -v val export EASYRSA_NS_SUPPORT="${val:-yes}" ;; --ns-comment) empty_ok=1 export EASYRSA_NS_COMMENT="$val" ;; --batch) empty_ok=1 export EASYRSA_BATCH=1 ;; -s|--silent) empty_ok=1 export EASYRSA_SILENT=1 ;; --sbatch|--silent-batch) empty_ok=1 export EASYRSA_SILENT=1 export EASYRSA_BATCH=1 ;; --verbose) empty_ok=1 export EASYRSA_VERBOSE=1 ;; -S|--silent-ssl) empty_ok=1 export EASYRSA_SILENT_SSL=1 ;; --force-safe-ssl) empty_ok=1 export EASYRSA_FORCE_SAFE_SSL=1 ;; --nopass|--no-pass) empty_ok=1 export EASYRSA_NO_PASS=1 ;; --passin) export EASYRSA_PASSIN="$val" ;; --passout) export EASYRSA_PASSOUT="$val" ;; --rawca|--raw-ca) empty_ok=1 export EASYRSA_RAW_CA=1 ;; --text) empty_ok=1 export EASYRSA_TEXT_ON=1 ;; --notext|--no-text) empty_ok=1 export EASYRSA_TEXT_OFF=1 ;; --subca-len) number_only=1 zero_allowed=1 export EASYRSA_SUBCA_LEN="$val" ;; --vars) export EASYRSA_VARS_FILE="$val" ;; --copy-ext) empty_ok=1 export EASYRSA_CP_EXT=1 ;; --subject-alt-name|--san) # This allows --san to be used multiple times if [ "$EASYRSA_SAN" ]; then EASYRSA_SAN="$EASYRSA_SAN, $val" else EASYRSA_SAN="$val" fi ;; --auto-san) empty_ok=1 export EASYRSA_AUTO_SAN=1 ;; --san-crit|--san-critical) empty_ok=1 export EASYRSA_SAN_CRIT='critical,' ;; --bc-crit|--bc-critical) empty_ok=1 export EASYRSA_BC_CRIT=1 ;; --ku-crit|--ku-critical) empty_ok=1 export EASYRSA_KU_CRIT=1 ;; --eku-crit|--eku-critical) empty_ok=1 export EASYRSA_EKU_CRIT=1 ;; --new-subj|--new-subject) export EASYRSA_NEW_SUBJECT="$val" ;; --usefn) export EASYRSA_P12_FR_NAME="$val" ;; --version) shift "$#" set -- "$@" "version" break ;; -h|--help|--usage) shift "$#" set -- "$@" "help" break ;; -*) user_error "\ Unknown option '$opt'. Run 'easyrsa help options' for option help." ;; *) break esac # fatal error when no value was provided if [ "$is_empty" ]; then [ "$empty_ok" ] || \ user_error "Missing value to option: $opt" fi # fatal error when a number is expected but not provided if [ "$number_only" ]; then case "$val" in (0) # Allow zero only [ "$zero_allowed" ] || \ user_error "$opt - Number expected: '$val'" ;; (*[!1234567890]*|0*) user_error "$opt - Number expected: '$val'" esac fi shift done # Be secure with a restrictive umask [ "$EASYRSA_NO_UMASK" ] || umask "${EASYRSA_UMASK:=077}" # option dependencies # Add full --san to extra extensions if [ "$EASYRSA_SAN" ]; then EASYRSA_EXTRA_EXTS="\ $EASYRSA_EXTRA_EXTS subjectAltName = ${EASYRSA_SAN_CRIT}${EASYRSA_SAN}" fi # Set cmd now cmd="$1" [ "$1" ] && shift # scrape off command # Hand off to the function responsible # ONLY verify_working_env() for valid commands case "$cmd" in init-pki|clean-all) require_pki=""; require_ca=""; verify_working_env init_pki "$@" ;; build-ca) require_pki=1; require_ca=""; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CA_EXPIRE="$alias_days" build_ca "$@" ;; self-sign-server) require_pki=1; require_ca=""; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CERT_EXPIRE="$alias_days" self_sign server "$@" ;; self-sign-client) require_pki=1; require_ca=""; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CERT_EXPIRE="$alias_days" self_sign client "$@" ;; self*) user_error "Self-sign syntax example: 'self-sign-server foo'" ;; gen-dh) require_pki=1; require_ca=""; verify_working_env gen_dh ;; gen-req) require_pki=1; require_ca=""; verify_working_env gen_req "$@" ;; sign|sign-req) require_pki=1; require_ca=1; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CERT_EXPIRE="$alias_days" sign_req "$@" ;; build-client-full) require_pki=1; require_ca=1; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CERT_EXPIRE="$alias_days" build_full client "$@" ;; build-server-full) require_pki=1; require_ca=1; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CERT_EXPIRE="$alias_days" build_full server "$@" ;; build-serverClient-full) require_pki=1; require_ca=1; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CERT_EXPIRE="$alias_days" build_full serverClient "$@" ;; gen-crl) require_pki=1; require_ca=1; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CRL_DAYS="$alias_days" gen_crl ;; revoke|revoke-issued) require_pki=1; require_ca=1; verify_working_env # Only move req and key if revoking an issued cert # renewed certs want to keep the req/key for further renewal # manually expired certs are intended to be renewed revoke_move_req_and_key=1 revoke 'issued' "$@" ;; revoke-expired) require_pki=1; require_ca=1; verify_working_env revoke_move_req_and_key= revoke 'expired' "$@" ;; revoke-renewed) require_pki=1; require_ca=1; verify_working_env revoke_move_req_and_key= revoke 'renewed/issued' "$@" ;; import-req) require_pki=1; require_ca=""; verify_working_env import_req "$@" ;; expire) require_pki=1; require_ca=1; verify_working_env expire_cert "$@" ;; inline) require_pki=1; require_ca=""; verify_working_env inline_file "$@" ;; export-p12) require_pki=1; require_ca=""; verify_working_env export_pkcs p12 "$@" ;; export-p7) require_pki=1; require_ca=""; verify_working_env export_pkcs p7 "$@" ;; export-p8) require_pki=1; require_ca=""; verify_working_env export_pkcs p8 "$@" ;; export-p1) require_pki=1; require_ca=""; verify_working_env export_pkcs p1 "$@" ;; set-pass|set-rsa-pass|set-ec-pass|set-ed-pass) require_pki=1; require_ca=""; verify_working_env set_pass "$@" ;; update-db) require_pki=1; require_ca=1; verify_working_env update_db ;; show-req) require_pki=1; require_ca=""; verify_working_env show req "$@" ;; show-cert) require_pki=1; require_ca=1; verify_working_env show cert "$@" ;; show-crl) require_pki=1; require_ca=1; verify_working_env show crl crl ;; show-ca) require_pki=1; require_ca=1; verify_working_env show_ca "$@" ;; show-host) require_pki=""; require_ca=""; verify_working_env show_host "$@" ;; renew-ca) require_pki=1; require_ca=1; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CA_EXPIRE="$alias_days" renew_ca_cert "$@" ;; renew) require_pki=1; require_ca=1; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_CERT_EXPIRE="$alias_days" renew "$@" ;; show-expire) require_pki=1; require_ca=1; verify_working_env [ -z "$alias_days" ] || \ export EASYRSA_PRE_EXPIRY_WINDOW="$alias_days" status expire "$@" ;; show-revoke) require_pki=1; require_ca=1; verify_working_env status revoke "$@" ;; show-renew) require_pki=1; require_ca=1; verify_working_env status renew "$@" ;; verify-cert) require_pki=1; require_ca=1; verify_working_env # Called with --batch, this will return error # when the certificate fails verification. # Therefore, on error, exit with error. verify_cert "$@" || easyrsa_exit_with_error=1 ;; gen-tls-auth|gen-tls-auth-*) require_pki=1; require_ca=""; verify_working_env tls_key_gen tls-auth "$@" ;; gen-tls-crypt|gen-tls-crypt-*) require_pki=1; require_ca=""; verify_working_env tls_key_gen tls-crypt "$@" ;; gen-tls-cryptv2|gen-tls-cryptv2-*) require_pki=1; require_ca=""; verify_working_env tls_key_gen tls-crypt-v2 "$@" ;; write) # Write legacy files to write_dir # or EASYRSA_PKI or EASYRSA case "$1" in legacy) require_pki=1; require_ca=""; verify_working_env # over-write NO all_legacy_files_v2 ;; legacy-hard) require_pki=1; require_ca=""; verify_working_env # over-write YES all_legacy_files_v2 overwrite ;; *) # Only allow 'type' on command line # Internally, overwrite and file-name is allowed write_legacy_file_v2 "$1" && unset -v EASYRSA_VERBOSE esac ;; serial|check-serial) require_pki=1; require_ca=1; verify_working_env # Called with --batch, this will return error # when the serial number is not unique. # Therefore, on error, exit with error. check_serial_unique "$@" || \ easyrsa_exit_with_error=1 ;; display-dn) require_pki=""; require_ca=""; verify_working_env display_dn "$@" ;; x509-eku|show-eku) require_pki=""; require_ca=""; verify_working_env ssl_cert_x509v3_eku "$@" || \ easyrsa_exit_with_error=1 ;; rand|random) require_pki=""; require_ca=""; verify_working_env easyrsa_random "$1" ;; help) cmd_help "$1" ;; '') require_pki=""; require_ca=""; verify_working_env default_overview ;; version) print_version ;; *) user_error "\ Unknown command '$cmd'. Run without commands for usage help." esac # Check for untrapped errors # shellcheck disable=SC2181 # Quote expand - pre-cleanup $? if [ $? = 0 ]; then # Do 'cleanup ok' on successful completion cleanup ok fi # Otherwise, exit with error print "Untrapped error detected!" cleanup # vim: ft=sh nu ai sw=8 ts=8 noet