From e618483395132317ef7cce2b14cc121976400717 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 13 Apr 2017 11:05:28 +0200 Subject: [PATCH] extract all code for encoding and decoding hashes --- www/common/cryptpad-common.js | 250 +++------------------------------- www/common/hash.js | 236 ++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 230 deletions(-) create mode 100644 www/common/hash.js diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 1b870371e..7fc848d24 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -3,6 +3,7 @@ define([ '/customize/messages.js?app=' + window.location.pathname.split('/').filter(function (x) { return x; }).join('.'), '/common/fsStore.js', '/common/common-util.js', + '/common/hash.js', '/bower_components/chainpad-crypto/crypto.js?v=0.1.5', '/bower_components/alertifyjs/dist/js/alertify.js', @@ -12,7 +13,7 @@ load pinpad dynamically only after you know that it will be needed */ '/customize/application_config.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Config, Messages, Store, Util, Crypto, Alertify, Clipboard, Pinpad, AppConfig) { +], function (Config, Messages, Store, Util, Hash, Crypto, Alertify, Clipboard, Pinpad, AppConfig) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata about pads to your local storage for future use and improved usability. @@ -41,6 +42,7 @@ load pinpad dynamically only after you know that it will be needed */ var store; var rpc; + // import common utilities for export var find = common.find = Util.find; var fixHTML = common.fixHTML = Util.fixHTML; var hexToBase64 = common.hexToBase64 = Util.hexToBase64; @@ -51,6 +53,22 @@ load pinpad dynamically only after you know that it will be needed */ var getHash = common.getHash = Util.getHash; var fixFileName = common.fixFileName = Util.fixFileName; + // import hash utilities for export + var createRandomHash = Hash.createRandomHash; + var parsePadUrl = common.parsePadUrl = Hash.parsePadUrl; + var isNotStrongestStored = common.isNotStrongestStored = Hash.isNotStrongestStored; + var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId; + var parseHash = common.parseHash = Hash.parseHash; + var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref; + + common.getEditHashFromKeys = Hash.getEditHashFromKeys; + common.getViewHashFromKeys = Hash.getViewHashFromKeys; + common.getSecrets = Hash.getSecrets; + common.getHashes = Hash.getHashes; + common.createChannelId = Hash.createChannelId; + common.findWeaker = Hash.findWeaker; + common.findStronger = Hash.findStronger; + var getStore = common.getStore = function () { if (store) { return store; } throw new Error("Store is not ready!"); @@ -197,134 +215,6 @@ load pinpad dynamically only after you know that it will be needed */ return text; }; - var parseHash = common.parseHash = function (hash) { - var parsed = {}; - if (hash.slice(0,1) !== '/' && hash.length >= 56) { - // Old hash - parsed.channel = hash.slice(0, 32); - parsed.key = hash.slice(32); - parsed.version = 0; - return parsed; - } - var hashArr = hash.split('/'); - if (hashArr[1] && hashArr[1] === '1') { - parsed.version = 1; - parsed.mode = hashArr[2]; - parsed.channel = hashArr[3]; - parsed.key = hashArr[4]; - parsed.present = hashArr[5] && hashArr[5] === 'present'; - return parsed; - } - return; - }; - - // CRYPTO - var getEditHashFromKeys = common.getEditHashFromKeys = function (chanKey, keys) { - if (typeof keys === 'string') { - return chanKey + keys; - } - if (!keys.editKeyStr) { return; } - return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr); - }; - // CRYPTO - var getViewHashFromKeys = common.getViewHashFromKeys = function (chanKey, keys) { - if (typeof keys === 'string') { - return; - } - return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr); - }; - - /* - * Returns all needed keys for a realtime channel - * - no argument: use the URL hash or create one if it doesn't exist - * - secretHash provided: use secretHash to find the keys - */ - // CRYPTO - var getSecrets = common.getSecrets = function (secretHash) { - var secret = {}; - var generate = function () { - secret.keys = Crypto.createEditCryptor(); - secret.key = Crypto.createEditCryptor().editKeyStr; - }; - if (!secretHash && !/#/.test(window.location.href)) { - generate(); - return secret; - } else { - var hash = secretHash || window.location.hash.slice(1); - if (hash.length === 0) { - generate(); - return secret; - } - // old hash system : #{hexChanKey}{cryptKey} - // new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey} - if (hash.slice(0,1) !== '/' && hash.length >= 56) { - // Old hash - secret.channel = hash.slice(0, 32); - secret.key = hash.slice(32); - } - else { - // New hash - var hashArray = hash.split('/'); - if (hashArray.length < 4) { - common.alert("Unable to parse the key"); - throw new Error("Unable to parse the key"); - } - var version = hashArray[1]; - if (version === "1") { - var mode = hashArray[2]; - if (mode === 'edit') { - secret.channel = base64ToHex(hashArray[3]); - var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/')); - secret.keys = keys; - secret.key = keys.editKeyStr; - if (secret.channel.length !== 32 || secret.key.length !== 24) { - common.alert("The channel key and/or the encryption key is invalid"); - throw new Error("The channel key and/or the encryption key is invalid"); - } - } - else if (mode === 'view') { - secret.channel = base64ToHex(hashArray[3]); - secret.keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/')); - if (secret.channel.length !== 32) { - common.alert("The channel key is invalid"); - throw new Error("The channel key is invalid"); - } - } - } - } - } - return secret; - }; - - // CRYPTO - var getHashes = common.getHashes = function (channel, secret) { - var hashes = {}; - if (secret.keys.editKeyStr) { - hashes.editHash = getEditHashFromKeys(channel, secret.keys); - } - if (secret.keys.viewKeyStr) { - hashes.viewHash = getViewHashFromKeys(channel, secret.keys); - } - return hashes; - }; - - // CRYPTO - var createChannelId = common.createChannelId = function () { - var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16)); - if (id.length !== 32 || /[^a-f0-9]/.test(id)) { - throw new Error('channel ids must consist of 32 hex characters'); - } - return id; - }; - - // CRYPTO - var createRandomHash = common.createRandomHash = function () { - // 16 byte channel Id - var channelId = hexToBase64(createChannelId()); - // 18 byte encryption key - var key = Crypto.b64RemoveSlashes(Crypto.rand64(18)); - return '/1/edit/' + [channelId, key].join('/'); - }; /* * localStorage formatting @@ -407,36 +297,6 @@ load pinpad dynamically only after you know that it will be needed */ }); }; - var getRelativeHref = common.getRelativeHref = function (href) { - if (!href) { return; } - if (href.indexOf('#') === -1) { return; } - var parsed = common.parsePadUrl(href); - return '/' + parsed.type + '/#' + parsed.hash; - }; - - var parsePadUrl = common.parsePadUrl = function (href) { - var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i; - - var ret = {}; - - if (!href) { return ret; } - - if (!/^https*:\/\//.test(href)) { - var idx = href.indexOf('/#'); - ret.type = href.slice(1, idx); - ret.hash = href.slice(idx + 2); - return ret; - } - - var hash = href.replace(patt, function (a, domain, type, hash) { - ret.domain = domain; - ret.type = type; - return ''; - }); - ret.hash = hash.replace(/#/g, ''); - return ret; - }; - var isNameAvailable = function (title, parsed, pads) { return !pads.some(function (pad) { // another pad is already using that title @@ -619,55 +479,6 @@ load pinpad dynamically only after you know that it will be needed */ } }; - // STORAGE - var findWeaker = common.findWeaker = function (href, recents) { - var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref); - if (!parsed.hash) { return false; } - var weaker; - recents.some(function (pad) { - var p = parsePadUrl(pad.href); - if (p.type !== parsed.type) { return; } // Not the same type - if (p.hash === parsed.hash) { return; } // Same hash, not stronger - var pHash = parseHash(p.hash); - var parsedHash = parseHash(parsed.hash); - if (!parsedHash || !pHash) { return; } - if (pHash.version !== parsedHash.version) { return; } - if (pHash.channel !== parsedHash.channel) { return; } - if (pHash.mode === 'view' && parsedHash.mode === 'edit') { - weaker = pad.href; - return true; - } - return; - }); - return weaker; - }; - var findStronger = common.findStronger = function (href, recents) { - var rHref = href || getRelativeHref(window.location.href); - var parsed = parsePadUrl(rHref); - if (!parsed.hash) { return false; } - var stronger; - recents.some(function (pad) { - var p = parsePadUrl(pad.href); - if (p.type !== parsed.type) { return; } // Not the same type - if (p.hash === parsed.hash) { return; } // Same hash, not stronger - var pHash = parseHash(p.hash); - var parsedHash = parseHash(parsed.hash); - if (!parsedHash || !pHash) { return; } - if (pHash.version !== parsedHash.version) { return; } - if (pHash.channel !== parsedHash.channel) { return; } - if (pHash.mode === 'edit' && parsedHash.mode === 'view') { - stronger = pad.href; - return true; - } - return; - }); - return stronger; - }; - var isNotStrongestStored = common.isNotStrongestStored = function (href, recents) { - return findStronger(href, recents); - }; - // TODO integrate pinning var setPadTitle = common.setPadTitle = function (name, cb) { var href = window.location.href; @@ -746,7 +557,7 @@ load pinpad dynamically only after you know that it will be needed */ var getPadTitle = common.getPadTitle = function (cb) { var href = window.location.href; var parsed = parsePadUrl(window.location.href); - var hashSlice = window.location.hash.slice(1,9); + var hashSlice = window.location.hash.slice(1,9); // TODO remove var title = ''; getRecentPads(function (err, pads) { @@ -862,27 +673,6 @@ load pinpad dynamically only after you know that it will be needed */ }); }; - var hrefToHexChannelId = common.hrefToHexChannelId = function (href) { - var parsed = common.parsePadUrl(href); - if (!parsed || !parsed.hash) { return; } - - parsed = common.parseHash(parsed.hash); - - if (parsed.version === 0) { - return parsed.channel; - } else if (parsed.version !== 1) { - console.error("parsed href had no version"); - console.error(parsed); - return; - } - - var channel = parsed.channel; - if (!channel) { return; } - - var hex = common.base64ToHex(channel); - return hex; - }; - var getUserChannelList = common.getUserChannelList = function () { var store = common.getStore(); var proxy = store.getProxy(); diff --git a/www/common/hash.js b/www/common/hash.js new file mode 100644 index 000000000..fa1407315 --- /dev/null +++ b/www/common/hash.js @@ -0,0 +1,236 @@ +define([ + '/Hash/Hash-util.js', + '/bower_components/chainpad-crypto/crypto.js', +], function (Util, Crypto) { + var Hash = {}; + + var uint8ArrayToHex = Util.uint8ArrayToHex; + var hexToBase64 = Util.hexToBase64; + var base64ToHex = Util.base64ToHex; + + var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) { + if (typeof keys === 'string') { + return chanKey + keys; + } + if (!keys.editKeyStr) { return; } + return '/1/edit/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.editKeyStr); + }; + var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) { + if (typeof keys === 'string') { + return; + } + return '/1/view/' + hexToBase64(chanKey) + '/' + Crypto.b64RemoveSlashes(keys.viewKeyStr); + }; + + var parsePadUrl = Hash.parsePadUrl = function (href) { + var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i; + + var ret = {}; + + if (!href) { return ret; } + + if (!/^https*:\/\//.test(href)) { + var idx = href.indexOf('/#'); + ret.type = href.slice(1, idx); + ret.hash = href.slice(idx + 2); + return ret; + } + + var hash = href.replace(patt, function (a, domain, type, hash) { + ret.domain = domain; + ret.type = type; + return ''; + }); + ret.hash = hash.replace(/#/g, ''); + return ret; + }; + + var getRelativeHref = Hash.getRelativeHref = function (href) { + if (!href) { return; } + if (href.indexOf('#') === -1) { return; } + var parsed = parsePadUrl(href); + return '/' + parsed.type + '/#' + parsed.hash; + }; + + /* + * Returns all needed keys for a realtime channel + * - no argument: use the URL hash or create one if it doesn't exist + * - secretHash provided: use secretHash to find the keys + */ + var getSecrets = Hash.getSecrets = function (secretHash) { + var secret = {}; + var generate = function () { + secret.keys = Crypto.createEditCryptor(); + secret.key = Crypto.createEditCryptor().editKeyStr; + }; + if (!secretHash && !/#/.test(window.location.href)) { + generate(); + return secret; + } else { + var hash = secretHash || window.location.hash.slice(1); + if (hash.length === 0) { + generate(); + return secret; + } + // old hash system : #{hexChanKey}{cryptKey} + // new hash system : #/{hashVersion}/{b64ChanKey}/{cryptKey} + if (hash.slice(0,1) !== '/' && hash.length >= 56) { + // Old hash + secret.channel = hash.slice(0, 32); + secret.key = hash.slice(32); + } + else { + // New hash + var hashArray = hash.split('/'); + if (hashArray.length < 4) { + Hash.alert("Unable to parse the key"); + throw new Error("Unable to parse the key"); + } + var version = hashArray[1]; + if (version === "1") { + var mode = hashArray[2]; + if (mode === 'edit') { + secret.channel = base64ToHex(hashArray[3]); + var keys = Crypto.createEditCryptor(hashArray[4].replace(/-/g, '/')); + secret.keys = keys; + secret.key = keys.editKeyStr; + if (secret.channel.length !== 32 || secret.key.length !== 24) { + Hash.alert("The channel key and/or the encryption key is invalid"); + throw new Error("The channel key and/or the encryption key is invalid"); + } + } + else if (mode === 'view') { + secret.channel = base64ToHex(hashArray[3]); + secret.keys = Crypto.createViewCryptor(hashArray[4].replace(/-/g, '/')); + if (secret.channel.length !== 32) { + Hash.alert("The channel key is invalid"); + throw new Error("The channel key is invalid"); + } + } + } + } + } + return secret; + }; + + var getHashes = Hash.getHashes = function (channel, secret) { + var hashes = {}; + if (secret.keys.editKeyStr) { + hashes.editHash = getEditHashFromKeys(channel, secret.keys); + } + if (secret.keys.viewKeyStr) { + hashes.viewHash = getViewHashFromKeys(channel, secret.keys); + } + return hashes; + }; + + var createChannelId = Hash.createChannelId = function () { + var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16)); + if (id.length !== 32 || /[^a-f0-9]/.test(id)) { + throw new Error('channel ids must consist of 32 hex characters'); + } + return id; + }; + + var createRandomHash = Hash.createRandomHash = function () { + // 16 byte channel Id + var channelId = Util.hexToBase64(createChannelId()); + // 18 byte encryption key + var key = Crypto.b64RemoveSlashes(Crypto.rand64(18)); + return '/1/edit/' + [channelId, key].join('/'); + }; + + var parseHash = Hash.parseHash = function (hash) { + var parsed = {}; + if (hash.slice(0,1) !== '/' && hash.length >= 56) { + // Old hash + parsed.channel = hash.slice(0, 32); + parsed.key = hash.slice(32); + parsed.version = 0; + return parsed; + } + var hashArr = hash.split('/'); + if (hashArr[1] && hashArr[1] === '1') { + parsed.version = 1; + parsed.mode = hashArr[2]; + parsed.channel = hashArr[3]; + parsed.key = hashArr[4]; + parsed.present = hashArr[5] && hashArr[5] === 'present'; + return parsed; + } + return; + }; + + // STORAGE + var findWeaker = Hash.findWeaker = function (href, recents) { + var rHref = href || getRelativeHref(window.location.href); + var parsed = parsePadUrl(rHref); + if (!parsed.hash) { return false; } + var weaker; + recents.some(function (pad) { + var p = parsePadUrl(pad.href); + if (p.type !== parsed.type) { return; } // Not the same type + if (p.hash === parsed.hash) { return; } // Same hash, not stronger + var pHash = parseHash(p.hash); + var parsedHash = parseHash(parsed.hash); + if (!parsedHash || !pHash) { return; } + if (pHash.version !== parsedHash.version) { return; } + if (pHash.channel !== parsedHash.channel) { return; } + if (pHash.mode === 'view' && parsedHash.mode === 'edit') { + weaker = pad.href; + return true; + } + return; + }); + return weaker; + }; + var findStronger = Hash.findStronger = function (href, recents) { + var rHref = href || getRelativeHref(window.location.href); + var parsed = parsePadUrl(rHref); + if (!parsed.hash) { return false; } + var stronger; + recents.some(function (pad) { + var p = parsePadUrl(pad.href); + if (p.type !== parsed.type) { return; } // Not the same type + if (p.hash === parsed.hash) { return; } // Same hash, not stronger + var pHash = parseHash(p.hash); + var parsedHash = parseHash(parsed.hash); + if (!parsedHash || !pHash) { return; } + if (pHash.version !== parsedHash.version) { return; } + if (pHash.channel !== parsedHash.channel) { return; } + if (pHash.mode === 'edit' && parsedHash.mode === 'view') { + stronger = pad.href; + return true; + } + return; + }); + return stronger; + }; + var isNotStrongestStored = Hash.isNotStrongestStored = function (href, recents) { + return findStronger(href, recents); + }; + + var hrefToHexChannelId = Hash.hrefToHexChannelId = function (href) { + var parsed = Hash.parsePadUrl(href); + if (!parsed || !parsed.hash) { return; } + + parsed = Hash.parseHash(parsed.hash); + + if (parsed.version === 0) { + return parsed.channel; + } else if (parsed.version !== 1) { + console.error("parsed href had no version"); + console.error(parsed); + return; + } + + var channel = parsed.channel; + if (!channel) { return; } + + var hex = Hash.base64ToHex(channel); + return hex; + }; + + + return Hash; +});