AWS API Dlang, hmac sha256 function.

holo via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Tue Oct 6 19:18:29 PDT 2015


> Congrats on getting it working!

@Rikki Thanks :)

I was trying to write my own lib from beginning based on examples 
but after some time i resign from that idea (will back to it when 
i will have some more experience) and right now im trying to 
customize that one from link which yawniek paste:

https://github.com/yannick/vibe-aws/blob/master/source/vibe/aws/sigv4.d

I removed import from vibe.d library and copy/paste missed 
functions formEncode and filterURLEncode (BTW: what that "(R)" 
mean in it? filterURLEncode(R)(ref R dst, ..., ..) ).

Next thing what i did was to replace hmac function to use hmac 
module from newest library. Now whole code looks like this:

module sigv4;

import std.array;
import std.algorithm;
import std.digest.sha;
import std.range;
import std.stdio;
import std.string;
import std.format;
import std.digest.hmac;


const algorithm = "AWS4-HMAC-SHA256";


void filterURLEncode(R)(ref R dst, string str, string 
allowed_chars = null, bool form_encoding = false)
{
	while( str.length > 0 ) {
		switch(str[0]) {
			case ' ':
				if (form_encoding) {
					dst.put('+');
					break;
				}
				goto default;
			case 'A': .. case 'Z':
			case 'a': .. case 'z':
			case '0': .. case '9':
			case '-': case '_': case '.': case '~':
				dst.put(str[0]);
				break;
			default:
				if (allowed_chars.canFind(str[0])) dst.put(str[0]);
				else formattedWrite(dst, "%%%02X", str[0]);
		}
		str = str[1 .. $];
	}
}

string formEncode(string str, string allowed_chars = null)
@safe {
	auto dst = appender!string();
	dst.reserve(str.length);
	filterURLEncode(dst, str, allowed_chars, true);
	return dst.data;
}

struct CanonicalRequest
{
     string method;
     string uri;
     string[string] queryParameters;
     string[string] headers;
     ubyte[] payload;
}

string canonicalQueryString(string[string] queryParameters)
{
     alias encode = formEncode;

     string[string] encoded;
     foreach (p; queryParameters.keys())
     {
         encoded[encode(p)] = encode(queryParameters[p]);
     }
     string[] keys = encoded.keys();
     sort(keys);
     return keys.map!(k => k ~ "=" ~ encoded[k]).join("&");
}

string canonicalHeaders(string[string] headers)
{
     string[string] trimmed;
     foreach (h; headers.keys())
     {
         trimmed[h.toLower().strip()] = headers[h].strip();
     }
     string[] keys = trimmed.keys();
     sort(keys);
     return keys.map!(k => k ~ ":" ~ trimmed[k] ~ "\n").join("");
}

string signedHeaders(string[string] headers)
{
     string[] keys = headers.keys().map!(k => k.toLower()).array();
     sort(keys);
     return keys.join(";");
}

string hash(T)(T payload)
{
     auto hash = sha256Of(payload);
     return hash.toHexString().toLower();
}

string makeCRSigV4(CanonicalRequest r)
{
     auto cr =
         r.method.toUpper() ~ "\n" ~
         (r.uri.empty ? "/" : r.uri) ~ "\n" ~
         canonicalQueryString(r.queryParameters) ~ "\n" ~
         canonicalHeaders(r.headers) ~ "\n" ~
         signedHeaders(r.headers) ~ "\n" ~
         hash(r.payload);

     return hash(cr);
}

unittest {
     string[string] empty;

     auto r = CanonicalRequest(
             "POST",
             "/",
             empty,
             ["content-type": "application/x-www-form-urlencoded; 
charset=utf-8",
              "host": "iam.amazonaws.com",
              "x-amz-date": "20110909T233600Z"],
             cast(ubyte[])"Action=ListUsers&Version=2010-05-08");

     auto sig = makeCRSigV4(r);

     assert(sig == 
"3511de7e95d28ecd39e9513b642aee07e54f4941150d8df8bf94b328ef7e55e2");
}

struct SignableRequest
{
     string dateString;
     string timeStringUTC;
     string region;
     string service;
     CanonicalRequest canonicalRequest;
}

string signableString(SignableRequest r) {
     return algorithm ~ "\n" ~
         r.dateString ~ "T" ~ r.timeStringUTC ~ "Z\n" ~
         r.dateString ~ "/" ~ r.region ~ "/" ~ r.service ~ 
"/aws4_request\n" ~
         makeCRSigV4(r.canonicalRequest);
}

