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