Skip to content

Instantly share code, notes, and snippets.

@Gooseus
Created October 28, 2017 22:42
Show Gist options
  • Save Gooseus/460ccc1b7d4551d7beba75ffc286cef1 to your computer and use it in GitHub Desktop.
Save Gooseus/460ccc1b7d4551d7beba75ffc286cef1 to your computer and use it in GitHub Desktop.
Nim AWS Request/Sigv4
#[
# AWS SDK Core Library
A core library for an ambitious attempt at a Nim AWS SDK.
]#
import os, times, math
import strutils except toLower
import sequtils, algorithm, tables, nimSHA2, unicode, uri
import httpclient, asyncdispatch, asyncfile
import securehash, hmac, base64
import sigv4
export sigv4.AWSCredentials, sigv4.AWSCredentialScope
const iso_8601_aws = "yyyyMMdd'T'HHmmss'Z'"
proc getAmzDateString*():string=
return format(getGMTime(getTime()), iso_8601_aws)
proc aws_request*(scope: var AWSCredentialScope, params: Table):Future[AsyncResponse]=
var client = newAsyncHttpClient("aws-sdk-nim/0.0.1; "&defUserAgent.replace(" ","-").toLower&"; darwin/16.7.0")
let
url = ("https://$1.amazonaws.com/" % scope.service ) & params["uri"]
req = (params["action"], url, params["payload"])
scope.signed_key = create_aws_authorization(req, client.headers.table, scope)
scope.expires = getGmTime(getTime()) + initInterval(days=7)
return client.request(url,params["action"],params["payload"])
import tables, times, math, asyncdispatch, httpclient
import awscore
let t0 = epochTime()
proc dt() : float =
round(epochTime() - t0,6)
var credentials : AWSCredentials = ("AWS_ACCESS_ID", "AWS_ACCESS_SECRET")
var scope : AWSCredentialScope = AWSCredentialScope(credentials:credentials, date: getAmzDateString(), region: "us-east-1", service: "s3", signed_key: "", expires: getGMTime(getTime()))
let bucket = "BUCKET-NAME"
let obj_path = "OBJECT_PATH"
let params = {
"uri": (bucket & "/" & obj_path),
"action": "GET",
"payload": ""
}.toTable
try:
let res = waitFor aws_request(scope,params)
echo waitFor res.body
except HttpRequestError:
echo "http request error: "
echo getCurrentExceptionMsg()
except:
echo "unknown request error: "
echo getCurrentExceptionMsg()
echo "Transfer Complete ", dt()
#[
# AWS SignatureV4 Authorization Library
]#
import os, times, math
import strutils except toLower
import sequtils, algorithm, tables, nimSHA2, unicode, uri
import securehash, hmac, base64, re
type
AWSCredentials* = tuple
id: string
secret: string
type
AWSCredentialScope* = object
credentials*: AWSCredentials
date*: string
region*: string
service*: string
signed_key*: string
expires*: TimeInfo
const
hash_algorithm = "AWS4-HMAC-SHA256"
termination_string = "aws4_request"
# Copied from cgi library and modified to fit the AWS-approved uri_encode
proc uri_encode(s:string, encSlash:bool):string=
result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars
for i in 0..s.len-1:
case s[i]
of 'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~': add(result, s[i])
of '/':
if encSlash: add(result, "%2F")
else: add(result,s[i])
else:
add(result, '%')
add(result, toHex(ord(s[i]), 2).toUpperASCII)
# Overloading so we can use in map without an error
proc uri_encode(s:string):string=
return uri_encode(s,true)
proc trim_headers(x:string):string=
return strip(x).replace(re"\s+"," ")
proc create_canonical_path(path:string):string=uri_encode(path,false)
proc create_canonical_qs(query:string):string=
if query.len<1:return query
var qs = query.split("&")
sort(qs, cmp[string])
qs = qs.map(proc (x:string):string=x.split("=").map(uri_encode).join("="))
result = qs.join("&")
proc create_canonical_and_signed_headers(headers:TableRef):(string,string)=
var canonical = ""
var signed = ""
var heads : seq[string] = @[]
var lhead = {"host": heads }.newTable
for k in keys(headers):
lhead[k.toLower()] = headers[k]
heads.add(k.toLower())
sort(heads, cmp[string])
signed = heads.join(";")
var val = ""
for name in heads:
val = lhead[name].map(trim_headers).join("")
canonical &= name & ":" & val
canonical &= "\n"
return (canonical,signed)
# create create scope string
proc create_credential_scope*(date,region,service:string, termination:string=termination_string) : string =
result = date[0..7]&"/"
result &= region&"/"
result &= service&"/"
result &= termination
# http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
proc create_canonical_request(action:string="GET", canonical_uri:string="/", canonical_qs:string="", canonical_headers: string="", signed_headers:string="", hashed_payload: string="") : string =
result = action & "\n"
result &= canonical_uri & "\n"
result &= canonical_qs & "\n"
result &= canonical_headers & "\n"
result &= signed_headers & "\n"
result &= hashed_payload
# overloaded
proc create_canonical_request*(headers: var TableRef, meth: string, url: string, payload: string="", unsignedPayload:bool=true, reqContentSha:bool=true): (string,string)=
let
uri_obj = parseUri(url)
can_uri = create_canonical_path(uri_obj.path)
can_qs = create_canonical_qs(uri_obj.query)
var hashed_payload = "UNSIGNED-PAYLOAD"
if payload.len>0 or not unsignedPayload:
hashed_payload = toLowerASCII(hex(computeSHA256(payload)))
headers["Host"] = @[uri_obj.hostname]
if reqContentSha:
headers["X-Amz-Content-Sha256"] = @[hashed_payload]
let (can_head, signed_head) = create_canonical_and_signed_headers(headers)
return (signed_head, create_canonical_request(meth, can_uri, can_qs, can_head, signed_head, hashed_payload))
proc create_signing_key*(secret,date,region,service:string,termination:string=termination_string): string =
$hmac_sha256($hmac_sha256($hmac_sha256($hmac_sha256("AWS4"&secret, date[0..7]),region),service),termination)
proc create_string_to_sign*(date,scope,c_str:string,alg:string=hash_algorithm):string=
result = alg & "\n"
result &= date & "\n"
result &= scope & "\n"
result &= toLowerASCII(hex(computeSHA256(c_str)))
proc create_sigv4*(signing_key,to_sign:string):string=
return toLowerASCII(hex(hmac_sha256(signing_key,to_sign)))
proc create_authorization*(id,scope,heads,sig:string,alg:string=hash_algorithm):string=
return alg&" Credential="&id&"/" & (scope) & ", SignedHeaders="&heads&", Signature="&sig
proc create_aws_authorization*(request:(string,string,string),
headers:var TableRef,
scope:AWSCredentialScope,
opts:(string,string)=(hash_algorithm,termination_string)):string=
# make credential scope string
let c_scope= create_credential_scope(scope.date, scope.region, scope.service, opts[1])
headers["X-Amz-Date"] = @[scope.date]
# get signed headers and canonical request string
let (signed_head,canonical_request) = create_canonical_request(headers, request[0], request[1], request[2])
# making string to sign
let to_sign = create_string_to_sign(scope.date,c_scope,canonical_request,opts[0])
# export signing key for caching later
result = create_signing_key(scope.credentials[1],scope.date,scope.region,scope.service,opts[1])
# add authorization header
headers["Authorization"] = @[create_authorization(scope.credentials[0],c_scope,signed_head, create_sigv4(result, to_sign), opts[0])]
headers.del("Host")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment