aws s3の署名バージョン「4」をcurlコマンドでリクエストする

## 前提

- s3の署名バージョン「２」が2019年6月24日で廃止になる
- curlで直接s3のエンドポイントを叩く場合は、自分で署名リクエストを作る必要がある

aws公式ドキュメントに疑似コードが乗っているので、それに沿って`bash`で実装していきます。

https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-canonical-request.html

## 署名のプロセス

1. 正規リクエストを作成します。
2. 正規リクエストと追加のメタデータを使用して、署名の文字列を作成します。
3. AWS シークレットアクセスキーから署名キーを取得します。次に、署名キーと、前の手順で準備した文字列を使用して、署名を作成します。
4. 作成した署名をヘッダーの HTTP リクエストに追加します

awsがリクエストを受診したあと、リクエストヘッダーに含まれているアクセスキー（シークレットではない方）を利用して上記の手順をもう一度実施して署名を作成します。その署名がリクエストに含まれている署名と比較して一致しているかが検証されます。

## 1. 正規リクエストを作成

こんな感じの文字列を作成する必要があります。

```
PUT
/test/test.txt #対象URI

content-type:text/plain
host:${bucket}.s3.amazonaws.com
x-amz-content-sha256:c911d4fcad1f5d53f3d74324cf57dc254a16182ee5662b8872ebe6159f6b3ce3 #アップロード対象ファイルのハッシュ
x-amz-date:20190529T165716Z  #Dateの形式は %Y%m%dT%H%M%SZ

content-type;host;x-amz-content-sha256;x-amz-date #headerの一覧
c911d4fcad1f5d53f3d74324cf57dc254a16182ee5662b8872ebe6159f6b3ce3" #アップロード対象ファイルのハッシュ
```

bashで書くとこんな感じ。最後にハッシュ化も行っています。

```sh
bucket=test-bucket
dateValueShort=$(date -u +'%Y%m%d')
dateValueLong=$(date -u +'%Y%m%dT%H%M%SZ')
fileLocal=test.txt
headerList='content-type;host;x-amz-content-sha256;x-amz-date'
#アップロード対象ファイルをハッシュする
payloadHash=$(openssl dgst -sha256 -hex < "${fileLocal}" 2>/dev/null | sed 's/^.* //')
canonicalRequest="\
PUT
/test/test.txt　

content-type:text/plain 
host:${bucket}.s3.amazonaws.com
x-amz-content-sha256:${payloadHash} 
x-amz-date:${dateValueLong}

${headerList}
${payloadHash}"

canonicalRequestHash=$(printf '%s' "${canonicalRequest}" | openssl dgst -sha256 -hex 2>/dev/null | sed 's/^.* //')
```

## 2. 正規リクエストと追加のメタデータを使用して、署名の文字列を作成

こんな感じの文字列を作成する必要があります

```
AWS4-HMAC-SHA256
20190529T171500Z   #Dateの形式は %Y%m%d
20190529           #Dateの形式は %Y%m%dT%H%M%SZ
20190529/ap-northeast-1/s3/aws4_request 
c911d4fcad1f5d53f3d74324cf57dc254a16182ee5662b8872ebe6159f6b3ce3 #上記で取得した正規リクエストのハッシュ
```

bashで書くとこんな感じ。

```sh
dateValueShort=$(date -u +'%Y%m%d')
dateValueLong=$(date -u +'%Y%m%dT%H%M%SZ')
region=ap-northeast-1

stringToSign="\
AWS4-HMAC-SHA256
${dateValueLong}
${dateValueShort}/${region}/s3/aws4_request 
${canonicalRequestHash}"
```

## 3. AWS シークレットアクセスキーから署名キーを取得します。次に、署名キーと、前の手順で準備した文字列を使用して、署名を作成


こんな感じの処理をする必要があるとのこと。AWSシークレットキーを起点にしてハッシュにしたものをさらにハッシュにしていく処理です。

```
kSecret = your secret access key
kDate = HMAC("AWS4" + kSecret, Date)
kRegion = HMAC(kDate, Region)
kService = HMAC(kRegion, Service)
kSigning = HMAC(kService, "aws4_request")
```

bashで書くとこんな感じ。

