Browse Source

Merge pull request #452 from darovic/ACMEv2

Applied ACMEv2 - thanks to darovic!!!
pull/454/head
Tim Kimber 6 years ago
committed by GitHub
parent
commit
e2084574cb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1284 additions and 234 deletions
  1. +1
    -0
      README.md
  2. +60
    -0
      dns_scripts/GoDaddy-README.txt
  3. +40
    -0
      dns_scripts/dns_add_godaddy
  4. +44
    -0
      dns_scripts/dns_add_joker
  5. +32
    -3
      dns_scripts/dns_add_nsupdate
  6. +33
    -33
      dns_scripts/dns_add_ovh
  7. +38
    -0
      dns_scripts/dns_del_godaddy
  8. +44
    -0
      dns_scripts/dns_del_joker
  9. +33
    -3
      dns_scripts/dns_del_nsupdate
  10. +35
    -35
      dns_scripts/dns_del_ovh
  11. +418
    -0
      dns_scripts/dns_godaddy
  12. +506
    -160
      getssl

+ 1
- 0
README.md View File

@ -13,6 +13,7 @@ Obtain SSL certificates from the letsencrypt.org ACME server. Suitable for auto
* **Simple and easy to use** * **Simple and easy to use**
* **Detailed debug info** - Whilst it shouldn't be needed, detailed debug information is available. * **Detailed debug info** - Whilst it shouldn't be needed, detailed debug information is available.
* **Reload services** - After a new certificate is obtained then the relevant services (e.g. apache/nginx/postfix) can be reloaded. * **Reload services** - After a new certificate is obtained then the relevant services (e.g. apache/nginx/postfix) can be reloaded.
* **ACME v1 and V2** - Supports both ACME versions 1 and 2
## Installation ## Installation
Since the script is only one file, you can use the following command for a quick installation of GetSSL only: Since the script is only one file, you can use the following command for a quick installation of GetSSL only:


+ 60
- 0
dns_scripts/GoDaddy-README.txt View File

@ -0,0 +1,60 @@
Using GoDaddy DNS for LetsEncrypt domain validation.
Quick guide to setting up getssl for domain validation of
GoDaddy DNS domains.
There are two prerequisites to using getssl with GoDaddy DNS:
1) Obtain an API access key from developer.godaddy.com
At first sign-up, you will be required to take a "test" key.
This is NOT what you need. Accept it, then get a "Production"
key. At this writing, there is no charge - but you must have
a GoDaddy customer account.
You must get the API key for the account which owns the domain
that you want to get certificates for. If the domains that you
manage are owned by more than one account, get a key for each.
The access key consists of a "Key" and a "Secret". You need
both.
2) Obtain JSON.sh - https://github.com/dominictarr/JSON.sh
With those in hand, the installation procedure is:
1) Put JSON.sh in the getssl DNS scripts directory
Default: /usr/share/getssl/dns_scripts
2) Open your config file (the global file in ~/.getssl/getssl.cfg
or the per-account file in ~/.getssl/example.net/getssl.cfg
3) Set the following options:
VALIDATE_VIA_DNS="true"
DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_godaddy"
DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_godaddy"
# The API key for your account/this domain
export GODADDY_KEY="..." GODADDY_SECRET="..."
4) Set any other options that you wish (per the standard
directions.) Use the test CA to make sure that
everything is setup correctly.
That's it. getssl example.net will now validate with DNS.
To trace record additions and removals, run getssl as
GODADDY_TRACE=Y getssl example.net
There are additional options, which are documented in the
*godaddy" files and dns_godaddy -h.
Copyright (2017) Timothe Litt litt at acm _dot org
This sofware may be freely used providing this notice is included with
all copies. The name of the author may not be used to endorse
any other product or derivative work. No warranty is provided
and the user assumes all responsibility for use of this software.
Report any issues to https://github.com/tlhackque/getssl/issues.
Enjoy.

+ 40
- 0
dns_scripts/dns_add_godaddy View File

@ -0,0 +1,40 @@
#!/bin/bash
# Copyright (2017) Timothe Litt litt at acm _dot org
# Add token to GoDaddy dns using dns_godaddy
# You do not have to customize this script.
#
# Obtain the Key and Secret from https://developer.godaddy.com/getstarted
# You must obtain a "Production" key - NOT the "Test" key you're required
# to get first.
#
# Obtain JSON.sh from https://github.com/dominictarr/JSON.sh
# Place it in (or softlink it to) the same directory as $GODADDY_SCRIPT,
# or specify its location with GODADDY_JSON The default is
# /usr/share/getssl/dns_scripts/
#
# Define GODADDY_KEY and GO_DADDY_SECRET in your account or domain getssl.cfg
#
# See GoDaddy-README.txt for complete instructions.
fulldomain="$1"
token="$2"
[ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="/usr/share/getssl/dns_scripts/dns_godaddy"
[[ "$GODADDY_SCRIPT" =~ ^~ ]] && \
eval 'GODADDY_SCRIPT=`readlink -nf ' $GODADDY_SCRIPT '`'
if [ ! -x "$GODADDY_SCRIPT" ]; then
echo "$GODADDY_SCRIPT: not found. Please install, softlink or set GODADDY_SCRIPT to its full path"
echo "See GoDaddy-README.txt for complete instructions."
exit 3
fi
# JSON.sh is not (currently) used by add
export GODADDY_KEY
export GODADDY_SECRET
$GODADDY_SCRIPT -q add "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}"

+ 44
- 0
dns_scripts/dns_add_joker View File

@ -0,0 +1,44 @@
#!/bin/bash
FULLDOMAIN=$1
TOKEN=$2
TMPFILE=$(mktemp /tmp/dns_add_joker.XXXXXXX)
USERNAME="youruser"
PASSWORD="yourpassword"
# Verify that required parameters are set
if [[ -z "${FULLDOMAIN}" ]]; then
echo "DNS script requires full domain name as first parameter"
exit 1
fi
if [[ -z "${TOKEN}" ]]; then
echo "DNS script requires challenge token as second parameter"
exit 1
fi
DOMAIN_ROOT=$(echo "${FULLDOMAIN}" | awk -F\. '{print $(NF-1) FS $NF}')
SID=$(curl --silent -X POST https://dmapi.joker.com/request/login \
-H "Accept: application/json" -H "User-Agent: getssl/0.1" \
-H "application/x-www-form-urlencoded" -d "username=${USERNAME}&password=${PASSWORD}" \
-i -k 2>/dev/null | grep Auth-Sid | awk '{ print $2 }')
## put zone data in tempfile
curl --silent -X POST https://dmapi.joker.com/request/dns-zone-get \
-H "Accept: application/json" -H "User-Agent: getssl/0.1" \
-H "application/x-www-form-urlencoded" -d "domain=${DOMAIN_ROOT}&auth-sid=${SID}" | \
tail -n +7 >"${TMPFILE}"
## add txt record
printf "_acme-challenge.%s. TXT 0 \"%s \" 300\n\n" "${FULLDOMAIN}" "${TOKEN}" >>"${TMPFILE}"
## generate encoded url data
URLDATA=$(cat "${TMPFILE}" | sed 's/ /%20/g' | sed 's/"/%22/g' | sed ':a;N;$!ba;s/\n/%0A/g')
## write new zonefile to joker
curl --silent --output /dev/null "https://dmapi.joker.com/request/dns-zone-put?domain=${DOMAIN_ROOT}&zone=${URLDATA}&auth-sid=${SID}" 2>&1
## remove tempfile
rm -f "${TMPFILE}"

+ 32
- 3
dns_scripts/dns_add_nsupdate View File

@ -2,9 +2,38 @@
# example of script to add token to local dns using nsupdate # example of script to add token to local dns using nsupdate
dnskeyfile="path/to/bla.key"
fulldomain="$1" fulldomain="$1"
token="$2" token="$2"
printf "update add _acme-challenge.%s. 300 in TXT \"%s\"\n\n" "${fulldomain}" "${token}" | nsupdate -k "${dnskeyfile}" -v
# VARIABLES:
#
# DNS_NSUPDATE_KEYFILE - path to a TSIG key file, if required
# DNS_NSUPDATE_GETKEY - command to execute if access to the key file requires
# some special action: mounting a disk, decrypting a file..
# Called with the operation 'add' and action 'open" / 'close'
if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then
if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'open' "${fulldomain}" ; then
exit $(( $? + 128 ))
fi
options="-k ${DNS_NSUPDATE_KEYFILE}"
fi
# Note that blank line is a "send" command to nsupdate
nsupdate "${options}" -v <<EOF
update add _acme-challenge.${fulldomain}. 300 in TXT "${token}"
EOF
sts=$?
if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then
if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! ${DNS_NSUPDATE_KEY_HOOK} 'add' 'close' "${fulldomain}"; then
exit $(( sts + ( $? * 10 ) ))
fi
fi
exit ${sts}

+ 33
- 33
dns_scripts/dns_add_ovh View File

@ -1,33 +1,33 @@
#!/bin/bash
domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g'))
challenge="$2"
# Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/
# permissions needed are /domain/zone/* in GET,POST,DELETE
applicationKey="YourAK"
applicationSecret="YourAS"
consumerKey="YourCK"
topDomain=${domains[2]}
subDomain=${domains[1]%%.}
function send
{
method=$1
url=$2
body=$3
ts=$(date +%s)
sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1)
curl -X ${method} -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" https://eu.api.ovh.com/1.0${url}
}
# Creation request
send POST /domain/zone/${topDomain}/record "{\"fieldType\":\"TXT\",\"subDomain\":\"$subDomain\",\"ttl\":60,\"target\":\"$challenge\"}"
# Refresh request
send POST /domain/zone/${topDomain}/refresh ""
# Pause for 10 seconds, for DNS propagation
sleep 10
#!/bin/bash
domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g'))
challenge="$2"
# Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/
# permissions needed are /domain/zone/* in GET,POST,DELETE
applicationKey="YourAK"
applicationSecret="YourAS"
consumerKey="YourCK"
topDomain=${domains[2]}
subDomain=${domains[1]%%.}
function send
{
method=$1
url=$2
body=$3
ts=$(date +%s)
sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1)
curl -X "${method}" -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" "https://eu.api.ovh.com/1.0${url}"
}
# Creation request
send POST "/domain/zone/${topDomain}/record" "{\"fieldType\":\"TXT\",\"subDomain\":\"$subDomain\",\"ttl\":60,\"target\":\"$challenge\"}"
# Refresh request
send POST "/domain/zone/${topDomain}/refresh" ""
# Pause for 10 seconds, for DNS propagation
sleep 10

