Last active
August 29, 2015 14:18
-
-
Save bellbind/e6a3d72946e1b1c6daea to your computer and use it in GitHub Desktop.
[webcrypto][websocket][usermedia]captured photo sending with specified with pubkey
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
;(function () { | |
"use strict"; | |
// helpers for ArrayBuffer representation | |
var s2b = function (str) { | |
return new Promise(function (fulfill, reject) { | |
var file = new FileReader(); | |
file.addEventListener("load", function (ev) { | |
fulfill(ev.target.result); | |
}, false); | |
file.addEventListener("error", reject, false); | |
file.addEventListener("abort", reject, false); | |
file.readAsArrayBuffer(new Blob([str])); | |
}); | |
}; | |
var b2s = function (buffer) { | |
return new Promise(function (fulfill, reject) { | |
var file = new FileReader(); | |
file.addEventListener("load", function (ev) { | |
fulfill(ev.target.result); | |
}, false); | |
file.addEventListener("error", reject, false); | |
file.addEventListener("abort", reject, false); | |
file.readAsText(new Blob([buffer])); | |
}); | |
}; | |
var b2h = function (buffer, conn) { | |
conn = conn === undefined ? "" : conn; | |
return [].map.call(new Uint8Array(buffer), function (b, i) { | |
var c = b.toString(16); | |
return c.length === 1 ? "0" + c : c; | |
}).join(conn); | |
}; | |
var h2b = function (hex, conn) { | |
var hexes = conn ? hex.split(conn) : hex.match(/.{1,2}/g); | |
return new Uint8Array(hexes.map(function (h) { | |
return parseInt(h, 16); | |
})); | |
}; | |
// polyfill ES6 Object.assign() | |
var assign = function (target) { | |
for (var i = 1; i < arguments.length; i++) { | |
var s = arguments[i]; | |
Object.keys(s).forEach(function (k) { | |
var d = Object.getOwnPropertyDescriptor(s, k); | |
if (d && d.enumerable) target[k] = s[k]; | |
}); | |
} | |
return target; | |
}; | |
// crypto specs | |
var conf = {}; | |
conf.system = { | |
name: "ECDH", | |
namedCurve: "P-256", | |
}; | |
conf.spec = { | |
name: conf.system.name, | |
namedCurve: conf.system.namedCurve, | |
}; | |
conf.alg = { | |
name: conf.system.name, | |
namedCurve: conf.system.namedCurve, | |
}; | |
conf.secSpec = { | |
name: "AES-CBC", | |
length: 256, | |
}; | |
conf.fmt = "jwk"; | |
conf.digest = {name: "SHA-256"}; | |
var initMe = function () { | |
return crypto.subtle.generateKey(conf.system, true, [ | |
"deriveKey", | |
]).then(function (keys) { | |
return Promise.all([ | |
keys.privateKey, | |
crypto.subtle.exportKey(conf.fmt, keys.publicKey)]); | |
}).then(function (keys) { | |
return Promise.all([ | |
keys[0], keys[1], | |
s2b(JSON.stringify(keys[1])).then(function (buf) { | |
return crypto.subtle.digest(conf.digest, buf); | |
}).then(b2h), | |
]); | |
}).then(function (keys) { | |
return {id: keys[2], priv: keys[0], pub: keys[1]}; | |
}); | |
}; | |
var importKey = function (jwk) { | |
return crypto.subtle.importKey(conf.fmt, jwk, conf.spec, false, []); | |
}; | |
var encrypt = function (me, pubkey, text) { | |
var iv = crypto.getRandomValues(new Uint8Array(16)); | |
return Promise.all([ | |
crypto.subtle.deriveKey( | |
assign({}, conf.system, {public: pubkey}), | |
me.priv, conf.secSpec, false, ["encrypt"]), | |
s2b(text), | |
]).then(function (km) { | |
return crypto.subtle.encrypt( | |
assign({}, conf.secSpec, {iv: iv}), km[0], km[1]); | |
}).then(function (cipher) { | |
console.log("byteLength", cipher.byteLength); | |
var iva = Array.apply(null, new Uint8Array(iv)); | |
//var body = Array.apply(null, new Uint8Array(cipher)); | |
var body = b2h(cipher); | |
return {key: me.pub, iv: iva, body: body}; | |
}); | |
}; | |
var decrypt = function (me, msg) { | |
var iv = new Uint8Array(msg.iv); | |
//var cipher = new Uint8Array(msg.body); | |
var cipher = h2b(msg.body); | |
return importKey(msg.key).then(function (pubkey) { | |
return crypto.subtle.deriveKey( | |
assign({}, conf.system, {public: pubkey}), | |
me.priv, conf.secSpec, false, ["decrypt"]); | |
}).then(function (skey) { | |
return crypto.subtle.decrypt( | |
assign({}, conf.secSpec, {iv: iv}), | |
skey, cipher); | |
}).then(b2s); | |
}; | |
window.CryptoSystem = { | |
initMe: initMe, | |
importKey: importKey, | |
encrypt: encrypt, | |
decrypt: decrypt, | |
}; | |
})(); |
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
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8" /> | |
<title>WebCrypto and WebSocket and getUserMedia</title> | |
<script src="crypto.js"></script> | |
<script src="script.js"></script> | |
<style> | |
.block { | |
display: inline-block; | |
margin: 0px; | |
padding: 0px; | |
width: 45%; | |
vertical-align: top; | |
} | |
</style> | |
</head> | |
<body> | |
<div> | |
[<input id="name" type="text" value="anon" style="width: 100px" | |
readonly="readonly" />] | |
send to <select id="targets"></select> (when tap camera) | |
</div> | |
<div style="height: 80%;"> | |
<div id="camera" class="block"></div> | |
<div id="block" class="block" style="overflow: scroll;"> | |
<div id="log"></div> | |
</div> | |
</div> | |
</body> | |
</html> |
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
{ | |
"name": "webcrypto-camera", | |
"version": "0.0.1", | |
"description": "crypted shot send with websocket server (ECDH/AES-CBC)", | |
"dependencies": { | |
"faye-websocket": "*", | |
"mime-types": "*" | |
}, | |
"scripts": { | |
"start": "iojs ws-server.js" | |
}, | |
"engines": { | |
"iojs": "1.x" | |
} | |
} |
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
web: npm start |
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
;window.addEventListener("load", function () { | |
"use strict"; | |
var getUserMedia = function (conf) { | |
if (navigator.mediaDevices) { | |
return navigator.mediaDevices.getUserMedia(conf); | |
} | |
if (navigator.webkitGetUserMedia) return new Promise(function (f, r) { | |
navigator.webkitGetUserMedia(conf, f, r); | |
}); | |
return Promise.reject(); | |
}; | |
var createObjectURL = function (stream) { | |
if (typeof URL !== "undefined") | |
return URL.createObjectURL(stream); | |
if (typeof webkitURL !== "undefined") | |
return webkitURL.createObjectURL(stream); | |
return ""; | |
}; | |
var capture = function (video) { | |
var canvas = document.createElement("canvas"); | |
//console.log(video.width, video.height); | |
canvas.width = video.clientWidth; | |
canvas.height = video.clientHeight; | |
var c2d = canvas.getContext("2d"); | |
c2d.drawImage(video, 0, 0, canvas.width, canvas.height); | |
var uri = canvas.toDataURL("image/png"); | |
return uri; | |
}; | |
getUserMedia({video: true}).then(function (stream) { | |
var video = document.createElement("video"); | |
document.getElementById("camera").appendChild(video); | |
// define video screen size beacuse | |
// browsers not yet support MediaStreamTrack.getConstraints() API | |
//window.track = stream.getVideoTracks()[0] | |
video.style.width = "100%"; | |
video.src = createObjectURL(stream); | |
video.play(); | |
return video; | |
}).then(function (video) { | |
CryptoSystem.initMe().then(function openSocket(me) { | |
var name = document.getElementById("name"); | |
var log = document.getElementById("log"); | |
var targets = document.getElementById("targets"); | |
console.log("clientHeight", video.clientHeight); | |
var block = document.getElementById("block"); | |
block.style.height = (0| window.innerHeight / 2) + "px"; | |
var pubkeys = []; | |
while (targets.firstChild) targets.removeChild(targets.firstChild); | |
name.value = me.id.substring(0, 8); | |
var wsurl = location.origin.replace(/^http/, "ws") + "/bs/"; | |
var socket = new WebSocket(wsurl); | |
socket.addEventListener("open", function (ev) { | |
console.log("[open]"); | |
// send my pubkey | |
socket.send(JSON.stringify({ | |
t: "hello", id: me.id, key: me.pub})); | |
video.addEventListener("click", sender, false); | |
}, false); | |
socket.addEventListener("message", function (ev) { | |
var msg = JSON.parse(ev.data); | |
if (msg.t === "hello") return acceptHello(msg); | |
if (msg.t === "chat") return acceptChat(msg); | |
if (msg.t === "bye") return acceptBye(msg); | |
}, false); | |
socket.addEventListener("close", function (ev) { | |
console.log("[close]"); | |
video.removeEventListener("click", sender, false); | |
// reconnect | |
requestAnimationFrame(openSocket.bind(null, me)); | |
}, false); | |
var sender = function (ev) { | |
var pubkey = pubkeys[targets.selectedIndex]; | |
if (!pubkey) return; | |
console.log("[send]"); | |
var uri = capture(video); | |
console.log("uri length", uri.length); | |
// prepare key and encrypt a message to the specified receiver | |
CryptoSystem.encrypt( | |
me, pubkey.key, uri | |
).then(function (msg) { | |
msg.t = "chat"; | |
socket.send(JSON.stringify(msg)); | |
var item = document.createElement("div"); | |
var img = document.createElement("img"); | |
img.src = uri; | |
item.appendChild(img); | |
log.insertBefore(item, log.firstChild); | |
}).catch(console.log.bind(console, "<<encrypt>>")); | |
}; | |
var acceptHello = function (msg) { | |
CryptoSystem.importKey(msg.key).then(function (pubkey) { | |
console.log("[import key]", pubkey); | |
pubkeys.push({id: msg.id, key: pubkey}); | |
var opt = document.createElement("option"); | |
opt.textContent = msg.id; | |
opt.id = msg.id; | |
targets.add(opt); | |
}).catch(console.log.bind(console, "<<import key>>")); | |
}; | |
var acceptChat = function (msg) { | |
CryptoSystem.decrypt(me, msg).then(function (uri) { | |
console.log("[message] " + uri); | |
var item = document.createElement("div"); | |
var img = document.createElement("img"); | |
img.src = uri; | |
item.appendChild(img); | |
log.insertBefore(item, log.firstChild); | |
}).catch(console.log.bind(console, "<<decrypt>>")); | |
}; | |
var acceptBye = function (msg) { | |
// remove receiver list | |
for (var i = 0; i < pubkeys.length; i++) { | |
if (pubkeys[i].id == msg.id) { | |
pubkeys.splice(i, 1); | |
break; | |
} | |
} | |
targets.removeChild(document.getElementById(msg.id)); | |
}; | |
}).catch(console.log.bind(console, "<<init me>>")); | |
}); | |
}, false); |
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
// Simple Broadcast WebSocket server | |
// $ npm install faya-websocket mime-types | |
// $ iojs ws-server.js | |
var http = require("http"); | |
var fs = require("fs"); | |
var path = require("path"); | |
// 3rd party libs | |
var WebSocket = require("faye-websocket"); // W3C API compliant interface | |
var mime = require("mime-types"); | |
var fileServer = function (req, res) { | |
console.log("[file server]", req.method, req.url); | |
if (req.method !== "GET") { | |
res.writeHead(405, {allow: "GET"}); | |
return res.end(); | |
} | |
var loc = req.url.endsWith("/") ? req.url + "index.html" : req.url; | |
var file = path.join(__dirname, loc); | |
fs.createReadStream(file).once("readable", function () { | |
console.log("[exist]", file); | |
var mtype = mime.lookup(loc) || "application/octet-stream"; | |
var charset = mime.charset(mtype); | |
var ctype = mtype + (charset ? "; charset=" + charset : ""); | |
res.writeHead(200, {"content-type": ctype}); | |
this.pipe(res); | |
}).once("error", function (er) { | |
console.log("[not exist]", file); | |
res.writeHead(404); | |
res.end(); | |
}); | |
}; | |
// single broadcast hub | |
var BroadcastServer = function () { | |
this.sockets = new Map(); | |
}; | |
BroadcastServer.prototype.onUpgrade = function (req, socket, head) { | |
console.log("[upgrade]", req.method, req.url); | |
var sockets = this.sockets; | |
var ws = new WebSocket(req, socket, head); | |
ws.addEventListener("open", function (ev) { | |
console.log("[open]", ws.url); | |
for (var val of sockets.values()) { | |
//console.log(val); | |
if (val.hello) ws.send(val.hello); | |
} | |
sockets.set(ws, {}); | |
}, false); | |
ws.addEventListener("message", function (ev) { | |
console.log("[message]", ev.data); | |
var msg = JSON.parse(ev.data); | |
if (msg.t === "hello") { | |
console.log("[hello]", msg.id); | |
sockets.set(ws, {id: msg.id, hello: ev.data}); | |
} | |
for (var socket of sockets.keys()) { | |
socket.send(ev.data); | |
} | |
}, false); | |
ws.addEventListener("close", function (ev) { | |
console.log("[close]", ws.url); | |
var id = sockets.get(ws).id; | |
sockets.delete(ws); | |
for (var socket of sockets.keys()) { | |
socket.send(JSON.stringify({t: "bye", id: id})); | |
} | |
console.log("[bye]", id); | |
}, false); | |
}; | |
var bs = new BroadcastServer(); | |
var server = http.createServer(fileServer); | |
server.on("upgrade", bs.onUpgrade.bind(bs)); | |
var port = process.env.PORT || 8080; | |
server.listen(port); | |
console.log("open http://localhost:" + port + "/"); |
Note for updating to DHE style encryption
Current implementation is that the sender use me.priv and chosed pubkey.
Instead of using the me.priv and me.pub, use a newly generated ECDH key pairs in each send event.
It make a cipher body with the temporal privkey, send the cipher body and the temporal pubkey,
then drop the keys after encryption.
DHE can anonymize the sender identity.
- Alice use the Bob's permanent pubkey for deriveKey
- Alice make a temporal DH key pair
- Alice encrypt a text with the permanent pubkey and the temporal privkey
- Alice send the cipher text and temporal pubkey to Bob
- Bob decrypt the cipher text with the permanent privkey and the temporal pubkey
DHE+DSA uses both DSA pubkey and DHE pubkey.
Make and send a sign of the temporal pubkey with DSA privkey,
It assures the Sender Identity lost by DHE system.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
demo: https://webcrypto-camera.herokuapp.com/