On 07/10/15 3:18 PM, holo wrote:
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, ..., ..) ).

If you see a template argument (which R is) it is typically meant for a range. In this case an output range. Although there should be isOutputRange!R in an template if condition, to check this.

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@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@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@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).

It's in signingKey. Remember auto just means work out type based upon assigning value's type. I would really really recommend since you keep having issues with hmac, to go the Botan way. Since it works.

Reply via email to