+ 38
- 0
dns_scripts/dns_del_godaddy View File

@ -0,0 +1,38 @@
#!/bin/bash
# Copyright (2017) Timothe Litt litt at acm _dot org
# Remove token from GoDaddy dns using dns_godaddy
# You do not have to customize this script.
#
# Obtain the Key and Secret from https://developer.godaddy.com/getstarted
# You must obtain a "Production" key - NOT the "Test" key you're required
# to get first.
#
# Obtain JSON.sh from https://github.com/dominictarr/JSON.sh
# Place it in (or softlink it to) the same directory as $GODADDY_SCRIPT,
# or specify its location with GODADDY_JSON The default is
# /usr/share/getssl/dns_scripts/
#
# Define GODADDY_KEY and GO_DADDY_SECRET in your account or domain getssl.cfg
#
# See GoDaddy-README.txt for complete instructions.
fulldomain="$1"
token="$2"
[ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="/usr/share/getssl/dns_scripts/dns_godaddy"
[[ "$GODADDY_SCRIPT" =~ ^~ ]] && \
eval 'GODADDY_SCRIPT=`readlink -nf ' "$GODADDY_SCRIPT" '`'
if ! [ -x "$GODADDY_SCRIPT" ]; then
echo "$GODADDY_SCRIPT: not found. Please install, softlink or set GODADDY_SCRIPT to its full path"
echo "See GoDaddy-README.txt for complete instructions."
exit 3
fi
export GODADDY_KEY
export GODADDY_SECRET
$GODADDY_SCRIPT -q del "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}"

+ 44
- 0
dns_scripts/dns_del_joker View File

@ -0,0 +1,44 @@
#!/bin/bash
FULLDOMAIN=$1
TOKEN=$2
TMPFILE=$(mktemp /tmp/dns_add_joker.XXXXXXX)
USERNAME="youruser"
PASSWORD="yourpassword"
# Verify that required parameters are set
if [[ -z "${FULLDOMAIN}" ]]; then
echo "DNS script requires full domain name as first parameter"
exit 1
fi
if [[ -z "${TOKEN}" ]]; then
echo "DNS script requires challenge token as second parameter"
exit 1
fi
DOMAIN_ROOT=$(echo "${FULLDOMAIN}" | awk -F\. '{print $(NF-1) FS $NF}')
SID=$(curl --silent -X POST https://dmapi.joker.com/request/login \
-H "Accept: application/json" -H "User-Agent: getssl/0.1" \
-H "application/x-www-form-urlencoded" -d "username=${USERNAME}&password=${PASSWORD}" \
-i -k 2>/dev/null | grep Auth-Sid | awk '{ print $2 }')
## put zone data in tempfile
curl --silent -X POST https://dmapi.joker.com/request/dns-zone-get \
-H "Accept: application/json" -H "User-Agent: getssl/0.1" \
-H "application/x-www-form-urlencoded" -d "domain=${DOMAIN_ROOT}&auth-sid=${SID}" | \
tail -n +7 >"${TMPFILE}"
## remove txt record
sed -i "/_acme-challenge.${FULLDOMAIN}.*${TOKEN}.*/d" "${TMPFILE}"
## generate encoded url data
URLDATA=$(cat "${TMPFILE}" | sed 's/ /%20/g' | sed 's/"/%22/g' | sed ':a;N;$!ba;s/\n/%0A/g')
## write new zonefile to joker
curl --silent --output /dev/null "https://dmapi.joker.com/request/dns-zone-put?domain=${DOMAIN_ROOT}&zone=${URLDATA}&auth-sid=${SID}" 2>&1
## remove tempfile
rm -f "${TMPFILE}"

+ 33
- 3
dns_scripts/dns_del_nsupdate View File

@ -1,9 +1,39 @@
#!/bin/bash #!/bin/bash
# example of script to add token to local dns using nsupdate
# example of script to remove token from local dns using nsupdate
dnskeyfile="path/to/bla.key"
fulldomain="$1" fulldomain="$1"
token="$2" token="$2"
printf "update delete _acme-challenge.%s. 300 in TXT \"%s\"\n\n" "${fulldomain}" "${token}" | nsupdate -k "${dnskeyfile}" -v
# VARIABLES:
#
# DNS_NSUPDATE_KEYFILE - path to a TSIG key file, if required
# DNS_NSUPDATE_GETKEY - command to execute if access to the key file requires
# some special action: dismounting a disk, encrypting a
# file... Called with the operation 'del' and action
# 'open" / 'close'
if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then
if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'open' "${fulldomain}" ; then
exit $(( $? + 128 ))
fi
options="-k ${DNS_NSUPDATE_KEYFILE}"
fi
# Note that blank line is a "send" command to nsupdate
nsupdate "${options}" -v <<EOF
update delete "_acme-challenge.${fulldomain}." 300 in TXT "${token}"
EOF
sts=$?
if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then
if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'close' "${fulldomain}" ; then
exit $(( sts + ( $? * 10 ) ))
fi
fi
exit ${sts}

+ 35
- 35
dns_scripts/dns_del_ovh View File

@ -1,35 +1,35 @@
#!/bin/bash
domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g'))
challenge="$2"
# Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/
# permissions needed are /domain/zone/* in GET,POST,DELETE
applicationKey="YourAK"
applicationSecret="YourAS"
consumerKey="YourCK"
topDomain=${domains[2]}
subDomain=${domains[1]%%.}
function send
{
method=$1
url=$2
body=$3
ts=$(date +%s)
sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1)
curl -X ${method} -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" https://eu.api.ovh.com/1.0${url}
}
# Creation request
oldResult=$(send GET "/domain/zone/${topDomain}/record?fieldType=TXT&subDomain=${subDomain}" ""|sed -e 's/\[//' -e 's/\]//')
for num in ${oldResult//,/ }
do
send DELETE "/domain/zone/${topDomain}/record/${num}" ""
done
# Refresh request
send POST /domain/zone/${topDomain}/refresh ""
#!/bin/bash
domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g'))
#challenge="$2"
# Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/
# permissions needed are /domain/zone/* in GET,POST,DELETE
applicationKey="YourAK"
applicationSecret="YourAS"
consumerKey="YourCK"
topDomain=${domains[2]}
subDomain=${domains[1]%%.}
function send
{
method=$1
url=$2
body=$3
ts=$(date +%s)
sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1)
curl -X "${method}" -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" "https://eu.api.ovh.com/1.0${url}"
}
# Creation request
oldResult=$(send GET "/domain/zone/${topDomain}/record?fieldType=TXT&subDomain=${subDomain}" ""|sed -e 's/\[//' -e 's/\]//')
for num in ${oldResult//,/ }
do
send DELETE "/domain/zone/${topDomain}/record/${num}" ""
done
# Refresh request
send POST "/domain/zone/${topDomain}/refresh" ""

+ 418
- 0
dns_scripts/dns_godaddy View File

@ -0,0 +1,418 @@
#!/bin/bash
# Copyright (2017) Timothe Litt litt at acm _dot org
VERSION="1.0.1"
PROG="`basename $0`"
# This script is used to update TXT records in GoDaddy DNS server
# It depends on JSON.sh from https://github.com/dominictarr/JSON.sh
# Place it in (or softlink it to) the same directory as this script,
# or specify its location with GODADDY_JSON
#
# See the usage text below, 00GoDaddy-README.txt, dns_add_godaddy
# and dns_del_godaddy for additional information.
#
# It may be freely used providing this notice is included with
# all copies. The name of the author may not be used to endorse
# any other product or derivative work. No warranty is provided
# and the user assumes all responsibility for use of this software.
#
# Bug reports are welcome at https://github.com/tlhackque/getssl/issues.
API='https://api.godaddy.com/v1/domains'
APISIGNUP='https://developer.godaddy.com/getstarted'
GETJSON='https://github.com/dominictarr/JSON.sh'
VERB="y"
DEBUG="$GODADDY_DEBUG"
[ -z "$JSON" ] && JSON="$GODADDY_JSON"
[ -z "$JSON" ] && JSON="`dirname $0`/JSON.sh"
while getopts 'dhj:k:s:t:qv' opt; do
case $opt in
d) DEBUG="Y" ;;
j) JSON="$OPTARG" ;;
k) GODADDY_KEY="$OPTARG" ;;
s) GODADDY_SECRET="$OPTARG" ;;
t) TRACE="$OPTARG" ;;
q) VERB= ;;
v) echo "dns_godaddy version $VERSION"; exit 0 ;;
*)
cat <<EOF
Usage
$PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] add domain name data [ttl]
$PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] del domain name data
Add or delete TXT records from GoDaddy DNS
Obtain the Key and Secret from $APISIGNUP
You must obtain a "Production" key - NOT the "Test" key you're required
to get first.
With getssl, this script is called from the dns_add_godaddy and
dns_del_godaddy wrapper scripts.
Arguments:
add - add the specified record to the domain
del - remove the specified record from the domain
domain is the domain name, e.g. example.org
name is the DNS record name to add, e.g. _acme-challenge.example.org.
Note that trailing '.' is significant in DNS.
data is the record data, e.g. "myverificationtoken"
ttl is optional, and defaults to the GoDaddy minimum of 600.
If it is necessary to turn on debugging externally, define
GODADDY_DEBUG="y" (any non-null string will do).
For minimal trace output (to override -q), define GODADDY_TRACE="y".
Options
-d Provide debugging output - all requests and responses
-h This help.
-j: Location of JSON.sh Default `dirname $0`/JSON.sh, or
the GODADDY_JSON variable.
-k: The GoDaddy API key Default from GODADDY_KEY
-s: The GoDaddy API secret Default from GODADDY_SECRET
-t: Detailed protocol trace data is appended to specified file
-q Quiet - omit normal success messages,
All output, except for this help text, is to stderr.
Environment variables
GODADDY_JSON location of the JSOH.sh script
GODADDY_KEY default API key
GODADDY_SCRIPT location of this script, default location of JSON.sh
GODADDY_SECRET default API secret
GODADDY_TRACE forces -q off if true
GODADDY_TFILE appends protocol trace to file. Overrides -t
BUGS
Due to a limitation of the gOdADDY API, deleting the last TXT record
would be too risky for my taste. So in that case, I replace it with
_dummy.record_.domain. TXT "Ihis record is not used". This record is
not automatically deleted by this script, though it's perfectly OK to
do so manually. (Via another mechanism, or if it's no longer the last.)
This really shouldn't happen often, since most domains have at least
one TXT record - for SPF, tracking APIs, etc.
Report any issues to https://github.com/tlhackque/getssl/issues
EOF
exit 0
;;
esac
done
shift $((OPTIND-1))
# Check for JSON -- required for delete, but if records are added,
# we assume they'll be deleted later & don't want to strand them.
[[ "$JSON" =~ ^~ ]] && \
eval 'JSON=`readlink -nf ' $JSON '`'
if [ ! -x "$JSON" ]; then
cat <<EOF >&2
$0: requires JSON.sh as "$JSON"
The full path to JSON.sh can be specified with -j, or the
GODADDY_JSON environment variable.
You can obtain a copy from $GETJSON
Then place or softlink it to $JSON or set GODADDY_JSON.
EOF
exit 2
fi
if [ -z "$GODADDY_KEY" ] || [ -z "$GODADDY_SECRET" ]; then
echo "GODADDY_KEY and GODADDY secret must be defined" >&2
exit 3
fi
[ -n "$DEBUG" ] && VERB="y"
[ -n "$GODADDY_TRACE" ] && VERB="Y"
[ -n "$GODADDY_TFILE" ] && TRACE="$GODADDY_TFILE"
# Get parameters & validate
op="$1"
if ! [[ "$op" =~ ^(add|del)$ ]]; then
echo "Operation must be \"add\" or \"del\"" >&2
exit 3
fi
domain="$2"
domain="${domain%'.'}"
if [ -z "$domain" ]; then
echo "'domain' parameter is required, see -h" >&2
exit 3
fi
name="$3"
if [ -z "$name" ]; then
echo "'name' parameter is required, see -h" >&2
exit 3
fi
! [[ "$name" =~ [.]$ ]] && name="${name}.${domain}."
data="$4"
if [ -z "$data" ]; then
echo "'data' parameter is required, see -h" >&2
exit 3
fi
if [ "$op" = 'del' ]; then
ttl=
elif [ -z "$5" ]; then
ttl="600" # GoDaddy minimum TTL is 600
elif ! [[ "$5" =~ ^[0-9]+$ ]]; then
echo "TTL $5 is not numeric" >&2
exit 3
elif [ $5 -lt 600 ]; then
[ -n "$VERB" ] && \
echo "$5 is less than GoDaddy minimum of 600; increased to 600" >&2
ttl="600"
else
ttl="$5"
fi
# --- Done with parameters
[ -n "$DEBUG" ] && \
echo "$PROG: $op $domain $name \"$data\" $ttl" >&2
# Authorization header has secret and key
authhdr="Authorization: sso-key $GODADDY_KEY:$GODADDY_SECRET"
if [ -n "$TRACE" ]; then
function timestamp { local tm="`LC_TIME=C date '+%T.%N'`"
local class="$1"; shift
echo "${tm:0:15} ** ${class}: $*" >>"$TRACE"
}
timestamp 'Info' "$PROG" "V$VERSION" 'Starting new protocol trace'
timestamp 'Args' "$@"
curl --help | grep -q -- --trace-time && CURL_TFLAGS="--trace-time" # 7.14.0
function curl {
command curl ${CURL_TFLAGS} --trace-ascii % "$@" 2>>"$TRACE"
}
[ -n "$VERB" ] && echo "Appending protocol trace to $TRACE"
fi
[ -n "$DEBUG" ] && echo "$authhdr" >&2
if [ "$op" = "add" ]; then
# May need to retry due to zone cuts
while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do
url="$API/$domain/records/TXT/$name"
request='{"data":"'$data'","ttl":'$ttl'}'
[ -n "$DEBUG" ] && cat >&2 <<EOF
Add request to: $url
--------
$request"
--------
EOF
result="$(curl -i -s -X PUT -d "$request" --config - "$url" <<EOF
header = "Content-Type: application/json"
header = "$authhdr"
EOF
)"
sts=$?
[ -n "$DEBUG" ] && cat >&2 <<EOF
Result:
curl status = $sts
--------
$result
--------
EOF
if [ $sts -ne 0 ]; then
echo "curl error $sts adding record" >&2
exit $sts
fi
if ! echo "$result" | grep -q '^HTTP/.* 200 '; then
code="`echo "$result" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`"
msg="`echo "$result" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`"
if [ "$code" = "DUPLICATE_RECORD" ]; then
if [ -n "$VERB" ]; then
echo "$msg in $domain" >&2
fi
exit 0 # Duplicate record is still success
fi
if [ "$code" = 'UNKNOWN_DOMAIN' ]; then
if [[ "$domain" =~ ^([^.]+)\.([^.]+\.[^.]+.*) ]]; then
[ -n "$DEBUG" ] && \
echo "$domain unknown, trying ${BASH_REMATCH[2]}" >&2
domain="${BASH_REMATCH[2]}"
continue;
fi
fi
echo "Request failed $msg" >&2
exit 1
fi
[ -n "$VERB" ] && echo "$domain: added $name $ttl TXT \"$data\"" >&2
exit 0
done
fi
# ----- Delete
# There is no delete API
# But, it is possible to replace all TXT records.
#
# So, first query for all TXT records
# May need to retry due to zone cuts
while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do
url="$API/$domain/records/TXT"
[ -n "$DEBUG" ] && echo "Query for TXT records to: $url" >&2
current="$(curl -i -s -X GET --config - "$url" <<EOF
header = "$authhdr"
EOF
)"
sts=$?
if [ $sts -ne 0 ]; then
echo "curl error $sts for query" >&2
exit $sts
fi
[ -n "$DEBUG" ] && cat >&2 <<EOF
Response
--------
$current
--------
EOF
if ! echo "$current" | grep -q '^HTTP/.* 200 '; then
code="`echo "$current" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`"
msg="`echo "$current" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`"
if [ "$code" = "UNKNOWN_DOMAIN" ]; then
if [[ "$domain" =~ ^([^.]+)\.([^.]+\.[^.]+.*) ]]; then
[ -n "$DEBUG" ] && echo \
"$domain unknown, trying ${BASH_REMATCH[2]}" >&2
domain="${BASH_REMATCH[2]}"
continue;
fi
fi
echo "Request failed $msg" >&2
exit 1
fi
# Remove headers
current="$(echo "$current" | sed -e'0,/^\r*$/d')"
break
done
# The zone cut is known, so the replace can't fail due to UNKNOWN domain
if [ "$current" = '[]' ]; then # No TXT records in zone
[ -n "$VERB" ] && echo "$domain: $name TXT \"$data\" does not exist" >&2
[ -n "$DEBUG" ] && echo "No TXT records in $domain" >&2
exit 1 # Intent was to change, so error status
fi
[ -n "$DEBUG" ] && echo "Response is valid"
# Prepare request to replace TXT RRSET
# Parse JSON and select only the record structures, which are [index] { ...}
current="$(echo "$current" | $JSON | sed -n -e'/^\[[0-9][0-9]*\]/{ s/^\[[0-9][0-9]*\]//; p}')"
base="$current"
[ -n "$DEBUG" ] && cat >&2 <<EOF
Old TXT RRSET:
$current
EOF
# Remove the desired record. The name must be relative.
eval 'name="$''{name%'"'.$domain.'}"'"'
match="$(printf '"name":"%s","data":"%s","ttl":' "$name" "$data")"
cmd="$(printf 'echo %s%s%s | grep -v %s%s%s' "'" "$current" "'" "'" "$match" "'")"
eval 'new="$('"$cmd"')"'
if [ "$new" = "$base" ]; then
[ -n "$VERB" ] && echo "$domain: $name TXT \"$data\" does not exist" >&2
exit 1 # Intent was to change DNS, so this is an error
fi
# Remove whitespace and insert needed commmas
#
fmtnew="$new"
new=$(echo "$new" | sed -e"s/}/},/g; \$s/},/}/;" | tr -d '\t\n')
if [ -z "$new" ]; then
[ -n "$VERB" ] && echo "Replacing last TXT record with a dummy (see -h)" >&2
new='{"type":"TXT","name":"_dummy.record_","data":"_This record is not used_","ttl":601}'
dummy="t"
TAB=$'\t'
fmtnew="${TAB}$new"
if [ "$fmtnew" = "$base" ]; then
[ -n "$VERB" ] && echo "This tool can't delete a placeholder when it is the only TXT record" >&2
exit 0 # Not really success, but retrying won't help.
fi
fi
request="[$new]"
[ -n "$DEBUG" ] && cat >&2 <<EOF
New TXT RRSET will be
$fmtnew
EOF
url="$API/$domain/records/TXT"
[ -n "$DEBUG" ] && cat >&2 <<EOF
Replace (delete) request to: $url
--------
$request
--------
EOF
result="$(curl -i -s -X PUT -d "$request" --config - "$url" <<EOF
header = "Content-Type: application/json"
header = "$authhdr"
EOF
)"
sts=$?
[ -n "$DEBUG" ] && cat >&2 <<EOF
Result:
curl status = $sts
--------
$result
--------
EOF
if [ $sts -ne 0 ]; then
echo "curl error $sts deleting record" >&2
exit $sts
fi
if ! echo "$result" | grep -q '^HTTP/.* 200 '; then
result="$(echo "$result" | sed -e'0,/^\r*$/d')"
code="`echo "$result" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`"
msg="`echo "$result" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`"
echo "Request failed $msg" >&2
exit 1
fi
if [ -n "$VERB" ]; then
if [ -n "$dummy" ]; then
echo "$domain: replaced $name TXT \"$data\" with a placeholder" >&2
else
echo "$domain: deleted $name TXT \"$data\"" >&2
fi
fi
exit 0

