From: Lukas Schauer Date: Wed, 6 Apr 2022 20:23:43 +0000 (+0200) Subject: implemented rfc 8738 support X-Git-Tag: v0.7.1~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ad3f08084ce893f7cb855a40036587e437607325;p=thirdparty%2Fdehydrated.git implemented rfc 8738 support --- diff --git a/CHANGELOG b/CHANGELOG index 2329c7c..9596b08 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ This file contains a log of major changes in dehydrated ## Added - Implemented EC for account keys - Domain list now also read from domains.txt.d subdirectory (behaviour might change, see docs) +- Implemented RFC 8738 (validating/signing certificates for IP addresses instead of domain names) support (this will not work with most public CAs, if any!) ## [0.7.0] - 2020-12-10 ## Added diff --git a/dehydrated b/dehydrated index fa2341c..c54dc56 100755 --- a/dehydrated +++ b/dehydrated @@ -241,6 +241,17 @@ jsonsh() { tokenize | parse } +# Convert IP addresses to their reverse dns variants. +# Used for ALPN certs as validation for IPs uses this in SNI since IPs aren't allowed there. +ip_to_ptr() { + ip="$(cat)" + if [[ "${ip}" =~ : ]]; then + printf "%sip6.arpa" "$(printf "%s" "${ip}" | awk -F: 'BEGIN {OFS=""; }{addCount = 9 - NF; for(i=1; i<=NF;i++){if(length($i) == 0){ for(j=1;j<=addCount;j++){$i = ($i "0000");} } else { $i = substr(("0000" $i), length($i)+5-4);}}; print}' | rev | sed -e "s/./&./g")" + else + printf "%s.in-addr.arpa" "$(printf "%s" "${ip}" | awk -F. '{print $4"."$3"." $2"."$1}')" + fi +} + # Create (identifiable) temporary files _mktemp() { mktemp "${TMPDIR:-/tmp}/dehydrated-XXXXXX" @@ -996,12 +1007,12 @@ extract_altnames() { # split to one per line: # shellcheck disable=SC1003 altnames="$( <<<"${altnames}" _sed -e 's/^[[:space:]]*//; s/, /\'$'\n''/g' )" - # we can only get DNS: ones signed - if grep -qEv '^(DNS|othername):' <<<"${altnames}"; then - _exiterr "Certificate signing request contains non-DNS Subject Alternative Names" + # we can only get DNS/IP: ones signed + if grep -qEv '^(DNS|IP( Address)*|othername):' <<<"${altnames}"; then + _exiterr "Certificate signing request contains non-DNS/IP Subject Alternative Names" fi - # strip away the DNS: prefix - altnames="$( <<<"${altnames}" _sed -e 's/^(DNS:|othername:)//' )" + # strip away the DNS/IP: prefix + altnames="$( <<<"${altnames}" _sed -e 's/^(DNS:|IP( Address)*:|othername:)//' )" printf "%s" "${altnames}" | tr '\n' ' ' else # No SANs, extract CN @@ -1047,7 +1058,11 @@ sign_csr() { # Request new order and store authorization URIs local challenge_identifiers="" for altname in ${altnames}; do - challenge_identifiers+="$(printf '{"type": "dns", "value": "%s"}, ' "${altname}")" + if [[ "${altname}" =~ ^ip: ]]; then + challenge_identifiers+="$(printf '{"type": "ip", "value": "%s"}, ' "${altname:3}")" + else + challenge_identifiers+="$(printf '{"type": "dns", "value": "%s"}, ' "${altname}")" + fi done challenge_identifiers="[${challenge_identifiers%, }]" @@ -1080,6 +1095,7 @@ sign_csr() { # Receive authorization ($authorization is authz uri) response="$(signed_request "$(echo "${authorization}" | _sed -e 's/\"(.*)".*/\1/')" "" | jsonsh)" identifier="$(echo "${response}" | get_json_string_value -p '"identifier","value"')" + identifier_type="$(echo "${response}" | get_json_string_value -p '"identifier","type"')" echo " + Handling authorization for ${identifier}" else # Request new authorization ($authorization is altname) @@ -1108,7 +1124,11 @@ sign_csr() { challenge="$(echo "${response}" | get_json_dict_value -p '"challenges",'"${challengeindex}")" # Gather challenge information - challenge_names[${idx}]="${identifier}" + if [ "${identifier_type:-}" = "ip" ]; then + challenge_names[${idx}]="$(echo "${identifier}" | ip_to_ptr)" + else + challenge_names[${idx}]="${identifier}" + fi challenge_tokens[${idx}]="$(echo "${challenge}" | get_json_string_value token)" if [[ ${API} -eq 2 ]]; then @@ -1136,12 +1156,16 @@ sign_csr() { ;; "tls-alpn-01") keyauth_hook="$(printf '%s' "${keyauth}" | "${OPENSSL}" dgst -sha256 -c -hex | awk '{print $NF}')" - generate_alpn_certificate "${identifier}" "${keyauth_hook}" + generate_alpn_certificate "${identifier}" "${identifier_type}" "${keyauth_hook}" ;; esac keyauths[${idx}]="${keyauth}" - deploy_args[${idx}]="${identifier} ${challenge_tokens[${idx}]} ${keyauth_hook}" + if [ "${identifier_type:-}" = "ip" ]; then + deploy_args[${idx}]="$(echo "${identifier}" | ip_to_ptr) ${challenge_tokens[${idx}]} ${keyauth_hook}" + else + deploy_args[${idx}]="${identifier} ${challenge_tokens[${idx}]} ${keyauth_hook}" + fi idx=$((idx+1)) done @@ -1353,7 +1377,8 @@ walk_chain() { # Generate ALPN verification certificate generate_alpn_certificate() { local altname="${1}" - local acmevalidation="${2}" + local identifier_type="${2}" + local acmevalidation="${3}" local alpncertdir="${ALPNCERTDIR}" if [[ ! -e "${alpncertdir}" ]]; then @@ -1364,10 +1389,17 @@ generate_alpn_certificate() { echo " + Generating ALPN certificate and key for ${1}..." tmp_openssl_cnf="$(_mktemp)" cat "${OPENSSL_CNF}" > "${tmp_openssl_cnf}" - printf "\n[SAN]\nsubjectAltName=DNS:%s\n" "${altname}" >> "${tmp_openssl_cnf}" + if [[ "${identifier_type}" = "ip" ]]; then + printf "\n[SAN]\nsubjectAltName=IP:%s\n" "${altname}" >> "${tmp_openssl_cnf}" + else + printf "\n[SAN]\nsubjectAltName=DNS:%s\n" "${altname}" >> "${tmp_openssl_cnf}" + fi printf "1.3.6.1.5.5.7.1.31=critical,DER:04:20:%s\n" "${acmevalidation}" >> "${tmp_openssl_cnf}" SUBJ="/CN=${altname}/" [[ "${OSTYPE:0:5}" = "MINGW" ]] && SUBJ="/${SUBJ}" + if [[ "${identifier_type}" = "ip" ]]; then + altname="$(echo "${altname}" | ip_to_ptr)" + fi _openssl req -x509 -new -sha256 -nodes -newkey rsa:2048 -keyout "${alpncertdir}/${altname}.key.pem" -out "${alpncertdir}/${altname}.crt.pem" -subj "${SUBJ}" -extensions SAN -config "${tmp_openssl_cnf}" chmod g+r "${alpncertdir}/${altname}.key.pem" "${alpncertdir}/${altname}.crt.pem" rm -f "${tmp_openssl_cnf}" @@ -1433,7 +1465,13 @@ sign_domain() { echo " + Generating signing request..." SAN="" for altname in ${altnames}; do - SAN="${SAN}DNS:${altname}, " + if [[ "${altname}" =~ ^ip: ]]; then + SAN="${SAN}IP:${altname:3}, " + SUBJ="/CN=${domain:3}/" + else + SAN="${SAN}DNS:${altname}, " + SUBJ="/CN=${domain}/" + fi done SAN="${SAN%%, }" local tmp_openssl_cnf @@ -1443,7 +1481,6 @@ sign_domain() { if [ "${OCSP_MUST_STAPLE}" = "yes" ]; then printf "\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >> "${tmp_openssl_cnf}" fi - SUBJ="/CN=${domain}/" if [[ "${OSTYPE:0:5}" = "MINGW" ]]; then # The subject starts with a /, so MSYS will assume it's a path and convert # it unless we escape it with another one: @@ -1754,9 +1791,9 @@ command_sign_domains() { if [[ -e "${cert}" && "${force_renew}" = "no" ]]; then printf " + Checking domain name(s) of existing cert..." - certnames="$("${OPENSSL}" x509 -in "${cert}" -text -noout | grep DNS: | _sed 's/DNS://g' | tr -d ' ' | tr ',' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//')" - givennames="$(echo "${domain}" "${morenames}"| tr ' ' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//' | _sed 's/^ //')" - + certnames="$("${OPENSSL}" x509 -in "${cert}" -text -noout | grep -E '(DNS|IP( Address*)):' | _sed 's/(DNS|IP( Address)*)://g' | tr -d ' ' | tr ',' '\n' | sort -u | tr '\n' ' ' | _sed 's/ $//')" + givennames="$(echo "${domain}" "${morenames}"| tr ' ' '\n' | sort -u | tr '\n' ' ' | _sed 's/ip://g' | _sed 's/ $//' | _sed 's/^ //')" + if [[ "${certnames}" = "${givennames}" ]]; then echo " unchanged." else