container.js | |
/* Crypton Client, Copyright 2013 SpiderOak, Inc.
*
* This file is part of Crypton Client.
*
* Crypton Client is free software: you can redistribute it and/or modify it
* under the terms of the Affero GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Crypton Client is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the Affero GNU General Public
* License for more details.
*
* You should have received a copy of the Affero GNU General Public License
* along with Crypton Client. If not, see <http://www.gnu.org/licenses/>.
*/
(function () {
'use strict'; | |
var Container = crypton.Container = function (session) {
this.keys = {};
this.session = session;
this.versions = {};
this.version = +new Date();
this.name = null;
}; | |
¶ add(key, callback)Add given Calls back without error if successful Calls back with error if unsuccessful Params keyString callbackFunction | Container.prototype.add = function (key, callback) {
if (this.keys[key]) {
callback('Key already exists');
return;
}
this.keys[key] = {};
callback();
}; |
¶ get(key, callback)Retrieve value for given Calls back with Calls back with error if unsuccessful Params keyString callbackFunction | Container.prototype.get = function (key, callback) {
if (!this.keys[key]) {
callback('Key does not exist');
return;
}
callback(null, this.keys[key]);
}; |
¶ save(callback, options)Get difference of container since last save (a record), encrypt the record, and send it to the server to be saved Calls back without error if successful Calls back with error if unsuccessful Params callbackFunction optionsObject(optional) | Container.prototype.save = function (callback, options) {
var that = this;
this.getDiff(function (err, diff) {
if (!diff) {
callback('Container has not changed');
return;
}
var now = +new Date();
that.versions[now] = JSON.parse(JSON.stringify(that.keys));
that.version = now;
var payloadCiphertext = sjcl.encrypt(that.hmacKey, JSON.stringify(diff), crypton.cipherOptions);
var chunk = {
type: 'addContainerRecord',
containerNameHmac: that.getPublicName(),
payloadCiphertext: payloadCiphertext
};
if (options && options.save == false) {
callback(null, chunk);
return;
}
// TODO handle errs
var tx = new crypton.Transaction(that.session, function (err) {
tx.save(chunk, function (err) {
tx.commit(function (err) {
callback();
});
});
});
});
}; |
¶ getDiff(callback, options)Compute difference of container since last save Calls back with diff object and without error if successful Calls back with error if unsuccessful Params callbackFunction | Container.prototype.getDiff = function (callback) {
var last = this.latestVersion();
var old = this.versions[last] || {};
callback(null, crypton.diff.create(old, this.keys));
}; |
¶ getVersions()Return a list of known save point timestamps Return Arraytimestamps | Container.prototype.getVersions = function () {
return Object.keys(this.versions);
}; |
¶ getVersion(version)Return full state of container at given Params timestampNumber Return Objectversion | Container.prototype.getVersion = function (timestamp) {
return this.versions[timestamp];
}; |
¶ getVersion()Return last known save point timestamp Return Numberversion | Container.prototype.latestVersion = function () {
var versions = this.getVersions();
if (!versions.length) {
return this.version;
} else {
return Math.max.apply(Math, versions);
}
}; |
¶ getPublicName()Compute the HMAC for the given name of the container Return Stringhmac | Container.prototype.getPublicName = function () {
var hmac = new sjcl.misc.hmac(this.session.account.containerNameHmacKey);
var containerNameHmac = hmac.encrypt(this.name);
return sjcl.codec.hex.fromBits(containerNameHmac);
}; |
¶ getHistory()Ask the server for all state records Calls back with diff object and without error if successful Calls back with error if unsuccessful Params callbackFunction | Container.prototype.getHistory = function (callback) {
var containerNameHmac = this.getPublicName();
var url = crypton.url() + '/container/' + containerNameHmac;
superagent.get(url)
.set('session-identifier', this.session.id)
.end(function (res) {
if (!res.body || res.body.success !== true) {
callback(res.body.error);
return;
}
callback(null, res.body.records);
});
}; |
¶ parseHistory(records, callback)Loop through given Calls back with full container state, history versions, and without error if successful Calls back with error if unsuccessful Params recordsArray callbackFunction | Container.prototype.parseHistory = function (records, callback) {
var keys = {};
var versions = {};
for (var i in records) {
var record = this.decryptRecord(records[i]);
keys = crypton.diff.apply(record.delta, keys);
versions[record.time] = JSON.parse(JSON.stringify(keys));
}
callback(null, keys, versions);
}; |
¶ decryptRecord(record)Use symkey to extract session and HMAC keys, decrypt record ciphertext with HMAC key Params recordObject Record | // TODO consider new scheme for extracting keys
// TODO handle potential JSON.parse errors here
Container.prototype.decryptRecord = function (record) {
var sessionKey = JSON.parse(sjcl.decrypt(this.session.account.symkey, record.sessionKeyCiphertext, crypton.cipherOptions));
var hmacKey = JSON.parse(sjcl.decrypt(this.session.account.symkey, record.hmacKeyCiphertext, crypton.cipherOptions));
this.sessionKey = sessionKey;
this.hmacKey = hmacKey;
var payload = JSON.parse(sjcl.decrypt(hmacKey, record.payloadCiphertext, crypton.cipherOptions));
return {
time: +new Date(record.creationTime),
delta: payload
};
}; |
¶ sync(callback)Retrieve history, decrypt it, and update container object with new state Calls back without error if successful Calls back with error if unsuccessful Params callbackFunction | Container.prototype.sync = function (callback) {
var that = this;
this.getHistory(function (err, records) {
if (err) {
callback(err);
return;
}
that.parseHistory(records, function (err, keys, versions) {
that.keys = keys;
that.versions = versions;
that.version = Math.max.apply(Math, Object.keys(versions));
callback(err);
});
});
};
})();
|