From 0f9b5703647f221e9fcc7acc36107dd96d3f7c18 Mon Sep 17 00:00:00 2001 From: Zergling_man Date: Mon, 2 Oct 2023 06:46:53 +1100 Subject: [PATCH] Major overhaul, should be more reliable now. --- mkcerts.sh | 147 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 95 insertions(+), 52 deletions(-) mode change 100644 => 100755 mkcerts.sh diff --git a/mkcerts.sh b/mkcerts.sh old mode 100644 new mode 100755 index 0a5c942..81f557c --- a/mkcerts.sh +++ b/mkcerts.sh @@ -19,7 +19,7 @@ function hex2bin { cat | sed 's/\([0-9a-f]\{2\}\)/\\\\\\x\1/g' | xargs printf; } # UTILS -function getCAURLs +function get_CA_URLs { declare -A CAs CAs[LE]=https://acme-v02.api.letsencrypt.org/directory @@ -31,11 +31,13 @@ function getCAURLs CAs[sslcom_ecc]=https://acme.ssl.com/sslcom-dv-ecc CAs[jewgle]=https://dv.acme-v02.api.pki.goog/directory CAs[jewgletest]=https://dv.acme-v02.test-api.pki.goog/directory - endpoints=$(curl -s ${CAs[$1]}) - export endpoints # I don't know if this works + CA=$1 + endpoints=$(curl -s ${CAs[$CA]}) + export CA + export endpoints # I don't know if this works, or if it matters } # $1=LE -function readACME +function read_ACME { # Extract signature info from key: read hex exp <<< $(openssl rsa -in account.key -noout -text | tr -d ': \r\n' | sed -E 's/^.*modulus00([a-f0-9]+)publicExponent[0-9]+\(0x([0-9a-f]+)\).*$/\1 \2/') @@ -48,16 +50,16 @@ function readACME thumb=$(echo -n "$jerk" | tr -d ' ' | short256 | hex2bin | b64u) # How many times can you b64 a bowl of ramen before it begins questioning its existence } -function loadkid +function load_kid { kid=$1 - if [ -z "$kid" -a -e "kid" ]; then kid=$(cat kid); fi + if [ -z "$kid" -a -e "$CA.kid" ]; then kid=$(cat $CA.kid); fi if [ -n "$kid" ]; then sig='{"alg": "RS256", "kid": "'$kid'"}' fi } -function getNonce +function get_nonce { # Get a nonce (nigger): nonce_resp=$1 @@ -67,86 +69,127 @@ function getNonce grep -i 'replay-nonce' <<< "$nonce_resp" | tr -d "\r" | grep -Eo '[0-9a-zA-Z_\-]+$' # Fucking Windows line endings } +# ZONEFILE UTILS +function czf { sed -i '/_ACME-CHALLENGE/d' "$1"; } # clear zone file +function szf # setup zone file +{ + file=$1 + shift + for tok in $@; do + echo _ACME-CHALLENGE 300 TXT $(echo -n "$tok.$thumb" | short256 | hex2bin | b64u) >> $file + done +} +function usoa # update SOA +{ + inc=$(grep -Eo 'SOA [^ ]+ [^ ]+ [0-9]+' $1 | grep -Eo '[0-9]+$') + sed -ie 's/SOA\t\([^\t]*\)\t\([^\t]*\)\t[0-9]*/SOA \1 \2 '$((inc+1))'/' $1 +} + # CHALLENGES -function order +function complete_challenges { - readACME # get thumb. It fucks up sig but I don't care, sendreq fixes it. - declare -a urls - i=0 - doms=$(jq -r '.[]' <<< "$1") - while read dom; do - urls[$i]=$(challenge "$(sendreq $dom)") # this is meant to be signed but it seems to work without it...? But sign it just in case it's a test-only thing. - i=$((i+1)) - done <<< "$doms" - systemctl reload named # Actually update DNS for real final7 - sleep 120 # Wait for DNS to update. Generally it won't have been requested in ages so it won't be in any caches. - for url in ${urls[@]}; do - sendreq $url '{}' | jq '(.validationRecord | map(.hostname) | join(" "))+" "+.status' # need empty dict to tell server to validate challenge, empty body only checks status. Just another certified ACME moment. + read_ACME # get thumb. It fucks up sig but I don't care, send_req fixes it. + declare -A chals toks + urls=$(jq -r '.[]' <<< "$1") + while read url; do + read dom chalurl tok <<< "$(send_req $url | jq -r '.identifier.value+" "+(.challenges | .[] | select(.type == "dns-01") | .url+" "+.token)')" + chals[$dom]="${chals[$dom]}$(base64 -w 0 <<< "$chalurl");" + toks[$dom]="${toks[$dom]}$(base64 -w 0 <<< "$tok");" + done <<< "$urls" + for dom in ${!toks[@]}; do + apply_challenge $dom ${toks[$dom]} done - sleep 30 # Wait for challenges to complete - the previous call can return before the challenge completes. - for url in ${urls[@]}; do - sendreq $url | jq '(.validationRecord | map(.hostname) | join(" "))+" "+.status' + systemctl reload named # Actually update DNS for real final7 + declare -a pending + for dom in ${!chals[@]}; do + IFS=';' read -a chalurls <<< ${chals[$dom]} + for chalurl in ${chalurls[@]}; do + sleep 1 # Spam a bit less + rurl=$(base64 -d <<< $chalurl) + send_req $rurl '{}' > /dev/null # need empty dict to tell server to validate challenge, empty body only checks status. Just another certified ACME moment. + pending+=("$rurl") + done + done + # now we wait for validations + dirty=1 + timer=10 + backoff=1 + tick=0 + while [ $dirty -gt 0 ]; do + dirty=0 + for url in ${pending[@]}; do + sleep 1 + oof=$(send_req $url) + oof2=$(jq -r .status <<< "$oof") + nonce=$(get_nonce) # For some reason checking status doesn't come back with a nonce, or comes back with same nonce. + if [ $oof2 != valid ]; then dirty=1; fi + done + sleep $timer # This gets slower each iteration, to spam the server less + timer=$(($timer+$backoff)) + tick=$(($tick+1)) + if [ $tick -eq $backoff ]; then tick=0; backoff=$(($backoff+1)); fi done } -function challenge +function apply_challenge { - read dom url tok <<< $(jq -r '.identifier.value+" "+(.challenges | .[] | select(.type == "dns-01") | .url+" "+.token)' <<< "$1") - tik=$(echo -n "$tok.$thumb" | short256 | hex2bin | b64u) - dnsname="/var/named/$(ifsrev $dom .).zone" - inc=$(grep -Eo 'SOA [^ ]+ [^ ]+ [0-9]+' $dnsname | grep -Eo '[0-9]+$') - if ! grep -i _ACME-CHALLENGE $dnsname > /dev/null; then echo >> $dnsname; echo "_ACME-CHALLENGE 300 TXT aa" >> $dnsname; fi - sed -i -e 's/^_ACME-CHALLENGE 300 TXT .*$/_ACME-CHALLENGE 300 TXT '$tik'/' -e 's/SOA\t\([^\t]*\)\t\([^\t]*\)\t[0-9]*/SOA \1 \2 '$((inc+1))'/' $dnsname # Actually update the DNS - echo $url # Then give back the verification URL to be used later + dnsname="/var/named/$(ifsrev $1 .).zone" + czf $dnsname + szf $dnsname $(tr ';' '\n' <<< $2 | base64 -d | tr '\n' ' ') # Actually update the DNS + usoa $dnsname # And don't forget to update the SOA number } # CORE ACTIONS -function genACMEkey { openssl genrsa -traditional 2048 > account.key; } +function gen_ACME_key { openssl genrsa -traditional 2048 > account.key; } -function registerAcc +function register_acc { # Create account: - readACME + read_ACME url=$(echo $endpoints | jq -r .newAccount) - req='{"termsOfServiceAgreed": true, "contact": ["mailto:zerglingman@fedora.email"]}' - out=$(sendreq "$url" "$req" yes) + req='{"termsOfServiceAgreed": true, "contact": ["mailto:zerglingman@fedora.email"], "externalAccountBinding":"zerglingman@fedora.email"}' + out=$(send_req "$url" "$req" yes) + echo $out >&2 loc=$(grep -i 'location' <<< "$out" | grep -io http.*$) - loadkid $loc + load_kid $loc echo $loc } -function getcert +function get_cert { conf=$1 shift url=$(echo $endpoints | jq -r .newOrder) req='{"identifiers": [' for n in $@; do - req=$req'{"type": "dns", "value": "'$n'"}, ' + req=$req'{"type": "dns", "value": "'$n'"}, {"type": "dns", "value": "*.'$n'"}, ' # Do not put wildcard domains in; this will do it automatically. You still need to put them in the nuconf. done req=${req:0:-2}']}' - order=$(sendreq $url "$req") + order=$(send_req $url "$req" yes) + headers=$(sed -n 1,/^$/p <<< "$order") + order=$(sed 1,/^$/d <<< "$order") # I can just access the headers lol - orderurl=$(grep -i location <<< "$req_heads" | grep -io http.*$) + # No I can't, assfucked by subshells again + orderurl=$(grep -i location <<< "$headers" | grep -io http.*$) auths=$(jq '.authorizations' <<< "$order") finalise=$(jq -r '.finalize' <<< "$order") - order "$auths" + complete_challenges "$auths" openssl req -new -key live.key -out live.csr -sha256 -noenc -config $conf -outform DER # I guess I should just include the conf file - sendreq $finalise '{"csr":"'$(cat live.csr | b64u)'"}' - certurl=$(sendreq $orderurl | jq -r .certificate) + send_req $finalise '{"csr":"'$(cat live.csr | b64u)'"}' + certurl=$(send_req $orderurl | jq -r .certificate) curl $certurl > out.crt } # SERVER COMMUNICATION -function sendreq +function send_req { - loadkid # Just to make sure the sig is set correctly. It won't overwrite it unless it should be. + load_kid # Just to make sure the sig is set correctly. It won't overwrite it unless it should be. url=$1; if [ -n "$2" ]; then req=$(b64u <<< "$2"); else req=""; fi - if [ -z "$nonce" ]; then nonce=$(getNonce); fi + if [ -z "$nonce" ]; then nonce=$(get_nonce); fi data=$(jq '.+{"nonce":"'$nonce'","url":"'$url'"}' <<< "$sig" | tr -d '\r\n' | tr -s ' ' ' ' | sed -e 's/{ /{/g' -e 's/ }/}/g') # Keys don't need to be sorted here, but they still are. # I don't know why the URL has to be in the body that is sent to the URL. # ACME is a terrible protocol. @@ -156,14 +199,14 @@ function sendreq req_resp=$(curl -isH "Content-type:application/jose+json" $url --data "$mexican" | tr -d "\r") # Here is the line that does the work req_heads=$(sed -n 1,/^$/p <<< "$req_resp") req_resp=$(sed 1,/^$/d <<< "$req_resp") - nonce=$(getNonce "$req_heads") - if [ -n "$3" ]; then echo "$req_heads"; fi + nonce=$(get_nonce "$req_heads") + if [ -n "$3" ]; then printf "$req_heads"; echo; echo; fi echo $req_resp } # MAIN -getCAURLs $1 +get_CA_URLs $1 shift -loadkid -$* \ No newline at end of file +load_kid +$@ \ No newline at end of file