From d03339f20bb651c682047e29e3f8eaaba279b4f1 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 20 Jun 2018 14:27:44 +0200 Subject: [PATCH] check for the presence of a blockHash in localStorage when logging in --- rpc.js | 3 +-- www/assert/main.js | 15 ++++++++++++ www/common/common-hash.js | 37 +++++++++++++++++++++++++++++ www/common/common-util.js | 8 +++---- www/common/cryptpad-common.js | 42 ++++++++++++++++++++++++++++++++- www/common/outer/login-block.js | 31 ++++++++++++++++++++---- www/settings/inner.js | 35 +++++++++++++++++++++++++-- 7 files changed, 157 insertions(+), 14 deletions(-) diff --git a/rpc.js b/rpc.js index d10fe6149..5f6288805 100644 --- a/rpc.js +++ b/rpc.js @@ -1380,8 +1380,7 @@ var createLoginBlockPath = function (Env, publicKey) { }; var writeLoginBlock = function (Env, msg, cb) { - console.log(msg); // XXX - + //console.log(msg); var publicKey = msg[0]; var signature = msg[1]; var block = msg[2]; diff --git a/www/assert/main.js b/www/assert/main.js index 4ff862511..a6a82e421 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -10,9 +10,12 @@ define([ '/common/wire.js', '/common/flat-dom.js', '/common/media-tag.js', + + '/bower_components/tweetnacl/nacl-fast.min.js', ], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag) { window.Hyperjson = Hyperjson; window.Sortify = Sortify; + var Nacl = window.nacl; var assertions = 0; var failed = false; @@ -296,6 +299,18 @@ define([ !secret.hashData.present); }, "test support for ugly tracking query paramaters in url"); + assert(function (cb) { + var href = 'https://cryptpad.fr/block/pe/pewpewpewpewpew'; + var key = Nacl.randomBytes(32); + + var hash = Hash.createBlockHash(href, key); + + var parsed = Hash.parseBlockHash(hash); + + cb(parsed && href === parsed.href && + parsed.keys.symmetric.length === key.length); + }, 'parse a block hash'); + assert(function (cb) { try { MediaTag(void 0).on('progress').on('decryption'); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 4b0c2c607..e283d2525 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -474,6 +474,43 @@ Version 1 '/' + curvePublic.replace(/\//g, '-') + '/'; }; + // XXX consider putting Block functions in /common/outer/login-block.js + Hash.createBlockHash = function (href, key) { + if (typeof(href) !== 'string') { return; } + if (!key instanceof Uint8Array) { return; } + + // TODO verify inputs + try { return href + '#' + Nacl.util.encodeBase64(key); } + catch (e) { return; } + }; + + var decodeSafeB64 = function (b64) { + try { + return Nacl.util.decodeBase64(b64.replace(/\-/g, '/')); + } catch (e) { + console.error(e); + return; + } + }; + + Hash.parseBlockHash = function (hash) { + if (typeof(hash) !== 'string') { return; } + var parts = hash.split('#'); + if (parts.length !== 2) { return; } + + try { + return { + href: parts[0], + keys: { + symmetric: decodeSafeB64(parts[1]), + } + }; + } catch (e) { + console.error(e); + return; + } + }; + // Create untitled documents when no name is given var getLocaleDate = function () { if (window.Intl && window.Intl.DateTimeFormat) { diff --git a/www/common/common-util.js b/www/common/common-util.js index 2a30133e9..cc56cd0d5 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -137,17 +137,15 @@ define([], function () { else if (bytes >= oneMegabyte) { return 'MB'; } }; + // given a path, asynchronously return an arraybuffer Util.fetch = function (src, cb) { var done = false; - var CB = function (err, res) { - if (done) { return; } - done = true; - cb(err, res); - }; + var CB = Util.once(cb); var xhr = new XMLHttpRequest(); xhr.open("GET", src, true); xhr.responseType = "arraybuffer"; + xhr.onerror = function (err) { CB(err); }; xhr.onload = function () { if (/^4/.test(''+this.status)) { return CB('XHR_ERROR'); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index d095d2b3c..dd7a46d25 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -8,11 +8,12 @@ define([ '/common/common-feedback.js', '/common/outer/local-store.js', '/common/outer/worker-channel.js', + '/common/outer/login-block.js', '/customize/application_config.js', '/bower_components/nthen/index.js', ], function (Config, Messages, Util, Hash, - Messaging, Constants, Feedback, LocalStore, Channel, + Messaging, Constants, Feedback, LocalStore, Channel, Block, AppConfig, Nthen) { /* This file exposes functionality which is specific to Cryptpad, but not to @@ -883,7 +884,46 @@ define([ if (AppConfig.beforeLogin) { AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor()); } + }).nThen(function (waitFor) { + var blockHash = LocalStore.getBlockHash(); + + if (blockHash) { + console.log(blockHash); + var parsed = Hash.parseBlockHash(blockHash); + + if (typeof(parsed) !== 'object') { + console.error("Failed to parse blockHash"); + console.log(parsed); + return; + } else { + console.log(parsed); + } + Util.fetch(parsed.href, waitFor(function (err, arraybuffer) { + if (err) { return void console.log(err); } + + // use the results to load your user hash and + // put your userhash into localStorage + try { + var block_info = Block.decrypt(arraybuffer, parsed.keys); + if (block_info[Constants.userHashKey]) { LocalStore.setUserHash(block_info[Constants.userHashKey]); } + } catch (e) { + console.error(e); + return void console.error("failed to decrypt or decode block content"); + } + })); + } else { + // XXX debugging + console.error("NO BLOCK HASH"); + } + }).nThen(function (waitFor) { + // XXX debugging + if (LocalStore.getUserHash()) { + console.log('User_hash detected'); + } else { + console.log("User_hash not detected"); + } + var cfg = { init: true, //query: onMessage, // TODO temporary, will be replaced by a webworker channel diff --git a/www/common/outer/login-block.js b/www/common/outer/login-block.js index 026fa289f..3e931488b 100644 --- a/www/common/outer/login-block.js +++ b/www/common/outer/login-block.js @@ -1,7 +1,8 @@ define([ '/common/common-util.js', + '/api/config', '/bower_components/tweetnacl/nacl-fast.min.js', -], function (Util) { +], function (Util, ApiConfig) { var Nacl = window.nacl; var Block = {}; @@ -30,9 +31,11 @@ define([ var symmetric = seed.subarray(Nacl.sign.seedLength, Nacl.sign.seedLength + Nacl.secretbox.keyLength); + console.log("symmetric key: ", Nacl.util.encodeBase64(symmetric)); + return { sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes - symmetric: symmetric, + symmetric: symmetric, // 32 bytes ... }; }; @@ -51,8 +54,15 @@ define([ Block.decrypt = function (u8_content, keys) { // version is currently ignored since there is only one var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength); - var box = content.subarray(1 + Nacl.secretbox.nonceLength); - return Nacl.secretbox.open(box, nonce, keys.symmetric); + var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength); + + var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric); + try { + return JSON.parse(Nacl.util.encodeUTF8(plaintext)); + } catch (e) { + console.error(e); + return; + } }; // (Uint8Array block) => signature @@ -86,5 +96,18 @@ define([ }; }; + // FIXME don't spread the functions below across this file and common-hash + // find a permanent home for these hacks + var urlSafeB64 = function (u8) { + return Nacl.util.encodeBase64(u8).replace(/\//g, '-'); + }; + + Block.getBlockHash = function (keys) { + var publicKey = urlSafeB64(keys.sign.publicKey); + var relative = 'block/' + publicKey.slice(0, 2) + '/' + publicKey; // XXX FIXME use configurable path from /api/config + var symmetric = urlSafeB64(keys.symmetric); + return ApiConfig.httpUnsafeOrigin + relative + '#' + symmetric; + }; + return Block; }); diff --git a/www/settings/inner.js b/www/settings/inner.js index acfa8bafd..3d59acb9c 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -11,6 +11,7 @@ define([ '/common/hyperscript.js', '/customize/application_config.js', '/api/config', + '/common/outer/login-block.js', // XXX HACK '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -28,7 +29,8 @@ define([ Messages, h, AppConfig, - ApiConfig + ApiConfig, + Block // XXX HACK ) { var saveAs = window.saveAs; @@ -389,7 +391,36 @@ define([ }); }; - updateBlock = updateBlock; // jshint.. + var removeBlock = function (data, cb) { + sframeChan.query('Q_REMOVE_LOGIN_BLOCK', data, function (err, obj) { + if (err || obj.error) { return void cb ({error: err || obj.error}); } + cb (obj); + }); + }; + + + // XXX + if (false) { // STUBBED, just for development purposes + console.error("TRYING TO WRITE A BLOCK"); + + var keys = Block.genkeys(Block.seed()); + var data = Block.serialize(JSON.stringify({ + a: 5, + b: 6, + User_hash: "XXX", /// TODO encode newly derived User_hash here + }), keys); + + updateBlock(data, function (err, thing) { + console.log(err, thing); + + console.log(Block.getBlockHash(keys)); + + return; + removeBlock(Block.remove(keys), function (err, obj) { + console.log(err, obj); + }); + }); + } return $div; };