AWS API Dlang, hmac sha256 function.

Rikki Cattermole via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Tue Oct 6 19:25:45 PDT 2015


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

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.


More information about the Digitalmars-d-learn mailing list