question

elangkamp avatar image
elangkamp asked

Encryption for DynamoDB access in cloud scripting

I've been searching for the last couple of days on how to get the HMAC encryption and the hashing for the AWS to work with cloud scripting. Because we can't include external libraries I'm at a loss on how to do the encryption. Can anyone help?

CloudScript
5 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

elangkamp avatar image elangkamp commented ·
Here's what I have and it's telling me InvalidSignatureException. I'm not sure if I have a working HMAC_SHA256_MAC or not or if SHA256_hash is working correctly. I pulled in code from https://gist.github.com/ryanjduffy/4686906

function getSignatureKey(key, dateStamp, regionName, serviceName) {
var kDate = HMAC_SHA256_MAC(dateStamp, "AWS4" + key);
var kRegion = HMAC_SHA256_MAC(regionName, kDate);
var kService = HMAC_SHA256_MAC(serviceName, kRegion);
var kSigning = HMAC_SHA256_MAC("aws4_request", kService);
return kSigning;
}

handlers.DynamoRequest = function (args, context) {
var method = "POST";
var service = "dynamodb";
var host = "dynamodb.us-east-2.amazonaws.com";
var region = "us-east-2";
var target = "DynamoDB_20120810.PutItem";
var contentType = "application/json";
var signedHeaders = "content-type;host;x-amz-date;x-amz-target";
var algorithm = "AWS4-HMAC-SHA256";
var accessKey = "keyInsertedHere";
var secretKey = "keyInsertedHere";

var date = new Date().toISOString();
date = date.split("-").join("");
date = date.split(":").join("");
date = date.substring(0, 15);
date = date + "Z";

var body = {
"TableName": "PlayerItems",
"Key": {
"PlayerID": {"S": "id1"}
}
};

0 Likes 0 ·
thelastvertex avatar image thelastvertex elangkamp commented ·

Did you find a working solution for this?

I believe I am having the same issue: "The request signature we calculated does not match the signature you provided."

I also had to add custom hashing and am unsure if that is the issue or if it's related to the headers as mentioned in this thread.

Let me know if you found a solution for this.

0 Likes 0 ·
erasmuscrowley avatar image erasmuscrowley thelastvertex commented ·

Unfortunately we never did manage to resolve this.

In testing, I was able to use some code to successfully make a call to the DynamoDB service, but when I copy/pasted that same code into Playfab, it just didn't work. I was also getting the "signature we calculated does not match" error.

Nobody ever managed to figure out what was causing the problem.

We ended up giving up and decided to design our own web services with a traditional SQL database backing it.

I wish I had better news.

0 Likes 0 ·
Show more comments
elangkamp avatar image elangkamp commented ·
body = JSON.stringify(body);


var canonicalHeaders = 'content-type:' + contentType + '\n' + 'host:' + host + '\n' + 'x-amz-date:' + date + '\n' + 'x-amz-target:' + target + '\n';


var payloadHash = SHA256_hash(body);
var	canonicalRequest = method + "\n" + "/" + "\n" + "" + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;
	
var credentialScope = date.substring(0, 8) + "/" + region + "/" + service + "/" + "aws4_request";
var stringToSign = algorithm + "\n" +  date + "\n" +  credentialScope + "\n" +  SHA256_hash(canonicalRequest);
var signingKey = getSignatureKey(secretKey, date.substring(0, 8), region, service);	
var signature = HMAC_SHA256_MAC(signingKey, stringToSign);
var authHeader = algorithm + " " + "Credential=" + accessKey + "/" + credentialScope + ", " +  "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
	
var headers = {
"Content-Type": contentType,
"Host": host,
"X-Amz-Target": target,
"X-Amz-Date": date,
"Authorization": authHeader
};


try{
var response = http.request("https://" + host, method, body, contentType, headers, true);	
return { responseContent: response };
}
catch(e){
return {error: e};
}
};

0 Likes 0 ·
brendan avatar image
brendan answered

Cloud Script runs in a generic V8 environment, but yes, it does not support plugins or libraries, due to security risks. So for any encryption you want to do, you'd need to include the source code for that encryption scheme directly in your script.

18 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

elangkamp avatar image elangkamp commented ·

Brendan,

Thanks for the reply! I added the code from https://gist.github.com/ryanjduffy/4686906 but I don't know if that's the one I need to get the correct encryption. I believe you guys use DynamoDB as well right? Is there a better options that you know will produce the correct results? Also, I don't know if I'm creating the correct signature in the code I have listed. I know that the accesskey and secret key are correct, I've copied it directly and checked it multiple times to be sure, so the only conclusion I can draw is that either the encryption is wrong or I'm building the signature incorrectly.

Thanks!

0 Likes 0 ·
elangkamp avatar image elangkamp commented ·

I don't think I was clear. I need help figuring out why this is giving me an invalidsignatureexception. @Brendan

0 Likes 0 ·
brendan avatar image brendan elangkamp commented ·

Have you tried running the JavaScript from a project running on your local machine, to see if you get the same error? Can you re-check the access key and secret key to make sure they are correct? Is there anything more than just "InvalidSignatureException" being returned?

0 Likes 0 ·
elangkamp avatar image elangkamp brendan commented ·

We're currently trying to troubleshoot it and we're getting different results on the local machine using a rest client. It gives us ValidationException. Is there some reason a rest client would give different results that you can think of?

0 Likes 0 ·
Show more comments
Show more comments
erasmuscrowley avatar image erasmuscrowley commented ·

I'm the guy that's been doing the bulk of the troubleshooting today.

So I can do all the hash calculations on my local PC, make the call using XMLHttpRequest, the request is successful, the DB updates with the new data.

I then user the exact same code in playfab (which is generating the exact same hash values), using http.request instead, and it throws an error.

 
                  
  1. "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.\"

It's seems like a header is being modified and throwing off the calculation on the AWS side.

Is it possible that the "host" header we are setting is being overwritten on the Playfab side before it's sent to the AWS server?

0 Likes 0 ·
brendan avatar image brendan erasmuscrowley commented ·

No, Cloud Script is simply a V8 engine that runs your JavaScript - the only thing "PlayFab" about it is that we included the Server API on it, and there are some default parameters available to you, like currentPlayerId. But it can't change anything in your http.request.

From what was said above, this same code also fails if you run it locally. Once you've got it working locally, try uploading that as your latest revision and re-testing.

0 Likes 0 ·
erasmuscrowley avatar image erasmuscrowley brendan commented ·

The code is now completely working locally.

I've verified that the same code is on both my local machine, and the playfab cloudscript.

I've verified the signature values and hashes generated are the same on both machines. I've compared the request message captured from my machine, to the one reported by the error message in given when the request fails in playfab. They appear to be identical for any given timestamp.

I'm completely at a loss.

1 Like 1 ·
Show more comments
erasmuscrowley avatar image erasmuscrowley commented ·

I wanted to share some evidence of weirdness and why I don't believe it to be a timestamp issue.

Note the timestamp in the header underlined in green. Also note the signature generated underlined in red. The result outlined in blue.

This is the output from the cloudscript.



This is the output from executing the code on my local machine by manually setting the same timestamp immediately afterwards.

0 Likes 0 ·
remote.png (188.4 KiB)
local.png (97.1 KiB)
brendan avatar image brendan erasmuscrowley commented ·

Oh! I'd completely forgotten about an idiosyncrasy in the http library. Can you try adding spaces to where you have the '=' keys in the auth header? So, for example, rather than "Signature=", try "Signature = ".

0 Likes 0 ·
erasmuscrowley avatar image erasmuscrowley brendan commented ·

Still no luck. Amazon really didn't like the spaces. :(

"responseContent": "{\"__type\":\"com.amazon.coral.service#IncompleteSignatureException\",\"message\":\"'Credential' not a valid key=value pair (missing equal-sign) in Authorization header: 'AWS4-HMAC-SHA256 Credential = ..."
0 Likes 0 ·
Show more comments
brendan avatar image
brendan answered

