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?
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?
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"} } };
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}; } };
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.
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!
I don't think I was clear. I need help figuring out why this is giving me an invalidsignatureexception. @Brendan
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?
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?
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.
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?
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.
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.
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.
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 = ".
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 = ..."
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.
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.
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.
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.
6 People are following this question.