Amazon Web Services Signature Version 4 request signing in Nim
For AWS APIs in Nim, see https://github.com/disruptek/atoz
The request signing process is documented at https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html and most of the procedures in this code should be identifiable in that documentation.
By default, we use https://github.com/jangko/nimSHA2 for SHA256/SHA512 routines.
If you already have a dependency on NimCrypto, you can use that instead by
passing --define:sigv4UseNimCrypto
to the compiler.
$ nimph clone disruptek/sigv4
or if you think package managers are stupid,
$ git clone https://github.com/disruptek/sigv4
$ echo '--path="$config/sigv4/"' >> nim.cfg
or if you're still using Nimble like it's 2012,
$ nimble install https://github.com/disruptek/sigv4
import json
import httpcore
import sigv4
let
# the URL of the request
url = "https://iam.amazonaws.com/?Action=ListUsers&Version=2010-05-08"
# an AWS Secret Key
secret = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
# the body of the request; eg. POST content
payload = ""
# the AWS region against which you are querying
region = "us-east-1"
# the short name of the service as you might find in, say, an ARN
service = "iam"
# an enum representing the signing algorithm, eg. SHA256 or SHA512
digest = SHA256
# an ISO8601 date string attached to the request
date = makeDateTime()
# a JsonNode holding the query string key/value pairs, as provided by the stdlib
query = %* {
"Action": "ListUsers",
"Version": "2010-05-08",
}
# http headers as provided by the stdlib
headers = newHttpHeaders(@[
("Host", "iam.amazonaws.com"),
("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"),
("X-Amz-Date", date),
])
# compose a credential scope
scope = credentialScope(region=region, service=service, date=date)
# compose the canonical request
request = canonicalRequest(HttpGet, url, query, headers, payload, digest=digest)
# use the request and scope to compose a string-to-sign
sts = stringToSign(request.hash(digest), scope, date=date, digest=digest)
# calculate the signature for the request using a secret key
signature = calculateSignature(secret=secret, date=date, region=region,
service=service, tosign=sts, digest=digest)
assert signature == "5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"
let
host = "my-bucket.s3-eu-west-1.amazonaws.com"
url = "https://" & host & "/2021/my-image.jpg"
region = "eu-west-1"
service = "s3"
secretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"
accessKey = "AKIAQ0BPAG50Q8KFTR7F"
#token = ""
payload = ""
digest = SHA256
expireSec = "60"
datetime = "20210330T032054Z" #makeDateTime()
scope = credentialScope(region=region, service=service, date=datetime)
query = %* {
"Action": "GetObject",
"X-Amz-Algorithm": $SHA256,
"X-Amz-Credential": accessKey & "/" & scope,
"X-Amz-Date": datetime,
"X-Amz-Expires": expireSec,
# "X-Amz-Security-Token": token,
"X-Amz-SignedHeaders": "host"
}
headers = newHttpHeaders(@[
("Host", host)
])
request = canonicalRequest(HttpGet, url, query, headers, payload, digest=UnsignedPayload)
sts = stringToSign(request.hash(digest), scope, date=datetime, digest=digest)
signature = calculateSignature(secret=secretKey, date=datetime, region=region,
service=service, tosign=sts, digest=digest)
finalUrl = url & "?" & request.split("\n")[2] & "&X-Amz-Signature=" & signature
assert finalUrl == "https://my-bucket.s3-eu-west-1.amazonaws.com/2021/my-image.jpg?Action=GetObject&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAQ0BPAG50Q8KFTR7F%2F20210330%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20210330T032054Z&X-Amz-Expires=60&X-Amz-SignedHeaders=host&X-Amz-Signature=e4506eac1665d53c658a3229118067a3f01bc00ee8ab3c8f9708b789ca5c9673"
See the documentation for the sigv4 module as generated directly from the source.
The tests use example values from the AWS documentation as above.
$ nimble test
MIT