Created
June 19, 2016 19:18
-
-
Save tristanls/df7dc76c739e604190c656a5f5342e5a to your computer and use it in GitHub Desktop.
Demo of securing local storage with encryption.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"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