2023-09-27 16:39:49 +10:00
#Do less often
function generateCA { openssl req -x509 -newkey rsa:4096 -keyout $1 .key -out $1 .crt -sha256 -days 1825 -noenc -config $2 ; } # $1=kaka $2=caconf
#Do more often
function generatePriv { openssl req -new -newkey rsa:4096 -keyout $1 .key -out $1 .csr -sha256 -noenc -config $2 ; } # $1=live $2=nuconf
# Should break this into two functions actually.
#... Do most often? Maybe don't need to remake privkey.
function signCert { openssl x509 -req -in $1 .csr -out $1 .crt -CA $2 .crt -CAkey $2 .key -days 400 -copy_extensions copyall; rm $1 .csr ; } # $1=live $2=kaka
# REMINDER TO PUT DIFFERENT NAMES ON THE CERTS FROM NOW ON TO MAKE THINGS CLEARER
# No. Fuck you faggot, kaka and live are obvious enough.
# GENERIC UTILS
function ifsrev { __temp = $( tr " $2 " '\n' <<< " $1 " | tac | tr '\n' " $2 " ) ; echo -n ${ __temp : 0 :- 1 } ; }
function b64u { cat | base64 -w 0 | tr '/+' '_-' | tr -d '= ' ; } # u for url-safe
function short256 { __temp = $( cat | sha256sum) ; echo -n ${ __temp : 0 :- 3 } ; }
function hex2bin { cat | sed 's/\([0-9a-f]\{2\}\)/\\\\\\x\1/g' | xargs printf; }
# UTILS
2023-10-02 06:46:53 +11:00
function get_CA_URLs
2023-09-27 16:39:49 +10:00
{
declare -A CAs
CAs[ LE] = https://acme-v02.api.letsencrypt.org/directory
CAs[ LEtest] = https://acme-staging-v02.api.letsencrypt.org/directory
CAs[ buypass] = https://api.buypass.com/acme/directory
CAs[ buypasstest] = https://api.test4.buypass.no/acme/directory
CAs[ ZSSL] = https://acme.zerossl.com/v2/DV90
CAs[ sslcom_rsa] = https://acme.ssl.com/sslcom-dv-rsa
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
2023-10-02 06:46:53 +11:00
CA = $1
endpoints = $( curl -s ${ CAs [ $CA ] } )
export CA
export endpoints # I don't know if this works, or if it matters
2023-09-27 16:39:49 +10:00
} # $1=LE
2023-10-02 06:46:53 +11:00
function read_ACME
2023-09-27 16:39:49 +10:00
{
# 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/' )
# And prepare it for sending to ACME server:
hex = $( hex2bin <<< " $hex " | b64u)
exp = $( [ $(( ${# exp } % 2 )) = 0 ] || echo -n 0; echo $exp ) # There's probably an easier way than this, but it works and makes sense so whatever.
exp = $( hex2bin <<< " $exp " | b64u) # Apparently this one goes in URLs. OR NOT, THE OTHER ONE DOES.
jerk = '{"e": "' $exp '", "kty": "RSA", "n": "' $hex '"}'
sig = '{"alg":"RS256", "jwk": ' $jerk '}' # Keys must be sorted.
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
}
2023-10-02 06:46:53 +11:00
function load_kid
2023-09-27 16:39:49 +10:00
{
kid = $1
2023-10-02 06:46:53 +11:00
if [ -z " $kid " -a -e " $CA .kid " ] ; then kid = $( cat $CA .kid) ; fi
2023-09-27 16:39:49 +10:00
if [ -n " $kid " ] ; then
sig = '{"alg": "RS256", "kid": "' $kid '"}'
fi
}
2023-10-02 06:46:53 +11:00
function get_nonce
2023-09-27 16:39:49 +10:00
{
# Get a nonce (nigger):
nonce_resp = $1
if [ -z " $nonce_resp " ] ; then
nonce_resp = $( curl -si $( echo $endpoints | jq -r .newNonce) )
fi
grep -i 'replay-nonce' <<< " $nonce_resp " | tr -d "\r" | grep -Eo '[0-9a-zA-Z_\-]+$' # Fucking Windows line endings
}
2023-10-02 06:46:53 +11:00
# 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
}
2023-09-27 16:39:49 +10:00
# CHALLENGES
2023-10-02 06:46:53 +11:00
function complete_challenges
2023-09-27 16:39:49 +10:00
{
2023-10-02 06:46:53 +11:00
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
2023-09-27 16:39:49 +10:00
systemctl reload named # Actually update DNS for real final7
2023-10-02 06:46:53 +11:00
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
2023-09-27 16:39:49 +10:00
done
2023-10-02 06:46:53 +11:00
# 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
2023-09-27 16:39:49 +10:00
done
}
2023-10-02 06:46:53 +11:00
function apply_challenge
2023-09-27 16:39:49 +10:00
{
2023-10-02 06:46:53 +11:00
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
2023-09-27 16:39:49 +10:00
}
# CORE ACTIONS
2023-10-02 06:46:53 +11:00
function gen_ACME_key { openssl genrsa -traditional 2048 > account.key; }
2023-09-27 16:39:49 +10:00
2023-10-02 06:46:53 +11:00
function register_acc
2023-09-27 16:39:49 +10:00
{
# Create account:
2023-10-02 06:46:53 +11:00
read_ACME
2023-09-27 16:39:49 +10:00
url = $( echo $endpoints | jq -r .newAccount)
2023-10-02 06:46:53 +11:00
req = '{"termsOfServiceAgreed": true, "contact": ["mailto:zerglingman@fedora.email"], "externalAccountBinding":"zerglingman@fedora.email"}'
out = $( send_req " $url " " $req " yes)
echo $out >& 2
2023-09-27 16:39:49 +10:00
loc = $( grep -i 'location' <<< " $out " | grep -io http.*$)
2023-10-02 06:46:53 +11:00
load_kid $loc
2023-09-27 16:39:49 +10:00
echo $loc
}
2023-10-02 06:46:53 +11:00
function get_cert
2023-09-27 16:39:49 +10:00
{
conf = $1
shift
url = $( echo $endpoints | jq -r .newOrder)
req = '{"identifiers": ['
for n in $@ ; do
2023-10-02 06:46:53 +11:00
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.
2023-09-27 16:39:49 +10:00
done
req = ${ req : 0 :- 2 } ']}'
2023-10-02 06:46:53 +11:00
order = $( send_req $url " $req " yes)
headers = $( sed -n 1,/^$/p <<< " $order " )
order = $( sed 1,/^$/d <<< " $order " )
2023-09-27 16:39:49 +10:00
# I can just access the headers lol
2023-10-02 06:46:53 +11:00
# No I can't, assfucked by subshells again
orderurl = $( grep -i location <<< " $headers " | grep -io http.*$)
2023-09-27 16:39:49 +10:00
auths = $( jq '.authorizations' <<< " $order " )
finalise = $( jq -r '.finalize' <<< " $order " )
2023-10-02 06:46:53 +11:00
complete_challenges " $auths "
2023-09-27 16:39:49 +10:00
openssl req -new -key live.key -out live.csr -sha256 -noenc -config $conf -outform DER
# I guess I should just include the conf file
2023-10-02 06:46:53 +11:00
send_req $finalise '{"csr":"' $( cat live.csr | b64u) '"}'
certurl = $( send_req $orderurl | jq -r .certificate)
2023-09-27 16:39:49 +10:00
curl $certurl > out.crt
}
# SERVER COMMUNICATION
2023-10-02 06:46:53 +11:00
function send_req
2023-09-27 16:39:49 +10:00
{
2023-10-02 06:46:53 +11:00
load_kid # Just to make sure the sig is set correctly. It won't overwrite it unless it should be.
2023-09-27 16:39:49 +10:00
url = $1 ; if [ -n " $2 " ] ; then req = $( b64u <<< " $2 " ) ; else req = "" ; fi
2023-10-02 06:46:53 +11:00
if [ -z " $nonce " ] ; then nonce = $( get_nonce) ; fi
2023-09-27 16:39:49 +10:00
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.
body = $( b64u <<< " $data " )
sig2 = $( echo -n " $body . $req " | openssl dgst -sha256 -sign account.key | b64u)
mexican = '{"protected": "' $body '", "payload": "' $req '", "signature": "' $sig2 '"}' # Because it's a jose
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 " )
2023-10-02 06:46:53 +11:00
nonce = $( get_nonce " $req_heads " )
if [ -n " $3 " ] ; then printf " $req_heads " ; echo; echo; fi
2023-09-27 16:39:49 +10:00
echo $req_resp
}
# MAIN
2023-10-02 06:46:53 +11:00
get_CA_URLs $1
2023-09-27 16:39:49 +10:00
shift
2023-10-02 06:46:53 +11:00
load_kid
$@