From 2b2cc345b1bc5c41f623e807e553aa307861c209 Mon Sep 17 00:00:00 2001 From: srvrco Date: Tue, 25 Oct 2016 22:02:01 +0100 Subject: [PATCH] Add option for dual ESA / EDSA certs --- getssl | 197 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 127 insertions(+), 70 deletions(-) diff --git a/getssl b/getssl index 44009dc..4b24f0c 100755 --- a/getssl +++ b/getssl @@ -137,10 +137,11 @@ # 2016-10-25 added CHECK_REMOTE_WAIT option ( to pause before final remote check) # 2016-10-25 Added EC account key support ( prime256v1, secp384r1 ) (1.68) # 2016-10-25 Ignore DNS_EXTRA_WAIT if all domains already validated (issue #146) (1.69) +# 2016-10-25 Add option for dual ESA / EDSA certs (1.70) # ---------------------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="1.69" +VERSION="1.70" # defaults CODE_LOCATION="https://raw.githubusercontent.com/srvrco/getssl/master/getssl" @@ -166,6 +167,7 @@ PUBLIC_DNS_SERVER="" CHALLENGE_CHECK_TYPE="http" DEACTIVATE_AUTH="false" PREVIOUSLY_VALIDATED="true" +DUAL_RSA_ECDSA="false" ORIG_UMASK=$(umask) _USE_DEBUG=0 _CREATE_CONFIG=0 @@ -187,8 +189,15 @@ cert_archive() { # Archive certificate file by copying with dates at end. mkdir -p "${DOMAIN_DIR}/archive/${date_time}" umask 077 cp "$CERT_FILE" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.crt" + cp "$CERT_FILE" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.csr" cp "$DOMAIN_DIR/${DOMAIN}.key" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.key" cp "$CA_CERT" "${DOMAIN_DIR}/archive/${date_time}/chain.crt" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + cp "$CERT_FILE" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.crt" + cp "$CERT_FILE" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.csr" + cp "$DOMAIN_DIR/${DOMAIN}.key" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.key" + cp "$CA_CERT" "${DOMAIN_DIR}/archive/${date_time}/chain.ec.crt" + fi umask "$ORIG_UMASK" debug "purging old GetSSL archives" purge_archive "$DOMAIN_DIR" @@ -370,6 +379,60 @@ copy_file_to_location() { # copies a file, using scp if required. fi } +create_csr() { # create a csr using a given key (if it doesn't already exist) + csr_file=$1 + csr_key=$2 + # check if domain csr exists - if not then create it + if [ -f "$csr_file" ]; then + debug "domain csr exists at - $csr_file" + # check all domains in config are in csr + alldomains=$(echo "$DOMAIN,$SANS" | sed -e 's/ //g; y/,/\n/' | sort -u) + domains_in_csr=$(openssl req -text -noout -in "$csr_file" | sed -n -e 's/^ *Subject: .* CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' | sort -u) + for d in $alldomains; do + if [ "$(echo "${domains_in_csr}"| grep "^${d}$")" != "${d}" ]; then + info "existing csr at $csr_file does not contain ${d} - re-create-csr .... $(echo "${domains_in_csr}"| grep "^${d}$")" + _RECREATE_CSR=1 + fi + done + # check all domains in csr are in config + if [ "$alldomains" != "$domains_in_csr" ]; then + info "existing csr at $csr_file does not have the same domains as the config - re-create-csr" + _RECREATE_CSR=1 + fi + fi # end of ... check if domain csr exists - if not then create it + + # if CSR does not exist, or flag set to recreate, then create csr + if [ ! -f "$csr_file" ] || [ "$_RECREATE_CSR" == "1" ]; then + info "creating domain csr - $csr_file" + openssl req -new -sha256 -key "$csr_key" -subj "/" -reqexts SAN -config \ + <(cat "$SSLCONF" <(printf "[SAN]\n%s" "$SANLIST")) > "$csr_file" + fi +} + +create_domain_key() { # create a domain key (if it doesn't already exist) + key_type=$1 # domain key type + key_loc=$2 # domain key location + # check if domain key exists, if not then create it. + if [ -f "$key_loc" ]; then + debug "domain key exists at $key_loc - skipping generation" + # ideally need to check validity of domain key + else + umask 077 + info "creating domain key - $key_loc" + case "$key_type" in + rsa) + openssl genrsa "$DOMAIN_KEY_LENGTH" > "$key_loc";; + prime256v1|secp384r1|secp521r1) + openssl ecparam -genkey -name "$key_type" > "$key_loc";; + *) + error_exit "unknown private key algorithm type $key_loc";; + esac + umask "$ORIG_UMASK" + # remove csr on generation of new domain key + rm -f "${key_loc::-4}.csr" + fi +} + date_epoc() { # convert the date into epoch time if [[ "$os" == "bsd" ]]; then date -j -f "%b %d %T %Y %Z" "$1" +%s @@ -479,6 +542,42 @@ get_auth_dns() { # get the authoritative dns server for a domain fi } +get_certificate() { # get certificate for csr, if all domains validated. + gc_csr=$1 # the csr file + gc_certfile=$2 # The filename for the certificate + gc_cafile=$3 # The filename for the CA certificate + + der=$(openssl req -in "$gc_csr" -outform DER | urlbase64) + debug "der $der" + send_signed_request "$CA/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" + + # convert certificate information into correct format and save to file. + CertData=$(awk ' $1 ~ "^Location" {print $2}' "$CURL_HEADER" |tr -d '\r') + debug "certdata location = $CertData" + if [ "$CertData" ] ; then + echo -----BEGIN CERTIFICATE----- > "$gc_certfile" + curl --silent "$CertData" | openssl base64 -e >> "$gc_certfile" + echo -----END CERTIFICATE----- >> "$gc_certfile" + info "Certificate saved in $CERT_FILE" + fi + + # If certificate wasn't a valid certificate, error exit. + if [ -z "$CertData" ] ; then + response2=$(echo "$response" | fold -w64 |openssl base64 -d) + debug "response was $response" + error_exit "Sign failed: $(echo "$response2" | grep "detail")" + fi + + # get a copy of the CA certificate. + IssuerData=$(grep -i '^Link' "$CURL_HEADER" | cut -d " " -f 2| cut -d ';' -f 1 | sed 's///g') + if [ "$IssuerData" ] ; then + echo -----BEGIN CERTIFICATE----- > "$gc_cafile" + curl --silent "$IssuerData" | openssl base64 -e >> "$gc_cafile" + echo -----END CERTIFICATE----- >> "$gc_cafile" + info "The intermediate CA cert is in $gc_cafile" + fi +} + get_os() { # function to get the current Operating System uname_res=$(uname -s) if [[ ${uname_res} == "Linux" ]]; then @@ -797,7 +896,6 @@ sign_string() { #sign a string with a given key and algorithm and return urlbase debug "S $S" signed64=$(printf '%s' "${R}${S}" | hex2bin | urlbase64 ) debug "encoded RS $signed64" - result=$(which "$1" 2>/dev/null) fi } @@ -1276,26 +1374,16 @@ if [ "$REUSE_PRIVATE_KEY" != "true" ]; then if [ -f "$DOMAIN_DIR/${DOMAIN}.key" ]; then rm -f "$DOMAIN_DIR/${DOMAIN}.key" fi + if [ -f "$DOMAIN_DIR/${DOMAIN}.ec.key" ]; then + rm -f "$DOMAIN_DIR/${DOMAIN}.ecs.key" + fi fi -# check if domain key exists, if not then create it. -if [ -f "$DOMAIN_DIR/${DOMAIN}.key" ]; then - debug "domain key exists at $DOMAIN_DIR/${DOMAIN}.key - skipping generation" - # ideally need to check validity of domain key +if [[ "$DUAL_RSA_ECDSA" == "false" ]]; then + create_domain_key "${PRIVATE_KEY_ALG}" "$DOMAIN_DIR/${DOMAIN}.key" else - umask 077 - info "creating domain key - $DOMAIN_DIR/${DOMAIN}.key" - case "${PRIVATE_KEY_ALG}" in - rsa) - openssl genrsa "$DOMAIN_KEY_LENGTH" > "$DOMAIN_DIR/${DOMAIN}.key";; - prime256v1|secp384r1|secp521r1) - openssl ecparam -genkey -name "${PRIVATE_KEY_ALG}" > "$DOMAIN_DIR/${DOMAIN}.key";; - *) - error_exit "unknown private key algorithm type ${PRIVATE_KEY_ALG}";; - esac - umask "$ORIG_UMASK" - # remove csr on generation of new domain key - rm -f "$DOMAIN_DIR/${DOMAIN}.csr" + create_domain_key "rsa" "$DOMAIN_DIR/${DOMAIN}.key" + create_domain_key "${PRIVATE_KEY_ALG}" "$DOMAIN_DIR/${DOMAIN}.ec.key" fi #create SAN @@ -1330,30 +1418,11 @@ for d in $alldomains; do fi done -# check if domain csr exists - if not then create it -if [ -f "$DOMAIN_DIR/${DOMAIN}.csr" ]; then - debug "domain csr exists at - $DOMAIN_DIR/${DOMAIN}.csr" - # check all domains in config are in csr - alldomains=$(echo "$DOMAIN,$SANS" | sed -e 's/ //g; y/,/\n/' | sort -u) - domains_in_csr=$(openssl req -text -noout -in "$DOMAIN_DIR/${DOMAIN}.csr" | sed -n -e 's/^ *Subject: .* CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' | sort -u) - for d in $alldomains; do - if [ "$(echo "${domains_in_csr}"| grep "^${d}$")" != "${d}" ]; then - info "existing csr at $DOMAIN_DIR/${DOMAIN}.csr does not contain ${d} - re-create-csr .... $(echo "${domains_in_csr}"| grep "^${d}$")" - _RECREATE_CSR=1 - fi - done - # check all domains in csr are in config - if [ "$alldomains" != "$domains_in_csr" ]; then - info "existing csr at $DOMAIN_DIR/${DOMAIN}.csr does not have the same domains as the config - re-create-csr" - _RECREATE_CSR=1 - fi -fi # end of ... check if domain csr exists - if not then create it - -# if CSR does not exist, or flag set to recreate, then create csr -if [ ! -f "$DOMAIN_DIR/${DOMAIN}.csr" ] || [ "$_RECREATE_CSR" == "1" ]; then - info "creating domain csr - $DOMAIN_DIR/${DOMAIN}.csr" - openssl req -new -sha256 -key "$DOMAIN_DIR/${DOMAIN}.key" -subj "/" -reqexts SAN -config \ - <(cat "$SSLCONF" <(printf "[SAN]\n%s" "$SANLIST")) > "$DOMAIN_DIR/${DOMAIN}.csr" +if [[ "$DUAL_RSA_ECDSA" == "false" ]]; then + create_csr "$DOMAIN_DIR/${DOMAIN}.csr" "$DOMAIN_DIR/${DOMAIN}.key" +else + create_csr "$DOMAIN_DIR/${DOMAIN}.csr" "$DOMAIN_DIR/${DOMAIN}.key" + create_csr "$DOMAIN_DIR/${DOMAIN}.ec.csr" "$DOMAIN_DIR/${DOMAIN}.ec.key" fi # use account key to register with CA @@ -1604,34 +1673,10 @@ fi # end of ... perform validation if via DNS challenge # Verification has been completed for all SANS, so request certificate. info "Verification completed, obtaining certificate." -der=$(openssl req -in "$DOMAIN_DIR/${DOMAIN}.csr" -outform DER | urlbase64) -debug "der $der" -send_signed_request "$CA/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" - -# convert certificate information into correct format and save to file. -CertData=$(awk ' $1 ~ "^Location" {print $2}' "$CURL_HEADER" |tr -d '\r') -debug "certdata location = $CertData" -if [ "$CertData" ] ; then - echo -----BEGIN CERTIFICATE----- > "$CERT_FILE" - curl --silent "$CertData" | openssl base64 -e >> "$CERT_FILE" - echo -----END CERTIFICATE----- >> "$CERT_FILE" - info "Certificate saved in $CERT_FILE" -fi - -# If certificate wasn't a valid certificate, error exit. -if [ -z "$CertData" ] ; then - response2=$(echo "$response" | fold -w64 |openssl base64 -d) - debug "response was $response" - error_exit "Sign failed: $(echo "$response2" | grep "detail")" -fi -# get a copy of the CA certificate. -IssuerData=$(grep -i '^Link' "$CURL_HEADER" | cut -d " " -f 2| cut -d ';' -f 1 | sed 's///g') -if [ "$IssuerData" ] ; then - echo -----BEGIN CERTIFICATE----- > "$CA_CERT" - curl --silent "$IssuerData" | openssl base64 -e >> "$CA_CERT" - echo -----END CERTIFICATE----- >> "$CA_CERT" - info "The intermediate CA cert is in $CA_CERT" +get_certificate "$DOMAIN_DIR/${DOMAIN}.csr" "$CERT_FILE" "$CA_CERT" +if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + get_certificate "$DOMAIN_DIR/${DOMAIN}.ec.csr" "${CERT_FILE::-4}.ec.crt" "${CA_CERT::-4}.ec.crt" fi # create Archive of new certs @@ -1644,6 +1689,18 @@ debug "Certificates obtained and archived locally, will now copy to specified lo copy_file_to_location "domain certificate" "$CERT_FILE" "$DOMAIN_CERT_LOCATION" copy_file_to_location "private key" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LOCATION" copy_file_to_location "CA certificate" "$CA_CERT" "$CA_CERT_LOCATION" +if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + if [ ! -z "$DOMAIN_CERT_LOCATION" ]; then + copy_file_to_location "ec domain certificate" "${CERT_FILE::-4}.ec.crt" "${DOMAIN_CERT_LOCATION::-4}.ec.crt" + fi + if [ ! -z "$DOMAIN_KEY_LOCATION" ]; then + copy_file_to_location "ec private key" "$DOMAIN_DIR/${DOMAIN}.ec.key" "${DOMAIN_KEY_LOCATION::-4}.ec.key" + fi + if [ ! -z "$CA_CERT_LOCATION" ]; then + copy_file_to_location "ec CA certificate" "${CA_CERT::-4}.ec.crt" "${CA_CERT_LOCATION::-4}.ec.crt" + fi +fi + # if DOMAIN_CHAIN_LOCATION is not blank, then create and copy file. if [ ! -z "$DOMAIN_CHAIN_LOCATION" ]; then if [[ "$(dirname "$DOMAIN_CHAIN_LOCATION")" == "." ]]; then