2
\$\begingroup\$

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}\""
asked Sep 6, 2017 at 2:21
\$\endgroup\$
1

1 Answer 1

2
\$\begingroup\$

This can be written a bit simpler:

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

Like this:

index=
for var; do
 if [ "${index}" ]; then
 PMAP[$index]=${#PARAM[@]}
 PARAM+=("$var")
 index=
 continue
 fi 
 if [ "$var" = --headers ]; then
 index=headers
 continue
 fi 
 ARGS+=("$var")
done

This too:

cr=$((cat - | uniq) <<EOF
...
EOF)

Like this:

cr=$(uniq <<EOF
...
EOF
)

Though in this case I get a warning if I don't break the line after the closing EOF, and in your version I don't. I was surprised your version worked at all, because I thought the here-document must always end with a line containing only the start label and nothing else. To be honest, as a general practice, I would not risk it, and just never put anything on the same line.

answered Sep 6, 2017 at 19:07
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.