implemented rfc 8738 support

This commit is contained in:
Lukas Schauer
2022-04-06 22:23:43 +02:00
parent 784fb806c8
commit ad3f08084c
2 changed files with 54 additions and 16 deletions

View File

@@ -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

View File

@@ -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:<unsupported>)//' )"
# strip away the DNS/IP: prefix
altnames="$( <<<"${altnames}" _sed -e 's/^(DNS:|IP( Address)*:|othername:<unsupported>)//' )"
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