2010-10-06 17:34:30 +02:00
|
|
|
#!/bin/sh
|
2018-01-17 12:21:39 +01:00
|
|
|
#
|
2020-05-04 23:12:56 +02:00
|
|
|
# shellpki is a wrapper around OpenSSL to manage a small PKI
|
2018-01-17 12:21:39 +01:00
|
|
|
#
|
2010-10-06 17:34:30 +02:00
|
|
|
|
2020-05-05 23:20:36 +02:00
|
|
|
set -u
|
|
|
|
|
2022-12-01 16:45:42 +01:00
|
|
|
VERSION="22.12"
|
2020-05-04 17:43:09 +02:00
|
|
|
|
|
|
|
show_version() {
|
|
|
|
cat <<END
|
|
|
|
shellpki version ${VERSION}
|
|
|
|
|
2022-03-29 18:48:45 +02:00
|
|
|
Copyright 2010-2022 Evolix <info@evolix.fr>,
|
2020-05-04 17:43:09 +02:00
|
|
|
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>,
|
2022-03-29 18:48:45 +02:00
|
|
|
Jérémy Lecour <jlecour@evolix.fr>,
|
|
|
|
Jérémy Dubois <jdubois@evolix.fr>
|
2020-05-04 17:43:09 +02:00
|
|
|
and others.
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-03-11 14:09:58 +01:00
|
|
|
show_usage() {
|
|
|
|
cat <<EOF
|
|
|
|
Usage: ${0} <subcommand> [options] [CommonName]
|
2022-03-29 18:10:47 +02:00
|
|
|
Warning: [options] always must be before [CommonName] and after <subcommand>
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-04-04 17:36:02 +02:00
|
|
|
EOF
|
|
|
|
show_usage_init
|
|
|
|
show_usage_create
|
|
|
|
show_usage_revoke
|
|
|
|
show_usage_list
|
|
|
|
show_usage_check
|
|
|
|
show_usage_ocsp
|
|
|
|
|
|
|
|
cat <<EOF
|
|
|
|
Show version :
|
|
|
|
|
|
|
|
${0} --version
|
|
|
|
|
|
|
|
Show help :
|
|
|
|
|
|
|
|
${0} --help
|
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
show_usage_init() {
|
|
|
|
cat <<EOF
|
2022-03-22 18:08:57 +01:00
|
|
|
Initialize PKI (create CA key and self-signed certificate) :
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-03-29 18:10:47 +02:00
|
|
|
${0} init [options] <commonName_for_CA>
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-03-22 18:01:22 +01:00
|
|
|
Options
|
|
|
|
--non-interactive do not prompt the user, and exit if an error occurs
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-04-04 17:36:02 +02:00
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
show_usage_create() {
|
|
|
|
cat <<EOF
|
2022-03-22 18:08:57 +01:00
|
|
|
Create a client certificate with key and CSR directly generated on server :
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-03-29 18:10:47 +02:00
|
|
|
${0} create [options] <commonName>
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-03-22 18:01:22 +01:00
|
|
|
Options
|
2022-03-22 18:08:57 +01:00
|
|
|
-f, --file, --csr-file create a client certificate from a CSR (doesn't need key)
|
2022-03-22 18:01:22 +01:00
|
|
|
-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
|
2022-04-14 15:53:59 +02:00
|
|
|
--end-date specify until which date the certificate should be valid, in "YYYY/MM/DD hh:mm:ss" format, UTC +0
|
2022-03-22 18:01:22 +01:00
|
|
|
--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
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-04-04 17:36:02 +02:00
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
show_usage_revoke() {
|
|
|
|
cat <<EOF
|
2022-03-22 18:08:57 +01:00
|
|
|
Revoke a client certificate :
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-03-29 18:10:47 +02:00
|
|
|
${0} revoke [options] <commonName>
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-03-22 18:01:22 +01:00
|
|
|
Options
|
|
|
|
--non-interactive do not prompt the user, and exit if an error occurs
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-04-04 17:36:02 +02:00
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
show_usage_list() {
|
|
|
|
cat <<EOF
|
|
|
|
List certificates :
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-03-22 18:01:22 +01:00
|
|
|
${0} list <options>
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-03-22 18:01:22 +01:00
|
|
|
Options
|
2022-03-29 18:17:03 +02:00
|
|
|
-a, --all list all certificates: valid and revoked ones
|
2022-03-22 18:04:03 +01:00
|
|
|
-v, --valid list all valid certificates
|
|
|
|
-r, --revoked list all revoked certificates
|
2022-03-11 14:09:58 +01:00
|
|
|
|
2022-04-04 17:36:02 +02:00
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
show_usage_check() {
|
|
|
|
cat <<EOF
|
2022-03-11 14:09:58 +01:00
|
|
|
Check expiration date of valid certificates :
|
|
|
|
|
|
|
|
${0} check
|
|
|
|
|
2022-04-04 17:36:02 +02:00
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
show_usage_ocsp() {
|
|
|
|
cat <<EOF
|
2022-03-22 18:01:22 +01:00
|
|
|
Run OCSP_D server :
|
|
|
|
|
|
|
|
${0} ocsp <ocsp_uri:ocsp_port>
|
|
|
|
|
2022-03-11 14:09:58 +01:00
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
error() {
|
|
|
|
echo "${1}" >&2
|
|
|
|
exit 1
|
|
|
|
}
|
|
|
|
|
|
|
|
warning() {
|
|
|
|
echo "${1}" >&2
|
|
|
|
}
|
|
|
|
|
|
|
|
verify_ca_password() {
|
2022-04-06 11:09:07 +02:00
|
|
|
"${OPENSSL_BIN}" pkey \
|
2022-03-11 14:09:58 +01:00
|
|
|
-in "${CA_KEY}" \
|
|
|
|
-passin pass:"${CA_PASSWORD}" \
|
|
|
|
>/dev/null 2>&1
|
|
|
|
}
|
|
|
|
get_real_path() {
|
|
|
|
# --canonicalize is supported on Linux
|
2022-04-06 11:09:07 +02:00
|
|
|
# -f is supported on Linux and OpenBSD
|
2022-03-11 14:09:58 +01:00
|
|
|
readlink -f -- "${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
|
2022-03-14 14:40:42 +01:00
|
|
|
revoke --non-interactive "${cn}"
|
2022-03-11 14:09:58 +01:00
|
|
|
else
|
|
|
|
error "${cn} already exists, use \`--replace-existing' to force"
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
if [ "${replace_existing}" -eq 1 ]; then
|
2022-03-14 14:40:42 +01:00
|
|
|
revoke "${cn}"
|
2022-03-11 14:09:58 +01:00
|
|
|
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')
|
2022-03-14 14:40:42 +01:00
|
|
|
|
|
|
|
if [ "${resp}" = "y" ]; then
|
|
|
|
revoke "${cn}"
|
|
|
|
else
|
|
|
|
error "Aborted"
|
|
|
|
fi
|
2022-03-11 14:09:58 +01:00
|
|
|
fi
|
|
|
|
fi
|
|
|
|
}
|
|
|
|
|
2010-10-06 17:34:30 +02:00
|
|
|
init() {
|
2018-01-17 12:21:39 +01:00
|
|
|
umask 0177
|
2010-10-06 17:34:30 +02:00
|
|
|
|
2022-12-13 17:40:12 +01:00
|
|
|
[ -d "${CA_DIR}" ] || mkdir -m 0751 "${CA_DIR}"
|
2020-05-04 18:16:07 +02:00
|
|
|
[ -d "${CRT_DIR}" ] || mkdir -m 0750 "${CRT_DIR}"
|
|
|
|
[ -f "${INDEX_FILE}" ] || touch "${INDEX_FILE}"
|
2022-07-11 11:09:37 +02:00
|
|
|
[ -f "${INDEX_FILE}.attr" ] || touch "${INDEX_FILE}.attr"
|
2018-06-27 11:45:03 +02:00
|
|
|
[ -f "${CRL}" ] || touch "${CRL}"
|
2018-01-31 12:43:18 +01:00
|
|
|
[ -f "${SERIAL}" ] || echo "01" > "${SERIAL}"
|
2018-01-17 12:21:39 +01:00
|
|
|
|
2020-10-12 23:27:05 +02:00
|
|
|
non_interactive=0
|
|
|
|
|
|
|
|
# Parse options
|
|
|
|
# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a
|
|
|
|
while :; do
|
2022-03-11 14:10:32 +01:00
|
|
|
case ${1:-} in
|
2020-10-12 23:27:05 +02:00
|
|
|
--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
|
|
|
|
|
2018-06-27 11:45:03 +02:00
|
|
|
cn="${1:-}"
|
2020-05-04 17:44:01 +02:00
|
|
|
if [ -z "${cn}" ]; then
|
2022-04-04 17:36:02 +02:00
|
|
|
show_usage_init >&2
|
2020-05-04 17:44:01 +02:00
|
|
|
exit 1
|
|
|
|
fi
|
2018-06-27 11:45:03 +02:00
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
if [ -f "${CA_KEY}" ]; then
|
2022-03-11 14:10:32 +01:00
|
|
|
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
|
2020-05-04 18:07:20 +02:00
|
|
|
fi
|
2018-06-27 11:45:03 +02:00
|
|
|
fi
|
|
|
|
|
2022-03-11 14:10:32 +01:00
|
|
|
passout_arg=""
|
|
|
|
if [ -n "${CA_PASSWORD:-}" ]; then
|
2022-04-06 11:09:07 +02:00
|
|
|
passout_arg="-pass pass:${CA_PASSWORD}"
|
2022-03-29 18:18:01 +02:00
|
|
|
elif [ "${non_interactive}" -eq 1 ]; then
|
|
|
|
error "In non-interactive mode, you must pass CA_PASSWORD as environment variable."
|
2022-03-11 14:10:32 +01:00
|
|
|
fi
|
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
if [ ! -f "${CA_KEY}" ]; then
|
2022-04-06 11:09:07 +02:00
|
|
|
"${OPENSSL_BIN}" genpkey \
|
|
|
|
-algorithm RSA \
|
2020-05-04 18:16:07 +02:00
|
|
|
-out "${CA_KEY}" \
|
2022-03-11 14:10:32 +01:00
|
|
|
${passout_arg} \
|
2020-05-06 00:00:00 +02:00
|
|
|
-aes256 \
|
2022-04-06 11:09:07 +02:00
|
|
|
-pkeyopt "rsa_keygen_bits:${CA_KEY_LENGTH}" \
|
2020-05-04 18:07:20 +02:00
|
|
|
>/dev/null 2>&1
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 11:45:11 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
2020-05-05 15:24:06 +02:00
|
|
|
error "Error generating the CA key"
|
2020-05-05 11:45:11 +02:00
|
|
|
fi
|
2020-05-04 18:07:20 +02:00
|
|
|
fi
|
2018-06-27 11:45:03 +02:00
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
if [ -f "${CA_CERT}" ]; then
|
2022-03-29 18:18:01 +02:00
|
|
|
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
|
2020-05-04 18:07:20 +02:00
|
|
|
fi
|
2018-06-27 11:45:03 +02:00
|
|
|
fi
|
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
if [ ! -f "${CA_CERT}" ]; then
|
2020-05-04 18:07:20 +02:00
|
|
|
ask_ca_password 0
|
|
|
|
fi
|
2018-06-27 11:45:03 +02:00
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
if [ ! -f "${CA_CERT}" ]; then
|
2020-05-05 10:46:15 +02:00
|
|
|
"${OPENSSL_BIN}" req \
|
2020-05-04 18:07:20 +02:00
|
|
|
-new \
|
|
|
|
-batch \
|
|
|
|
-sha512 \
|
|
|
|
-x509 \
|
|
|
|
-days 3650 \
|
|
|
|
-extensions v3_ca \
|
2022-03-11 11:44:09 +01:00
|
|
|
-passin pass:"${CA_PASSWORD}" \
|
2020-05-04 18:16:07 +02:00
|
|
|
-key "${CA_KEY}" \
|
|
|
|
-out "${CA_CERT}" \
|
2020-05-04 18:07:20 +02:00
|
|
|
-config /dev/stdin <<EOF
|
2020-05-04 18:16:07 +02:00
|
|
|
$(cat "${CONF_FILE}")
|
2018-06-27 11:45:03 +02:00
|
|
|
commonName_default = ${cn}
|
|
|
|
EOF
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 11:45:11 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
2020-05-05 15:24:06 +02:00
|
|
|
error "Error generating the CA certificate"
|
2020-05-05 11:45:11 +02:00
|
|
|
fi
|
2022-04-04 18:13:37 +02:00
|
|
|
|
|
|
|
"${OPENSSL_BIN}" ca \
|
|
|
|
-config "${CONF_FILE}" \
|
|
|
|
-passin pass:${CA_PASSWORD} \
|
|
|
|
-gencrl \
|
|
|
|
-out "${CRL}"
|
2022-04-14 15:51:07 +02:00
|
|
|
fi
|
2018-01-17 12:21:39 +01:00
|
|
|
}
|
2010-10-06 17:34:30 +02:00
|
|
|
|
2018-06-27 12:52:20 +02:00
|
|
|
ocsp() {
|
|
|
|
umask 0177
|
|
|
|
|
|
|
|
ocsp_uri="${1:-}"
|
2020-05-04 17:44:01 +02:00
|
|
|
if [ -z "${ocsp_uri}" ]; then
|
2022-04-04 17:36:02 +02:00
|
|
|
show_usage_ocsp >&2
|
2020-05-04 17:44:01 +02:00
|
|
|
exit 1
|
|
|
|
fi
|
2020-05-05 00:28:00 +02:00
|
|
|
ocsp_csr_file="${CSR_DIR}/ocsp.csr"
|
2018-06-27 12:52:20 +02:00
|
|
|
|
2020-05-04 18:07:20 +02:00
|
|
|
url=$(echo "${ocsp_uri}" | cut -d':' -f1)
|
|
|
|
port=$(echo "${ocsp_uri}" | cut -d':' -f2)
|
2018-06-27 12:52:20 +02:00
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
if [ ! -f "${OCSP_KEY}" ]; then
|
2022-04-06 11:09:07 +02:00
|
|
|
"${OPENSSL_BIN}" genpkey \
|
|
|
|
-algorithm RSA \
|
2020-05-04 18:16:07 +02:00
|
|
|
-out "${OCSP_KEY}" \
|
2022-04-06 11:09:07 +02:00
|
|
|
-pkeyopt "rsa_keygen_bits:${KEY_LENGTH}" \
|
2020-05-04 18:07:20 +02:00
|
|
|
>/dev/null 2>&1
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 11:45:11 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
2020-05-05 15:24:06 +02:00
|
|
|
error "Error generating the OCSP key"
|
2020-05-05 11:45:11 +02:00
|
|
|
fi
|
2020-05-04 18:07:20 +02:00
|
|
|
fi
|
2018-06-27 12:52:20 +02:00
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
"${OPENSSL_BIN}" req \
|
2020-05-04 18:07:20 +02:00
|
|
|
-batch \
|
|
|
|
-new \
|
2020-05-04 18:16:07 +02:00
|
|
|
-key "${OCSP_KEY}" \
|
2020-05-05 00:28:00 +02:00
|
|
|
-out "${ocsp_csr_file}" \
|
2018-06-27 12:52:20 +02:00
|
|
|
-config /dev/stdin <<EOF
|
2020-05-04 18:16:07 +02:00
|
|
|
$(cat "${CONF_FILE}")
|
2018-06-27 12:52:20 +02:00
|
|
|
commonName_default = ${url}
|
|
|
|
[ usr_cert ]
|
|
|
|
authorityInfoAccess = OCSP;URI:http://${ocsp_uri}
|
|
|
|
EOF
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 11:45:11 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
2020-05-05 15:24:06 +02:00
|
|
|
error "Error generating the OCSP request"
|
2020-05-05 11:45:11 +02:00
|
|
|
fi
|
2018-06-27 12:52:20 +02:00
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
if [ ! -f "${OCSP_CERT}" ]; then
|
2020-05-04 18:07:20 +02:00
|
|
|
ask_ca_password 0
|
|
|
|
fi
|
2018-06-27 12:52:20 +02:00
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
if [ ! -f "${OCSP_CERT}" ]; then
|
2020-05-05 10:46:15 +02:00
|
|
|
"${OPENSSL_BIN}" ca \
|
2020-05-04 18:07:20 +02:00
|
|
|
-extensions v3_ocsp \
|
2020-05-05 00:28:00 +02:00
|
|
|
-in "${ocsp_csr_file}" \
|
2020-05-04 18:16:07 +02:00
|
|
|
-out "${OCSP_CERT}" \
|
2022-03-11 11:44:09 +01:00
|
|
|
-passin pass:"${CA_PASSWORD}" \
|
2020-05-04 18:16:07 +02:00
|
|
|
-config "${CONF_FILE}"
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 11:45:11 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
2020-05-05 15:24:06 +02:00
|
|
|
error "Error generating the OCSP certificate"
|
2020-05-05 11:45:11 +02:00
|
|
|
fi
|
2020-05-04 18:07:20 +02:00
|
|
|
fi
|
2018-06-27 12:52:20 +02:00
|
|
|
|
2020-05-04 18:16:07 +02:00
|
|
|
exec "${OPENSSL_BIN}" ocsp \
|
2020-05-04 18:07:20 +02:00
|
|
|
-ignore_err \
|
2020-05-04 18:16:07 +02:00
|
|
|
-index "${INDEX_FILE}" \
|
2020-05-04 18:07:20 +02:00
|
|
|
-port "${port}" \
|
2020-05-04 18:16:07 +02:00
|
|
|
-rsigner "${OCSP_CERT}" \
|
|
|
|
-rkey "${OCSP_KEY}" \
|
|
|
|
-CA "${CA_CERT}" \
|
2020-05-04 18:07:20 +02:00
|
|
|
-text
|
2018-06-27 12:52:20 +02:00
|
|
|
}
|
|
|
|
|
2018-01-23 16:52:42 +01:00
|
|
|
create() {
|
2020-05-04 14:21:58 +02:00
|
|
|
from_csr=0
|
2020-05-04 17:58:13 +02:00
|
|
|
ask_pass=0
|
2020-05-05 23:14:32 +02:00
|
|
|
non_interactive=0
|
2020-05-06 00:38:57 +02:00
|
|
|
replace_existing=0
|
2020-05-05 23:20:36 +02:00
|
|
|
days=""
|
|
|
|
end_date=""
|
2022-03-29 18:20:16 +02:00
|
|
|
days_set=0
|
|
|
|
end_date_set=0
|
2022-04-14 15:01:09 +02:00
|
|
|
password_set=0
|
|
|
|
password_file_set=0
|
2018-01-23 16:52:42 +01:00
|
|
|
|
2020-05-04 14:21:58 +02:00
|
|
|
# Parse options
|
|
|
|
# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a
|
2019-03-11 11:06:53 +01:00
|
|
|
while :; do
|
2022-03-29 18:19:33 +02:00
|
|
|
case ${1:-} in
|
2020-05-04 14:21:58 +02:00
|
|
|
-f|--file|--csr-file)
|
|
|
|
# csr-file option, with value separated by space
|
|
|
|
if [ -n "$2" ]; then
|
|
|
|
from_csr=1
|
2022-03-11 11:38:01 +01:00
|
|
|
csr_file=$(get_real_path "${2}")
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 15:24:06 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
|
|
|
error "Error accessing file \`${2}'"
|
|
|
|
fi
|
2020-05-04 14:21:58 +02:00
|
|
|
shift
|
|
|
|
else
|
2020-05-05 23:49:10 +02:00
|
|
|
error "Argument error: \`--csr-file' requires a value"
|
2020-05-04 14:21:58 +02:00
|
|
|
fi
|
|
|
|
;;
|
|
|
|
--file=?*|--csr-file=?*)
|
|
|
|
from_csr=1
|
|
|
|
# csr-file option, with value separated by =
|
2022-03-11 11:38:01 +01:00
|
|
|
csr_file=$(get_real_path "${1#*=}")
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 15:24:06 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
|
|
|
error "Error accessing file \`${1#*=}'"
|
|
|
|
fi
|
2020-05-04 14:21:58 +02:00
|
|
|
;;
|
|
|
|
--file=|--csr-file=)
|
|
|
|
# csr-file options, without value
|
2020-05-05 23:49:10 +02:00
|
|
|
error "Argument error: \`--csr-file' requires a value"
|
2020-05-04 14:21:58 +02:00
|
|
|
;;
|
2019-03-11 11:06:53 +01:00
|
|
|
-p|--password)
|
2020-05-04 14:21:58 +02:00
|
|
|
ask_pass=1
|
2022-04-14 15:01:09 +02:00
|
|
|
password_set=1
|
2020-05-04 14:21:58 +02:00
|
|
|
;;
|
|
|
|
--password-file)
|
|
|
|
# password-file option, with value separated by space
|
|
|
|
if [ -n "$2" ]; then
|
2022-03-11 11:38:01 +01:00
|
|
|
password_file=$(get_real_path "${2}")
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 15:24:06 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
|
|
|
error "Error accessing file \`${2}'"
|
|
|
|
fi
|
2022-04-14 15:01:09 +02:00
|
|
|
password_file_set=1
|
2020-05-04 14:21:58 +02:00
|
|
|
shift
|
|
|
|
else
|
2020-05-05 23:49:10 +02:00
|
|
|
error "Argument error: \`--password-file' requires a value"
|
2020-05-04 14:21:58 +02:00
|
|
|
fi
|
|
|
|
;;
|
|
|
|
--password-file=?*)
|
|
|
|
# password-file option, with value separated by =
|
2022-03-11 11:38:01 +01:00
|
|
|
password_file=$(get_real_path "${1#*=}")
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 15:24:06 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
|
|
|
error "Error accessing file \`${1#*=}'"
|
|
|
|
fi
|
2022-04-14 15:01:09 +02:00
|
|
|
password_file_set=1
|
2020-05-04 14:21:58 +02:00
|
|
|
;;
|
|
|
|
--password-file=)
|
|
|
|
# password-file options, without value
|
2020-05-05 23:49:10 +02:00
|
|
|
error "Argument error: \`--password-file' requires a value"
|
2020-05-04 14:21:58 +02:00
|
|
|
;;
|
2020-05-05 00:22:35 +02:00
|
|
|
--days)
|
|
|
|
# days option, with value separated by space
|
|
|
|
if [ -n "$2" ]; then
|
|
|
|
days=${2}
|
2022-03-29 18:20:16 +02:00
|
|
|
days_set=1
|
2020-05-05 00:22:35 +02:00
|
|
|
shift
|
|
|
|
else
|
2020-05-05 23:49:10 +02:00
|
|
|
error "Argument error: \`--days' requires a value"
|
2020-05-05 00:22:35 +02:00
|
|
|
fi
|
|
|
|
;;
|
|
|
|
--days=?*)
|
|
|
|
# days option, with value separated by =
|
|
|
|
days=${1#*=}
|
2022-03-29 18:20:16 +02:00
|
|
|
days_set=1
|
2020-05-05 00:22:35 +02:00
|
|
|
;;
|
|
|
|
--days=)
|
|
|
|
# days options, without value
|
2020-05-05 23:49:10 +02:00
|
|
|
error "Argument error: \`--days' requires a value"
|
2020-05-05 00:22:35 +02:00
|
|
|
;;
|
|
|
|
--end-date)
|
|
|
|
# end-date option, with value separated by space
|
|
|
|
if [ -n "$2" ]; then
|
|
|
|
end_date=${2}
|
2022-03-29 18:20:16 +02:00
|
|
|
end_date_set=1
|
2020-05-05 00:22:35 +02:00
|
|
|
shift
|
|
|
|
else
|
2020-05-05 23:49:10 +02:00
|
|
|
error "Argument error: \`--end-date' requires a value"
|
2020-05-05 00:22:35 +02:00
|
|
|
fi
|
|
|
|
;;
|
|
|
|
--end-date=?*)
|
|
|
|
# end-date option, with value separated by =
|
|
|
|
end_date=${1#*=}
|
2022-03-29 18:20:16 +02:00
|
|
|
end_date_set=1
|
2020-05-05 00:22:35 +02:00
|
|
|
;;
|
|
|
|
--end-date=)
|
|
|
|
# end-date options, without value
|
2020-05-05 23:49:10 +02:00
|
|
|
error "Argument error: \`--end-date' requires a value"
|
2020-05-05 00:22:35 +02:00
|
|
|
;;
|
2020-05-05 23:14:32 +02:00
|
|
|
--non-interactive)
|
|
|
|
non_interactive=1
|
|
|
|
;;
|
2020-05-06 00:38:57 +02:00
|
|
|
--replace-existing)
|
|
|
|
replace_existing=1
|
2020-05-05 23:50:04 +02:00
|
|
|
;;
|
2019-03-11 11:06:53 +01:00
|
|
|
--)
|
2020-05-04 14:21:58 +02:00
|
|
|
# End of all options.
|
2019-03-11 11:06:53 +01:00
|
|
|
shift
|
2020-05-04 14:21:58 +02:00
|
|
|
break
|
|
|
|
;;
|
2019-03-11 11:06:53 +01:00
|
|
|
-?*)
|
2020-05-04 14:21:58 +02:00
|
|
|
# ignore unknown options
|
2020-05-05 23:49:10 +02:00
|
|
|
warning "Warning: unknown option (ignored): \`$1'"
|
2020-05-04 14:21:58 +02:00
|
|
|
;;
|
2019-03-11 11:06:53 +01:00
|
|
|
*)
|
2020-05-04 14:21:58 +02:00
|
|
|
# Default case: If no more options then break out of the loop.
|
|
|
|
break
|
|
|
|
;;
|
2018-01-23 16:52:42 +01:00
|
|
|
esac
|
2020-05-04 14:21:58 +02:00
|
|
|
|
|
|
|
shift
|
2018-01-23 16:52:42 +01:00
|
|
|
done
|
|
|
|
|
2022-03-29 18:20:16 +02:00
|
|
|
if [ "${days_set}" -eq 1 ] && [ "${end_date_set}" -eq 1 ]; then
|
|
|
|
error "Argument error: \`--end-date' and \`--days' cannot be used together."
|
|
|
|
fi
|
|
|
|
|
2022-04-14 15:01:09 +02:00
|
|
|
if [ "${password_set}" -eq 1 ] && [ "${password_file_set}" -eq 1 ]; then
|
|
|
|
error "Argument error: \`--password' and \`--password-file' cannot be used together."
|
|
|
|
fi
|
|
|
|
|
2020-05-05 00:22:35 +02:00
|
|
|
# The name of the certificate
|
2018-01-23 16:52:42 +01:00
|
|
|
cn="${1:-}"
|
|
|
|
|
2020-05-05 00:22:35 +02:00
|
|
|
# Set expiration argument
|
|
|
|
crt_expiration_arg=""
|
2022-03-29 18:42:28 +02:00
|
|
|
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
|
2020-05-05 00:22:35 +02:00
|
|
|
fi
|
|
|
|
if [ -n "${end_date}" ]; then
|
2022-03-29 18:15:57 +02:00
|
|
|
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
|
2022-04-04 17:01:19 +02:00
|
|
|
error "Invalid end date format: \`${end_date}' can't be parsed by date(1). Expected format: YYYY/MM/DD [hh[:mm[:ss]]]."
|
2022-03-29 18:15:57 +02:00
|
|
|
else
|
|
|
|
crt_expiration_arg="-enddate ${cert_end_date}"
|
|
|
|
fi
|
|
|
|
elif [ "${SYSTEM}" = "openbsd" ]; then
|
2022-04-04 17:01:19 +02:00
|
|
|
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)
|
2022-03-29 18:15:57 +02:00
|
|
|
# shellcheck disable=SC2181
|
|
|
|
if [ "$?" -ne 0 ]; then
|
2022-04-04 17:01:19 +02:00
|
|
|
error "Invalid end date format: \`${end_date}' can't be parsed by date(1). Expected format: YYYY/MM/DD hh:mm:ss."
|
2022-03-29 18:15:57 +02:00
|
|
|
else
|
|
|
|
crt_expiration_arg="-enddate ${cert_end_date}"
|
|
|
|
fi
|
2020-05-05 00:22:35 +02:00
|
|
|
else
|
2022-03-29 18:15:57 +02:00
|
|
|
error "System ${SYSTEM} not supported."
|
2020-05-05 00:22:35 +02:00
|
|
|
fi
|
|
|
|
fi
|
2020-05-05 23:14:32 +02:00
|
|
|
if [ "${non_interactive}" -eq 1 ]; then
|
|
|
|
batch_arg="-batch"
|
|
|
|
else
|
|
|
|
batch_arg=""
|
|
|
|
fi
|
2020-05-05 00:22:35 +02:00
|
|
|
|
2020-05-04 14:21:58 +02:00
|
|
|
if [ "${from_csr}" -eq 1 ]; then
|
2020-05-04 18:07:20 +02:00
|
|
|
if [ "${ask_pass}" -eq 1 ]; then
|
|
|
|
warning "Warning: -p|--password is ignored with -f|--file|--crt-file"
|
|
|
|
fi
|
2020-10-12 23:27:24 +02:00
|
|
|
if [ -n "${password_file:-}" ]; then
|
2020-05-04 18:07:20 +02:00
|
|
|
warning "Warning: --password-file is ignored with -f|--file|--crt-file"
|
|
|
|
fi
|
2018-01-23 16:52:42 +01:00
|
|
|
|
2020-05-05 00:28:00 +02:00
|
|
|
crt_file="${CRT_DIR}/${cn}.crt"
|
|
|
|
|
2018-01-23 16:52:42 +01:00
|
|
|
# ask for CA passphrase
|
|
|
|
ask_ca_password 0
|
|
|
|
|
|
|
|
# check if csr_file is a CSR
|
2020-05-04 18:16:07 +02:00
|
|
|
"${OPENSSL_BIN}" req \
|
2020-05-04 23:08:19 +02:00
|
|
|
-noout \
|
|
|
|
-subject \
|
2020-05-04 18:07:20 +02:00
|
|
|
-in "${csr_file}" \
|
2020-05-04 23:06:51 +02:00
|
|
|
>/dev/null 2>&1
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-04 23:06:51 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
|
|
|
error "${csr_file} is not a valid CSR !"
|
|
|
|
fi
|
2018-01-23 16:52:42 +01:00
|
|
|
|
|
|
|
# check if csr_file contain a CN
|
2020-05-04 18:16:07 +02:00
|
|
|
"${OPENSSL_BIN}" req \
|
2020-05-04 23:08:19 +02:00
|
|
|
-noout \
|
|
|
|
-subject \
|
2020-05-04 18:07:20 +02:00
|
|
|
-in "${csr_file}" \
|
|
|
|
| grep -Eo "CN\s*=[^,/]*" \
|
2020-05-04 23:06:51 +02:00
|
|
|
>/dev/null 2>&1
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-04 23:06:51 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
|
|
|
error "${csr_file} doesn't contain a CommonName !"
|
|
|
|
fi
|
2018-01-23 16:52:42 +01:00
|
|
|
|
|
|
|
# get CN from CSR
|
2020-05-04 18:16:07 +02:00
|
|
|
cn=$("${OPENSSL_BIN}" req -noout -subject -in "${csr_file}" | grep -Eo "CN\s*=[^,/]*" | cut -d'=' -f2 | xargs)
|
2018-01-23 16:52:42 +01:00
|
|
|
|
2022-04-14 15:18:57 +02:00
|
|
|
# check if CN already exists
|
2020-05-05 23:50:04 +02:00
|
|
|
if [ -f "${crt_file}" ]; then
|
2020-05-06 00:38:57 +02:00
|
|
|
replace_existing_or_abort "${cn}"
|
2020-05-04 18:07:20 +02:00
|
|
|
fi
|
2018-01-23 16:52:42 +01:00
|
|
|
|
|
|
|
# ca sign and generate cert
|
2020-05-05 23:14:32 +02:00
|
|
|
if [ "${non_interactive}" -eq 1 ]; then
|
|
|
|
batch_arg="-batch"
|
|
|
|
else
|
|
|
|
batch_arg=""
|
|
|
|
fi
|
2020-05-05 10:46:15 +02:00
|
|
|
"${OPENSSL_BIN}" ca \
|
2020-05-05 23:14:32 +02:00
|
|
|
${batch_arg} \
|
2020-05-04 18:16:07 +02:00
|
|
|
-config "${CONF_FILE}" \
|
2020-05-04 18:07:20 +02:00
|
|
|
-in "${csr_file}" \
|
2022-03-11 11:44:09 +01:00
|
|
|
-passin pass:"${CA_PASSWORD}" \
|
2020-05-05 00:28:00 +02:00
|
|
|
-out "${crt_file}" \
|
2020-05-05 00:22:35 +02:00
|
|
|
${crt_expiration_arg}
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-05 11:45:11 +02:00
|
|
|
if [ "$?" -ne 0 ]; then
|
2020-05-05 15:24:06 +02:00
|
|
|
error "Error generating the certificate"
|
2020-05-05 11:45:11 +02:00
|
|
|
else
|
|
|
|
echo "The certificate file is available at \`${crt_file}'"
|
|
|
|
fi
|
2018-01-23 16:52:42 +01:00
|
|
|
else
|
2020-05-04 17:44:01 +02:00
|
|
|
if [ -z "${cn}" ]; then
|
2022-04-04 17:36:02 +02:00
|
|
|
show_usage_create >&2
|
2020-05-04 17:44:01 +02:00
|
|
|
exit 1
|
|
|
|
fi
|
2020-05-05 00:28:00 +02:00
|
|
|
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"
|
2018-01-23 16:52:42 +01:00
|
|
|
|
2020-04-30 16:00:34 +02:00
|
|
|
# ask for CA passphrase
|
|
|
|
ask_ca_password 0
|
|
|
|
|
2020-05-05 09:24:09 +02:00
|
|
|
if [ "${ask_pass}" -eq 1 ]; then
|
2020-05-05 10:47:09 +02:00
|
|
|
ask_user_password
|
2018-01-23 16:52:42 +01:00
|
|
|
fi
|
|
|
|
|
2022-04-14 15:18:57 +02:00
|
|
|
# check if CN already exists
|
|
|
|
if [ -f "${crt_file}" ]; then
|
|
|
|
replace_existing_or_abort "${cn}"
|
|
|
|
fi
|
|
|
|
|
2018-01-23 16:52:42 +01:00
|
|
|
# generate private key
|
2020-05-05 23:20:54 +02:00
|
|
|
pass_args=""
|
2020-10-12 23:27:24 +02:00
|
|
|
if [ -n "${password_file:-}" ]; then
|
2022-04-06 11:09:07 +02:00
|
|
|
pass_args="-aes256 -pass file:${password_file}"
|
2020-10-12 23:27:24 +02:00
|
|
|
elif [ -n "${PASSWORD:-}" ]; then
|
2022-04-06 11:09:07 +02:00
|
|
|
pass_args="-aes256 -pass pass:${PASSWORD}"
|
2018-01-23 16:52:42 +01:00
|
|
|
fi
|
2022-04-06 11:09:07 +02:00
|
|
|
"${OPENSSL_BIN}" genpkey \
|
|
|
|
-algorithm RSA \
|
2020-05-05 09:42:54 +02:00
|
|
|
-out "${key_file}" \
|
2020-05-05 23:20:54 +02:00
|
|
|
${pass_args} \
|
2022-04-06 11:09:07 +02:00
|
|
|
-pkeyopt "rsa_keygen_bits:${KEY_LENGTH}" \
|
2020-05-05 10:49:33 +02:00
|
|
|
>/dev/null 2>&1
|
2022-03-11 11:44:09 +01:00
|
|
|
# shellcheck disable=SC2181
|
2020-05-06 00:39:23 +02:00
|
|
|
if [ "$?" -eq 0 ]; then
|
2022-08-31 11:35:12 +02:00
|
|
|
chmod 600 "${key_file}"
|
2020-05-06 00:39:23 +02:00
|
|
|
echo "The KEY file is available at \`${key_file}'"
|
|
|
|
else
|
2020-05-05 15:24:06 +02:00
|
|
|
error "Error generating the private key"
|
2020-05-05 11:45:11 +02:00
|
|
|
fi
|
2018-01-23 16:52:42 +01:00
|
|
|
|
2020-05-05 09:42:54 +02:00
|
|
|
# generate csr req
|
|