unittest {
     string[string] empty;

     SignableRequest r;
     r.dateString = "20110909";
     r.timeStringUTC = "233600";
     r.region = "us-east-1";
     r.service = "iam";
     r.canonicalRequest = CanonicalRequest(
             "POST",
             "/",
             empty,
             ["content-type": "application/x-www-form-urlencoded; 
charset=utf-8",
              "host": "iam.amazonaws.com",
              "x-amz-date": "20110909T233600Z"],
             cast(ubyte[])"Action=ListUsers&Version=2010-05-08");

     auto sampleString =
         algorithm ~ "\n" ~
         "20110909T233600Z\n" ~
         "20110909/us-east-1/iam/aws4_request\n" ~
         
"3511de7e95d28ecd39e9513b642aee07e54f4941150d8df8bf94b328ef7e55e2";

     assert(sampleString == signableString(r));
}

ubyte[] array_xor(ubyte[] b1, ubyte[] b2)
{
     assert(b1.length == b2.length);
     ubyte[] ret;
     for (uint i = 0; i < b1.length; i++)
         ret ~= b1[i] ^ b2[i];
     return ret;
}

auto hmac_sha256(string key, string message)
{
     auto hmac = hmac!SHA256(key.representation);
     hmac.put(message.representation);
     return hmac.finish;
}

unittest {
     string key = "key";
     string message = "The quick brown fox jumps over the lazy 
dog";

     string mac = hmac_sha256(key, 
message).toHexString().toLower();
     assert(mac == 
"f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8");
}

auto signingKey(string secret, string dateString, string region, 
string service)
{
     auto kSecret = "AWS4" ~ secret;
     auto kDate = hmac_sha256(kSecret, dateString);
     auto kRegion = hmac_sha256(kDate, region);
     auto kService = hmac_sha256(kRegion, service);
     return hmac_sha256(kService, "aws4_request");
}

unittest {
     string secretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
     auto signKey = signingKey(secretKey, "20110909", "us-east-1", 
"iam");

     ubyte[] expected = [152, 241, 216, 137, 254, 196, 244, 66, 
26, 220, 82, 43, 171, 12, 225, 248, 46, 105, 41, 194, 98, 237, 
21, 229, 169, 76, 144, 239, 209, 227, 176, 231 ];
     assert(expected == signKey);
}

alias sign = hmac_sha256;

unittest {
     auto sampleString =
         "AWS4-HMAC-SHA256\n" ~
         "20110909T233600Z\n" ~
         "20110909/us-east-1/iam/aws4_request\n" ~
         
"3511de7e95d28ecd39e9513b642aee07e54f4941150d8df8bf94b328ef7e55e2";

     auto secretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
     auto signKey = signingKey(secretKey, "20110909", "us-east-1", 
"iam");

     auto signature = sign(signKey, 
sampleString).toHexString().toLower();
     auto expected = 
"ced6826de92d2bdeed8f846f0bf508e8559e98e4b0199114b84c54174deb456c";

     assert(signature == expected);
}

/**
  * CredentialScope == date / region / service / aws4_request
  */
string createSignatureHeader(string accessKeyID, string 
credentialScope, string[string] reqHeaders, ubyte[] signature)
{
     return algorithm ~ " Credential=" ~ accessKeyID ~ "/" ~ 
credentialScope ~ "/aws4_request, SignedHeaders=" ~ 
signedHeaders(reqHeaders) ~ ", Signature=" ~ 
signature.toHexString().toLower();
}

string dateFromISOString(string iso)
{
     auto i = iso.indexOf('T');
     if (i == -1) throw new Exception("ISO time in wrong format: " 
~ iso);
     return iso[0..i];
}

string timeFromISOString(string iso)
{
     auto t = iso.indexOf('T');
     auto z = iso.indexOf('Z');
     if (t == -1 || z == -1) throw new Exception("ISO time in 
wrong format: " ~ iso);
     return iso[t+1..z];
}

unittest {
     assert(dateFromISOString("20110909T1203Z") == "20110909");
}


void main()
{
     auto sampleString =
         "AWS4-HMAC-SHA256\n" ~
         "20110909T233600Z\n" ~
         "20110909/us-east-1/iam/aws4_request\n" ~
         
"3511de7e95d28ecd39e9513b642aee07e54f4941150d8df8bf94b328ef7e55e2";

     auto secretKey = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
     auto signKey = signingKey(secretKey, "20110909", "us-east-1", 
"iam");

     auto signature = sign(signKey, 
sampleString).toHexString().toLower();

     writeln(signature);
}

When i try to compile it im getting such error:

[holo at ultraxps test]$ dmd -unittest hello.d
hello.d(196): Error: function sigv4.hmac_sha256 (string key, 
string message) is not callable using argument types (ubyte[32], 
string)
[holo at ultraxps test]$ dmd hello.d
hello.d(196): Error: function sigv4.hmac_sha256 (string key, 
string message) is not callable using argument types (ubyte[32], 
string)
[holo at ultraxps test]$


Line 196 is: "auto kRegion = hmac_sha256(kDate, region);"

I was looking for ubyte[32] variable but i cant find it anywhere. 
What is strange unit tests are passing (i think, maybe im wrong).


More information about the Digitalmars-d-learn mailing list