```sh
awsSecret=
dateValueShort=$(date -u +'%Y%m%d')
kSecret="AWS4${awsSecret}"
region=ap-northeast-1
kDate=$(printf         '%s' "${dateValueShort}" | openssl dgst -sha256 -hex -mac HMAC -macopt "key:${kSecret}"     2>/dev/null | sed 's/^.* //')
kRegion=$(printf       '%s' "${region}"         | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kDate}"    2>/dev/null | sed 's/^.* //')
kService=$(printf      "s3"                     | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kRegion}"  2>/dev/null | sed 's/^.* //')
kSigning=$(printf      "aws4_request"           | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kService}" 2>/dev/null | sed 's/^.* //')
signature=$(printf     '%s' "${stringToSign}"   | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kSigning}" 2>/dev/null | sed 's/^.* //')
```

## 4. 作成した署名をヘッダーの HTTP リクエストに追加

headerとともにcurlコマンド。

```sh
curl -I -L --proto-redir =https -X PUT -T "${fileLocal}" \
  -H "Content-Type: text/plain" \
  -H "Host: ${bucket}.s3.amazonaws.com" \
  -H "X-Amz-Content-SHA256: ${payloadHash}" \
  -H "X-Amz-Date: ${dateValueLong}" \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=${awsAccess}/${dateValueShort}/ap-northeast-1/s3/aws4_request, SignedHeaders=${headerList}, Signature=${signature}" \
  "https://${bucket}.s3.amazonaws.com/test/test.txt"
```

以下のレスポンスが返って来ればOKです

```
HTTP/1.1 200 OK
x-amz-id-2: hLP5G3rE9Ph0OACv7v4ZsxvPZwj/0N8PuGdR7ns8XO8gl9gKN2aSeuTg+9KxF+vUY2QnDF8t4hA=
x-amz-request-id: BDFF6DF613BA53EA
Date: Wed, 29 May 2019 18:55:58 GMT
ETag: "d8e8fca2dc0f896fd7cb4cb0031ba249"
Content-Length: 0
Server: AmazonS3
```

## はまったところ

正規リクエストに半角スペースが含まれていたりすると、`he request signature we calculated does not match the signature you provided. Check your key and signing method.`が出ます。しかもデバッグもできないので、ハマると結構キツいです。

## 全体のコード

```sh
#!/bin/bash -u

awsAccess=
awsSecret=
bucket=
fileLocal=test.txt
fileRemote=/test/test.txt
region=ap-northeast-1
dateValueShort=$(date -u +'%Y%m%d')
dateValueLong=$(date -u +'%Y%m%dT%H%M%SZ')
headerList='content-type;host;x-amz-content-sha256;x-amz-date'
#アップロード対象ファイルをハッシュする
payloadHash=$(openssl dgst -sha256 -hex < "${fileLocal}" 2>/dev/null | sed 's/^.* //')
#正規リクエストの作成
canonicalRequest="\
PUT
${fileRemote}

content-type:text/plain
host:${bucket}.s3.amazonaws.com
x-amz-content-sha256:${payloadHash}
x-amz-date:${dateValueLong}

${headerList}
${payloadHash}"

canonicalRequestHash=$(printf '%s' "${canonicalRequest}" | openssl dgst -sha256 -hex 2>/dev/null | sed 's/^.* //')

#署名の文字列を作成
stringToSign="\
AWS4-HMAC-SHA256
${dateValueLong}
${dateValueShort}/${region}/s3/aws4_request
${canonicalRequestHash}"

#署名の作成
kSecret="AWS4${awsSecret}"
kDate=$(printf         '%s' "${dateValueShort}" | openssl dgst -sha256 -hex -mac HMAC -macopt "key:${kSecret}"     2>/dev/null | sed 's/^.* //')
kRegion=$(printf       '%s' "${region}"         | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kDate}"    2>/dev/null | sed 's/^.* //')
kService=$(printf      "s3"                     | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kRegion}"  2>/dev/null | sed 's/^.* //')
kSigning=$(printf      "aws4_request"           | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kService}" 2>/dev/null | sed 's/^.* //')
signature=$(printf     '%s' "${stringToSign}"   | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kSigning}" 2>/dev/null | sed 's/^.* //')

#http request
curl -I -L --proto-redir =https -X PUT -T "${fileLocal}" \
  -H "Content-Type: text/plain" \
  -H "Host: ${bucket}.s3.amazonaws.com" \
  -H "X-Amz-Content-SHA256: ${payloadHash}" \
  -H "X-Amz-Date: ${dateValueLong}" \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=${awsAccess}/${dateValueShort}/${region}/s3/aws4_request, SignedHeaders=${headerList}, Signature=${signature}" \
  "https://${bucket}.s3.amazonaws.com${fileRemote}"
```