Created
October 28, 2017 22:42
-
-
Save Gooseus/460ccc1b7d4551d7beba75ffc286cef1 to your computer and use it in GitHub Desktop.
Nim AWS Request/Sigv4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[ | |
# 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"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[ | |
# 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