+ 506
- 160
getssl View File

@ -184,18 +184,20 @@
# 2017-01-30 issue #243 compatibility with bash 3.0 (2.08) # 2017-01-30 issue #243 compatibility with bash 3.0 (2.08)
# 2017-01-30 issue #243 additional compatibility with bash 3.0 (2.09) # 2017-01-30 issue #243 additional compatibility with bash 3.0 (2.09)
# 2017-02-18 add OCSP Must-Staple to the domain csr generation (2.10) # 2017-02-18 add OCSP Must-Staple to the domain csr generation (2.10)
# 2018-01-04 updating to use the updated letsencrypt APIv2
# 2019-09-30 issue #423 Use HTTP 1.1 as workaround atm (2.11) # 2019-09-30 issue #423 Use HTTP 1.1 as workaround atm (2.11)
# 2019-10-02 issue #425 Case insensitive processing of agreement url because of HTTP/2 (2.12) # 2019-10-02 issue #425 Case insensitive processing of agreement url because of HTTP/2 (2.12)
# 2019-10-07 update DNS checks to allow use of CNAMEs (2.13) # 2019-10-07 update DNS checks to allow use of CNAMEs (2.13)
# 2019-11-18 Rebased master onto APIv2 and added Content-Type: application/jose+json (2.14)
# ---------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------
PROGNAME=${0##*/} PROGNAME=${0##*/}
VERSION="2.13"
VERSION="2.14"
# defaults # defaults
ACCOUNT_KEY_LENGTH=4096 ACCOUNT_KEY_LENGTH=4096
ACCOUNT_KEY_TYPE="rsa" ACCOUNT_KEY_TYPE="rsa"
CA="https://acme-staging.api.letsencrypt.org"
CA="https://acme-staging-v02.api.letsencrypt.org/directory"
CA_CERT_LOCATION="" CA_CERT_LOCATION=""
CHALLENGE_CHECK_TYPE="http" CHALLENGE_CHECK_TYPE="http"
CHECK_ALL_AUTH_DNS="false" CHECK_ALL_AUTH_DNS="false"
@ -241,6 +243,7 @@ _UPGRADE_CHECK=1
_USE_DEBUG=0 _USE_DEBUG=0
config_errors="false" config_errors="false"
LANG=C LANG=C
API=1
# store copy of original command in case of upgrading script and re-running # store copy of original command in case of upgrading script and re-running
ORIGCMD="$0 $*" ORIGCMD="$0 $*"
@ -258,11 +261,11 @@ cert_archive() { # Archive certificate file by copying files to dated archive d
cp "$CA_CERT" "${DOMAIN_DIR}/archive/${date_time}/chain.crt" cp "$CA_CERT" "${DOMAIN_DIR}/archive/${date_time}/chain.crt"
cat "$CERT_FILE" "$CA_CERT" > "${DOMAIN_DIR}/archive/${date_time}/fullchain.crt" cat "$CERT_FILE" "$CA_CERT" > "${DOMAIN_DIR}/archive/${date_time}/fullchain.crt"
if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then
cp "${CERT_FILE::-4}.ec.crt" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.crt"
cp "${CERT_FILE%.*}.ec.crt" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.crt"
cp "$DOMAIN_DIR/${DOMAIN}.ec.csr" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.csr" cp "$DOMAIN_DIR/${DOMAIN}.ec.csr" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.csr"
cp "$DOMAIN_DIR/${DOMAIN}.ec.key" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.key" cp "$DOMAIN_DIR/${DOMAIN}.ec.key" "${DOMAIN_DIR}/archive/${date_time}/${DOMAIN}.ec.key"
cp "${CA_CERT::-4}.ec.crt" "${DOMAIN_DIR}/archive/${date_time}/chain.ec.crt"
cat "${CERT_FILE::-4}.ec.crt" "${CA_CERT::-4}.ec.crt" > "${DOMAIN_DIR}/archive/${date_time}/fullchain.ec.crt"
cp "${CA_CERT%.*}.ec.crt" "${DOMAIN_DIR}/archive/${date_time}/chain.ec.crt"
cat "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "${DOMAIN_DIR}/archive/${date_time}/fullchain.ec.crt"
fi fi
umask "$ORIG_UMASK" umask "$ORIG_UMASK"
debug "purging old GetSSL archives" debug "purging old GetSSL archives"
@ -278,8 +281,14 @@ check_challenge_completion() { # checks with the ACME server if our challenge is
send_signed_request "$uri" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}" send_signed_request "$uri" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}"
# check response from our request to perform challenge # check response from our request to perform challenge
if [[ ! -z "$code" ]] && [[ ! "$code" == '202' ]] ; then
error_exit "$domain:Challenge error: $code"
if [[ $API -eq 1 ]]; then
if [[ ! -z "$code" ]] && [[ ! "$code" == '202' ]] ; then
error_exit "$domain:Challenge error: $code"
fi
else # APIv2
if [[ ! -z "$code" ]] && [[ ! "$code" == '200' ]] ; then
error_exit "$domain:Challenge error: $code"
fi
fi fi
# loop "forever" to keep checking for a response from the ACME server. # loop "forever" to keep checking for a response from the ACME server.
@ -370,7 +379,7 @@ check_config() { # check the config files for all obvious errors
fi fi
dn=0 dn=0
tmplist=$(mktemp)
tmplist=$(mktemp 2>/dev/null || mktemp -t getssl)
for d in $alldomains; do # loop over domains (dn is domain number) for d in $alldomains; do # loop over domains (dn is domain number)
debug "checking domain $d" debug "checking domain $d"
if [[ "$(grep "^${d}$" "$tmplist")" = "$d" ]]; then if [[ "$(grep "^${d}$" "$tmplist")" = "$d" ]]; then
@ -393,7 +402,7 @@ check_config() { # check the config files for all obvious errors
fi fi
# check domain exist # check domain exist
if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then
if [[ "$($DNS_CHECK_FUNC "${d}" SOA|grep -c "^${d}")" -ge 1 ]]; then
if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "${d}")" -ge 1 ]]; then
debug "found IP for ${d}" debug "found IP for ${d}"
else else
info "${DOMAIN}: DNS lookup failed for ${d}" info "${DOMAIN}: DNS lookup failed for ${d}"
@ -428,7 +437,7 @@ check_config() { # check the config files for all obvious errors
} }
check_getssl_upgrade() { # check if a more recent version of code is available available check_getssl_upgrade() { # check if a more recent version of code is available available
TEMP_UPGRADE_FILE="$(mktemp)"
TEMP_UPGRADE_FILE="$(mktemp 2>/dev/null || mktemp -t getssl)"
curl --silent "$CODE_LOCATION" --output "$TEMP_UPGRADE_FILE" curl --silent "$CODE_LOCATION" --output "$TEMP_UPGRADE_FILE"
errcode=$? errcode=$?
if [[ $errcode -eq 60 ]]; then if [[ $errcode -eq 60 ]]; then
@ -526,7 +535,8 @@ copy_file_to_location() { # copies a file, using scp, sftp or ftp if required.
debug "servername $servername" debug "servername $servername"
debug "file $tofile" debug "file $tofile"
# shellcheck disable=SC2029 # shellcheck disable=SC2029
ssh "$servername" "chown $TOKEN_USER_ID $tofile"
# shellcheck disable=SC2086
ssh $SSH_OPTS "$servername" "chown $TOKEN_USER_ID $tofile"
fi fi
elif [[ "${to:0:4}" == "ftp:" ]] ; then elif [[ "${to:0:4}" == "ftp:" ]] ; then
if [[ "$cert" != "challenge token" ]] ; then if [[ "$cert" != "challenge token" ]] ; then
@ -622,7 +632,7 @@ create_csr() { # create a csr using a given key (if it doesn't already exist)
if [[ ! -s "$csr_file" ]] || [[ "$_RECREATE_CSR" == "1" ]]; then if [[ ! -s "$csr_file" ]] || [[ "$_RECREATE_CSR" == "1" ]]; then
info "creating domain csr - $csr_file" info "creating domain csr - $csr_file"
# create a temporary config file, for portability. # create a temporary config file, for portability.
tmp_conf=$(mktemp)
tmp_conf=$(mktemp 2>/dev/null || mktemp -t getssl)
cat "$SSLCONF" > "$tmp_conf" cat "$SSLCONF" > "$tmp_conf"
printf "[SAN]\n%s" "$SANLIST" >> "$tmp_conf" printf "[SAN]\n%s" "$SANLIST" >> "$tmp_conf"
# add OCSP Must-Staple to the domain csr # add OCSP Must-Staple to the domain csr
@ -656,8 +666,8 @@ create_key() { # create a domain key (if it doesn't already exist)
esac esac
umask "$ORIG_UMASK" umask "$ORIG_UMASK"
# remove csr on generation of new domain key # remove csr on generation of new domain key
if [[ -e "${key_loc::-4}.csr" ]]; then
rm -f "${key_loc::-4}.csr"
if [[ -e "${key_loc%.*}.csr" ]]; then
rm -f "${key_loc%.*}.csr"
fi fi
fi fi
} }
@ -807,36 +817,45 @@ get_certificate() { # get certificate for csr, if all domains validated.
der=$(openssl req -in "$gc_csr" -outform DER | urlbase64) der=$(openssl req -in "$gc_csr" -outform DER | urlbase64)
debug "der $der" debug "der $der"
send_signed_request "$URL_new_cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64"
# convert certificate information into correct format and save to file.
CertData=$(awk ' $1 ~ "^Location" {print $2}' "$CURL_HEADER" |tr -d '\r')
debug "certdata location = $CertData"
if [[ "$CertData" ]] ; then
echo -----BEGIN CERTIFICATE----- > "$gc_certfile"
curl --silent "$CertData" | openssl base64 -e >> "$gc_certfile"
echo -----END CERTIFICATE----- >> "$gc_certfile"
info "Certificate saved in $CERT_FILE"
fi
if [[ $API -eq 1 ]]; then
send_signed_request "$URL_new_cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64"
# convert certificate information into correct format and save to file.
CertData=$(awk ' $1 ~ "^Location" {print $2}' "$CURL_HEADER" |tr -d '\r')
debug "certdata location = $CertData"
if [[ "$CertData" ]] ; then
echo -----BEGIN CERTIFICATE----- > "$gc_certfile"
curl --silent "$CertData" | openssl base64 -e >> "$gc_certfile"
echo -----END CERTIFICATE----- >> "$gc_certfile"
info "Certificate saved in $CERT_FILE"
fi
# If certificate wasn't a valid certificate, error exit.
if [[ -z "$CertData" ]] ; then
response2=$(echo "$response" | fold -w64 |openssl base64 -d)
debug "response was $response"
error_exit "Sign failed: $(echo "$response2" | grep "detail")"
fi
# If certificate wasn't a valid certificate, error exit.
if [[ -z "$CertData" ]] ; then
response2=$(echo "$response" | fold -w64 |openssl base64 -d)
debug "response was $response"
error_exit "Sign failed: $(echo "$response2" | grep "detail")"
fi
# get a copy of the CA certificate.
IssuerData=$(grep -i '^Link' "$CURL_HEADER" \
| cut -d " " -f 2\
| cut -d ';' -f 1 \
| sed 's/<//g' \
| sed 's/>//g')
if [[ "$IssuerData" ]] ; then
echo -----BEGIN CERTIFICATE----- > "$gc_cafile"
curl --silent "$IssuerData" | openssl base64 -e >> "$gc_cafile"
echo -----END CERTIFICATE----- >> "$gc_cafile"
info "The intermediate CA cert is in $gc_cafile"
# get a copy of the CA certificate.
IssuerData=$(grep -i '^Link' "$CURL_HEADER" \
| cut -d " " -f 2\
| cut -d ';' -f 1 \
| sed 's/<//g' \
| sed 's/>//g')
if [[ "$IssuerData" ]] ; then
echo -----BEGIN CERTIFICATE----- > "$gc_cafile"
curl --silent "$IssuerData" | openssl base64 -e >> "$gc_cafile"
echo -----END CERTIFICATE----- >> "$gc_cafile"
info "The intermediate CA cert is in $gc_cafile"
fi
else # APIv2
send_signed_request "$FinalizeLink" "{\"csr\": \"$der\"}" "needbase64"
debug "order link was $OrderLink"
cd=$(curl --silent "$OrderLink")
CertData=$(json_get "$cd" "certificate")
debug "CertData is at $CertData"
curl --silent "$CertData" > "$CERT_FILE"
info "Certificate saved in $CERT_FILE"
fi fi
} }
@ -966,28 +985,207 @@ info() { # write out info as long as the quiet flag has not been set.
fi fi
} }
json_get() { # get the value corresponding to $2 in the JSON passed as $1.
# remove newlines, so it's a single chunk of JSON
json_data=$( echo "$1" | tr '\n' ' ')
# if $3 is defined, this is the section which the item is in.
if [[ ! -z "$3" ]]; then
jg_section=$(echo "$json_data" | awk -F"[}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${3}"'\"/){print $i}}}')
if [[ "$2" == "uri" ]]; then
jg_subsect=$(echo "$jg_section" | awk -F"[,]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i)}}}')
jg_result=$(echo "$jg_subsect" | awk -F'"' '{print $4}')
json_awk() { # AWK json converter used for API2 - needs tidying up ;)
# shellcheck disable=SC2086
echo $1 | awk '
{
tokenize($0) # while(get_token()) {print TOKEN}
if (0 == parse()) {
apply(JPATHS, NJPATHS)
}
}
function apply (ary,size,i) {
for (i=1; i<size; i++)
print ary[i]
}
function get_token() {
TOKEN = TOKENS[++ITOKENS] # for internal tokenize()
return ITOKENS < NTOKENS
}
function parse_array(a1,idx,ary,ret) {
idx=0
ary=""
get_token()
if (TOKEN != "]") {
while (1) {
if (ret = parse_value(a1, idx)) {
return ret
}
idx=idx+1
ary=ary VALUE
get_token()
if (TOKEN == "]") {
break
} else if (TOKEN == ",") {
ary = ary ","
} else {
report(", or ]", TOKEN ? TOKEN : "EOF")
return 2
}
get_token()
}
}
VALUE=""
return 0
}
function parse_object(a1,key,obj) {
obj=""
get_token()
if (TOKEN != "}") {
while (1) {
if (TOKEN ~ /^".*"$/) {
key=TOKEN
} else {
report("string", TOKEN ? TOKEN : "EOF")
return 3
}
get_token()
if (TOKEN != ":") {
report(":", TOKEN ? TOKEN : "EOF")
return 4
}
get_token()
if (parse_value(a1, key)) {
return 5
}
obj=obj key ":" VALUE
get_token()
if (TOKEN == "}") {
break
} else if (TOKEN == ",") {
obj=obj ","
} else {
report(", or }", TOKEN ? TOKEN : "EOF")
return 6
}
get_token()
}
}
VALUE=""
return 0
}
function parse_value(a1, a2, jpath,ret,x) {
jpath=(a1!="" ? a1 "," : "") a2 # "${1:+$1,}$2"
if (TOKEN == "{") {
if (parse_object(jpath)) {
return 7
}
} else if (TOKEN == "[") {
if (ret = parse_array(jpath)) {
return ret
}
} else if (TOKEN == "") { #test case 20150410 #4
report("value", "EOF")
return 9
} else if (TOKEN ~ /^([^0-9])$/) {
# At this point, the only valid single-character tokens are digits.
report("value", TOKEN)
return 9
} else {
VALUE=TOKEN
}
if (! ("" == jpath || "" == VALUE)) {
x=sprintf("[%s]\t%s", jpath, VALUE)
print x
}
return 0
}
function parse( ret) {
get_token()
if (ret = parse_value()) {
return ret
}
if (get_token()) {
report("EOF", TOKEN)
return 11
}
return 0
}
function report(expected, got, i,from,to,context) {
from = ITOKENS - 10; if (from < 1) from = 1
to = ITOKENS + 10; if (to > NTOKENS) to = NTOKENS
for (i = from; i < ITOKENS; i++)
context = context sprintf("%s ", TOKENS[i])
context = context "<<" got ">> "
for (i = ITOKENS + 1; i <= to; i++)
context = context sprintf("%s ", TOKENS[i])
scream("json_awk expected <" expected "> but got <" got "> at input token " ITOKENS "\n" context)
}
function reset() {
TOKEN=""; delete TOKENS; NTOKENS=ITOKENS=0
delete JPATHS; NJPATHS=0
VALUE=""
}
function scream(msg) {
FAILS[FILENAME] = FAILS[FILENAME] (FAILS[FILENAME]!="" ? "\n" : "") msg
msg = FILENAME ": " msg
print msg >"/dev/stderr"
}
function tokenize(a1,pq,pb,ESCAPE,CHAR,STRING,NUMBER,KEYWORD,SPACE) {
SPACE="[[:space:]]+"
gsub(/\"[^[:cntrl:]\"\\]*((\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})[^[:cntrl:]\"\\]*)*\"|-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?|null|false|true|[[:space:]]+|./, "\n&", a1)
gsub("\n" SPACE, "\n", a1)
sub(/^\n/, "", a1)
ITOKENS=0 # get_token() helper
return NTOKENS = split(a1, TOKENS, /\n/)
}'
}
json_get() { # get values from json
if [[ -z "$1" ]] || [[ "$1" == "null" ]]; then
echo "json was blank"
return
fi
if [[ $API = 1 ]]; then
# remove newlines, so it's a single chunk of JSON
json_data=$( echo "$1" | tr '\n' ' ')
# if $3 is defined, this is the section which the item is in.
if [[ ! -z "$3" ]]; then
jg_section=$(echo "$json_data" | awk -F"[}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${3}"'\"/){print $i}}}')
if [[ "$2" == "uri" ]]; then
jg_subsect=$(echo "$jg_section" | awk -F"[,]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i)}}}')
jg_result=$(echo "$jg_subsect" | awk -F'"' '{print $4}')
else
jg_result=$(echo "$jg_section" | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i+1)}}}')
fi
else else
jg_result=$(echo "$jg_section" | awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i+1)}}}')
jg_result=$(echo "$json_data" |awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i+1)}}}')
fi
# check number of quotes
jg_q=${jg_result//[^\"]/}
# if 2 quotes, assume it's a quoted variable and just return the data within the quotes.
if [[ ${#jg_q} -eq 2 ]]; then
echo "$jg_result" | awk -F'"' '{print $2}'
else
echo "$jg_result"
fi fi
else else
jg_result=$(echo "$json_data" |awk -F"[,:}]" '{for(i=1;i<=NF;i++){if($i~/\"'"${2}"'\"/){print $(i+1)}}}')
fi
# check number of quotes
jg_q=${jg_result//[^\"]/}
# if 2 quotes, assume it's a quoted variable and just return the data within the quotes.
if [[ ${#jg_q} -eq 2 ]]; then
echo "$jg_result" | awk -F'"' '{print $2}'
else
echo "$jg_result"
if [[ ! -z "$6" ]]; then
full=$(json_awk "$1")
section=$(echo "$full" | grep "\"$2\"" | grep "\"$3\"" | grep "\"$4\"" | awk -F"," '{print $2}')
echo "$full" | grep "^..${5}\",$section" | awk '{print $2}' | tr -d '"'
elif [[ ! -z "$5" ]]; then
full=$(json_awk "$1")
section=$(echo "$full" | grep "\"$2\"" | grep "\"$3\"" | grep "\"$4\"" | awk -F"," '{print $2}')
echo "$full" | grep "^..${2}\",$section" | grep "$5" | awk '{print $2}' | tr -d '"'
elif [[ ! -z "$3" ]]; then
json_awk "$1" | grep "^..${2}...${3}" | awk '{print $2}' | tr -d '"'
elif [[ ! -z "$2" ]]; then
json_awk "$1" | grep "^..${2}" | awk '{print $2}' | tr -d '"'
else
json_awk "$1"
fi
fi fi
} }
@ -1033,9 +1231,10 @@ reload_service() { # Runs a command to reload services ( via ssh if needed)
sshhost=$(echo "$RELOAD_CMD"| awk -F: '{print $2}') sshhost=$(echo "$RELOAD_CMD"| awk -F: '{print $2}')
command=${RELOAD_CMD:(( ${#sshhost} + 5))} command=${RELOAD_CMD:(( ${#sshhost} + 5))}
debug "running following command to reload cert" debug "running following command to reload cert"
debug "ssh $sshhost ${command}"
debug "ssh $SSH_OPTS $sshhost ${command}"
# shellcheck disable=SC2029 # shellcheck disable=SC2029
ssh "$sshhost" "${command}" 1>/dev/null 2>&1
# shellcheck disable=SC2086
ssh $SSH_OPTS "$sshhost" "${command}" 1>/dev/null 2>&1
# allow 2 seconds for services to restart # allow 2 seconds for services to restart
sleep 2 sleep 2
else else
@ -1053,7 +1252,7 @@ revoke_certificate() { # revoke a certificate
ACCOUNT_KEY="$REVOKE_KEY" ACCOUNT_KEY="$REVOKE_KEY"
# need to set the revoke key as "account_key" since it's used in send_signed_request. # need to set the revoke key as "account_key" since it's used in send_signed_request.
get_signing_params "$REVOKE_KEY" get_signing_params "$REVOKE_KEY"
TEMP_DIR=$(mktemp -d)
TEMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t getssl)
debug "revoking from $CA" debug "revoking from $CA"
rcertdata=$(openssl x509 -in "$REVOKE_CERT" -inform PEM -outform DER | urlbase64) rcertdata=$(openssl x509 -in "$REVOKE_CERT" -inform PEM -outform DER | urlbase64)
send_signed_request "$URL_revoke" "{\"resource\": \"revoke-cert\", \"certificate\": \"$rcertdata\"}" send_signed_request "$URL_revoke" "{\"resource\": \"revoke-cert\", \"certificate\": \"$rcertdata\"}"
@ -1134,7 +1333,6 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p
needbase64=$3 needbase64=$3
debug url "$url" debug url "$url"
debug payload "$payload"
CURL_HEADER="$TEMP_DIR/curl.header" CURL_HEADER="$TEMP_DIR/curl.header"
dp="$TEMP_DIR/curl.dump" dp="$TEMP_DIR/curl.dump"
@ -1152,57 +1350,105 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p
# convert payload to url base 64 # convert payload to url base 64
payload64="$(printf '%s' "${payload}" | urlbase64)" payload64="$(printf '%s' "${payload}" | urlbase64)"
debug payload64 "$payload64"
# get nonce from ACME server # get nonce from ACME server
nonceurl="$CA/directory"
nonce=$($CURL -I $nonceurl | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ')
debug nonce "$nonce"
# Build header with just our public key and algorithm information
header='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"'}'
# Build another header which also contains the previously received nonce and encode it as urlbase64
protected='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"', "nonce": "'"${nonce}"'", "url": "'"${url}"'"}'
protected64="$(printf '%s' "${protected}" | urlbase64)"
debug protected "$protected"
# Sign header with nonce and our payload with our private key and encode signature as urlbase64
sign_string "$(printf '%s' "${protected64}.${payload64}")" "${ACCOUNT_KEY}" "$signalg"
# Send header + extended header + payload + signature to the acme-server
body="{\"header\": ${header},"
body="${body}\"protected\": \"${protected64}\","
body="${body}\"payload\": \"${payload64}\","
body="${body}\"signature\": \"${signed64}\"}"
debug "header, payload and signature = $body"
code="500"
loop_limit=5
while [[ "$code" -eq 500 ]]; do
if [[ "$needbase64" ]] ; then
response=$($CURL -X POST --data "$body" "$url" | urlbase64)
if [[ $API -eq 1 ]]; then
nonceurl="$CA/directory"
nonce=$($CURL -I $nonceurl | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ')
else # APIv2
nonce=$($CURL -I "$URL_newNonce" | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ')
fi
nonceproblem="true"
while [[ "$nonceproblem" == "true" ]]; do
debug nonce "$nonce"
# Build header with just our public key and algorithm information
header='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"'}'
# Build another header which also contains the previously received nonce and encode it as urlbase64
if [[ $API -eq 1 ]]; then
protected='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"', "nonce": "'"${nonce}"'", "url": "'"${url}"'"}'
protected64="$(printf '%s' "${protected}" | urlbase64)"
else # APIv2
if [[ -z "$KID" ]]; then
debug "KID is blank, so using jwk"
protected='{"alg": "'"$jwkalg"'", "jwk": '"$jwk"', "nonce": "'"${nonce}"'", "url": "'"${url}"'"}'
protected64="$(printf '%s' "${protected}" | urlbase64)"
else
debug "using KID=${KID}"
protected="{\"alg\": \"$jwkalg\", \"kid\": \"$KID\",\"nonce\": \"${nonce}\", \"url\": \"${url}\"}"
debug "protected = $protected"
protected64="$(printf '%s' "${protected}" | urlbase64)"
fi
fi
# Sign header with nonce and our payload with our private key and encode signature as urlbase64
sign_string "$(printf '%s' "${protected64}.${payload64}")" "${ACCOUNT_KEY}" "$signalg"
# Send header + extended header + payload + signature to the acme-server
if [[ $API -eq 1 ]]; then
debug "header = $header"
debug "protected = $protected"
debug "payload = $payload"
body="{\"header\": ${header},"
body="${body}\"protected\": \"${protected64}\","
body="${body}\"payload\": \"${payload64}\","
body="${body}\"signature\": \"${signed64}\"}"
debug "header, payload and signature = $body"
else else
response=$($CURL -X POST --data "$body" "$url")
debug "protected = $protected"
debug "payload = $payload"
body="{"
body="${body}\"protected\": \"${protected64}\","
body="${body}\"payload\": \"${payload64}\","
body="${body}\"signature\": \"${signed64}\"}"
debug "header, payload and signature = $body"
fi fi
responseHeaders=$(cat "$CURL_HEADER")
debug responseHeaders "$responseHeaders"
debug response "$response"
code=$(awk ' $1 ~ "^HTTP" {print $2}' "$CURL_HEADER" | tail -1)
debug code "$code"
response_status=$(json_get "$response" status \
| head -1| awk -F'"' '{print $2}')
debug "response status = $response_status"
if [[ "$code" -eq 500 ]]; then
info "error on acme server - trying again ...."
sleep 2
loop_limit=$((loop_limit - 1))
if [[ $loop_limit -lt 1 ]]; then
error_exit "500 error from ACME server: $response"
code="500"
loop_limit=5
while [[ "$code" -eq 500 ]]; do
if [[ "$needbase64" ]] ; then
response=$($CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url" | urlbase64)
else
response=$($CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url")
fi fi
responseHeaders=$(cat "$CURL_HEADER")
debug responseHeaders "$responseHeaders"
debug response "$response"
code=$(awk ' $1 ~ "^HTTP" {print $2}' "$CURL_HEADER" | tail -1)
debug code "$code"
if [[ $API -eq 1 ]]; then
response_status=$(json_get "$response" status \
| head -1| awk -F'"' '{print $2}')
else # APIv2
if [[ ${response##*()} == "{"* ]]; then
response_status=$(json_get "$response" status)
else
debug "response not in json format"
debug "$response"
fi
fi
debug "response status = $response_status"
if [[ "$code" -eq 500 ]]; then
info "error on acme server - trying again ...."
debug "loop_limit = $loop_limit"
sleep 5
loop_limit=$((loop_limit - 1))
if [[ $loop_limit -lt 1 ]]; then
error_exit "500 error from ACME server: $response"
fi
fi
done
if [[ $response == *"error:badNonce"* ]]; then
debug "bad nonce"
nonce=$(echo "$responseHeaders" | grep -i "^replay-nonce:" | awk '{print $2}' | tr -d '\r\n ')
debug "trying new nonce $nonce"
else
nonceproblem="false"
fi fi
done done
} }
@ -1291,7 +1537,7 @@ write_domain_template() { # write out a template file for a domain.
# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs
# #
# The staging server is best for testing # The staging server is best for testing
#CA="https://acme-staging.api.letsencrypt.org"
#CA="https://acme-staging-v02.api.letsencrypt.org/directory"
# This server issues full certificates, however has rate limits # This server issues full certificates, however has rate limits
#CA="https://acme-v01.api.letsencrypt.org" #CA="https://acme-v01.api.letsencrypt.org"
@ -1319,11 +1565,11 @@ write_domain_template() { # write out a template file for a domain.
# Location for all your certs, these can either be on the server (full path name) # Location for all your certs, these can either be on the server (full path name)
# or using ssh /sftp as for the ACL # or using ssh /sftp as for the ACL
#DOMAIN_CERT_LOCATION="/etc/ssl/${DOMAIN}.crt"
#DOMAIN_KEY_LOCATION="/etc/ssl/${DOMAIN}.key"
#CA_CERT_LOCATION="/etc/ssl/chain.crt"
#DOMAIN_CERT_LOCATION="/etc/ssl/${DOMAIN}.crt" # this is domain cert
#DOMAIN_KEY_LOCATION="/etc/ssl/${DOMAIN}.key" # this is domain key
#CA_CERT_LOCATION="/etc/ssl/chain.crt" # this is CA cert
#DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert #DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert
#DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert
#DOMAIN_PEM_LOCATION="" # this is the domain key, domain cert and CA cert
# The command needed to reload apache / nginx or whatever you use # The command needed to reload apache / nginx or whatever you use
#RELOAD_CMD="" #RELOAD_CMD=""
@ -1334,6 +1580,7 @@ write_domain_template() { # write out a template file for a domain.
# an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true
#SERVER_TYPE="https" #SERVER_TYPE="https"
#CHECK_REMOTE="true" #CHECK_REMOTE="true"
#CHECK_REMOTE_WAIT="2" # wait 2 seconds before checking the remote server
_EOF_domain_ _EOF_domain_
} }
@ -1343,7 +1590,7 @@ write_getssl_template() { # write out the main template file
# see https://github.com/srvrco/getssl/wiki/Config-variables for details # see https://github.com/srvrco/getssl/wiki/Config-variables for details
# #
# The staging server is best for testing (hence set as default) # The staging server is best for testing (hence set as default)
CA="https://acme-staging.api.letsencrypt.org"
CA="https://acme-staging-v02.api.letsencrypt.org/directory"
# This server issues full certificates, however has rate limits # This server issues full certificates, however has rate limits
#CA="https://acme-v01.api.letsencrypt.org" #CA="https://acme-v01.api.letsencrypt.org"
@ -1631,10 +1878,37 @@ if [[ -e "$DOMAIN_DIR/FORCE_RENEWAL" ]]; then
fi fi
# Obtain CA resource locations # Obtain CA resource locations
ca_all_loc=$(curl "${CA}/directory" 2>/dev/null)
ca_all_loc=$(curl "${CA}" 2>/dev/null)
debug "ca_all_loc from ${CA} gives $ca_all_loc"
# APIv1
URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}') URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}')
URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}') URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}')
URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}')
#API v2
URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}')
URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}')
URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}')
if [[ -z "$URL_new_reg" ]] && [[ -z "$URL_newAccount" ]]; then
ca_all_loc=$(curl "${CA}/directory" 2>/dev/null)
debug "ca_all_loc from ${CA}/directory gives $ca_all_loc"
# APIv1
URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}')
URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}')
URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}')
#API v2
URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}')
URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}')
URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}')
fi
if [[ ! -z "$URL_new_reg" ]]; then
API=1
elif [[ ! -z "$URL_newAccount" ]]; then
API=2
else
info "unknown API version"
graceful_exit
fi
# if check_remote is true then connect and obtain the current certificate (if not forcing renewal) # if check_remote is true then connect and obtain the current certificate (if not forcing renewal)
if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then
@ -1786,23 +2060,42 @@ fi
# currently the code registers every time, and gets an "already registered" back if it has been. # currently the code registers every time, and gets an "already registered" back if it has been.
get_signing_params "$ACCOUNT_KEY" get_signing_params "$ACCOUNT_KEY"
if [[ "$ACCOUNT_EMAIL" ]] ; then
regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}'
else
regjson='{"resource": "new-reg", "agreement": "'$AGREEMENT'"}'
fi
info "Registering account" info "Registering account"
# send the request to the ACME server. # send the request to the ACME server.
send_signed_request "$URL_new_reg" "$regjson"
if [[ $API -eq 1 ]]; then
if [[ "$ACCOUNT_EMAIL" ]] ; then
regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}'
else
regjson='{"resource": "new-reg", "agreement": "'$AGREEMENT'"}'
fi
send_signed_request "$URL_new_reg" "$regjson"
elif [[ $API -eq 2 ]]; then
if [[ "$ACCOUNT_EMAIL" ]] ; then
regjson='{"termsOfServiceAgreed": true, "contact": ["mailto: '$ACCOUNT_EMAIL'"]}'
else
regjson='{"termsOfServiceAgreed": true}'
fi
send_signed_request "$URL_newAccount" "$regjson"
else
debug "cant determine account API"
graceful_exit
fi
if [[ "$code" == "" ]] || [[ "$code" == '201' ]] ; then if [[ "$code" == "" ]] || [[ "$code" == '201' ]] ; then
info "Registered" info "Registered"
KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ')
debug "KID=_$KID}_"
echo "$response" > "$TEMP_DIR/account.json" echo "$response" > "$TEMP_DIR/account.json"
elif [[ "$code" == '409' ]] ; then elif [[ "$code" == '409' ]] ; then
debug "Already registered"
KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ')
debug responseHeaders "$responseHeaders"
debug "Already registered KID=$KID"
elif [[ "$code" == '200' ]] ; then
KID=$(echo "$responseHeaders" | grep -i "^location" | awk '{print $2}'| tr -d '\r\n ')
debug responseHeaders "$responseHeaders"
debug "Already registered account, KID=${KID}"
else else
error_exit "Error registering account ... $(json_get "$response" detail)"
error_exit "Error registering account ...$responseHeaders ... $(json_get "$response" detail)"
fi fi
# end of registering account with CA # end of registering account with CA
@ -1811,10 +2104,35 @@ info "Verify each domain"
# loop through domains for cert ( from SANS list) # loop through domains for cert ( from SANS list)
if [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then if [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then
alldomains=${SANS//,/ }
alldomains=${SANS//,/ }
else else
alldomains=$(echo "$DOMAIN,$SANS" | sed "s/,/ /g") alldomains=$(echo "$DOMAIN,$SANS" | sed "s/,/ /g")
fi fi
if [[ $API -eq 2 ]]; then
dstring="["
for d in $alldomains; do
dstring="${dstring}{\"type\":\"dns\",\"value\":\"$d\"},"
done
dstring="${dstring: : -1}]"
# request NewOrder currently seems to ignore the dates ....
# dstring="${dstring},\"notBefore\": \"$(date -d "-1 hour" --utc +%FT%TZ)\""
# dstring="${dstring},\"notAfter\": \"$(date -d "2 days" --utc +%FT%TZ)\""
request="{\"identifiers\": $dstring}"
send_signed_request "$URL_newOrder" "$request"
OrderLink=$(echo "$responseHeaders" | grep -i location | awk '{print $2}'| tr -d '\r\n ')
debug "Order link $OrderLink"
FinalizeLink=$(json_get "$response" "finalize")
debug "finalise link $FinalizeLink"
dn=0
for d in $alldomains; do
# get authorizations link
AuthLink[$dn]=$(json_get "$response" "identifiers" "value" "$d" "authorizations" "x")
debug "authorizations link for $d - ${AuthLink[$dn]}"
((dn++))
done
fi
dn=0 dn=0
for d in $alldomains; do for d in $alldomains; do
# $d is domain in current loop, which is number $dn for ACL # $d is domain in current loop, which is number $dn for ACL
@ -1826,13 +2144,17 @@ for d in $alldomains; do
fi fi
# request a challenge token from ACME server # request a challenge token from ACME server
request="{\"resource\":\"new-authz\",\"identifier\":{\"type\":\"dns\",\"value\":\"$d\"}}"
send_signed_request "$URL_new_authz" "$request"
debug "completed send_signed_request"
# check if we got a valid response and token, if not then error exit
if [[ ! -z "$code" ]] && [[ ! "$code" == '201' ]] ; then
error_exit "new-authz error: $response"
if [[ $API -eq 1 ]]; then
request="{\"resource\":\"new-authz\",\"identifier\":{\"type\":\"dns\",\"value\":\"$d\"}}"
send_signed_request "$URL_new_authz" "$request"
debug "completed send_signed_request"
# check if we got a valid response and token, if not then error exit
if [[ ! -z "$code" ]] && [[ ! "$code" == '201' ]] ; then
error_exit "new-authz error: $response"
fi
else
response_status=""
fi fi
if [[ $response_status == "valid" ]]; then if [[ $response_status == "valid" ]]; then
@ -1848,13 +2170,24 @@ for d in $alldomains; do
else else
PREVIOUSLY_VALIDATED="false" PREVIOUSLY_VALIDATED="false"
if [[ $VALIDATE_VIA_DNS == "true" ]]; then # set up the correct DNS token for verification if [[ $VALIDATE_VIA_DNS == "true" ]]; then # set up the correct DNS token for verification
# get the dns component of the ACME response
# get the token from the dns component
token=$(json_get "$response" "token" "dns-01")
debug token "$token"
# get the uri from the dns component
uri=$(json_get "$response" "uri" "dns-01")
debug uri "$uri"
if [[ $API -eq 1 ]]; then
# get the dns component of the ACME response
# get the token from the dns component
token=$(json_get "$response" "token" "dns-01")
debug token "$token"
# get the uri from the dns component
uri=$(json_get "$response" "uri" "dns-01")
debug uri "$uri"
else # APIv2
response=$(curl --silent "${AuthLink[$dn]}" 2>/dev/null)
debug "authlink response = $response"
# get the token from the http-01 component
token=$(json_get "$response" "challenges" "type" "dns-01" "token")
debug token "$token"
# get the uri from the http component
uri=$(json_get "$response" "challenges" "type" "dns-01" "url")
debug uri "$uri"
fi
keyauthorization="$token.$thumbprint" keyauthorization="$token.$thumbprint"
debug keyauthorization "$keyauthorization" debug keyauthorization "$keyauthorization"
@ -1895,12 +2228,23 @@ for d in $alldomains; do
_EOF_ _EOF_
else # set up the correct http token for verification else # set up the correct http token for verification
# get the token from the http component
token=$(json_get "$response" "token" "http-01")
debug token "$token"
# get the uri from the http component
uri=$(json_get "$response" "uri" "http-01")
debug uri "$uri"
if [[ $API -eq 1 ]]; then
# get the token from the http component
token=$(json_get "$response" "token" "http-01")
debug token "$token"
# get the uri from the http component
uri=$(json_get "$response" "uri" "http-01")
debug uri "$uri"
else # APIv2
response=$(curl --silent "${AuthLink[$dn]}" 2>/dev/null)
debug "authlink response = $response"
# get the token from the http-01 component
token=$(json_get "$response" "challenges" "type" "http-01" "token")
debug token "$token"
# get the uri from the http component
uri=$(json_get "$response" "challenges" "type" "http-01" "url")
debug uri "$uri"
fi
#create signed authorization key from token. #create signed authorization key from token.
keyauthorization="$token.$thumbprint" keyauthorization="$token.$thumbprint"
@ -1943,9 +2287,10 @@ for d in $alldomains; do
sshhost=$(echo "${t_loc}"| awk -F: '{print $2}') sshhost=$(echo "${t_loc}"| awk -F: '{print $2}')
command="rm -f ${t_loc:(( ${#sshhost} + 5))}/${token:?}" command="rm -f ${t_loc:(( ${#sshhost} + 5))}/${token:?}"
debug "running following command to remove token" debug "running following command to remove token"
debug "ssh $sshhost ${command}"
debug "ssh $SSH_OPTS $sshhost ${command}"
# shellcheck disable=SC2029 # shellcheck disable=SC2029
ssh "$sshhost" "${command}" 1>/dev/null 2>&1
# shellcheck disable=SC2086
ssh $SSH_OPTS "$sshhost" "${command}" 1>/dev/null 2>&1
rm -f "${TEMP_DIR:?}/${token:?}" rm -f "${TEMP_DIR:?}/${token:?}"
elif [[ "${t_loc:0:4}" == "ftp:" ]] ; then elif [[ "${t_loc:0:4}" == "ftp:" ]] ; then
debug "using ftp to remove token file" debug "using ftp to remove token file"
@ -2053,8 +2398,8 @@ get_certificate "$DOMAIN_DIR/${DOMAIN}.csr" \
"$CA_CERT" "$CA_CERT"
if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then
get_certificate "$DOMAIN_DIR/${DOMAIN}.ec.csr" \ get_certificate "$DOMAIN_DIR/${DOMAIN}.ec.csr" \
"${CERT_FILE::-4}.ec.crt" \
"${CA_CERT::-4}.ec.crt"
"${CERT_FILE%.*}.ec.crt" \
"${CA_CERT%.*}.ec.crt"
fi fi
# create Archive of new certs and keys. # create Archive of new certs and keys.
@ -2063,6 +2408,7 @@ cert_archive
debug "Certificates obtained and archived locally, will now copy to specified locations" debug "Certificates obtained and archived locally, will now copy to specified locations"
# copy certs to the correct location (creating concatenated files as required) # copy certs to the correct location (creating concatenated files as required)
umask 077
copy_file_to_location "domain certificate" "$CERT_FILE" "$DOMAIN_CERT_LOCATION" copy_file_to_location "domain certificate" "$CERT_FILE" "$DOMAIN_CERT_LOCATION"
copy_file_to_location "private key" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LOCATION" copy_file_to_location "private key" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LOCATION"
@ -2070,18 +2416,18 @@ copy_file_to_location "CA certificate" "$CA_CERT" "$CA_CERT_LOCATION"
if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then
if [[ ! -z "$DOMAIN_CERT_LOCATION" ]]; then if [[ ! -z "$DOMAIN_CERT_LOCATION" ]]; then
copy_file_to_location "ec domain certificate" \ copy_file_to_location "ec domain certificate" \
"${CERT_FILE::-4}.ec.crt" \
"${DOMAIN_CERT_LOCATION::-4}.ec.crt"
"${CERT_FILE%.*}.ec.crt" \
"${DOMAIN_CERT_LOCATION%.*}.ec.crt"
fi fi
if [[ ! -z "$DOMAIN_KEY_LOCATION" ]]; then if [[ ! -z "$DOMAIN_KEY_LOCATION" ]]; then
copy_file_to_location "ec private key" \ copy_file_to_location "ec private key" \
"$DOMAIN_DIR/${DOMAIN}.ec.key" \ "$DOMAIN_DIR/${DOMAIN}.ec.key" \
"${DOMAIN_KEY_LOCATION::-4}.ec.key"
"${DOMAIN_KEY_LOCATION%.*}.ec.key"
fi fi
if [[ ! -z "$CA_CERT_LOCATION" ]]; then if [[ ! -z "$CA_CERT_LOCATION" ]]; then
copy_file_to_location "ec CA certificate" \ copy_file_to_location "ec CA certificate" \
"${CA_CERT::-4}.ec.crt" \
"${CA_CERT_LOCATION::-4}.ec.crt"
"${CA_CERT%.*}.ec.crt" \
"${CA_CERT_LOCATION%.*}.ec.crt"
fi fi
fi fi
@ -2095,7 +2441,7 @@ if [[ ! -z "$DOMAIN_CHAIN_LOCATION" ]]; then
cat "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}_chain.pem" cat "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}_chain.pem"
copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem" "$to_location" copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem" "$to_location"
if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then
cat "${CERT_FILE::-4}.ec.crt" "${CA_CERT::-4}.ec.crt" > "$TEMP_DIR/${DOMAIN}_chain.pem.ec"
cat "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_chain.pem.ec"
copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location}.ec" copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location}.ec"
fi fi
fi fi
@ -2109,7 +2455,7 @@ if [[ ! -z "$DOMAIN_KEY_CERT_LOCATION" ]]; then
cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem"
copy_file_to_location "private key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem" "$to_location" copy_file_to_location "private key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem" "$to_location"
if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then
cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE::-4}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec"
cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec"
copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location}.ec" copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location}.ec"
fi fi
fi fi
@ -2123,12 +2469,12 @@ if [[ ! -z "$DOMAIN_PEM_LOCATION" ]]; then
cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem"
copy_file_to_location "full key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem" "$to_location" copy_file_to_location "full key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem" "$to_location"
if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then
cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE::-4}.ec.crt" "${CA_CERT::-4}.ec.crt" > "$TEMP_DIR/${DOMAIN}.pem.ec"
cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}.pem.ec"
copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location}.ec" copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location}.ec"
fi fi
fi fi
# end of copying certs. # end of copying certs.
umask "$ORIG_UMASK"
# Run reload command to restart apache / nginx or whatever system # Run reload command to restart apache / nginx or whatever system
reload_service reload_service


Loading…
Cancel
Save