mirror of https://github.com/xwiki-labs/cryptpad
initial implementation of serverside password change RPCs
This commit is contained in:
parent
2463dd0c9e
commit
9d3d8c3ba2
|
@ -211,6 +211,11 @@ module.exports = {
|
|||
*/
|
||||
taskPath: './tasks',
|
||||
|
||||
/* if you would like users' authenticated blocks to be stored in
|
||||
a custom location, change the path below:
|
||||
*/
|
||||
blockPath: './block',
|
||||
|
||||
/*
|
||||
* By default, CryptPad also contacts our accounts server once a day to check for changes in
|
||||
* the people who have accounts. This check-in will also send the version of your CryptPad
|
||||
|
|
171
rpc.js
171
rpc.js
|
@ -1297,6 +1297,164 @@ var upload_status = function (Env, publicKey, filesize, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
/*
|
||||
We assume that the server is secured against MitM attacks
|
||||
via HTTPS, and that malicious actors do not have code execution
|
||||
capabilities. If they do, we have much more serious problems.
|
||||
|
||||
The capability to replay a block write or remove results in either
|
||||
a denial of service for the user whose block was removed, or in the
|
||||
case of a write, a rollback to an earlier password.
|
||||
|
||||
Since block modification is destructive, this can result in loss
|
||||
of access to the user's drive.
|
||||
|
||||
So long as the detached signature is never observed by a malicious
|
||||
party, and the server discards it after proof of knowledge, replays
|
||||
are not possible. However, this precludes verification of the signature
|
||||
at a later time.
|
||||
|
||||
Despite this, an integrity check is still possible by the original
|
||||
author of the block, since we assume that the block will have been
|
||||
encrypted with xsalsa20-poly1305 which is authenticated.
|
||||
*/
|
||||
var validateLoginBlock = function (Env, publicKey, signature, block, cb) {
|
||||
// convert the public key to a Uint8Array and validate it
|
||||
if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); }
|
||||
|
||||
var u8_public_key;
|
||||
try {
|
||||
u8_public_key = Nacl.util.decodeBase64(publicKey);
|
||||
} catch (e) {
|
||||
return void cb('E_INVALID_KEY');
|
||||
}
|
||||
|
||||
var u8_signature;
|
||||
try {
|
||||
u8_signature = Nacl.util.decodeBase64(signature);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return void cb('E_INVALID_SIGNATURE');
|
||||
}
|
||||
|
||||
// convert the block to a Uint8Array
|
||||
var u8_block;
|
||||
try {
|
||||
u8_block = Nacl.util.decodeBase64(block);
|
||||
} catch (e) {
|
||||
// TODO print to console
|
||||
return void cb('E_INVALID_BLOCK');
|
||||
}
|
||||
|
||||
// take its hash
|
||||
var hash = Nacl.hash(u8_block);
|
||||
|
||||
// validate the signature against the hash of the content
|
||||
var verified = Nacl.sign.detached.verify(hash, u8_signature, u8_public_key);
|
||||
|
||||
// existing authentication ensures that users cannot replay old blocks
|
||||
|
||||
// call back with (err) if unsuccessful
|
||||
if (!verified) { return void cb("E_COULD_NOT_VERIFY"); }
|
||||
|
||||
return void cb(null, u8_block);
|
||||
|
||||
// signature 64 bytes
|
||||
// sign.detached(hash(decodeBase64_content(base64_content)), decodeBase64(publicKey))
|
||||
|
||||
// 1 byte version
|
||||
// base64_content
|
||||
};
|
||||
|
||||
var createLoginBlockPath = function (Env, publicKey) {
|
||||
// prepare publicKey to be used as a file name
|
||||
var safeKey = escapeKeyCharacters(publicKey);
|
||||
|
||||
// validate safeKey
|
||||
if (typeof(safeKey) !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
// derive the full path
|
||||
// /home/cryptpad/cryptpad/block/fg/fg32kefksjdgjkewrjksdfksjdfsdfskdjfsfd
|
||||
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
|
||||
};
|
||||
|
||||
var writeLoginBlock = function (Env, msg, cb) {
|
||||
console.log(msg); // XXX
|
||||
|
||||
var publicKey = msg[0];
|
||||
var signature = msg[1];
|
||||
var block = msg[2];
|
||||
|
||||
validateLoginBlock(Env, publicKey, signature, block, function (e, verified_block) {
|
||||
if (e) { return void cb(e); }
|
||||
|
||||
// derive the filepath
|
||||
var path = createLoginBlockPath(Env, publicKey);
|
||||
|
||||
// make sure the path is valid
|
||||
if (typeof(path) !== 'string') {
|
||||
return void cb('E_INVALID_BLOCK_PATH');
|
||||
}
|
||||
|
||||
var parsed = Path.parse(path);
|
||||
if (!parsed || typeof(parsed.dir) !== 'string') {
|
||||
return void cb("E_INVALID_BLOCK_PATH_2");
|
||||
}
|
||||
|
||||
nThen(function (w) {
|
||||
// make sure the path to the file exists
|
||||
Mkdirp(parsed.dir, w(function (e) {
|
||||
if (e) {
|
||||
w.abort();
|
||||
cb(e);
|
||||
}
|
||||
}));
|
||||
}).nThen(function () {
|
||||
// actually write the block
|
||||
Fs.writeFile(path, new Buffer(verified_block), { encoding: "binary", }, function (err) {
|
||||
if (err) { return void cb(err); }
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
When users write a block, they upload the block, and provide
|
||||
a signature proving that they deserve to be able to write to
|
||||
the location determined by the public key.
|
||||
|
||||
When removing a block, there is nothing to upload, but we need
|
||||
to sign something. Since the signature is considered sensitive
|
||||
information, we can just sign some constant and use that as proof.
|
||||
|
||||
*/
|
||||
var removeLoginBlock = function (Env, msg, cb) {
|
||||
console.log(msg); // XXX
|
||||
|
||||
var publicKey = msg[0];
|
||||
var signature = msg[1];
|
||||
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
|
||||
|
||||
validateLoginBlock(Env, publicKey, signature, block, function (e) {
|
||||
if (e) { return void cb(e); }
|
||||
// derive the filepath
|
||||
var path = createLoginBlockPath(Env, publicKey);
|
||||
|
||||
// make sure the path is valid
|
||||
if (typeof(path) !== 'string') {
|
||||
return void cb('E_INVALID_BLOCK_PATH');
|
||||
}
|
||||
|
||||
Fs.unlink(path, function (err) {
|
||||
if (err) { return void cb(err); }
|
||||
cb();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var isNewChannel = function (Env, channel, cb) {
|
||||
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
||||
if (channel.length !== 32) { return void cb('INVALID_CHAN'); }
|
||||
|
@ -1353,6 +1511,8 @@ var isAuthenticatedCall = function (call) {
|
|||
'CLEAR_OWNED_CHANNEL',
|
||||
'REMOVE_OWNED_CHANNEL',
|
||||
'REMOVE_PINS',
|
||||
'WRITE_LOGIN_BLOCK',
|
||||
'REMOVE_LOGIN_BLOCK',
|
||||
].indexOf(call) !== -1;
|
||||
};
|
||||
|
||||
|
@ -1423,6 +1583,7 @@ RPC.create = function (
|
|||
var pinPath = paths.pin = keyOrDefaultString('pinPath', './pins');
|
||||
var blobPath = paths.blob = keyOrDefaultString('blobPath', './blob');
|
||||
var blobStagingPath = paths.staging = keyOrDefaultString('blobStagingPath', './blobstage');
|
||||
paths.block = keyOrDefaultString('blockPath', './block');
|
||||
|
||||
var isUnauthenticateMessage = function (msg) {
|
||||
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]);
|
||||
|
@ -1692,6 +1853,16 @@ RPC.create = function (
|
|||
WARN(e, 'UPLOAD_CANCEL');
|
||||
Respond(e);
|
||||
});
|
||||
case 'WRITE_LOGIN_BLOCK':
|
||||
return void writeLoginBlock(Env, msg, function (e) {
|
||||
// TODO handle response
|
||||
e = e;
|
||||
});
|
||||
case 'REMOVE_LOGIN_BLOCK':
|
||||
return void removeLoginBlock(Env, msg, function (e) {
|
||||
// TODO handle response
|
||||
e = e;
|
||||
});
|
||||
default:
|
||||
return void Respond('UNSUPPORTED_RPC_CALL', msg);
|
||||
}
|
||||
|
|
|
@ -126,6 +126,9 @@ app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob
|
|||
app.use("/datastore", Express.static(Path.join(__dirname, (config.filePath || './datastore')), {
|
||||
maxAge: "0d"
|
||||
}));
|
||||
app.use("/block", Express.static(Path.join(__dirname, (config.blockPath || '/block')), {
|
||||
maxAge: "0d",
|
||||
}));
|
||||
|
||||
app.use("/customize", Express.static(__dirname + '/customize'));
|
||||
app.use("/customize", Express.static(__dirname + '/customize.dist'));
|
||||
|
|
Loading…
Reference in New Issue