+
+ // [b64_public, b64_sig, b64_block [version, nonce, content]]
+
+ Block.seed = function () {
+ return Nacl.hash(Nacl.util.decodeUTF8('pewpewpew'));
+ };
+
+ // should be deterministic from a seed...
+ Block.genkeys = function (seed) {
+ if (!(seed instanceof Uint8Array)) {
+ throw new Error('INVALID_SEED_FORMAT');
+ }
+ if (!seed || typeof(seed.length) !== 'number' || seed.length < 64) {
+ throw new Error('INVALID_SEED_LENGTH');
+ }
+
+ var signSeed = seed.subarray(0, Nacl.sign.seedLength);
+ 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, // 32 bytes ...
+ };
+ };
+
+ // (UTF8 content, keys object) => Uint8Array block
+ Block.encrypt = function (version, content, keys) {
+ var u8 = Nacl.util.decodeUTF8(content);
+ var nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
+ return Block.join([
+ [0],
+ nonce,
+ Nacl.secretbox(u8, nonce, keys.symmetric)
+ ]);
+ };
+
+ // (uint8Array block) => payload object
+ 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 = 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
+ Block.sign = function (ciphertext, keys) {
+ return Nacl.sign.detached(Nacl.hash(ciphertext), keys.sign.secretKey);
+ };
+
+ Block.serialize = function (content, keys) {
+ // encrypt the content
+ var ciphertext = Block.encrypt(0, content, keys);
+
+ // generate a detached signature
+ var sig = Block.sign(ciphertext, keys);
+
+ // serialize {publickey, sig, ciphertext}
+ return {
+ publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
+ signature: Nacl.util.encodeBase64(sig),
+ ciphertext: Nacl.util.encodeBase64(ciphertext),
+ };
+ };
+
+ Block.remove = function (keys) {
+ // sign the hash of the text 'DELETE_BLOCK'
+ var sig = Nacl.sign.detached(Nacl.hash(
+ Nacl.util.decodeUTF8('DELETE_BLOCK')), keys.sign.secretKey);
+
+ return {
+ publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
+ signature: Nacl.util.encodeBase64(sig),
+ };
+ };
+
+ // 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);
+ // 'block/' here is hardcoded because it's hardcoded on the server
+ // if we want to make CryptPad work in server subfolders, we'll need
+ // to update this path derivation
+ var relative = 'block/' + publicKey.slice(0, 2) + '/' + publicKey;
+ var symmetric = urlSafeB64(keys.symmetric);
+ return ApiConfig.httpUnsafeOrigin + relative + '#' + symmetric;
+ };
+
+/*
+ Block.createBlockHash = function (href, key) {
+ if (typeof(href) !== 'string') { return; }
+ if (!(key instanceof Uint8Array)) { return; }
+
+ 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;
+ }
+ };
+
+ Block.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;
+ }
+ };
+
+ return Block;
+});
diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js
index 972776abe..2ed424610 100644
--- a/www/common/outer/store-rpc.js
+++ b/www/common/outer/store-rpc.js
@@ -24,6 +24,7 @@ define([
UPLOAD_STATUS: Store.uploadStatus,
UPLOAD_CANCEL: Store.uploadCancel,
WRITE_LOGIN_BLOCK: Store.writeLoginBlock,
+ REMOVE_LOGIN_BLOCK: Store.removeLoginBlock,
PIN_PADS: Store.pinPads,
UNPIN_PADS: Store.unpinPads,
GET_DELETED_PADS: Store.getDeletedPads,
diff --git a/www/common/pinpad.js b/www/common/pinpad.js
index 1e4dd6046..721f8a94b 100644
--- a/www/common/pinpad.js
+++ b/www/common/pinpad.js
@@ -222,7 +222,34 @@ define([
};
exp.writeLoginBlock = function (data, cb) {
- cb();
+ if (!data) { return void cb('NO_DATA'); }
+ if (!data.publicKey || !data.signature || !data.ciphertext) {
+ console.log(data);
+ return void cb("MISSING_PARAMETERS");
+ }
+
+ rpc.send('WRITE_LOGIN_BLOCK', [
+ data.publicKey,
+ data.signature,
+ data.ciphertext
+ ], function (e) {
+ cb(e);
+ });
+ };
+
+ exp.removeLoginBlock = function (data, cb) {
+ if (!data) { return void cb('NO_DATA'); }
+ if (!data.publicKey || !data.signature) {
+ console.log(data);
+ return void cb("MISSING_PARAMETERS");
+ }
+
+ rpc.send('REMOVE_LOGIN_BLOCK', [
+ data.publicKey, // publicKey
+ data.signature, // signature
+ ], function (e) {
+ cb(e);
+ });
};
cb(e, exp);
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index 8b30aa2e2..6009b35cc 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -661,10 +661,18 @@ define([
Cryptpad.changePadPassword(Cryptget, href, data.password, edPublic, cb);
});
+ sframeChan.on('Q_CHANGE_USER_PASSWORD', function (data, cb) {
+ Cryptpad.changeUserPassword(Cryptget, edPublic, data, cb);
+ });
+
sframeChan.on('Q_WRITE_LOGIN_BLOCK', function (data, cb) {
Cryptpad.writeLoginBlock(data, cb);
});
+ sframeChan.on('Q_REMOVE_LOGIN_BLOCK', function (data, cb) {
+ Cryptpad.removeLoginBlock(data, cb);
+ });
+
if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad, Utils);
}
diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js
index a23124a65..d65062fa8 100644
--- a/www/common/sframe-protocol.js
+++ b/www/common/sframe-protocol.js
@@ -77,6 +77,9 @@ define({
// Write/update the login block when the account password is changed
'Q_WRITE_LOGIN_BLOCK': true,
+ // Remove login blocks
+ 'Q_REMOVE_LOGIN_BLOCK': true,
+
// Check the pin limit to determine if we can store the pad in the drive or if we should.
// display a warning
'Q_GET_PIN_LIMIT_STATUS': true,
@@ -235,6 +238,9 @@ define({
// Change pad password
'Q_PAD_PASSWORD_CHANGE': true,
+ // Migrate drive to owned drive
+ 'Q_CHANGE_USER_PASSWORD': true,
+
// Loading events to display in the loading screen
'EV_LOADING_INFO': true,
// Critical error outside the iframe during loading screen
diff --git a/www/settings/inner.js b/www/settings/inner.js
index 8d06b2da5..9cdda9d00 100644
--- a/www/settings/inner.js
+++ b/www/settings/inner.js
@@ -50,7 +50,7 @@ define([
'cp-settings-resettips',
'cp-settings-thumbnails',
'cp-settings-userfeedback',
- //'cp-settings-change-password',
+ 'cp-settings-change-password',
'cp-settings-delete'
],
'creation': [
@@ -404,12 +404,11 @@ define([
$(form).appendTo($div);
var updateBlock = function (data, cb) {
- sframeChan.query('Q_WRITE_LOGIN_BLOCK', data, function (err, obj) {
+ sframeChan.query('Q_CHANGE_USER_PASSWORD', data, function (err, obj) {
if (err || obj.error) { return void cb ({error: err || obj.error}); }
cb (obj);
});
};
- updateBlock = updateBlock; // jshint..
var todo = function () {
var oldPassword = $(form).find('#cp-settings-change-password-current').val();
@@ -432,8 +431,15 @@ define([
UI.confirm(Messages.settings_changePasswordConfirm,
function (yes) {
if (!yes) { return; }
- // TODO
- console.log(oldPassword, newPassword, newPasswordConfirm);
+ updateBlock({
+ password: oldPassword,
+ newPassword: newPassword
+ }, function (obj) {
+ if (obj && obj.error) {
+ // TODO
+ UI.alert(Messages.settings_changePasswordError);
+ }
+ });
}, {
ok: Messages.register_writtenPassword,
cancel: Messages.register_cancel,
@@ -461,6 +467,50 @@ define([
return $div;
};
+ create['migrate'] = function () {
+ if (true) { return; } // XXX js hint
+ // TODO
+ // if (!loginBlock) { return; }
+ // if (alreadyMigrated) { return; }
+ if (!common.isLoggedIn()) { return; }
+
+ var $div = $('', { 'class': 'cp-settings-migrate cp-sidebarlayout-element'});
+
+ $('', {'class': 'label'}).text(Messages.settings_ownDriveTitle).appendTo($div);
+
+ $('', {'class': 'cp-sidebarlayout-description'})
+ .append(Messages.settings_ownDriveHint).appendTo($div);
+
+ var $ok = $('', {'class': 'fa fa-check', title: Messages.saved});
+ var $spinner = $('', {'class': 'fa fa-spinner fa-pulse'});
+
+ var $button = $('