Commit ef7a3adb authored by Lukas Schauer's avatar Lukas Schauer

Merge pull request #95 from nielslaukens/master

Add ability to sign "arbitrary" CSRs
parents e5c4c0ff 429ec400
...@@ -8,6 +8,7 @@ Other dependencies are: curl, sed, grep, mktemp (all found on almost any system, ...@@ -8,6 +8,7 @@ Other dependencies are: curl, sed, grep, mktemp (all found on almost any system,
Current features: Current features:
- Signing of a list of domains - Signing of a list of domains
- Signing of a CSR
- Renewal if a certificate is about to expire or SAN (subdomains) changed - Renewal if a certificate is about to expire or SAN (subdomains) changed
- Certificate revocation - Certificate revocation
...@@ -25,6 +26,7 @@ Default command: help ...@@ -25,6 +26,7 @@ Default command: help
Commands: Commands:
--cron (-c) Sign/renew non-existant/changed/expiring certificates. --cron (-c) Sign/renew non-existant/changed/expiring certificates.
--signcsr (-s) path/to/csr.pem Sign a given CSR, output CRT on stdout (advanced usage)
--revoke (-r) path/to/cert.pem Revoke specified certificate --revoke (-r) path/to/cert.pem Revoke specified certificate
--help (-h) Show help text --help (-h) Show help text
--env (-e) Output configuration variables for use in other scripts --env (-e) Output configuration variables for use in other scripts
......
...@@ -280,47 +280,55 @@ signed_request() { ...@@ -280,47 +280,55 @@ signed_request() {
http_request post "${1}" "${data}" http_request post "${1}" "${data}"
} }
# Create certificate for domain(s) # Extracts all subject names from a CSR
sign_domain() { # Outputs either the CN, or the SANs, one per line
domain="${1}" extract_altnames() {
altnames="${*}" csr="${1}" # the CSR itself (not a file)
timestamp="$(date +%s)"
echo " + Signing domains..." if ! <<<"${csr}" openssl req -verify -noout 2>/dev/null; then
if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then _exiterr "Certificate signing request isn't valid"
_exiterr "Certificate authority doesn't allow certificate signing"
fi fi
# If there is no existing certificate directory => make it reqtext="$( <<<"${csr}" openssl req -noout -text )"
if [[ ! -e "${BASEDIR}/certs/${domain}" ]]; then if <<<"$reqtext" grep -q '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$'; then
echo " + Creating new directory ${BASEDIR}/certs/${domain} ..." # SANs used, extract these
mkdir -p "${BASEDIR}/certs/${domain}" altnames="$( <<<"${reqtext}" grep -A1 '^[[:space:]]*X509v3 Subject Alternative Name:[[:space:]]*$' | tail -n1 )"
# split to one per line:
altnames="$( <<<"${altnames}" _sed -e 's/^[[:space:]]*//; s/, /\'$'\n''/' )"
# we can only get DNS: ones signed
if [ -n "$( <<<"${altnames}" grep -v '^DNS:' )" ]; then
_exiterr "Certificate signing request contains non-DNS Subject Alternative Names"
fi
# strip away the DNS: prefix
altnames="$( <<<"${altnames}" _sed -e 's/^DNS://' )"
echo "$altnames"
else
# No SANs, extract CN
altnames="$( <<<"${reqtext}" grep '^[[:space:]]*Subject:' | _sed -e 's/.* CN=([^ /,]*).*/\1/' )"
echo "$altnames"
fi fi
}
privkey="privkey.pem" # Create certificate for domain(s) and outputs it FD 3
# generate a new private key if we need or want one sign_csr() {
if [[ ! -f "${BASEDIR}/certs/${domain}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then csr="${1}" # the CSR itself (not a file)
echo " + Generating private key..."
privkey="privkey-${timestamp}.pem" if { true >&3; } 2>/dev/null; then
case "${KEY_ALGO}" in : # fd 3 looks OK
rsa) _openssl genrsa -out "${BASEDIR}/certs/${domain}/privkey-${timestamp}.pem" "${KEYSIZE}";; else
prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${BASEDIR}/certs/${domain}/privkey-${timestamp}.pem";; _exiterr "sign_csr: FD 3 not open"
esac
fi fi
# Generate signing request config and the actual signing request shift 1 || true
echo " + Generating signing request..." altnames="${*:-}"
SAN="" if [ -z "$altnames" ]; then
for altname in ${altnames}; do altnames="$( extract_altnames "$csr" )"
SAN+="DNS:${altname}, " fi
done
SAN="${SAN%%, }" if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then
local tmp_openssl_cnf _exiterr "Certificate authority doesn't allow certificate signing"
tmp_openssl_cnf="$(mktemp -t XXXXXX)" fi
cat "${OPENSSL_CNF}" > "${tmp_openssl_cnf}"
printf "[SAN]\nsubjectAltName=%s" "${SAN}" >> "${tmp_openssl_cnf}"
openssl req -new -sha256 -key "${BASEDIR}/certs/${domain}/${privkey}" -out "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" -subj "/CN=${domain}/" -reqexts SAN -config "${tmp_openssl_cnf}"
rm -f "${tmp_openssl_cnf}"
# Request and respond to challenges # Request and respond to challenges
for altname in ${altnames}; do for altname in ${altnames}; do
...@@ -385,14 +393,64 @@ sign_domain() { ...@@ -385,14 +393,64 @@ sign_domain() {
# Finally request certificate from the acme-server and store it in cert-${timestamp}.pem and link from cert.pem # Finally request certificate from the acme-server and store it in cert-${timestamp}.pem and link from cert.pem
echo " + Requesting certificate..." echo " + Requesting certificate..."
csr64="$(openssl req -in "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" -outform DER | urlbase64)" csr64="$( <<<"${csr}" openssl req -outform DER | urlbase64)"
crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | openssl base64 -e)" crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | openssl base64 -e)"
crt_path="${BASEDIR}/certs/${domain}/cert-${timestamp}.pem" crt="$( printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" )"
printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" > "${crt_path}"
# Try to load the certificate to detect corruption # Try to load the certificate to detect corruption
echo " + Checking certificate..." echo " + Checking certificate..."
_openssl x509 -text < "${crt_path}" _openssl x509 -text <<<"${crt}"
echo "${crt}" >&3
unset challenge_token
echo " + Done!"
}
# Create certificate for domain(s)
sign_domain() {
domain="${1}"
altnames="${*}"
timestamp="$(date +%s)"
echo " + Signing domains..."
if [[ -z "${CA_NEW_AUTHZ}" ]] || [[ -z "${CA_NEW_CERT}" ]]; then
_exiterr "Certificate authority doesn't allow certificate signing"
fi
# If there is no existing certificate directory => make it
if [[ ! -e "${BASEDIR}/certs/${domain}" ]]; then
echo " + Creating new directory ${BASEDIR}/certs/${domain} ..."
mkdir -p "${BASEDIR}/certs/${domain}"
fi
privkey="privkey.pem"
# generate a new private key if we need or want one
if [[ ! -f "${BASEDIR}/certs/${domain}/privkey.pem" ]] || [[ "${PRIVATE_KEY_RENEW}" = "yes" ]]; then
echo " + Generating private key..."
privkey="privkey-${timestamp}.pem"
case "${KEY_ALGO}" in
rsa) _openssl genrsa -out "${BASEDIR}/certs/${domain}/privkey-${timestamp}.pem" "${KEYSIZE}";;
prime256v1|secp384r1) _openssl ecparam -genkey -name "${KEY_ALGO}" -out "${BASEDIR}/certs/${domain}/privkey-${timestamp}.pem";;
esac
fi
# Generate signing request config and the actual signing request
echo " + Generating signing request..."
SAN=""
for altname in ${altnames}; do
SAN+="DNS:${altname}, "
done
SAN="${SAN%%, }"
local tmp_openssl_cnf
tmp_openssl_cnf="$(mktemp -t XXXXXX)"
cat "${OPENSSL_CNF}" > "${tmp_openssl_cnf}"
printf "[SAN]\nsubjectAltName=%s" "${SAN}" >> "${tmp_openssl_cnf}"
openssl req -new -sha256 -key "${BASEDIR}/certs/${domain}/${privkey}" -out "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" -subj "/CN=${domain}/" -reqexts SAN -config "${tmp_openssl_cnf}"
rm -f "${tmp_openssl_cnf}"
crt_path="${BASEDIR}/certs/${domain}/cert-${timestamp}.pem"
sign_csr "$(< "${BASEDIR}/certs/${domain}/cert-${timestamp}.csr" )" ${altnames} 3>"${crt_path}"
# Create fullchain.pem # Create fullchain.pem
echo " + Creating fullchain.pem..." echo " + Creating fullchain.pem..."
...@@ -492,6 +550,25 @@ command_sign_domains() { ...@@ -492,6 +550,25 @@ command_sign_domains() {
exit 0 exit 0
} }
# Usage: --signcsr (-s) path/to/csr.pem
# Description: Sign a given CSR, output CRT on stdout (advanced usage)
command_sign_csr() {
# redirect stdout to stderr
# leave stdout over at fd 3 to output the cert
exec 3>&1 1>&2
init_system
csrfile="${1}"
if [ ! -r "${csrfile}" ]; then
_exiterr "Could not read certificate signing request ${csrfile}"
fi
sign_csr "$(< "${csrfile}" )"
exit 0
}
# Usage: --revoke (-r) path/to/cert.pem # Usage: --revoke (-r) path/to/cert.pem
# Description: Revoke specified certificate # Description: Revoke specified certificate
command_revoke() { command_revoke() {
...@@ -588,6 +665,13 @@ main() { ...@@ -588,6 +665,13 @@ main() {
set_command sign_domains set_command sign_domains
;; ;;
--signcsr|-s)
shift 1
set_command sign_csr
check_parameters "${1:-}"
PARAM_CSR="${1}"
;;
--revoke|-r) --revoke|-r)
shift 1 shift 1
set_command revoke set_command revoke
...@@ -668,6 +752,7 @@ main() { ...@@ -668,6 +752,7 @@ main() {
case "${COMMAND}" in case "${COMMAND}" in
env) command_env;; env) command_env;;
sign_domains) command_sign_domains;; sign_domains) command_sign_domains;;
sign_csr) command_sign_csr "${PARAM_CSR}";;
revoke) command_revoke "${PARAM_REVOKECERT}";; revoke) command_revoke "${PARAM_REVOKECERT}";;
*) command_help; exit 1;; *) command_help; exit 1;;
esac esac
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment