Follow up on previous review: Sign an EC2/S3 URL
I have fixed the previous comments. I have also added the code needed to sign HTTP header fields as part of the request.
#!/usr/bin/env bash
url=$1
key=$2
secret=$3
if [[ $# < 3 || $# > 6 ]]; then
echo "Usage:"
echo " sign <url> <key> <secret> [<TimeStamp>] [--headers <fileName>]"
echo
echo "Note 1: TimeStamp: must be YYYYMMDD'T'hhmmsa'Z's"
echo " eg 20170901T230559Z"
echo " YYYY => Year MM => Month DD => day hh => hour mm => minute ss => second"
echo
echo "Note 2: Currently does not support URL with Query or Fragment sections."
exit 1
fi
#
# Pull flags out of the argument list
declare -a ARGS
declare -a PARAM
declare -a PMAP
index=""
for var in "$@"; do
if [[ "${index}" != "" ]]; then
PMAP[$index]=${#PARAM[@]}
PARAM[${#PARAM[@]}]=$var
index=""
continue
fi
if [[ "$var" = '--headers' ]]; then
index=headers
continue
fi
ARGS[${#ARGS[@]}]="$var"
done
dateTime=${ARGS[3]-$(date -u +"%Y%m%dT%H%M%SZ")}
#
# Read the list of headers.
# Create the appropriate variables for the HERE Document below
if [[ ${PARAM[headers]} ]]; then
file=${PARAM[headers]}
IFS=$'\n' read -d '' -r -a headers < ${file}
crheaders=()
headerExtra=""
prev="host"
headerList=""
for loop in "${headers[@]}"; do
name=$(tr '[A-Z]' '[a-z]' <<< "${loop%:*}")
crheaders+=("${name}:${loop#*:}")
headerExtra+="%3B${name}"
headerList+=";${name}"
if [[ "${name}" < "${prev}" ]]; then
echo "Headers Not in Lexographical Order"
echo "$name < ${prev}"
exit 1
fi
prev=${name}
done
fi
#
# The First part of <dateTime> before the T
date=${dateTime%%T*}
#
# Amazon Hosted URLS are built up in sections.
# http://<service>-<region>.<Amazon End Point><Path>
#
# Strip out these parts from the url
urlNoSchema=${url#https://}
host=${urlNoSchema%%/*}
serviceRegion=${host%%\.*}
service=${serviceRegion%%-*}
region=${serviceRegion#*-}
path=/${url#https://*/}
file=${path##*/}
expires=3600
#
# Build the canonical request
# Note: If crheaders is empty we get an extra blank line.
# So run through uniq to remove the blank.
IFS=$'\n'
cr=$((cat - | uniq) <<CanonicalRequest
GET
${path}
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${key}%2F${date}%2F${region}%2F${service}%2Faws4_request&X-Amz-Date=${dateTime}&X-Amz-Expires=${expires}&X-Amz-SignedHeaders=host${headerExtra}
host:${host}
${crheaders[*]}
host${headerList}
UNSIGNED-PAYLOAD
CanonicalRequest)
#
# Hash the canonical request
HashedCanonicalRequest=$(echo -n "${cr}" | openssl dgst -sha256)
#
# Build the String to sign.
ss=$(cat - <<StringToSign
AWS4-HMAC-SHA256
${dateTime}
${date}/${region}/${service}/aws4_request
${HashedCanonicalRequest}
StringToSign)
#
# Calculate the signature
kDate=$(echo -n ${date} | openssl dgst -sha256 -binary -hmac "AWS4${secret}")
kRegn=$(echo -n ${region} | openssl dgst -sha256 -binary -hmac "${kDate}")
kServ=$(echo -n ${service} | openssl dgst -sha256 -binary -hmac "${kRegn}")
kSign=$(echo -n "aws4_request" | openssl dgst -sha256 -binary -hmac "${kServ}")
signature=$(echo -n "${ss}" | openssl dgst -sha256 -hmac "${kSign}")
#
# Dump intermediate values to compare against language specific implementation.
kDateH=$(echo -n ${date} | openssl dgst -sha256 -hmac "AWS4${secret}")
kRegnH=$(echo -n ${region} | openssl dgst -sha256 -hmac "${kDate}")
kServH=$(echo -n ${service} | openssl dgst -sha256 -hmac "${kRegn}")
kSignH=$(echo -n "aws4_request" | openssl dgst -sha256 -hmac "${kServ}")
cat - <<IntermediateValues
Intermediate Values
url: ${url}
key: ${key}
secret: ${secret}
dateTime: ${dateTime}
date: ${date}
host: ${host}
path: ${path}
service: ${service}
region: ${region}
expires: ${expires}
HashedCanonicalRequest: ${HashedCanonicalRequest}
kDate: ${kDateH}
kRegn: ${kRegnH}
kServ: ${kServH}
kSign: ${kSignH}
signature: ${signature}
Cononical Request:
==================
${cr}
Signing String:
===============
${ss}
Signed URL:
===========
${url}?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${key}%2F${date}%2F${region}%2F${service}%2Faws4_request&X-Amz-Date=${dateTime}&X-Amz-Expires=${expires}&X-Amz-SignedHeaders=host;x-amx-extrastuff;x-amz-stuff&X-Amz-Signature=${signature}
CURL COMMAND:
=============
IntermediateValues
echo -n "curl -o ${file} "
for loop in "${headers[@]}"; do
echo -n "--header \"${loop}\" "
done
echo "\"${url}?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${key}%2F${date}%2F${region}%2F${service}%2Faws4_request&X-Amz-Date=${dateTime}&X-Amz-Expires=${expires}&X-Amz-SignedHeaders=host;x-amx-extrastuff;x-amz-stuff&X-Amz-Signature=${signature}\""