|
|
|
@ -19,7 +19,50 @@ define([
|
|
|
|
|
.decodeUTF8(JSON.stringify(list))));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
|
|
|
|
|
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
|
|
|
|
|
var version = secret.version;
|
|
|
|
|
var data = secret.keys;
|
|
|
|
|
if (version === 0) {
|
|
|
|
|
return secret.channel + secret.key;
|
|
|
|
|
}
|
|
|
|
|
if (version === 1) {
|
|
|
|
|
if (!data.editKeyStr) { return; }
|
|
|
|
|
return '/1/edit/' + hexToBase64(secret.channel) +
|
|
|
|
|
'/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
|
|
|
|
|
}
|
|
|
|
|
if (version === 2) {
|
|
|
|
|
if (!data.editKeyStr) { return; }
|
|
|
|
|
var pass = secret.password ? 'p/' : '';
|
|
|
|
|
return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
|
|
|
|
|
var version = secret.version;
|
|
|
|
|
var data = secret.keys;
|
|
|
|
|
if (version === 0) { return; }
|
|
|
|
|
if (version === 1) {
|
|
|
|
|
if (!data.viewKeyStr) { return; }
|
|
|
|
|
return '/1/view/' + hexToBase64(secret.channel) +
|
|
|
|
|
'/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
|
|
|
|
|
}
|
|
|
|
|
if (version === 2) {
|
|
|
|
|
if (!data.viewKeyStr) { return; }
|
|
|
|
|
var pass = secret.password ? 'p/' : '';
|
|
|
|
|
return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
|
|
|
|
|
var version = secret.version;
|
|
|
|
|
var data = secret.keys;
|
|
|
|
|
if (version === 0) { return; }
|
|
|
|
|
if (version === 1) {
|
|
|
|
|
return '/1/' + hexToBase64(secret.channel) + '/' +
|
|
|
|
|
Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// V1
|
|
|
|
|
/*var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
|
|
|
|
|
if (typeof keys === 'string') {
|
|
|
|
|
return chanKey + keys;
|
|
|
|
|
}
|
|
|
|
@ -34,7 +77,7 @@ define([
|
|
|
|
|
};
|
|
|
|
|
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
|
|
|
|
|
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
|
|
|
|
|
};
|
|
|
|
|
};*/
|
|
|
|
|
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
|
|
|
|
|
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
|
|
|
|
|
};
|
|
|
|
@ -43,6 +86,24 @@ define([
|
|
|
|
|
return s.replace(/\/+/g, '/');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Hash.createRandomHash = function (type, password) {
|
|
|
|
|
var cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
|
|
|
|
|
return getEditHashFromKeys({
|
|
|
|
|
password: Boolean(password),
|
|
|
|
|
version: 2,
|
|
|
|
|
type: type,
|
|
|
|
|
keys: { editKeyStr: cryptor.editKeyStr }
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Version 0
|
|
|
|
|
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
|
|
|
|
@ -50,25 +111,49 @@ Version 1
|
|
|
|
|
/code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
|
|
|
|
|
var parseTypeHash = Hash.parseTypeHash = function (type, hash, password) {
|
|
|
|
|
if (!hash) { return; }
|
|
|
|
|
var parsed = {};
|
|
|
|
|
var hashArr = fixDuplicateSlashes(hash).split('/');
|
|
|
|
|
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
|
|
|
|
|
parsed.type = 'pad';
|
|
|
|
|
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
|
|
|
|
|
if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
|
|
|
|
|
// Old hash
|
|
|
|
|
parsed.channel = hash.slice(0, 32);
|
|
|
|
|
parsed.key = hash.slice(32, 56);
|
|
|
|
|
parsed.version = 0;
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
if (hashArr[1] && hashArr[1] === '1') {
|
|
|
|
|
var options;
|
|
|
|
|
if (hashArr[1] && hashArr[1] === '1') { // Version 1
|
|
|
|
|
parsed.version = 1;
|
|
|
|
|
parsed.mode = hashArr[2];
|
|
|
|
|
parsed.channel = hashArr[3];
|
|
|
|
|
parsed.key = hashArr[4].replace(/-/g, '/');
|
|
|
|
|
var options = hashArr.slice(5);
|
|
|
|
|
parsed.key = Crypto.b64AddSlashes(hashArr[4]);
|
|
|
|
|
|
|
|
|
|
options = hashArr.slice(5);
|
|
|
|
|
parsed.present = options.indexOf('present') !== -1;
|
|
|
|
|
parsed.embed = options.indexOf('embed') !== -1;
|
|
|
|
|
return parsed;
|
|
|
|
|
}
|
|
|
|
|
if (hashArr[1] && hashArr[1] === '2') { // Version 2
|
|
|
|
|
parsed.version = 2;
|
|
|
|
|
parsed.app = hashArr[2];
|
|
|
|
|
parsed.mode = hashArr[3];
|
|
|
|
|
parsed.key = hashArr[4];
|
|
|
|
|
|
|
|
|
|
var cryptor;
|
|
|
|
|
if (parsed.mode === "edit") {
|
|
|
|
|
cryptor = Crypto.createEditCryptor2(parsed.key, void 0, password);
|
|
|
|
|
} else if (parsed.mode === "view") {
|
|
|
|
|
cryptor = Crypto.createViewCryptor2(parsed.key, password);
|
|
|
|
|
}
|
|
|
|
|
parsed.channel = cryptor.chanId;
|
|
|
|
|
parsed.cryptKey = cryptor.cryptKey;
|
|
|
|
|
parsed.validateKey = cryptor.validateKey;
|
|
|
|
|
|
|
|
|
|
options = hashArr.slice(5);
|
|
|
|
|
parsed.password = options.indexOf('p') !== -1;
|
|
|
|
|
parsed.present = options.indexOf('present') !== -1;
|
|
|
|
|
parsed.embed = options.indexOf('embed') !== -1;
|
|
|
|
|
return parsed;
|
|
|
|
@ -107,7 +192,7 @@ Version 1
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
var parsePadUrl = Hash.parsePadUrl = function (href) {
|
|
|
|
|
var parsePadUrl = Hash.parsePadUrl = function (href, password) {
|
|
|
|
|
var patt = /^https*:\/\/([^\/]*)\/(.*?)\//i;
|
|
|
|
|
|
|
|
|
|
var ret = {};
|
|
|
|
@ -125,17 +210,33 @@ Version 1
|
|
|
|
|
url += ret.type + '/';
|
|
|
|
|
if (!ret.hashData) { return url; }
|
|
|
|
|
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
|
|
|
|
|
if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
|
|
|
|
|
url += '#/' + ret.hashData.version +
|
|
|
|
|
'/' + ret.hashData.mode +
|
|
|
|
|
'/' + ret.hashData.channel.replace(/\//g, '-') +
|
|
|
|
|
'/' + ret.hashData.key.replace(/\//g, '-') +'/';
|
|
|
|
|
if (options.embed) {
|
|
|
|
|
url += 'embed/';
|
|
|
|
|
if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
|
|
|
|
|
var hash;
|
|
|
|
|
if (options.readOnly === true ||
|
|
|
|
|
(typeof (options.readOnly === "undefined") && ret.hashData.mode === "view")) {
|
|
|
|
|
hash = getViewHashFromKeys({
|
|
|
|
|
version: ret.hashData.version,
|
|
|
|
|
type: ret.hashData.app,
|
|
|
|
|
channel: base64ToHex(ret.hashData.channel || ''),
|
|
|
|
|
password: ret.hashData.password,
|
|
|
|
|
keys: {
|
|
|
|
|
viewKeyStr: ret.hashData.key
|
|
|
|
|
}
|
|
|
|
|
if (options.present) {
|
|
|
|
|
url += 'present/';
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
hash = getEditHashFromKeys({
|
|
|
|
|
version: ret.hashData.version,
|
|
|
|
|
type: ret.hashData.app,
|
|
|
|
|
channel: base64ToHex(ret.hashData.channel || ''),
|
|
|
|
|
password: ret.hashData.password,
|
|
|
|
|
keys: {
|
|
|
|
|
editKeyStr: ret.hashData.key
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
url += '#' + hash;
|
|
|
|
|
if (options.embed) { url += 'embed/'; }
|
|
|
|
|
if (options.present) { url += 'present/'; }
|
|
|
|
|
return url;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -143,7 +244,7 @@ Version 1
|
|
|
|
|
idx = href.indexOf('/#');
|
|
|
|
|
ret.type = href.slice(1, idx);
|
|
|
|
|
ret.hash = href.slice(idx + 2);
|
|
|
|
|
ret.hashData = parseTypeHash(ret.type, ret.hash);
|
|
|
|
|
ret.hashData = parseTypeHash(ret.type, ret.hash, password);
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -154,7 +255,7 @@ Version 1
|
|
|
|
|
});
|
|
|
|
|
idx = href.indexOf('/#');
|
|
|
|
|
ret.hash = href.slice(idx + 2);
|
|
|
|
|
ret.hashData = parseTypeHash(ret.type, ret.hash);
|
|
|
|
|
ret.hashData = parseTypeHash(ret.type, ret.hash, password);
|
|
|
|
|
return ret;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
@ -170,11 +271,13 @@ Version 1
|
|
|
|
|
* - no argument: use the URL hash or create one if it doesn't exist
|
|
|
|
|
* - secretHash provided: use secretHash to find the keys
|
|
|
|
|
*/
|
|
|
|
|
Hash.getSecrets = function (type, secretHash) {
|
|
|
|
|
Hash.getSecrets = function (type, secretHash, password) {
|
|
|
|
|
var secret = {};
|
|
|
|
|
var generate = function () {
|
|
|
|
|
secret.keys = Crypto.createEditCryptor();
|
|
|
|
|
secret.key = Crypto.createEditCryptor().editKeyStr;
|
|
|
|
|
secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
|
|
|
|
|
secret.channel = base64ToHex(secret.keys.chanId);
|
|
|
|
|
secret.version = 2;
|
|
|
|
|
secret.type = type;
|
|
|
|
|
};
|
|
|
|
|
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
|
|
|
|
|
generate();
|
|
|
|
@ -203,9 +306,10 @@ Version 1
|
|
|
|
|
// Old hash
|
|
|
|
|
secret.channel = parsed.channel;
|
|
|
|
|
secret.key = parsed.key;
|
|
|
|
|
}
|
|
|
|
|
else if (parsed.version === 1) {
|
|
|
|
|
secret.version = 0;
|
|
|
|
|
} else if (parsed.version === 1) {
|
|
|
|
|
// New hash
|
|
|
|
|
secret.version = 1;
|
|
|
|
|
if (parsed.type === "pad") {
|
|
|
|
|
secret.channel = base64ToHex(parsed.channel);
|
|
|
|
|
if (parsed.mode === 'edit') {
|
|
|
|
@ -229,45 +333,60 @@ Version 1
|
|
|
|
|
// version 2 hashes are to be used for encrypted blobs
|
|
|
|
|
throw new Error("User hashes can't be opened (yet)");
|
|
|
|
|
}
|
|
|
|
|
} else if (parsed.version === 2) {
|
|
|
|
|
// New hash
|
|
|
|
|
secret.version = 2;
|
|
|
|
|
secret.type = type;
|
|
|
|
|
secret.password = Boolean(password);
|
|
|
|
|
if (parsed.type === "pad") {
|
|
|
|
|
if (parsed.mode === 'edit') {
|
|
|
|
|
secret.keys = Crypto.createEditCryptor2(parsed.key);
|
|
|
|
|
secret.channel = base64ToHex(secret.keys.chanId);
|
|
|
|
|
secret.key = secret.keys.editKeyStr;
|
|
|
|
|
if (secret.channel.length !== 32 || secret.key.length !== 24) {
|
|
|
|
|
throw new Error("The channel key and/or the encryption key is invalid");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (parsed.mode === 'view') {
|
|
|
|
|
secret.keys = Crypto.createViewCryptor2(parsed.key);
|
|
|
|
|
secret.channel = base64ToHex(secret.keys.chanId);
|
|
|
|
|
if (secret.channel.length !== 32) {
|
|
|
|
|
throw new Error("The channel key is invalid");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (parsed.type === "file") {
|
|
|
|
|
throw new Error("File hashes should be version 1");
|
|
|
|
|
} else if (parsed.type === "user") {
|
|
|
|
|
throw new Error("User hashes can't be opened (yet)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return secret;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Hash.getHashes = function (channel, secret) {
|
|
|
|
|
Hash.getHashes = function (secret) {
|
|
|
|
|
var hashes = {};
|
|
|
|
|
if (!secret.keys) {
|
|
|
|
|
secret = JSON.parse(JSON.stringify(secret));
|
|
|
|
|
|
|
|
|
|
if (!secret.keys && !secret.key) {
|
|
|
|
|
console.error('e');
|
|
|
|
|
return hashes;
|
|
|
|
|
} else if (!secret.keys) {
|
|
|
|
|
secret.keys = {};
|
|
|
|
|
}
|
|
|
|
|
if (secret.keys.editKeyStr) {
|
|
|
|
|
hashes.editHash = getEditHashFromKeys(channel, secret.keys);
|
|
|
|
|
|
|
|
|
|
if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
|
|
|
|
|
hashes.editHash = getEditHashFromKeys(secret);
|
|
|
|
|
}
|
|
|
|
|
if (secret.keys.viewKeyStr) {
|
|
|
|
|
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
|
|
|
|
|
hashes.viewHash = getViewHashFromKeys(secret);
|
|
|
|
|
}
|
|
|
|
|
if (secret.keys.fileKeyStr) {
|
|
|
|
|
hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
|
|
|
|
|
hashes.fileHash = getFileHashFromKeys(secret);
|
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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('/') + '/';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// STORAGE
|
|
|
|
|
Hash.findWeaker = function (href, recents) {
|
|
|
|
|
var rHref = href || getRelativeHref(window.location.href);
|
|
|
|
@ -336,7 +455,7 @@ Version 1
|
|
|
|
|
parsed = parsed.hashData;
|
|
|
|
|
if (parsed.version === 0) {
|
|
|
|
|
return parsed.channel;
|
|
|
|
|
} else if (parsed.version !== 1 && parsed.version !== 2) {
|
|
|
|
|
} else if (!parsed.version) {
|
|
|
|
|
console.error("parsed href had no version");
|
|
|
|
|
console.error(parsed);
|
|
|
|
|
return;
|
|
|
|
|