tinyscripts/mkcerts.sh

169 lines
6.7 KiB
Bash

#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
function getCAURLs
{
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
endpoints=$(curl -s ${CAs[$1]})
export endpoints # I don't know if this works
} # $1=LE
function readACME
{
# 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
}
function loadkid
{
kid=$1
if [ -z "$kid" -a -e "kid" ]; then kid=$(cat kid); fi
if [ -n "$kid" ]; then
sig='{"alg": "RS256", "kid": "'$kid'"}'
fi
}
function getNonce
{
# 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
}
# CHALLENGES
function order
{
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.
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'
done
}
function 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
}
# CORE ACTIONS
function genACMEkey { openssl genrsa -traditional 2048 > account.key; }
function registerAcc
{
# Create account:
readACME
url=$(echo $endpoints | jq -r .newAccount)
req='{"termsOfServiceAgreed": true, "contact": ["mailto:zerglingman@fedora.email"]}'
out=$(sendreq "$url" "$req" yes)
loc=$(grep -i 'location' <<< "$out" | grep -io http.*$)
loadkid $loc
echo $loc
}
function getcert
{
conf=$1
shift
url=$(echo $endpoints | jq -r .newOrder)
req='{"identifiers": ['
for n in $@; do
req=$req'{"type": "dns", "value": "'$n'"}, '
done
req=${req:0:-2}']}'
order=$(sendreq $url "$req")
# I can just access the headers lol
orderurl=$(grep -i location <<< "$req_heads" | grep -io http.*$)
auths=$(jq '.authorizations' <<< "$order")
finalise=$(jq -r '.finalize' <<< "$order")
order "$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)
curl $certurl > out.crt
}
# SERVER COMMUNICATION
function sendreq
{
loadkid # 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
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")
nonce=$(getNonce "$req_heads")
if [ -n "$3" ]; then echo "$req_heads"; fi
echo $req_resp
}
# MAIN
getCAURLs $1
shift
loadkid
$*