Apologies for the silence on this, but I've been a bit baffled as well. About the only thing I can see that's different in the headers is that we're sending them in the standard format, rather than all lowercase, as in the actual code in your example. So, for example, I tried sending the same call as a GET to http://httpbin.org/headers (changing your access key and secret key to garbage first, of course), and got this back:

"Accept-Encoding": "gzip",
"Authorization": "AWS4-HMAC-SHA256 Credential=ThisIsATestAccessKey/20181223/us-east-2/dynamodb/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-target, Signature=875c9cfe9492e4de970ce7258e53c902258d06fdb7d5a2052cc38c02461475c0",
"Connection": "close",
"Crypto0": "4157533454686973497341546573745365637265744b6579",
"Crypto1": "79f36785c98fe6fa629ba781337b5ea694bfeb3e95c51fbe2b02209719088988",
"Crypto2": "46e18abd003a081fd5f70c66b105efa239a3f9c563ab8ca04db28cacbd05a443",
"Crypto3": "75886540008945d8dd74b53499d5c616138f46787c2e1fe126bf9eade6c5724e",
"Crypto4": "0e57cbaf41d41e84515f771c8f562d6ad3d56b96ae33af5d79ee3cbee8890ad1",
"Host": "httpbin.org",
"Payloadhash": "19c8fec641cfe51749c406d678f43e2354a39c4d1f558d922ee38e868751b530",
"Stringtosign": "AWS4-HMAC-SHA256\n20181223T122513Z\n20181223/us-east-2/dynamodb/aws4_request\\na84ddc4f36e9b9fca3acd3c673ecf913983e641adb69a0ef0412826c4542d527",
"X-Amz-Date": "20181223T122513Z",
"X-Amz-Target": "DynamoDB_20120810.PutItem"

I'm not convinced it would make any difference, but you might try changing to the above format, and see if that helps.

17 comments
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

thelastvertex avatar image thelastvertex commented ·

I'm investigating moving from GameSparks to PlayFab but I'm struggling to get AWS Requests to work.

@Brendan Is there any new information on this or working examples of AWS Request with cloud script? This is unfortunately a make or break feature for me as using an alternative to AWS is not an option.

I have confirmed outside PlayFab that the signature from the custom hashing I've added is correct. The hashing code generates the same signature as the previous built in solution with GameSparks. I am also able to send a custom hashed AWS Request with GameSparks, yet with PlayFab I get the following error.

"The request signature we calculated does not match the signature you provided"

You mention different header formats above, can you elaborate on this? What's the easiest way to change this and see if it has an impact? I'm not overly familiar with http requests and hashing so please bare with me.

0 Likes 0 ·
thelastvertex avatar image thelastvertex thelastvertex commented ·

I did try passing in the same time/date information with both GameSparks and PlayFab. They end up with different signatures with GameSparks succeeding and PlayFab not. So perhaps there is something different in the input data?

Could it be a JSON.stringify() or JSON.parse() issue? log.debug() shows "/" slashes in my headers and content. I can't tell if these are necessary escape characters or caused because they are getting stringified twice. But the is the only difference I see the with the input data when I print them out.

0 Likes 0 ·
thelastvertex avatar image thelastvertex thelastvertex commented ·

I think I messed up the timestamp earlier. I was passing time stamp and user name dynamically. I've since changed this to hardcoded values to make sure I don't accidentally miss it again in the future.

With that said I'm at a loss. I've stepped through every bit of the header, content, hashes, and hashing code and can't find a difference in input or output.

I did come across mention that including "content type" in http.request(url, httpMethod, content, contentType, headers); could cause signature errors. Since content type is already included in the header this could cause issues when AWS verifies it.

Is there an alternative to http.request() that doesn't require contentType? I can't find any information on possible overload functions for this and nothing seems to work when just excluding it.

0 Likes 0 ·
Show more comments

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.