-
-
Save agrueneberg/6585680 to your computer and use it in GitHub Desktop.
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>HMAC-SHA256 Example</title> | |
</head> | |
<body> | |
<script src="http://crypto.stanford.edu/sjcl/sjcl.js"></script> | |
<script> | |
var sharedSecret, query, signature, hmac, xhr; | |
// No longer secret shared secret ;-) | |
sharedSecret = "super-secret"; | |
query = "key=value"; | |
hmac = new sjcl.misc.hmac(sjcl.codec.utf8String.toBits(sharedSecret), sjcl.hash.sha256); | |
signature = sjcl.codec.hex.fromBits(hmac.encrypt(query)); | |
xhr = new XMLHttpRequest(); | |
xhr.open("GET", "http://localhost:1337/?" + query); | |
xhr.setRequestHeader("X-Signature", signature); | |
xhr.onload = function () { | |
console.log(xhr.status, xhr.responseText); | |
} | |
xhr.send(null); | |
</script> | |
</body> | |
</html> |
var http, crypto, sharedSecret, query, signature; | |
http = require("http"); | |
crypto = require("crypto"); | |
sharedSecret = "super-secret"; | |
query = "key=value"; | |
signature = crypto.createHmac("sha256", sharedSecret).update(query).digest("hex"); | |
http.get({ | |
port: 1337, | |
path: "/?" + query, | |
headers: { | |
"X-Signature": signature | |
} | |
}, function (res) { | |
console.log(res.statusCode); | |
}); |
var http, url, crypto, sharedSecret; | |
http = require("http"); | |
url = require("url"); | |
crypto = require("crypto"); | |
sharedSecret = "super-secret"; | |
http.createServer(function (req, res) { | |
var retrievedSignature, parsedUrl, computedSignature; | |
// Deal with CORS. | |
res.setHeader("Access-Control-Allow-Origin", "*"); | |
if (req.method === "OPTIONS") { | |
res.setHeader("Access-Control-Allow-Headers", "X-Signature"); | |
res.writeHead(204); | |
res.end(); | |
} else { | |
// Get signature. | |
retrievedSignature = req.headers["x-signature"]; | |
// Recalculate signature. | |
parsedUrl = url.parse(req.url); | |
computedSignature = crypto.createHmac("sha256", sharedSecret).update(parsedUrl.query).digest("hex"); | |
// Compare signatures. | |
if (computedSignature === retrievedSignature) { | |
res.writeHead(200, { | |
"Content-Type": "text/plain" | |
}); | |
res.end("Hello World\n"); | |
} else { | |
res.writeHead(403, { | |
"Content-Type": "text/plain" | |
}); | |
res.end("Get Out\n"); | |
} | |
} | |
}).listen(1337); | |
console.log("Server running on port 1337"); |
I think client.js is the node version (alternative) to the client.html.
So to answer your question, in your use case you don't use client.js.
Looks good for the server-to-server, but for the client-to-server, seems like it's pretty easy to fake - you've got the [// No longer secret shared secret ;-)] and the method in plain view. Am I missing something?
or maybe we're assuming that the client only arrives at the page (or receives code-on-demand) after initial login...
Notice that Hmac.update(query)
should get a Buffer
and not a string.
It's case problems on unicode strings..
This is an insecure implementation that is vulnerable to replay attacks. Each request should include an incremental nonce that is kept track on the server side.
@tymat How can I achieve that? I wast searching like a crazy the best way to implement a secure HMAC protection for node.
Thank you.
you saved my day
This is an insecure implementation that is vulnerable to replay attacks. Each request should include an incremental nonce that is kept track on the server side.
Not only that, it's also vulnerable to timing attacks due to the comparison with pure ===
, see https://gist.github.com/agrueneberg/6585680#file-server-js-L24
@mfn I think this should fixes the timing attack.
const computedSignatureBuffer = Buffer.from(computedSignature, 'hex');
const retrievedSignatureBuffer = Buffer.from(retrievedSignature, 'hex');
const valid = crypto.timingSafeEqual(computedSignatureBuffer, retrievedSignatureBuffer);
if (valid) {
res.writeHead(200, {
"content-type": "text/plain"
});
res.end("hello world\n");
} else {
res.writeHead(403, {
"content-type": "text/plain"
});
res.end("get out\n");
}
Awesome ! thanks !
@mfn I think this should fixes the timing attack.
const computedSignatureBuffer = Buffer.from(computedSignature, 'hex'); const retrievedSignatureBuffer = Buffer.from(retrievedSignature, 'hex'); const valid = crypto.timingSafeEqual(computedSignatureBuffer, retrievedSignatureBuffer); if (valid) { res.writeHead(200, { "content-type": "text/plain" }); res.end("hello world\n"); } else { res.writeHead(403, { "content-type": "text/plain" }); res.end("get out\n"); }
That helps!
Why do you have and where do u user client.js if the comm diagram is: nodejs:Server (Test) <-- (fsdhf83sdf!) --> (Test) Client:browser