2
\$\begingroup\$

This is a follow on to a previous question:

Part 1: Signature

All the code I am submitting here is available in github

The reason to write these tools is so I can prototype and quickly verify that I am doing things correctly before implementing them in C++. This is mainly necessary because the documentation these scripts are based on are relatively bad and confusing.

s3/signature

#!/usr/bin/env bash

check=$(echo -n s3 | openssl dgst -sha256 -binary -mac HMAC -macopt hexkey:01dda345af12 2> /dev/null | xxd -p -c 256)
if [[ "${check}" != "6d00e194dca4f102300b638bb3404a1c9b8c1ba73514d22a7013f9832c20366a" ]]; then
    echo "Check of openssl failed"
    echo "You need a verions greater than 0"
    exit 1
fi


#
#   method
#   url
#   key
#   secret
#   dateTime        if empty use now utc
#   expires
#   service         if blank extracts from the URL
#   region          if blank extracts from the URL
#   signHeaders     if 1 creates headers for signing otherwise creates query parameters
#   verbose         if blank (empty or just white space) then no extra output.
#                   otherwise it is a filename to output (note - represents stdout)
#   headers[@]

function createSignature {

    local saveIFS=${IFS}

    local method=$1
    local url=$2
    local key=$3
    local secret=$4
    local dateTime=$(echo $5 | xargs)
    local expires=$6
    local service=$7
    local region=$8
    local signHeaders=$9
    local verbose=$(echo ${10} | xargs)
    local headers=("${!11}")

    if [[ "${dateTime}" == "" ]]; then
        dateTime=$(date -u +"%Y%m%dT%H%M%SZ")
    fi

    #
    # The First part of <dateTime> before the T
    local 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
    local urlNoSchema=${url#https://}
    local host=${urlNoSchema%%/*}
    local serviceRegion=${host%%\.*}
    local service=${service-${serviceRegion%%-*}}
    local region=${region-${serviceRegion#*-}}
    local pathAndQueryFrag=/${url#https://*/}
    local pathAndQuery=${pathAndQueryFrag%%#*}
    local path=${pathAndQuery%%\?*}
    local queryItems=${pathAndQuery##*\?}
    if [[ "${queryItems}" == "${pathAndQuery}" ]]; then
        queryItems=""
    fi
    local fragment=${pathAndQueryFrag##*#}
    if [[ "${fragment}" == "${pathAndQueryFrag}" ]]; then
        fragment=""
    else
        fragment="#${fragment}"
    fi
    local file=${path##*/}

    local -a querys
    for loop in $(echo ${queryItems} | sed -e 's/&/ /'); do
        querys+=($loop)
    done

    headers+=("Host:${host}")
    if [[ "${signHeaders}" == "1" ]]; then
        headers+=("X-Amz-Date:${dateTime}")
        headers+=("X-Amz-Content-SHA256:UNSIGNED-PAYLOAD")
    fi

    local crheaders=()
    local headerExtra=""
    local headerList=""
    for loop in "${headers[@]}"; do
        local name=$(tr '[A-Z]' '[a-z]' <<< "${loop%:*}")
        crheaders+=("${name}:${loop#*:}")
    done
    IFS=$'\n' sortedHeaders=($(sort <<<"${crheaders[*]}"))
    for loop in "${sortedHeaders[@]}"; do
        local name=${loop%:*}
        headerExtra+="%3B${name}"
        headerList+=";${name}"
    done

    if [[ "${signHeaders}" != "1" ]]; then
        querys+=("X-Amz-Algorithm=AWS4-HMAC-SHA256")
        querys+=("X-Amz-Credential=${key}%2F${date}%2F${region}%2F${service}%2Faws4_request")
        querys+=("X-Amz-Date=${dateTime}")
        querys+=("X-Amz-Expires=${expires}")
        querys+=("X-Amz-SignedHeaders=${headerExtra#%3B}")
    fi

    IFS=$'\n' sortedQuerys=($(sort <<<"${querys[*]}"))
    local queryParam=""
    local querySep='?'
    for loop in "${sortedQuerys[@]}"; do
        queryParam+="${querySep}${loop}"
        querySep='&'
    done

    #
    # Build the canonical request
    IFS=$'\n'
    cr=$(uniq <<CanonicalRequest
${method}
${path}
${queryParam#?}
${sortedHeaders[*]}

${headerList#;}
UNSIGNED-PAYLOAD
CanonicalRequest
    )
    IFS=${saveIFS}

    #
    # Hash the canonical request
    local HashedCanonicalRequest=$(echo -n "${cr}" | openssl dgst -sha256 -binary | xxd -p -c 256)

    #
    # Build the String to sign.
    local ss=$(cat - <<StringToSign
AWS4-HMAC-SHA256
${dateTime}
${date}/${region}/${service}/aws4_request
${HashedCanonicalRequest}
StringToSign
    )

    #
    # Calculate the signature
    local kDate=$(echo -n ${date}        | openssl dgst -sha256 -binary -hmac "AWS4${secret}" | xxd -p -c 256)
    local kRegn=$(echo -n ${region}      | openssl dgst -sha256 -binary -mac HMAC -macopt "hexkey:${kDate}" | xxd -p -c 256)
    local kServ=$(echo -n ${service}     | openssl dgst -sha256 -binary -mac HMAC -macopt "hexkey:${kRegn}" | xxd -p -c 256)
    local kSign=$(echo -n "aws4_request" | openssl dgst -sha256 -binary -mac HMAC -macopt "hexkey:${kServ}" | xxd -p -c 256)
    local signature=$(echo -n "${ss}"    | openssl dgst -sha256 -binary -mac HMAC -macopt "hexkey:${kSign}" | xxd -p -c 256)

    if [[ "${verbose}" != "" ]]; then
        cat - <<IntermediateValues > ${verbose}
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:                    ${kDate}
kRegn:                    ${kRegn}
kServ:                    ${kServ}
kSign:                    ${kSign}
signature:                ${signature}


Cononical Request:
==================
${cr}

Signing String:
===============
${ss}

Signed URL:
===========
https://${host}${path}${queryParam}${querySep}X-Amz-Signature=${signature}${fragment}

Headers:
========
IntermediateValues

        printf "%s\n" ${headers[@]}                 >> ${verbose}
        echo                                        >> ${verbose}

        echo "CURL COMMAND:"                        >> ${verbose}
        echo "============="                        >> ${verbose}

        echo -n "curl -o ${file} "                  >> ${verbose}
        for loop in "${headers[@]}"; do
            if [[ ${loop%:*} != "Host" ]]; then
                echo -n "--header \"${loop}\" "     >> ${verbose}
            fi
        done
        if [[ "${signHeaders}" == "1" ]]; then
            echo "--header \"Authorization: AWS4-HMAC-SHA256 Credential=${key}/${date}/${region}/${service}/aws4_request, SignedHeaders=${headerList#;}, Signature=${signature}\" \"https://${host}${path}${queryParam}${fragment}\""   >> ${verbose}
        else
            echo "\"https://${host}${path}${queryParam}${querySep}X-Amz-Signature=${signature}${fragment}\"" >> ${verbose}
        fi
        echo                                        >> ${verbose}
        echo "OUTPUT"                               >> ${verbose}
        echo "======"                               >> ${verbose}
    fi

    if [[ "${signHeaders}" == "1" ]]; then
        echo "Authorization: AWS4-HMAC-SHA256 Credential=${key}/${date}/${region}/${service}/aws4_request, SignedHeaders=${headerList#;}, Signature=${signature}"
    else
        echo "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=${headerExtra#%3B}&X-Amz-Signature=${signature}"
    fi

}
\$\endgroup\$

0

Your Answer

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

Browse other questions tagged or ask your own question.