exec 3>&-
exec 4>&-
-VERSION="0.6.5"
+. ./json.sh
+
+VERSION="0.6.6"
# Find directory in which this script is stored by traversing all symbolic links
SOURCE="${0}"
fi
# Get CA URLs
- CA_DIRECTORY="$(http_request get "${CA}")"
+ CA_DIRECTORY_RAW="$(http_request get "${CA}")"
+ CA_DIRECTORY="$(printf "%s" "${CA_DIRECTORY_RAW}" | jsonsh)"
# Automatic discovery of API version
if [[ "${API}" = "auto" ]]; then
# Read account information or request from CA if missing
if [[ -e "${ACCOUNT_KEY_JSON}" ]]; then
if [[ ${API} -eq 1 ]]; then
- ACCOUNT_ID="$(cat "${ACCOUNT_KEY_JSON}" | get_json_int_value id)"
+ ACCOUNT_ID="$(cat "${ACCOUNT_KEY_JSON}" | jsonsh | get_json_int_value id)"
ACCOUNT_URL="${CA_REG}/${ACCOUNT_ID}"
else
if [[ -e "${ACCOUNT_ID_JSON}" ]]; then
- ACCOUNT_URL="$(cat "${ACCOUNT_ID_JSON}" | get_json_string_value url)"
+ ACCOUNT_URL="$(cat "${ACCOUNT_ID_JSON}" | jsonsh | get_json_string_value url)"
fi
# if account URL is not storred, fetch it from the CA
if [[ -z "${ACCOUNT_URL:-}" ]]; then
printf -- "$(cat | _sed -e 's/[[:space:]]//g' -e 's/^(.(.{2})*)$/0\1/' -e 's/(.{2})/\\x\1/g')"
}
-# Get string value from json dictionary
-get_json_string_value() {
- local filter
- filter=$(printf 's/.*"%s": *"\([^"]*\)".*/\\1/p' "$1")
- sed -n "${filter}"
-}
-
-# Get array value from json dictionary
-get_json_array_value() {
- local filter
- filter=$(printf 's/.*"%s": *\\[\([^]]*\)\\].*/\\1/p' "$1")
- sed -n "${filter}"
-}
-
-# Get sub-dictionary from json
-get_json_dict_value() {
- local filter
- filter=$(printf 's/.*"%s": *{\([^}]*\)}.*/\\1/p' "$1")
- sed -n "${filter}"
-}
-
-# Get integer value from json
-get_json_int_value() {
- local filter
- filter=$(printf 's/.*"%s": *\([0-9]*\).*/\\1/p' "$1")
- sed -n "${filter}"
-}
-
-rm_json_arrays() {
- local filter
- filter='s/\[[^][]*\]/null/g'
- # remove three levels of nested arrays
- sed -e "${filter}" -e "${filter}" -e "${filter}"
-}
-
# OpenSSL writes to stderr/stdout even when there are no errors. So just
# display the output if the exit code was != 0 to simplify debugging.
_openssl() {
echo " + Requesting new certificate order from CA..."
order_location="$(signed_request "${CA_NEW_ORDER}" '{"identifiers": '"${challenge_identifiers}"'}' 4>&1 | grep -i ^Location: | awk '{print $2}' | tr -d '\r\n')"
- result="$(signed_request "${order_location}" "" | clean_json)"
+ result="$(signed_request "${order_location}" "" | jsonsh)"
- order_authorizations="$(echo ${result} | get_json_array_value authorizations)"
+ order_authorizations="$(echo "${result}" | get_json_array_values authorizations)"
finalize="$(echo "${result}" | get_json_string_value finalize)"
local idx=0
for uri in ${order_authorizations}; do
- authorizations[${idx}]="$(echo "${uri}" | _sed -e 's/\"(.*)".*/\1/')"
+ authorizations[${idx}]="${uri}"
idx=$((idx+1))
done
echo " + Received ${idx} authorizations URLs from the CA"
for authorization in ${authorizations[*]}; do
if [[ "${API}" -eq 2 ]]; then
# Receive authorization ($authorization is authz uri)
- response="$(signed_request "$(echo "${authorization}" | _sed -e 's/\"(.*)".*/\1/')" "" | clean_json)"
+ response="$(signed_request "$(echo "${authorization}" | _sed -e 's/\"(.*)".*/\1/')" "" | jsonsh)"
identifier="$(echo "${response}" | get_json_dict_value identifier | get_json_string_value value)"
echo " + Handling authorization for ${identifier}"
else
# Request new authorization ($authorization is altname)
identifier="${authorization}"
echo " + Requesting authorization for ${identifier}..."
- response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${identifier}"'"}}' | clean_json)"
+ response="$(signed_request "${CA_NEW_AUTHZ}" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${identifier}"'"}}' | jsonsh)"
fi
# Check if authorization has already been validated
fi
# Find challenge in authorization
- challenges="$(echo "${response}" | _sed 's/.*"challenges": \[(\{.*\})\].*/\1/')"
- challenge="$(<<<"${challenges}" _sed -e 's/^[^\[]+\[(.+)\]$/\1/' -e 's/\}(, (\{)|(\]))/}\'$'\n''\2/g' | grep \""${CHALLENGETYPE}"\" || true)"
- if [ -z "${challenge}" ]; then
- allowed_validations="$(grep -Eo '"type": "[^"]+"' <<< "${challenges}" | grep -Eo ' "[^"]+"' | _sed -e 's/"//g' -e 's/^ //g')"
+ challengeindex="$(echo "${response}" | grep -E '^\["challenges",[0-9]+,"type"\]\s+"'"${CHALLENGETYPE}"'"' | cut -d',' -f2 || true)"
+
+ if [ -z "${challengeindex}" ]; then
+ allowed_validations="$(echo "${response}" | grep -E '^\["challenges",[0-9]+,"type"\]' | sed -e 's/\[[^\]*\]\s*//g' -e 's/^"//' -e 's/"$//' | tr '\n' ' ')"
_exiterr "Validating this certificate is not possible using ${CHALLENGETYPE}. Possible validation methods are: ${allowed_validations}"
fi
+ challenge="$(echo "${response}" | get_json_dict_value -p '"challenges",'"${challengeindex}")"
# Gather challenge information
challenge_names[${idx}]="${identifier}"
challenge_tokens[${idx}]="$(echo "${challenge}" | get_json_string_value token)"
+
if [[ ${API} -eq 2 ]]; then
- challenge_uris[${idx}]="$(echo "${challenge}" | _sed 's/"validationRecord": ?\[[^]]+\]//g' | get_json_string_value url)"
+ challenge_uris[${idx}]="$(echo "${challenge}" | get_json_string_value url)"
else
- challenge_uris[${idx}]="$(echo "${challenge}" | _sed 's/"validationRecord": ?\[[^]]+\]//g' | get_json_string_value uri)"
+ challenge_uris[${idx}]="$(echo "${challenge}" | get_json_dict_value validationRecord | get_json_string_value uri)"
fi
# Prepare challenge tokens and deployment parameters
# Ask the acme-server to verify our challenge and wait until it is no longer pending
if [[ ${API} -eq 1 ]]; then
- result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauths[${idx}]}"'"}' | clean_json)"
+ result="$(signed_request "${challenge_uris[${idx}]}" '{"resource": "challenge", "keyAuthorization": "'"${keyauths[${idx}]}"'"}' | jsonsh)"
else
- result="$(signed_request "${challenge_uris[${idx}]}" '{}' | clean_json)"
+ result="$(signed_request "${challenge_uris[${idx}]}" '{}' | jsonsh)"
fi
- reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
+ reqstatus="$(echo "${result}" | get_json_string_value status)"
while [[ "${reqstatus}" = "pending" ]]; do
sleep 1
else
result="$(http_request get "${challenge_uris[${idx}]}")"
fi
- reqstatus="$(printf '%s\n' "${result}" | get_json_string_value status)"
+ reqstatus="$(echo "${result}" | get_json_string_value status)"
done
[[ "${CHALLENGETYPE}" = "http-01" ]] && rm -f "${WELLKNOWN}/${challenge_tokens[${idx}]}"
crt64="$(signed_request "${CA_NEW_CERT}" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | "${OPENSSL}" base64 -e)"
crt="$( printf -- '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----\n' "${crt64}" )"
else
- result="$(signed_request "${finalize}" '{"csr": "'"${csr64}"'"}' | clean_json)"
+ result="$(signed_request "${finalize}" '{"csr": "'"${csr64}"'"}' | jsonsh)"
while :; do
status="$(echo "${result}" | get_json_string_value status)"
case "${status}"
--- /dev/null
+#!/bin/sh
+
+# Generate json.sh path matching string
+json_path() {
+ if [ ! "${1}" = "-p" ]; then
+ printf '"%s"' "${1}"
+ else
+ printf '%s' "${2}"
+ fi
+}
+
+# Get string value from json dictionary
+get_json_string_value() {
+ local filter
+ filter="$(printf 's/.*\[%s\]\s*"\([^"]*\)"/\\1/p' "$(json_path "${1:-}" "${2:-}")")"
+ sed -n "${filter}"
+}
+
+# Get array values from json dictionary
+get_json_array_values() {
+ grep -E '^\["'"$1"'",[0-9]*\]' | sed -e 's/\[[^\]*\]\s*//g' -e 's/^"//' -e 's/"$//'
+}
+
+# Get sub-dictionary from json
+get_json_dict_value() {
+ local filter
+ echo "$(json_path "${1:-}" "${2:-}")"
+ filter="$(printf 's/.*\[%s\]\s*\(.*\)/\\1/p' "$(json_path "${1:-}" "${2:-}")")"
+ sed -n "${filter}" | jsonsh
+}
+
+# Get integer value from json
+get_json_int_value() {
+ local filter
+ filter="$(printf 's/.*\[%s\]\s*\([^"]*\)/\\1/p' "$(json_path "${1:-}" "${2:-}")")"
+ sed -n "${filter}"
+}
+
+jsonsh() {
+ # Modified from https://github.com/dominictarr/JSON.sh
+ # Original Copyright (c) 2011 Dominic Tarr
+ # Licensed under The MIT License
+
+ throw() {
+ echo "$*" >&2
+ exit 1
+ }
+
+ awk_egrep () {
+ local pattern_string=$1
+
+ gawk '{
+ while ($0) {
+ start=match($0, pattern);
+ token=substr($0, start, RLENGTH);
+ print token;
+ $0=substr($0, start+RLENGTH);
+ }
+ }' pattern="$pattern_string"
+ }
+
+ tokenize () {
+ local GREP
+ local ESCAPE
+ local CHAR
+
+ if echo "test string" | egrep -ao --color=never "test" >/dev/null 2>&1
+ then
+ GREP='egrep -ao --color=never'
+ else
+ GREP='egrep -ao'
+ fi
+
+ if echo "test string" | egrep -o "test" >/dev/null 2>&1
+ then
+ ESCAPE='(\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
+ CHAR='[^[:cntrl:]"\\]'
+ else
+ GREP=awk_egrep
+ ESCAPE='(\\\\[^u[:cntrl:]]|\\u[0-9a-fA-F]{4})'
+ CHAR='[^[:cntrl:]"\\\\]'
+ fi
+
+ local STRING="\"$CHAR*($ESCAPE$CHAR*)*\""
+ local NUMBER='-?(0|[1-9][0-9]*)([.][0-9]*)?([eE][+-]?[0-9]*)?'
+ local KEYWORD='null|false|true'
+ local SPACE='[[:space:]]+'
+
+ # Force zsh to expand $A into multiple words
+ local is_wordsplit_disabled=$(unsetopt 2>/dev/null | grep -c '^shwordsplit$')
+ if [ $is_wordsplit_disabled != 0 ]; then setopt shwordsplit; fi
+ $GREP "$STRING|$NUMBER|$KEYWORD|$SPACE|." | egrep -v "^$SPACE$"
+ if [ $is_wordsplit_disabled != 0 ]; then unsetopt shwordsplit; fi
+ }
+
+ parse_array () {
+ local index=0
+ local ary=''
+ read -r token
+ case "$token" in
+ ']') ;;
+ *)
+ while :
+ do
+ parse_value "$1" "$index"
+ index=$((index+1))
+ ary="$ary""$value"
+ read -r token
+ case "$token" in
+ ']') break ;;
+ ',') ary="$ary," ;;
+ *) throw "EXPECTED , or ] GOT ${token:-EOF}" ;;
+ esac
+ read -r token
+ done
+ ;;
+ esac
+ value=$(printf '[%s]' "$ary") || value=
+ :
+ }
+
+ parse_object () {
+ local key
+ local obj=''
+ read -r token
+ case "$token" in
+ '}') ;;
+ *)
+ while :
+ do
+ case "$token" in
+ '"'*'"') key=$token ;;
+ *) throw "EXPECTED string GOT ${token:-EOF}" ;;
+ esac
+ read -r token
+ case "$token" in
+ ':') ;;
+ *) throw "EXPECTED : GOT ${token:-EOF}" ;;
+ esac
+ read -r token
+ parse_value "$1" "$key"
+ obj="$obj$key:$value"
+ read -r token
+ case "$token" in
+ '}') break ;;
+ ',') obj="$obj," ;;
+ *) throw "EXPECTED , or } GOT ${token:-EOF}" ;;
+ esac
+ read -r token
+ done
+ ;;
+ esac
+ value=$(printf '{%s}' "$obj") || value=
+ :
+ }
+
+ parse_value () {
+ local jpath="${1:+$1,}${2:-}" isleaf=0 isempty=0 print=0
+ case "$token" in
+ '{') parse_object "$jpath" ;;
+ '[') parse_array "$jpath" ;;
+ # At this point, the only valid single-character tokens are digits.
+ ''|[!0-9]) throw "EXPECTED value GOT ${token:-EOF}" ;;
+ *) value=$token
+ # replace solidus ("\/") in json strings with normalized value: "/"
+ value=$(echo "$value" | sed 's#\\/#/#g')
+ isleaf=1
+ [ "$value" = '""' ] && isempty=1
+ ;;
+ esac
+ [ "$value" = '' ] && return
+ [ -z "$jpath" ] && return # do not print head
+
+ printf "[%s]\t%s\n" "$jpath" "$value"
+ :
+ }
+
+ parse () {
+ read -r token
+ parse_value
+ read -r token
+ case "$token" in
+ '') ;;
+ *) throw "EXPECTED EOF GOT $token" ;;
+ esac
+ }
+
+ tokenize | parse
+}
+# vi: expandtab sw=2 ts=2