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
[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
### Debian
@ -49,47 +54,87 @@ proto udp
remote ovpn.example.com 1194
nobind
user nobody
group nogroup
persist-key
persist-tun
cipher AES-256-CBC
cipher AES-256-GCM
~~~
## 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
(use -p for set a password on client key) :
Create a client certificate with key and CSR directly generated on server :
~~~
./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

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 ]
default_ca = CA_default

1205
shellpki
View file

@ -1,124 +1,116 @@
#!/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() {
umask 0177
VERSION="22.04"
[ -d "${CADIR}" ] || mkdir -m 0750 "${CADIR}"
[ -d "${CRTDIR}" ] || mkdir -m 0750 "${CRTDIR}"
[ -f "${INDEX}" ] || touch "${INDEX}"
[ -f "${CRL}" ] || touch "${CRL}"
[ -f "${SERIAL}" ] || echo "01" > "${SERIAL}"
show_version() {
cat <<END
shellpki version ${VERSION}
cn="${1:-}"
[ -z "${cn}" ] && usage >&2 && exit 1
Copyright 2010-2022 Evolix <info@evolix.fr>,
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
printf "%s already exists, do you really want to erase it ? [y/N] " "${CAKEY}"
read -r REPLY
resp=$(echo "${REPLY}"|tr 'Y' 'y')
[ "${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
shellpki comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
See the MIT Licence for details.
END
}
ocsp() {
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() {
show_usage() {
cat <<EOF
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
(use -p for set a password on client key) :
${0} --help
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 :
${0} check
@ -126,6 +118,15 @@ Check expiration date of valid certificates :
EOF
}
show_usage_ocsp() {
cat <<EOF
Run OCSP_D server :
${0} ocsp <ocsp_uri:ocsp_port>
EOF
}
error() {
echo "${1}" >&2
exit 1
@ -135,271 +136,840 @@ warning() {
echo "${1}" >&2
}
ask_ca_password() {
[ ! -f "${CAKEY}" ] && error "You must initialize your's PKI with shellpki init !"
attempt=$((${1} + 1))
[ "${attempt}" -gt 1 ] && warning "Invalid password, retry."
trap 'unset CA_PASSWORD' 0
stty -echo
printf "Password for CA key : "
read -r CA_PASSWORD
stty echo
printf "\n"
[ "${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}"
verify_ca_password() {
"${OPENSSL_BIN}" rsa \
-in "${CA_KEY}" \
-passin pass:"${CA_PASSWORD}" \
>/dev/null 2>&1
}
get_real_path() {
# --canonicalize is supported on Linux
# -f is supported on Linux and OpenBSD
readlink -f -- "${1}"
}
create() {
from_csr=1
with_pass=1
ask_ca_password() {
attempt=${1:-0}
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
case "${1}" in
-f|--file)
shift
[ ! -f "${1}" ] && error "${1} must be a file"
from_csr=0
csr_file=$(readlink -f "${1}")
shift;;
-p|--password)
with_pass=0
shift;;
case ${1:-} in
--non-interactive)
non_interactive=1
;;
--)
# End of all options.
shift
break;;
break
;;
-?*)
warning "unknow option ${1} (ignored)"
shift;;
# ignore unknown options
warning "Warning: unknown option (ignored): \`$1'"
;;
*)
break;;
# Default case: If no more options then break out of the loop.
break
;;
esac
shift
done
cn="${1:-}"
if [ -z "${cn}" ]; then
show_usage_init >&2
exit 1
fi
if [ "${from_csr}" -eq 0 ]; then
[ "${with_pass}" -eq 0 ] && warning "Warning: -p made nothing with -f"
if [ -f "${CA_KEY}" ]; then
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_ca_password 0
# check if csr_file is a CSR
"${OPENSSL}" req \
-noout -subject \
-in "${csr_file}" \
>/dev/null 2>&1 \
|| error "${csr_file} is not a valid CSR !"
"${OPENSSL_BIN}" req \
-noout \
-subject \
-in "${csr_file}" \
>/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
"${OPENSSL}" req \
-noout -subject \
-in "${csr_file}" \
| grep -Eo "CN\s*=[^,/]*" \
>/dev/null 2>&1 \
|| error "${csr_file} don't contain a CommonName !"
"${OPENSSL_BIN}" req \
-noout \
-subject \
-in "${csr_file}" \
| grep -Eo "CN\s*=[^,/]*" \
>/dev/null 2>&1
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "${csr_file} doesn't contain a CommonName !"
fi
# 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
[ -f "${CRTDIR}/${cn}.crt" ] && error "${cn} already used !"
# check if CN already exists
if [ -f "${crt_file}" ]; then
replace_existing_or_abort "${cn}"
fi
# ca sign and generate cert
CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" ca \
-config "${CONFFILE}" \
-in "${csr_file}" \
-passin env:CA_PASSWORD \
-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"
if [ "${non_interactive}" -eq 1 ]; then
batch_arg="-batch"
else
batch_arg=""
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_ca_password 0
# generate private key
if [ "${with_pass}" -eq 0 ]; then
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
if [ "${ask_pass}" -eq 1 ]; then
ask_user_password
fi
if [ "${with_pass}" -eq 0 ]; then
# generate csr req
PASSWORD="${PASSWORD}" "$OPENSSL" req \
-batch -new \
-key "${KEYDIR}/${cn}-${TIMESTAMP}.key" \
-passin env:PASSWORD \
-out "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \
-config /dev/stdin <<EOF
$(cat "${CONFFILE}")
commonName_default = ${cn}
EOF
# check if CN already exists
if [ -f "${crt_file}" ]; then
replace_existing_or_abort "${cn}"
fi
# generate private key
pass_args=""
if [ -n "${password_file:-}" ]; then
pass_args="-aes256 -passout file:${password_file}"
elif [ -n "${PASSWORD:-}" ]; then
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
# generate csr req
"$OPENSSL" req \
-batch -new \
-key "${KEYDIR}/${cn}-${TIMESTAMP}.key" \
-out "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \
-config /dev/stdin <<EOF
$(cat "${CONFFILE}")
error "Error generating the private key"
fi
# generate csr req
pass_args=""
if [ -n "${password_file:-}" ]; then
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}
EOF
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the CSR"
fi
# ca sign and generate cert
CA_PASSWORD="${CA_PASSWORD}" "${OPENSSL}" ca \
-config "${CONFFILE}" \
-passin env:CA_PASSWORD \
-in "${CSRDIR}/${cn}-${TIMESTAMP}.csr" \
-out "${CRTDIR}/${cn}.crt"
# check if CRT is a valid
"${OPENSSL}" x509 \
-noout -subject \
-in "${CRTDIR}/${cn}.crt" \
>/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"
"${OPENSSL_BIN}" ca \
${batch_arg} \
-config "${CONF_FILE}" \
-passin pass:${CA_PASSWORD} \
-in "${csr_file}" \
-out "${crt_file}" \
${crt_expiration_arg}
# shellcheck disable=SC2181
if [ "$?" -ne 0 ]; then
error "Error generating the certificate"
fi
chmod 640 "${PKCS12DIR}/${cn}-${TIMESTAMP}.p12"
echo "The PKCS12 config file is available in ${PKCS12DIR}/${cn}-${TIMESTAMP}.p12"
# check if CRT is a valid
"${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
if [ -e "${CADIR}/ovpn.conf" ]; then
cat "${CADIR}/ovpn.conf" - > "${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn" <<EOF
if [ -e "${CA_DIR}/ovpn.conf" ]; then
cat "${CA_DIR}/ovpn.conf" - > "${ovpn_file}" <<EOF
<ca>
$(cat "${CACERT}")
$(cat "${CA_CERT}")
</ca>
<cert>
$(cat "${CRTDIR}/${cn}.crt")
$(cat "${crt_file}")
</cert>
<key>
$(cat "${KEYDIR}/${cn}-${TIMESTAMP}.key")
$(cat "${key_file}")
</key>
EOF
chmod 640 "${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn"
echo "The OpenVPN config file is available in ${OVPNDIR}/${cn}-${TIMESTAMP}.ovpn"
chmod 640 "${ovpn_file}"
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
}
revoke() {
[ "${1}" = "" ] && usage >&2 && exit 1
non_interactive=0
# get CN from param
cn="${1}"
# Parse options
# 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
[ ! -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
"${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_ca_password 0
echo "Revoke certificate ${CRTDIR}/${cn}.crt :"
CA_PASSWORD="${CA_PASSWORD}" "$OPENSSL" ca \
-config "${CONFFILE}" \
-passin env:CA_PASSWORD \
-revoke "${CRTDIR}/${cn}.crt" \
&& rm "${CRTDIR}/${cn}.crt"
echo "Revoke certificate ${crt_file} :"
"${OPENSSL_BIN}" ca \
-config "${CONF_FILE}" \
-passin pass:"${CA_PASSWORD}" \
-revoke "${crt_file}"
# shellcheck disable=SC2181
if [ "$?" -eq 0 ]; then
rm "${crt_file}"
fi
CA_PASSWORD="${CA_PASSWORD}" "$OPENSSL" ca \
-config "${CONFFILE}" \
-passin env:CA_PASSWORD \
-gencrl -out "${CRL}"
"${OPENSSL_BIN}" ca \
-config "${CONF_FILE}" \
-passin pass:"${CA_PASSWORD}" \
-gencrl \
-out "${CRL}"
}
list() {
[ -f "${INDEX}" ] || exit 0
if [ ! -f "${INDEX_FILE}" ]; then
exit 0
fi
list_valid=0
list_revoked=1
if [ -z "${1:-}" ]; then
show_usage_list >&2
exit 1
fi
while :; do
case "${1}" in
case "${1:-}" in
-a|--all)
list_valid=0
list_revoked=0
shift;;
;;
-v|--valid)
list_valid=0
list_revoked=1
shift;;
;;
-r|--revoked)
list_valid=1
list_revoked=0
shift;;
--)
shift
break;;
;;
-?*)
warning "unknow option ${1} (ignored)"
shift;;
;;
*)
break;;
break
;;
esac
shift
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
}
cert_end_date() {
"${OPENSSL_BIN}" x509 -noout -enddate -in "${1}" | cut -d'=' -f2
}
check() {
# default expiration alert
# TODO : permit override with parameters
# TODO: permit override with parameters
min_day=90
cur_epoch=$(date -u +'%s')
for cert in ${CRTDIR}/*; do
end_date=$(openssl x509 -noout -enddate -in "${cert}" | cut -d'=' -f2)
for cert in "${CRT_DIR}"/*; do
end_date=$(cert_end_date "${cert}")
end_epoch=$(date -ud "${end_date}" +'%s')
diff_epoch=$((end_epoch - cur_epoch))
diff_day=$((diff_epoch/60/60/24))
diff_epoch=$(( end_epoch - cur_epoch ))
diff_day=$(( diff_epoch / 60 / 60 / 24 ))
if [ "${diff_day}" -lt "${min_day}" ]; then
if [ "${diff_day}" -le 0 ]; then
echo "${cert} has expired"
@ -410,43 +980,72 @@ check() {
done
}
is_user() {
getent passwd "${1}" >/dev/null
}
is_group() {
getent group "${1}" >/dev/null
}
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
# TODO : override with /etc/default/shellpki
CONFFILE="/etc/shellpki/openssl.cnf"
PKIUSER="shellpki"
[ "$(uname)" = "OpenBSD" ] && PKIUSER="_shellpki"
# TODO: override with /etc/default/shellpki
CONF_FILE="/etc/shellpki/openssl.cnf"
[ "${USER}" != "root" ] || [ "${USER}" != "${PKIUSER}" ] || error "Please become root before running ${0} !"
# retrieve CA path from config file
CADIR=$(grep -E "^dir" "${CONFFILE}" | cut -d'=' -f2|xargs -n1)
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 !"
if [ "$(uname)" = "OpenBSD" ]; then
PKI_USER="_shellpki"
else
PKI_USER="shellpki"
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}
@ -481,17 +1080,27 @@ main() {
check "$@"
;;
version|--version)
show_version
exit 0
;;
help|--help)
show_usage
exit 0
;;
*)
usage >&2
show_usage >&2
exit 1
;;
esac
# fix right
chown -R "${PKIUSER}":"${PKIUSER}" "${CADIR}"
chmod 750 "${CADIR}" "${CRTDIR}" "${KEYDIR}" "${CSRDIR}" "${PKCS12DIR}" "${OVPNDIR}" "${TMPDIR}"
chmod 600 "${INDEX}"* "${SERIAL}"* "${CAKEY}" "${CRL}"
chmod 640 "${CACERT}"
chown -R "${PKI_USER}":"${PKI_USER}" "${CA_DIR}"
chmod 750 "${CA_DIR}" "${CRT_DIR}" "${KEY_DIR}" "${CSR_DIR}" "${PKCS12_DIR}" "${OVPN_DIR}" "${TMP_DIR}"
chmod 600 "${INDEX_FILE}"* "${SERIAL}"* "${CA_KEY}" "${CRL}"
chmod 640 "${CA_CERT}"
}
main "$@"