rewrite #5

Merged
jdubois merged 89 commits from dev into master 2022-04-14 17:20:36 +02:00
6 changed files with 1067 additions and 311 deletions

51
CHANGELOG.md Normal file
View file

@ -0,0 +1,51 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
### Changed
### Fixed
### Removed
### Security
## [22.04] 2022-04-14
### Added
* Create a changelog
* Add a version number and `version` command
* Accept a `password-file` command line option to read password from a file
* Accept `--days` and `--end-date` command line options
* CA key length is configurable (minimum 4096)
* Add `--non-interactive` command line option
* Add `--replace-existing` command line option
* Copy files if destination exists
* Generate the CRL file after initialization of the CA
* `cert-expirations.sh` script to print out certificates expiration dates
### Changed
* Rename internal function usage() to show_usage()
* Split show_usage() for each subcommand
* More readable variable names
* verify_ca_password() looks for a previously set password and verifies it
* Extract cert_end_date() function
* Extract is_user() and is_group() functions
* Extract ask_user_password() function
* Extract variables for files
* Use inline pass phrase arguments
* Create files with a human readable date instead of epoch
* Remove "set -e" and add many return code checks
* Prevent use of uninitialized variables
### Fixed
* Check on $USER was always true

View file

@ -3,6 +3,11 @@
This script is a wrapper around OpenSSL to manage a small This script is a wrapper around OpenSSL to manage a small
[PKI](https://en.wikipedia.org/wiki/Public_key_infrastructure). [PKI](https://en.wikipedia.org/wiki/Public_key_infrastructure).
## Contribution
After an update of this repo and if everything is working fine, some files must
be copied to [ansible-roles/openvpn](https://gitea.evolix.org/evolix/ansible-roles/src/branch/unstable/openvpn/files/shellpki)
## Install ## Install
### Debian ### Debian
@ -49,47 +54,87 @@ proto udp
remote ovpn.example.com 1194 remote ovpn.example.com 1194
nobind
user nobody
group nogroup
persist-key persist-key
persist-tun persist-tun
cipher AES-256-CBC cipher AES-256-GCM
~~~ ~~~
## Usage ## Usage
~~~ ~~~
Usage: ./shellpki <subcommand> [options] [CommonName] Usage: shellpki <subcommand> [options] [CommonName]
~~~ ~~~
Initialize PKI (create CA key and self-signed cert) : Initialize PKI (create CA key and self-signed certificate) :
~~~ ~~~
./shellpki init <commonName_for_CA> shellpki init [options] <commonName_for_CA>
Options
--non-interactive do not prompt the user, and exit if an error occurs
~~~ ~~~
Create a client cert with key and CSR directly generated on server Create a client certificate with key and CSR directly generated on server :
(use -p for set a password on client key) :
~~~ ~~~
./shellpki create [-p] <commonName> shellpki create [options] <commonName>
Options
-f, --file, --csr-file create a client certificate from a CSR (doesn't need key)
-p, --password prompt the user for a password to set on the client key
--password-file if provided with a path to a readable file, the first line is read and set as password on the client key
--days specify how many days the certificate should be valid
--end-date specify until which date the certificate should be valid, in YYYY/MM/DD hh:mm:ss format, UTC +0
--non-interactive do not prompt the user, and exit if an error occurs
--replace-existing if the certificate already exists, revoke it before creating a new one
~~~ ~~~
Create a client cert from a CSR (doesn't need key) : Revoke a client certificate :
~~~ ~~~
./shellpki create -f <path> shellpki revoke [options] <commonName>
Options
--non-interactive do not prompt the user, and exit if an error occurs
~~~ ~~~
Revoke a client cert with is commonName (CN) : List all certificates :
~~~ ~~~
./shellpki revoke <commonName> shellpki list <options>
Options
-a, --all list all certificates : valid and revoked ones
-v, --valid list all valid certificates
-r, --revoked list all revoked certificates
~~~ ~~~
List all actually valid commonName (CN) : Check expiration date of valid certificates :
~~~ ~~~
./shellpki list shellpki check
~~~
Run OCSP_D server :
~~~
shellpki ocsp <ocsp_uri:ocsp_port>
~~~
Show version :
~~~
shellpki version
~~~
Show help :
~~~
shellpki help
~~~ ~~~
## License ## License

28
cert-expirations.sh Normal file
View file

@ -0,0 +1,28 @@
#!/bin/sh
VERSION="22.04"
carp=$(/sbin/ifconfig carp0 2>/dev/null | grep 'status' | cut -d' ' -f2)
if [ "$carp" = "backup" ]; then
exit 0
fi
echo "Warning : all times are in UTC !\n"
echo "CA certificate:"
openssl x509 -enddate -noout -in /etc/shellpki/cacert.pem \
| cut -d '=' -f 2 \
| sed -e "s/^\(.*\)\ \(20..\).*/- \2 \1/"
echo ""
echo "Client certificates:"
cat /etc/shellpki/index.txt \
| grep ^V \
| awk -F "/" '{print $1,$5}' \
| awk '{print $2,$5}' \
| sed 's/CN=//' \
| sed -E 's/([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})([[:digit:]]{2})Z (.*)/- 20\1 \2 \3 \4:\5:\6 \7/' \
| awk '{if ($3 == "01") $3="Jan"; else if ($3 == "02") $3="Feb"; else if ($3 == "03") $3="Mar"; else if ($3 == "04") $3="Apr"; else if ($3 == "05") $3="May"; else if ($3 == "06") $3="Jun"; else if ($3 == "07") $3="Jul"; else if ($3 == "08") $3="Aug"; else if ($3 == "09") $3="Sep"; else if ($3 == "10") $3="Oct"; else if ($3 == "11") $3="Nov"; else if ($3 == "12") $3="Dec"; print $0;}' \
| sort -n -k 2 -k 3M -k 4

21
cn-validation.sh Normal file
View file

@ -0,0 +1,21 @@
#!/bin/sh
#
# cn-validation.sh is a client-connect script for OpenVPN server
# When connecting using the PAM plugin, it allow clients to connect only if their CN is equal to their UNIX username
#
# You need this parameters in your's server config :
#
# script-security 2
# client-connect <path-to-cn-filter>/cn-validation.sh
#
set -u
if [ "${common_name}" = "${username}" ]; then
logger -i -t openvpn-cn-validation -p auth.info "Accepted login for ${common_name} from ${trusted_ip} port ${trusted_port}"
exit 0
else
logger -i -t openvpn-cn-validation -p auth.notice "Failed login for CN ${common_name} / username ${username} from ${trusted_ip} port ${trusted_port}"
fi
exit 1

View file

@ -1,3 +1,5 @@
# VERSION="22.04"
[ ca ] [ ca ]
default_ca = CA_default default_ca = CA_default

1205
shellpki
View file

@ -1,124 +1,116 @@
#!/bin/sh #!/bin/sh
# #
# shellpki is a wrapper around openssl to manage a small PKI # shellpki is a wrapper around OpenSSL to manage a small PKI
# #
set -e set -u
Review

This cause somes bugs, especially with "list" options.

Whit "set -u" :

root@test-vpn:~# shellpki list -a
/usr/local/sbin/shellpki: 775: 1: parameter not set

root@test-vpn:~# shellpki list -a anything
test-vpn.evolix.net

Without "set -u" :

root@test-vpn:~# shellpki list -a
test-vpn.evolix.net

This bug is also present on the current master version if I add "set -u" but I cannot find the reason.
Not sure if some code need a fix or not, but I think we should at least remove "set -u".

This option also cause the error messages added in the code to handle a missing variable to not be displayed.

This cause somes bugs, especially with "list" options. Whit "set -u" : ``` root@test-vpn:~# shellpki list -a /usr/local/sbin/shellpki: 775: 1: parameter not set root@test-vpn:~# shellpki list -a anything test-vpn.evolix.net ``` Without "set -u" : ``` root@test-vpn:~# shellpki list -a test-vpn.evolix.net ``` This bug is also present on the current master version if I add "set -u" but I cannot find the reason. Not sure if some code need a fix or not, but I think we should at least remove "set -u". This option also cause the error messages added in the code to handle a missing variable to not be displayed.
Review

I'm pretty sure we can find a way to keep the security added by set -u and fix this.

I'm pretty sure we can find a way to keep the security added by `set -u` and fix this.
init() { VERSION="22.04"
umask 0177
[ -d "${CADIR}" ] || mkdir -m 0750 "${CADIR}" show_version() {
[ -d "${CRTDIR}" ] || mkdir -m 0750 "${CRTDIR}" cat <<END
[ -f "${INDEX}" ] || touch "${INDEX}" shellpki version ${VERSION}
[ -f "${CRL}" ] || touch "${CRL}"
[ -f "${SERIAL}" ] || echo "01" > "${SERIAL}"
cn="${1:-}" Copyright 2010-2022 Evolix <info@evolix.fr>,
[ -z "${cn}" ] && usage >&2 && exit 1 Thomas Martin <tmartin@evolix.fr>,
Gregory Colpart <reg@evolix.fr>,
Romain Dessort <rdessort@evolix.fr>,
Benoit Série <bserie@evolix.fr>,
Victor Laborie <vlaborie@evolix.fr>,
Daniel Jakots <djakots@evolix.fr>,
Patrick Marchand <pmarchand@evolix.fr>,
Jérémy Lecour <jlecour@evolix.fr>,
Jérémy Dubois <jdubois@evolix.fr>
and others.
if [ -f "${CAKEY}" ]; then shellpki comes with ABSOLUTELY NO WARRANTY. This is free software,
printf "%s already exists, do you really want to erase it ? [y/N] " "${CAKEY}" and you are welcome to redistribute it under certain conditions.
read -r REPLY See the MIT Licence for details.
resp=$(echo "${REPLY}"|tr 'Y' 'y') END
[ "${resp}" = "y" ] && rm -f "${CAKEY}" "${CACERT}"
fi
[ ! -f "${CAKEY}" ] && "$OPENSSL" \
genrsa \
-out "${CAKEY}" \
-aes256 4096 >/dev/null 2>&1
if [ -f "${CACERT}" ]; then
printf "%s already exists, do you really want to erase it ? [y/N] " "${CACERT}"
read -r REPLY
resp=$(echo "${REPLY}"|tr 'Y' 'y')
[ "${resp}" = "y" ] && rm "${CACERT}"
fi
[ ! -f "${CACERT}" ] && ask_ca_password 0
[ ! -f "${CACERT}" ] && CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" \
req -new \
-batch -sha512 \
-x509 -days 3650 \
-extensions v3_ca \
-key "${CAKEY}" \
-out "${CACERT}" \
-passin env:CA_PASSWORD \
-config /dev/stdin <<EOF
$(cat "${CONFFILE}")
commonName_default = ${cn}
EOF
} }
ocsp() { show_usage() {
umask 0177
ocsp_uri="${1:-}"
[ -z "${ocsp_uri}" ] && usage >&2 && exit 1
url=$(echo "${ocsp_uri}"|cut -d':' -f1)
port=$(echo "${ocsp_uri}"|cut -d':' -f2)
[ ! -f "${OCSPKEY}" ] && "$OPENSSL" \
genrsa \
-out "${OCSPKEY}" \
2048 >/dev/null 2>&1
"$OPENSSL" req \
-batch -new \
-key "${OCSPKEY}" \
-out "${CSRDIR}/ocsp.csr" \
-config /dev/stdin <<EOF
$(cat "${CONFFILE}")
commonName_default = ${url}
[ usr_cert ]
authorityInfoAccess = OCSP;URI:http://${ocsp_uri}
EOF
[ ! -f "${OCSPCERT}" ] && ask_ca_password 0
[ ! -f "${OCSPCERT}" ] && CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" \
ca \
-extensions v3_ocsp \
-in "${CSRDIR}/ocsp.csr" \
-out "${OCSPCERT}" \
-passin env:CA_PASSWORD \
-config "${CONFFILE}"
exec "${OPENSSL}" ocsp -ignore_err -index "${INDEX}" -port "${port}" -rsigner "${OCSPCERT}" -rkey "${OCSPKEY}" -CA "${CACERT}" -text
}
usage() {
cat <<EOF cat <<EOF
Usage: ${0} <subcommand> [options] [CommonName] Usage: ${0} <subcommand> [options] [CommonName]
Warning: [options] always must be before [CommonName] and after <subcommand>
Initialize PKI (create CA key and self-signed cert) : EOF
show_usage_init
show_usage_create
show_usage_revoke
show_usage_list
show_usage_check
show_usage_ocsp
${0} init <commonName_for_CA> cat <<EOF
Show version :
Run OCSPD server : ${0} --version
${0} ocsp <ocsp_uri:ocsp_port> Show help :
Create a client cert with key and CSR directly generated on server ${0} --help
(use -p for set a password on client key) : EOF
}
${0} create [-p] <commonName> show_usage_init() {
cat <<EOF
Initialize PKI (create CA key and self-signed certificate) :
Create a client cert from a CSR (doesn't need key) : ${0} init [options] <commonName_for_CA>
${0} create -f <path> Options
--non-interactive do not prompt the user, and exit if an error occurs
Revoke a client cert with is commonName (CN) : EOF
}
${0} revoke <commonName> show_usage_create() {
cat <<EOF
Create a client certificate with key and CSR directly generated on server :
List all actually valid commonName (CN) : ${0} create [options] <commonName>
${0} list [-a|v|r] Options
-f, --file, --csr-file create a client certificate from a CSR (doesn't need key)
-p, --password prompt the user for a password to set on the client key
--password-file if provided with a path to a readable file, the first line is read and set as password on the client key
--days specify how many days the certificate should be valid
--end-date specify until which date the certificate should be valid, in "YYYY/MM/DD hh:mm:ss" format, UTC +0
--non-interactive do not prompt the user, and exit if an error occurs
--replace-existing if the certificate already exists, revoke it before creating a new one
EOF
}
show_usage_revoke() {
cat <<EOF
Revoke a client certificate :
${0} revoke [options] <commonName>
Options
--non-interactive do not prompt the user, and exit if an error occurs
EOF
}
show_usage_list() {
cat <<EOF
List certificates :
${0} list <options>
Options
-a, --all list all certificates: valid and revoked ones
-v, --valid list all valid certificates
-r, --revoked list all revoked certificates
EOF
}
show_usage_check() {
cat <<EOF
Check expiration date of valid certificates : Check expiration date of valid certificates :
${0} check ${0} check
@ -126,6 +118,15 @@ Check expiration date of valid certificates :
EOF EOF
} }
show_usage_ocsp() {
cat <<EOF
Run OCSP_D server :
${0} ocsp <ocsp_uri:ocsp_port>
EOF
}
error() { error() {
echo "${1}" >&2 echo "${1}" >&2
exit 1 exit 1
@ -135,271 +136,840 @@ warning() {
echo "${1}" >&2 echo "${1}" >&2
} }
ask_ca_password() { verify_ca_password() {
[ ! -f "${CAKEY}" ] && error "You must initialize your's PKI with shellpki init !" "${OPENSSL_BIN}" rsa \
attempt=$((${1} + 1)) -in "${CA_KEY}" \
[ "${attempt}" -gt 1 ] && warning "Invalid password, retry." -passin pass:"${CA_PASSWORD}" \
trap 'unset CA_PASSWORD' 0 >/dev/null 2>&1
stty -echo }
printf "Password for CA key : " get_real_path() {
read -r CA_PASSWORD # --canonicalize is supported on Linux
stty echo # -f is supported on Linux and OpenBSD
printf "\n" readlink -f -- "${1}"
[ "${CA_PASSWORD}" != "" ] || ask_ca_password "${attempt}"
CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" rsa \
-in "${CAKEY}" \
-passin env:CA_PASSWORD \
>/dev/null 2>&1 \
|| ask_ca_password "${attempt}"
} }
create() { ask_ca_password() {
from_csr=1 attempt=${1:-0}
with_pass=1 max_attempts=3
trap 'unset CA_PASSWORD' 0
if [ ! -f "${CA_KEY}" ]; then
error "You must initialize your PKI with \`shellpki init' !"
fi
if [ "${attempt}" -gt 0 ]; then
warning "Invalid password, retry."
fi
if [ "${attempt}" -ge "${max_attempts}" ]; then
error "Maximum number of attempts reached (${max_attempts})."
fi
if [ -z "${CA_PASSWORD:-}" ]; then
if [ "${non_interactive}" -eq 1 ]; then
error "In non-interactive mode, you must pass CA_PASSWORD as environment variable"
fi
stty -echo
printf "Password for CA key: "
read -r CA_PASSWORD
stty echo
printf "\n"
fi
if [ -z "${CA_PASSWORD:-}" ] || ! verify_ca_password; then
unset CA_PASSWORD
attempt=$(( attempt + 1 ))
ask_ca_password "${attempt}"
fi
}
ask_user_password() {
trap 'unset PASSWORD' 0
if [ -z "${PASSWORD:-}" ]; then
if [ "${non_interactive}" -eq 1 ]; then
error "In non-interactive mode, you must pass PASSWORD as environment variable or use --password-file"
fi
stty -echo
printf "Password for user key: "
read -r PASSWORD
stty echo
printf "\n"
fi
if [ -z "${PASSWORD:-}" ]; then
warning "Warning: empty password from input"
fi
}
replace_existing_or_abort() {
cn=${1:?}
if [ "${non_interactive}" -eq 1 ]; then
if [ "${replace_existing}" -eq 1 ]; then
revoke --non-interactive "${cn}"
else
error "${cn} already exists, use \`--replace-existing' to force"
fi
else
if [ "${replace_existing}" -eq 1 ]; then
revoke "${cn}"
else
printf "%s already exists, do you want to revoke and recreate it ? [y/N] " "${cn}"
read -r REPLY
resp=$(echo "${REPLY}" | tr 'Y' 'y')
if [ "${resp}" = "y" ]; then
revoke "${cn}"
else
error "Aborted"
fi
fi
fi
}
init() {
umask 0177
[ -d "${CA_DIR}" ] || mkdir -m 0750 "${CA_DIR}"
[ -d "${CRT_DIR}" ] || mkdir -m 0750 "${CRT_DIR}"
[ -f "${INDEX_FILE}" ] || touch "${INDEX_FILE}"
[ -f "${CRL}" ] || touch "${CRL}"
[ -f "${SERIAL}" ] || echo "01" > "${SERIAL}"
non_interactive=0
# Parse options
# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a
while :; do while :; do
case "${1}" in case ${1:-} in
-f|--file) --non-interactive)
shift non_interactive=1
[ ! -f "${1}" ] && error "${1} must be a file" ;;
from_csr=0
csr_file=$(readlink -f "${1}")
shift;;
-p|--password)
with_pass=0
shift;;
--) --)
# End of all options.
shift shift
break;; break
;;
-?*) -?*)
warning "unknow option ${1} (ignored)" # ignore unknown options
shift;; warning "Warning: unknown option (ignored): \`$1'"
;;
*) *)
break;; # Default case: If no more options then break out of the loop.
break
;;
esac esac
shift
done done
cn="${1:-}" cn="${1:-}"
if [ -z "${cn}" ]; then
show_usage_init >&2
exit 1
fi
if [ "${from_csr}" -eq 0 ]; then if [ -f "${CA_KEY}" ]; then
[ "${with_pass}" -eq 0 ] && warning "Warning: -p made nothing with -f" if [ "${non_interactive}" -eq 1 ]; then
error "${CA_KEY} already exists, erase it manually if you want to start over."
else
printf "%s already exists, do you really want to erase it ? [y/N] " "${CA_KEY}"
read -r REPLY
resp=$(echo "${REPLY}" | tr 'Y' 'y')
if [ "${resp}" = "y" ]; then
rm -f "${CA_KEY}" "${CA_CERT}"
fi
fi
fi
passout_arg=""
if [ -n "${CA_PASSWORD:-}" ]; then
passout_arg="-passout pass:${CA_PASSWORD}"
elif [ "${non_interactive}" -eq 1 ]; then
error "In non-interactive mode, you must pass CA_PASSWORD as environment variable."
fi
if [ ! -f "${CA_KEY}" ]; then
"${OPENSSL_BIN}" genrsa \
-out "${CA_KEY}" \
${passout_arg} \
-aes256 \
"${CA_KEY_LENGTH}" \
>/dev/null 2>&1
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the CA key"
fi
fi
if [ -f "${CA_CERT}" ]; then
if [ "${non_interactive}" -eq 1 ]; then
error "${CA_CERT} already exists, erase it manually if you want to start over."
else
printf "%s already exists, do you really want to erase it ? [y/N] " "${CA_CERT}"
read -r REPLY
resp=$(echo "${REPLY}" | tr 'Y' 'y')
if [ "${resp}" = "y" ]; then
rm "${CA_CERT}"
fi
fi
fi
if [ ! -f "${CA_CERT}" ]; then
ask_ca_password 0
fi
if [ ! -f "${CA_CERT}" ]; then
"${OPENSSL_BIN}" req \
-new \
-batch \
-sha512 \
-x509 \
-days 3650 \
-extensions v3_ca \
-passin pass:"${CA_PASSWORD}" \
-key "${CA_KEY}" \
-out "${CA_CERT}" \
-config /dev/stdin <<EOF
$(cat "${CONF_FILE}")
commonName_default = ${cn}
EOF
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the CA certificate"
fi
"${OPENSSL_BIN}" ca \
-config "${CONF_FILE}" \
-passin pass:${CA_PASSWORD} \
-gencrl \
-out "${CRL}"
fi
}
ocsp() {
umask 0177
ocsp_uri="${1:-}"
if [ -z "${ocsp_uri}" ]; then
show_usage_ocsp >&2
exit 1
fi
ocsp_csr_file="${CSR_DIR}/ocsp.csr"
url=$(echo "${ocsp_uri}" | cut -d':' -f1)
port=$(echo "${ocsp_uri}" | cut -d':' -f2)
if [ ! -f "${OCSP_KEY}" ]; then

Option --canonicalize is not compatible with OpenBSD, but option -f is :

On Debian, man readlink :

       -f, --canonicalize
              canonicalize by following every symlink in  every  component  of
              the  given name recursively; all but the last component must ex‐
              ist

On OpenBSD, man readlink :

     -f      Canonicalize by following every symlink in every component of the
             given path recursively.  readlink will resolve both absolute and
             relative paths and return the absolute pathname corresponding to
             file.  The argument does not need to be a symbolic link.

I suggest to replace all "--canonicalize" occurences by "-f".

Option `--canonicalize` is not compatible with OpenBSD, but option `-f` is : On Debian, `man readlink` : ``` -f, --canonicalize canonicalize by following every symlink in every component of the given name recursively; all but the last component must ex‐ ist ``` On OpenBSD, `man readlink` : ``` -f Canonicalize by following every symlink in every component of the given path recursively. readlink will resolve both absolute and relative paths and return the absolute pathname corresponding to file. The argument does not need to be a symbolic link. ``` I suggest to replace all "--canonicalize" occurences by "-f".
"${OPENSSL_BIN}" genrsa \
-out "${OCSP_KEY}" \
"${KEY_LENGTH}" \
>/dev/null 2>&1
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the OCSP key"
fi
fi
"${OPENSSL_BIN}" req \
-batch \
-new \
-key "${OCSP_KEY}" \
-out "${ocsp_csr_file}" \
-config /dev/stdin <<EOF
$(cat "${CONF_FILE}")
commonName_default = ${url}
[ usr_cert ]
authorityInfoAccess = OCSP;URI:http://${ocsp_uri}
EOF
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the OCSP request"
fi
if [ ! -f "${OCSP_CERT}" ]; then
ask_ca_password 0
fi
if [ ! -f "${OCSP_CERT}" ]; then
"${OPENSSL_BIN}" ca \
-extensions v3_ocsp \
-in "${ocsp_csr_file}" \
-out "${OCSP_CERT}" \
-passin pass:"${CA_PASSWORD}" \
-config "${CONF_FILE}"
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the OCSP certificate"
fi
fi
exec "${OPENSSL_BIN}" ocsp \
-ignore_err \
-index "${INDEX_FILE}" \
-port "${port}" \
-rsigner "${OCSP_CERT}" \
-rkey "${OCSP_KEY}" \
-CA "${CA_CERT}" \
-text
}
create() {
from_csr=0
ask_pass=0
non_interactive=0
replace_existing=0
days=""
end_date=""
days_set=0
end_date_set=0
password_set=0
password_file_set=0
# Parse options
# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a
while :; do
case ${1:-} in
-f|--file|--csr-file)
# csr-file option, with value separated by space
if [ -n "$2" ]; then
from_csr=1
csr_file=$(get_real_path "${2}")
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error accessing file \`${2}'"
fi
shift
else
error "Argument error: \`--csr-file' requires a value"
fi
;;
--file=?*|--csr-file=?*)
from_csr=1
# csr-file option, with value separated by =
csr_file=$(get_real_path "${1#*=}")
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error accessing file \`${1#*=}'"
fi
;;
--file=|--csr-file=)
# csr-file options, without value
error "Argument error: \`--csr-file' requires a value"
;;
-p|--password)
ask_pass=1
password_set=1
;;
--password-file)
# password-file option, with value separated by space
if [ -n "$2" ]; then
password_file=$(get_real_path "${2}")
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error accessing file \`${2}'"
fi
password_file_set=1
shift
else
error "Argument error: \`--password-file' requires a value"
fi
;;
--password-file=?*)
# password-file option, with value separated by =
password_file=$(get_real_path "${1#*=}")
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error accessing file \`${1#*=}'"
fi
password_file_set=1
;;
--password-file=)
# password-file options, without value
error "Argument error: \`--password-file' requires a value"
;;
--days)
# days option, with value separated by space
if [ -n "$2" ]; then
days=${2}
days_set=1
shift
else
error "Argument error: \`--days' requires a value"
fi
;;
--days=?*)
# days option, with value separated by =
days=${1#*=}
days_set=1
;;
--days=)
# days options, without value
error "Argument error: \`--days' requires a value"
;;
--end-date)
# end-date option, with value separated by space
if [ -n "$2" ]; then
end_date=${2}
end_date_set=1
shift
else
error "Argument error: \`--end-date' requires a value"
fi
;;
--end-date=?*)
# end-date option, with value separated by =
end_date=${1#*=}
end_date_set=1
;;
--end-date=)
# end-date options, without value
error "Argument error: \`--end-date' requires a value"
;;
--non-interactive)
non_interactive=1
;;
--replace-existing)
replace_existing=1
;;
--)
# End of all options.
shift
break
;;
-?*)
# ignore unknown options
warning "Warning: unknown option (ignored): \`$1'"
;;
*)
# Default case: If no more options then break out of the loop.
break
;;
esac
shift
done
if [ "${days_set}" -eq 1 ] && [ "${end_date_set}" -eq 1 ]; then
error "Argument error: \`--end-date' and \`--days' cannot be used together."
fi
if [ "${password_set}" -eq 1 ] && [ "${password_file_set}" -eq 1 ]; then
error "Argument error: \`--password' and \`--password-file' cannot be used together."
fi
# The name of the certificate
cn="${1:-}"
# Set expiration argument
crt_expiration_arg=""
if [ -n "${days}" ]; then
if [ "${days}" -gt 0 ]; then
crt_expiration_arg="-days ${days}"
else
error "Argument error: \"${days}\" is not a valid value for \`--days'."
fi
fi
if [ -n "${end_date}" ]; then
if [ "${SYSTEM}" = "linux" ]; then
cert_end_date=$(TZ=:Zulu date --date "${end_date}" +"%Y%m%d%H%M%SZ" 2> /dev/null)
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Invalid end date format: \`${end_date}' can't be parsed by date(1). Expected format: YYYY/MM/DD [hh[:mm[:ss]]]."
else
crt_expiration_arg="-enddate ${cert_end_date}"
fi
elif [ "${SYSTEM}" = "openbsd" ]; then
cert_end_date=$(TZ=:Zulu date -f "%C%y/%m/%d %H:%M:%S" -j "${end_date}" +"%Y%m%d%H%M%SZ" 2> /dev/null)
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Invalid end date format: \`${end_date}' can't be parsed by date(1). Expected format: YYYY/MM/DD hh:mm:ss."
else
crt_expiration_arg="-enddate ${cert_end_date}"
fi
else
error "System ${SYSTEM} not supported."
fi
fi
if [ "${non_interactive}" -eq 1 ]; then
batch_arg="-batch"
else
batch_arg=""
fi
if [ "${from_csr}" -eq 1 ]; then
if [ "${ask_pass}" -eq 1 ]; then
warning "Warning: -p|--password is ignored with -f|--file|--crt-file"
fi
if [ -n "${password_file:-}" ]; then
warning "Warning: --password-file is ignored with -f|--file|--crt-file"
fi
crt_file="${CRT_DIR}/${cn}.crt"
# ask for CA passphrase # ask for CA passphrase
ask_ca_password 0 ask_ca_password 0
# check if csr_file is a CSR # check if csr_file is a CSR
"${OPENSSL}" req \ "${OPENSSL_BIN}" req \
-noout -subject \ -noout \
-in "${csr_file}" \ -subject \
>/dev/null 2>&1 \ -in "${csr_file}" \
|| error "${csr_file} is not a valid CSR !" >/dev/null 2>&1
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "${csr_file} is not a valid CSR !"
fi
# check if csr_file contain a CN # check if csr_file contain a CN
"${OPENSSL}" req \ "${OPENSSL_BIN}" req \
-noout -subject \ -noout \
-in "${csr_file}" \ -subject \
| grep -Eo "CN\s*=[^,/]*" \ -in "${csr_file}" \
>/dev/null 2>&1 \ | grep -Eo "CN\s*=[^,/]*" \
|| error "${csr_file} don't contain a CommonName !" >/dev/null 2>&1
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "${csr_file} doesn't contain a CommonName !"
fi
# get CN from CSR # get CN from CSR
cn=$("${OPENSSL}" req -noout -subject -in "${csr_file}"|grep -Eo "CN\s*=[^,/]*"|cut -d'=' -f2|xargs) cn=$("${OPENSSL_BIN}" req -noout -subject -in "${csr_file}" | grep -Eo "CN\s*=[^,/]*" | cut -d'=' -f2 | xargs)
# check if CN already exist # check if CN already exists
[ -f "${CRTDIR}/${cn}.crt" ] && error "${cn} already used !" if [ -f "${crt_file}" ]; then
replace_existing_or_abort "${cn}"
fi
# ca sign and generate cert # ca sign and generate cert
CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" ca \ if [ "${non_interactive}" -eq 1 ]; then
-config "${CONFFILE}" \ batch_arg="-batch"
-in "${csr_file}" \ else
-passin env:CA_PASSWORD \ batch_arg=""
-out "${CRTDIR}/${cn}.crt"
echo "The CRT file is available in ${CRTDIR}/${cn}.crt"
else
[ -z "${cn}" ] && usage >&2 && exit 1
# check if CN already exist
[ -f "${CRTDIR}/${cn}.crt" ] && error "${cn} already used !"
# ask for client key passphrase
if [ "${with_pass}" -eq 0 ]; then
trap 'unset PASSWORD' 0
stty -echo
printf "Password for user key : "
read -r PASSWORD
stty echo
printf "\n"
fi fi
"${OPENSSL_BIN}" ca \
${batch_arg} \
-config "${CONF_FILE}" \
-in "${csr_file}" \
-passin pass:"${CA_PASSWORD}" \
-out "${crt_file}" \
${crt_expiration_arg}
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the certificate"
else
echo "The certificate file is available at \`${crt_file}'"
fi
else
if [ -z "${cn}" ]; then
show_usage_create >&2
exit 1
fi
csr_file="${CSR_DIR}/${cn}-${SUFFIX}.csr"
crt_file="${CRT_DIR}/${cn}.crt"
key_file="${KEY_DIR}/${cn}-${SUFFIX}.key"
ovpn_file="${OVPN_DIR}/${cn}-${SUFFIX}.ovpn"
pkcs12_file="${PKCS12_DIR}/${cn}-${SUFFIX}.p12"
# ask for CA passphrase # ask for CA passphrase
ask_ca_password 0 ask_ca_password 0
# generate private key if [ "${ask_pass}" -eq 1 ]; then
if [ "${with_pass}" -eq 0 ]; then ask_user_password
PASSWORD="${PASSWORD}" "$OPENSSL" genrsa \
-aes256 -passout env:PASSWORD \
-out "${KEYDIR}/${cn}-${TIMESTAMP}.key" \
2048 >/dev/null 2>&1
else
"$OPENSSL" genrsa \
-out "${KEYDIR}/${cn}-${TIMESTAMP}.key" \
2048 >/dev/null 2>&1
fi fi
if [ "${with_pass}" -eq 0 ]; then # check if CN already exists
# generate csr req if [ -f "${crt_file}" ]; then
PASSWORD="${PASSWORD}" "$OPENSSL" req \ replace_existing_or_abort "${cn}"
-batch -new \ fi
-key "${KEYDIR}/${cn}-${TIMESTAMP}.key" \
-passin env:PASSWORD \ # generate private key
-out "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \ pass_args=""
-config /dev/stdin <<EOF if [ -n "${password_file:-}" ]; then
$(cat "${CONFFILE}") pass_args="-aes256 -passout file:${password_file}"
commonName_default = ${cn} elif [ -n "${PASSWORD:-}" ]; then
EOF pass_args="-aes256 -passout pass:${PASSWORD}"
fi
"${OPENSSL_BIN}" genrsa \
-out "${key_file}" \
${pass_args} \
"${KEY_LENGTH}" \
>/dev/null 2>&1
# shellcheck disable=SC2181
if [ "$?" -eq 0 ]; then
echo "The KEY file is available at \`${key_file}'"
else else
# generate csr req error "Error generating the private key"
"$OPENSSL" req \ fi
-batch -new \
-key "${KEYDIR}/${cn}-${TIMESTAMP}.key" \ # generate csr req
-out "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \ pass_args=""
-config /dev/stdin <<EOF if [ -n "${password_file:-}" ]; then
$(cat "${CONFFILE}") pass_args="-passin file:${password_file}"
elif [ -n "${PASSWORD:-}" ]; then
pass_args="-passin pass:${PASSWORD}"
fi
"${OPENSSL_BIN}" req \
-batch \
-new \
-key "${key_file}" \
-out "${csr_file}" \
${pass_args} \
-config /dev/stdin <<EOF
$(cat "${CONF_FILE}")
commonName_default = ${cn} commonName_default = ${cn}
EOF EOF
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the CSR"
fi fi
# ca sign and generate cert # ca sign and generate cert
CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" ca \ "${OPENSSL_BIN}" ca \
-config "${CONFFILE}" \ ${batch_arg} \
-passin env:CA_PASSWORD \ -config "${CONF_FILE}" \
-in "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \ -passin pass:${CA_PASSWORD} \
-out "${CRTDIR}/${cn}.crt" -in "${csr_file}" \
-out "${crt_file}" \
# check if CRT is a valid ${crt_expiration_arg}
"${OPENSSL}" x509 \ # shellcheck disable=SC2181
-noout -subject \ if [ "$?" -ne 0 ]; then
-in "${CRTDIR}/${cn}.crt" \ error "Error generating the certificate"
>/dev/null 2>&1 \
|| rm -f "${CRTDIR}/${cn}.crt"
[ -f "${CRTDIR}/${cn}.crt" ] || error "Error in CSR creation"
chmod 640 "${CRTDIR}/${cn}.crt"
echo "The CRT file is available in ${CRTDIR}/${cn}.crt"
# generate pkcs12 format
if [ "${with_pass}" -eq 0 ]; then
PASSWORD="${PASSWORD}" "${OPENSSL}" pkcs12 -export -nodes -passin env:PASSWORD -passout env:PASSWORD -inkey "${KEYDIR}/${cn}-${TIMESTAMP}.key" -in "${CRTDIR}/${cn}.crt" -out "${PKCS12DIR}/${cn}-${TIMESTAMP}.p12"
else
"${OPENSSL}" pkcs12 -export -nodes -passout pass: -inkey "${KEYDIR}/${cn}-${TIMESTAMP}.key" -in "${CRTDIR}/${cn}.crt" -out "${PKCS12DIR}/${cn}-${TIMESTAMP}.p12"
fi fi
chmod 640 "${PKCS12DIR}/${cn}-${TIMESTAMP}.p12" # check if CRT is a valid
echo "The PKCS12 config file is available in ${PKCS12DIR}/${cn}-${TIMESTAMP}.p12" "${OPENSSL_BIN}" x509 \
-noout \
-subject \
-in "${crt_file}" \
>/dev/null 2>&1
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
rm -f "${crt_file}"
fi
if [ ! -f "${crt_file}" ]; then
error "Error in CSR creation"
fi
chmod 640 "${crt_file}"
echo "The CRT file is available in ${crt_file}"
# generate pkcs12 format
pass_args=""
if [ -n "${password_file:-}" ]; then
# Hack for pkcs12 :
# If passin and passout files are the same path, it expects 2 lines
# so we make a temporary copy of the password file
password_file_out=$(mktemp)
cp "${password_file}" "${password_file_out}"
pass_args="-passin file:${password_file} -passout file:${password_file_out}"
elif [ -n "${PASSWORD:-}" ]; then
pass_args="-passin pass:${PASSWORD} -passout pass:${PASSWORD}"
else
pass_args="-passout pass:"
fi
"${OPENSSL_BIN}" pkcs12 \
-export \
-nodes \
-inkey "${key_file}" \

This is not needed as "shellpki list" should, thanks to lines 771 and 772, list all valids certificates by default.

This is not needed as "shellpki list" should, thanks to lines 771 and 772, list all valids certificates by default.
-in "${crt_file}" \
-out "${pkcs12_file}" \
${pass_args}
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the pkcs12 file"
fi
if [ -n "${password_file_out:-}" ]; then
# Hack for pkcs12 :
# Destroy the temporary file
rm -f "${password_file_out}"
fi
chmod 640 "${pkcs12_file}"
echo "The PKCS12 config file is available at \`${pkcs12_file}'"
# generate openvpn format # generate openvpn format
if [ -e "${CADIR}/ovpn.conf" ]; then if [ -e "${CA_DIR}/ovpn.conf" ]; then
cat "${CADIR}/ovpn.conf" - > "${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn" <<EOF cat "${CA_DIR}/ovpn.conf" - > "${ovpn_file}" <<EOF
<ca> <ca>
$(cat "${CACERT}") $(cat "${CA_CERT}")
</ca> </ca>
<cert> <cert>
$(cat "${CRTDIR}/${cn}.crt") $(cat "${crt_file}")
</cert> </cert>
<key> <key>
$(cat "${KEYDIR}/${cn}-${TIMESTAMP}.key") $(cat "${key_file}")
</key> </key>
EOF EOF
chmod 640 "${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn" chmod 640 "${ovpn_file}"
echo "The OpenVPN config file is available in ${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn" echo "The OpenVPN config file is available at \`${ovpn_file}'"
fi
# Copy files if destination exists
if [ -d "${COPY_DIR}" ]; then
for file in "${crt_file}" "${key_file}" "${pkcs12_file}" "${ovpn_file}"; do
if [ -f "${file}" ]; then
new_file="${COPY_DIR}/$(basename "${file}")"
if [ "${replace_existing}" -eq 1 ]; then
cp -f "${file}" "${COPY_DIR}/"
else
if [ "${non_interactive}" -eq 1 ]; then
if [ -f "${new_file}" ]; then
echo "File \`${file}' has not been copied to \`${new_file}', it already exists" >&2
continue
else
cp "${file}" "${COPY_DIR}/"
fi
else
cp -i "${file}" "${COPY_DIR}/"
fi
fi
echo "File \`${file}' has been copied to \`${new_file}'"
fi
done
# shellcheck disable=SC2086
chown -R ${PKI_USER}:${PKI_USER} "${COPY_DIR}/"
chmod -R u=rwX,g=rwX,o= "${COPY_DIR}/"
fi fi
fi fi
} }
revoke() { revoke() {
[ "${1}" = "" ] && usage >&2 && exit 1 non_interactive=0
# get CN from param # Parse options
cn="${1}" # based on https://gist.github.com/deshion/10d3cb5f88a21671e17a
while :; do
case ${1:-} in
--non-interactive)
non_interactive=1
;;
--)
# End of all options.
shift
break
;;
-?*)
# ignore unknown options
warning "Warning: unknown option (ignored): \`$1'"
;;
*)
# Default case: If no more options then break out of the loop.
break
;;
esac
shift
done
# The name of the certificate
cn="${1:-}"
if [ -z "${cn}" ]; then
show_usage_revoke >&2
exit 1
fi
crt_file="${CRT_DIR}/${cn}.crt"
# check if CRT exists # check if CRT exists
[ ! -f "${CRTDIR}/${cn}.crt" ] && error "Unknow CN : ${cn}" if [ ! -f "${crt_file}" ]; then
error "Unknow CN: ${cn} (\`${crt_file}' not found)"
fi
# check if CRT is a valid # check if CRT is a valid
"${OPENSSL}" x509 -noout -subject -in "${CRTDIR}/${cn}.crt" >/dev/null 2>&1 || error "${CRTDIR}/${cn}.crt is not a valid CRT, you msust delete it !" "${OPENSSL_BIN}" x509 \
-noout \
-subject \
-in "${crt_file}" \
>/dev/null 2>&1
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "${crt_file} is not a valid CRT, you must delete it !"
fi
# ask for CA passphrase # ask for CA passphrase
ask_ca_password 0 ask_ca_password 0
echo "Revoke certificate ${CRTDIR}/${cn}.crt :" echo "Revoke certificate ${crt_file} :"
CA_PASSWORD="${CA_PASSWORD}" "$OPENSSL" ca \ "${OPENSSL_BIN}" ca \
-config "${CONFFILE}" \ -config "${CONF_FILE}" \
-passin env:CA_PASSWORD \ -passin pass:"${CA_PASSWORD}" \
-revoke "${CRTDIR}/${cn}.crt" \ -revoke "${crt_file}"
&& rm "${CRTDIR}/${cn}.crt" # shellcheck disable=SC2181
if [ "$?" -eq 0 ]; then
rm "${crt_file}"
fi
CA_PASSWORD="${CA_PASSWORD}" "$OPENSSL" ca \ "${OPENSSL_BIN}" ca \
-config "${CONFFILE}" \ -config "${CONF_FILE}" \
-passin env:CA_PASSWORD \ -passin pass:"${CA_PASSWORD}" \
-gencrl -out "${CRL}" -gencrl \
-out "${CRL}"
} }
list() { list() {
[ -f "${INDEX}" ] || exit 0 if [ ! -f "${INDEX_FILE}" ]; then
exit 0
fi
list_valid=0 if [ -z "${1:-}" ]; then
list_revoked=1 show_usage_list >&2
exit 1
fi
while :; do while :; do
case "${1}" in case "${1:-}" in
-a|--all) -a|--all)
list_valid=0 list_valid=0
list_revoked=0 list_revoked=0
shift;; ;;
-v|--valid) -v|--valid)
list_valid=0 list_valid=0
list_revoked=1 list_revoked=1
shift;; ;;
-r|--revoked) -r|--revoked)
list_valid=1 list_valid=1
list_revoked=0 list_revoked=0
shift;; ;;
--)
shift
break;;
-?*) -?*)
warning "unknow option ${1} (ignored)" warning "unknow option ${1} (ignored)"
shift;; ;;
*) *)
break;; break
;;
esac esac
shift
done done
[ "${list_valid}" -eq 0 ] && certs=$(grep "^V" "${INDEX}") if [ "${list_valid}" -eq 0 ]; then
certs=$(grep "^V" "${INDEX_FILE}")
fi
[ "${list_revoked}" -eq 0 ] && certs=$(grep "^R" "${INDEX}") if [ "${list_revoked}" -eq 0 ]; then
certs=$(grep "^R" "${INDEX_FILE}")
fi
[ "${list_valid}" -eq 0 ] && [ "${list_revoked}" -eq 0 ] && certs=$(cat "${INDEX}") if [ "${list_valid}" -eq 0 ] && [ "${list_revoked}" -eq 0 ]; then
certs=$(cat "${INDEX_FILE}")
fi
echo "${certs}" | grep -Eo "CN\s*=[^,/]*" | cut -d'=' -f2 | xargs -n1 echo "${certs}" | grep -Eo "CN\s*=[^,/]*" | cut -d'=' -f2 | xargs -n1
} }
cert_end_date() {
"${OPENSSL_BIN}" x509 -noout -enddate -in "${1}" | cut -d'=' -f2
}
check() { check() {
# default expiration alert # default expiration alert
# TODO : permit override with parameters # TODO: permit override with parameters
min_day=90 min_day=90
cur_epoch=$(date -u +'%s') cur_epoch=$(date -u +'%s')
for cert in ${CRTDIR}/*; do for cert in "${CRT_DIR}"/*; do
end_date=$(openssl x509 -noout -enddate -in "${cert}" | cut -d'=' -f2) end_date=$(cert_end_date "${cert}")
end_epoch=$(date -ud "${end_date}" +'%s') end_epoch=$(date -ud "${end_date}" +'%s')
diff_epoch=$((end_epoch - cur_epoch)) diff_epoch=$(( end_epoch - cur_epoch ))
diff_day=$((diff_epoch/60/60/24)) diff_day=$(( diff_epoch / 60 / 60 / 24 ))
if [ "${diff_day}" -lt "${min_day}" ]; then if [ "${diff_day}" -lt "${min_day}" ]; then
if [ "${diff_day}" -le 0 ]; then if [ "${diff_day}" -le 0 ]; then
echo "${cert} has expired" echo "${cert} has expired"
@ -410,43 +980,72 @@ check() {
done done
} }
is_user() {
getent passwd "${1}" >/dev/null
}
is_group() {
getent group "${1}" >/dev/null
}
main() { main() {
# Know what system we are on, because OpenBSD and Linux do not implement date(1) in the same way
SYSTEM=$(uname | tr '[:upper:]' '[:lower:]')
# default config # default config
# TODO : override with /etc/default/shellpki # TODO: override with /etc/default/shellpki
CONFFILE="/etc/shellpki/openssl.cnf" CONF_FILE="/etc/shellpki/openssl.cnf"
PKIUSER="shellpki"
[ "$(uname)" = "OpenBSD" ] && PKIUSER="_shellpki"
[ "${USER}" != "root" ] || [ "${USER}" != "${PKIUSER}" ] || error "Please become root before running ${0} !" if [ "$(uname)" = "OpenBSD" ]; then
PKI_USER="_shellpki"
# retrieve CA path from config file else
CADIR=$(grep -E "^dir" "${CONFFILE}" | cut -d'=' -f2|xargs -n1) PKI_USER="shellpki"
CAKEY=$(grep -E "^private_key" "${CONFFILE}" | cut -d'=' -f2|xargs -n1|sed "s~\$dir~${CADIR}~")
CACERT=$(grep -E "^certificate" "${CONFFILE}" | cut -d'=' -f2|xargs -n1|sed "s~\$dir~${CADIR}~")
OCSPKEY="${CADIR}/ocsp.key"
OCSPCERT="${CADIR}/ocsp.pem"
CRTDIR=$(grep -E "^certs" "${CONFFILE}" | cut -d'=' -f2|xargs -n1|sed "s~\$dir~${CADIR}~")
TMPDIR=$(grep -E "^new_certs_dir" "${CONFFILE}" | cut -d'=' -f2|xargs -n1|sed "s~\$dir~${CADIR}~")
INDEX=$(grep -E "^database" "${CONFFILE}" | cut -d'=' -f2|xargs -n1|sed "s~\$dir~${CADIR}~")
SERIAL=$(grep -E "^serial" "${CONFFILE}" | cut -d'=' -f2|xargs -n1|sed "s~\$dir~${CADIR}~")
CRL=$(grep -E "^crl" "${CONFFILE}" | cut -d'=' -f2|xargs -n1|sed "s~\$dir~${CADIR}~")
# directories for clients key, csr, crt
KEYDIR="${CADIR}/private"
CSRDIR="${CADIR}/requests"
PKCS12DIR="${CADIR}/pkcs12"
OVPNDIR="${CADIR}/openvpn"
OPENSSL=$(command -v openssl)
TIMESTAMP=$(/bin/date +"%s")
if ! getent passwd "${PKIUSER}" >/dev/null || ! getent group "${PKIUSER}" >/dev/null; then
error "You must create ${PKIUSER} user and group !"
fi fi
[ -e "${CONFFILE}" ] || error "${CONFFILE} is missing" if [ "${USER}" != "root" ] && [ "${USER}" != "${PKI_USER}" ]; then
error "Please become root before running ${0} !"
fi
mkdir -p "${CADIR}" "${CRTDIR}" "${KEYDIR}" "${CSRDIR}" "${PKCS12DIR}" "${OVPNDIR}" "${TMPDIR}" # retrieve CA path from config file
CA_DIR=$(grep -E "^dir" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1)
CA_KEY=$(grep -E "^private_key" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~")
CA_CERT=$(grep -E "^certificate" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~")
OCSP_KEY="${CA_DIR}/ocsp.key"
OCSP_CERT="${CA_DIR}/ocsp.pem"
CRT_DIR=$(grep -E "^certs" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~")
TMP_DIR=$(grep -E "^new_certs_dir" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~")
INDEX_FILE=$(grep -E "^database" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~")
SERIAL=$(grep -E "^serial" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~")
CRL=$(grep -E "^crl" "${CONF_FILE}" | cut -d'=' -f2 | xargs -n1 | sed "s~\$dir~${CA_DIR}~")
# directories for clients key, csr, crt
KEY_DIR="${CA_DIR}/private"
CSR_DIR="${CA_DIR}/requests"
PKCS12_DIR="${CA_DIR}/pkcs12"
OVPN_DIR="${CA_DIR}/openvpn"
COPY_DIR="$(dirname "${CONF_FILE}")/copy_output"
CA_KEY_LENGTH=4096
if [ "${CA_KEY_LENGTH}" -lt 4096 ]; then
error "CA key must be at least 4096 bits long."
fi
KEY_LENGTH=2048
if [ "${KEY_LENGTH}" -lt 2048 ]; then
error "User key must be at least 2048 bits long."
fi
OPENSSL_BIN=$(command -v openssl)
SUFFIX=$(TZ=:Zulu /bin/date +"%Y%m%d%H%M%SZ")
if ! is_user "${PKI_USER}" || ! is_group "${PKI_USER}"; then
error "You must create ${PKI_USER} user and group !"
fi
if [ ! -e "${CONF_FILE}" ]; then
error "${CONF_FILE} is missing"
fi
mkdir -p "${CA_DIR}" "${CRT_DIR}" "${KEY_DIR}" "${CSR_DIR}" "${PKCS12_DIR}" "${OVPN_DIR}" "${TMP_DIR}"
command=${1:-help} command=${1:-help}
@ -481,17 +1080,27 @@ main() {
check "$@" check "$@"
;; ;;
version|--version)
show_version
exit 0
;;
help|--help)
show_usage
exit 0
;;
*) *)
usage >&2 show_usage >&2
exit 1 exit 1
;; ;;
esac esac
# fix right # fix right
chown -R "${PKIUSER}":"${PKIUSER}" "${CADIR}" chown -R "${PKI_USER}":"${PKI_USER}" "${CA_DIR}"
chmod 750 "${CADIR}" "${CRTDIR}" "${KEYDIR}" "${CSRDIR}" "${PKCS12DIR}" "${OVPNDIR}" "${TMPDIR}" chmod 750 "${CA_DIR}" "${CRT_DIR}" "${KEY_DIR}" "${CSR_DIR}" "${PKCS12_DIR}" "${OVPN_DIR}" "${TMP_DIR}"
chmod 600 "${INDEX}"* "${SERIAL}"* "${CAKEY}" "${CRL}" chmod 600 "${INDEX_FILE}"* "${SERIAL}"* "${CA_KEY}" "${CRL}"
chmod 640 "${CACERT}" chmod 640 "${CA_CERT}"
} }
main "$@" main "$@"