Skip to content

Instantly share code, notes, and snippets.

@tristanls
Created June 19, 2016 19:18
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save tristanls/df7dc76c739e604190c656a5f5342e5a to your computer and use it in GitHub Desktop.
Demo of securing local storage with encryption.
"use strict";
const crypto = require("crypto");
const http = require("http");
const url = require("url");
const indexHtml =
`<!doctype html>
<!--[if lt IE 7]><html class="no-js lt-ie9 lt-ie8 lt-ie7"><![endif]-->
<!--[if IE 7]><html class="no-js lt-ie9 lt-ie8"><![endif]-->
<!--[if IE 8]><html class="no-js lt-ie9"><![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js"><!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>¯\_(ツ)_/¯</title>
</head>
<body>
<div id="csrf-token">{{CSRF-TOKEN}}</div>
<script>
let demo = () =>
{
let csrfToken = document.getElementById("csrf-token").innerHTML;
console.log("csrf-token: ", csrfToken);
let ciphertext = localStorage.getItem("ciphertext");
console.log("from storage ciphertext: ", ciphertext);
if (!ciphertext)
{
let xhttp = new XMLHttpRequest();
xhttp.open("POST", "/encrypt", false);
xhttp.setRequestHeader("CSRF-Token", csrfToken);
let plaintext = "my secret " + new Date().toISOString();
console.log("sending plaintext: ", plaintext);
xhttp.send(plaintext); // some secret
if (xhttp.status != 200)
{
console.log("error o__O");
return;
}
ciphertext = xhttp.responseText;
console.log("storing ciphertext: ", ciphertext);
localStorage.setItem("ciphertext", ciphertext);
}
console.log("ciphertext: ", ciphertext);
let xhttp = new XMLHttpRequest();
xhttp.open("POST", "/decrypt", false);
xhttp.setRequestHeader("CSRF-Token", csrfToken);
xhttp.send(ciphertext);
if (xhttp.status != 200)
{
console.log("error o__O");
return;
}
let plaintext = xhttp.responseText;
console.log("plaintext: ", plaintext);
};
demo();
</script>
</body>
</html>
`;
const csrfTokens = {};
const parseClientCryptoKey = (cookie) =>
{
return cookie.split(";")
.map(c => c.trim())
.map(c => c.split("="))
.filter(c => c.length > 1)
.filter(c => c[0] == "LocalStorageCryptoKey")
.map(c => c.slice(1).join("="))
.reduce((_, c) => c, []);
}
const server = http.createServer((req, res) =>
{
let uri = url.parse(req.url);
if (uri.path == "/")
{
res.statusCode = 200;
const csrfToken = crypto.randomBytes(10).toString("base64");
csrfTokens[csrfToken] = true;
res.write(indexHtml.replace("{{CSRF-TOKEN}}", csrfToken));
res.end();
return;
}
if (!csrfTokens[req.headers["csrf-token"]])
{
res.statusCode = 401;
res.end();
return;
}
console.log(uri.path);
if (uri.path == "/decrypt")
{
if (req.headers.cookie)
{
let clientCryptoKey = parseClientCryptoKey(req.headers.cookie);
console.log(`client LocalStorageCryptoKey: ${clientCryptoKey}`);
if (clientCryptoKey && clientCryptoKey.length > 0)
{
res.statusCode = 200;
// this is just an example server-side crypto
let decipher = crypto.createDecipher("aes-256-ctr", clientCryptoKey);
let plaintext = "";
req.on("data", chunk =>
{
plaintext += decipher.update(chunk.toString("utf8"), "base64", "utf8");
});
req.on("end", () =>
{
plaintext += decipher.final("utf8");
res.write(plaintext);
res.end();
});
return;
}
}
res.statusCode = 400; // or whatever
res.end();
return;
}
else if (uri.path == "/encrypt")
{
let clientCryptoKey;
if (req.headers.cookie)
{
clientCryptoKey = parseClientCryptoKey(req.headers.cookie);
}
if (!clientCryptoKey || clientCryptoKey.length == 0)
{
console.log("generating new LocalStorageCryptoKey");
clientCryptoKey = crypto.randomBytes(128).toString("base64");
res.setHeader("Set-Cookie", [
`LocalStorageCryptoKey=${clientCryptoKey}; path=/decrypt; HttpOnly`,
`LocalStorageCryptoKey=${clientCryptoKey}; path=/encrypt; HttpOnly`
]);
}
console.log(`client LocalStorageCryptoKey: ${clientCryptoKey}`);
// this is just an example server-side crypto
let cipher = crypto.createCipher("aes-256-ctr", clientCryptoKey);
let ciphertext = "";
req.on("data", chunk =>
{
ciphertext += cipher.update(chunk.toString("utf8"), "utf8", "base64");
});
req.on("end", () =>
{
ciphertext += cipher.final("base64");
res.statusCode = 200;
res.write(ciphertext);
res.end();
});
return;
}
else
{
res.statusCode = 404;
res.end();
}
});
server.listen(8888, () => console.log("http://localhost:8888"));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment