1
0
mirror of https://github.com/moparisthebest/mail synced 2024-11-22 08:52:15 -05:00

Refactor REST dao

This commit is contained in:
Tankred Hase 2014-06-06 18:36:23 +02:00
parent e720753779
commit 5244c5c2d7
10 changed files with 1378 additions and 167 deletions

View File

@ -243,7 +243,6 @@ module.exports = function(grunt) {
// Load the plugin(s)
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-qunit');
grunt.loadNpmTasks('grunt-mocha');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-csso');

View File

@ -25,8 +25,8 @@ define(function(require) {
*/
app.config = {
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
symKeySize: 128,
symIvSize: 128,
symKeySize: 256,
symIvSize: 96,
asymKeySize: 2048,
workerPath: 'js',
gmail: {

View File

@ -6,18 +6,20 @@ define(function(require) {
'use strict';
var _ = require('underscore'),
util = require('js/crypto/util');
util = require('js/crypto/util'),
config = require('js/app-config').config;
var DB_PUBLICKEY = 'publickey',
DB_PRIVATEKEY = 'privatekey',
DB_DEVICENAME = 'devicename',
DB_DEVICEKEY = 'devicekey';
DB_DEVICE_SECRET = 'devicesecret';
var KeychainDAO = function(localDbDao, publicKeyDao, privateKeyDao, crypto) {
var KeychainDAO = function(localDbDao, publicKeyDao, privateKeyDao, crypto, pgp) {
this._localDbDao = localDbDao;
this._publicKeyDao = publicKeyDao;
this._privateKeyDao = privateKeyDao;
this._crypto = crypto;
this._pgp = pgp;
};
//
@ -260,14 +262,13 @@ define(function(require) {
};
/**
* Geneate a device specific key and secret to authenticate to the private key service.
* @param {Function} callback(error, deviceSecret:[base64 encoded string])
* Get the device' memorable name from local storage. Throws an error if not set
* @param {Function} callback(error, deviceName)
* @return {String} The device name
*/
KeychainDAO.prototype.getDeviceSecret = function(callback) {
var self = this;
// check if deviceName is already persisted in storage and if not return an error
self._localDbDao.read(DB_DEVICENAME, function(err, deviceName) {
KeychainDAO.prototype.getDeviceName = function(callback) {
// check if deviceName is already persisted in storage
this._localDbDao.read(DB_DEVICENAME, function(err, deviceName) {
if (err) {
callback(err);
return;
@ -278,54 +279,130 @@ define(function(require) {
return;
}
readOrGenerateDeviceKey(function(deviceKey) {
generateDeciceSecret(deviceName, deviceKey);
});
callback(null, deviceName);
});
};
// generate random deviceKey or get from storage
function readOrGenerateDeviceKey(localCallback) {
self._localDbDao.read(DB_DEVICEKEY, function(err, storedDevKey) {
/**
* Geneate a device specific key and secret to authenticate to the private key service.
* @param {Function} callback(error, deviceSecret:[base64 encoded string])
*/
KeychainDAO.prototype.getDeviceSecret = function(callback) {
var self = this;
// generate random deviceSecret or get from storage
self._localDbDao.read(DB_DEVICE_SECRET, function(err, storedDevSecret) {
if (err) {
callback(err);
return;
}
if (storedDevSecret) {
// a device key is already available locally
callback(null, storedDevSecret);
return;
}
// generate random deviceSecret
var deviceSecret = util.random(config.symKeySize);
// persist deviceSecret to local storage (in plaintext)
self._localDbDao.persist(DB_DEVICE_SECRET, deviceSecret, function(err) {
if (err) {
callback(err);
return;
}
if (storedDevKey) {
// a device key is already available locally
localCallback(storedDevKey);
callback(null, deviceSecret);
});
});
};
/**
* Register the device on the private key server. This will give the device access to upload an encrypted private key.
* @param {String} options.userId The user's email address
* @param {Function} callback(error)
*/
KeychainDAO.prototype.registerDevice = function(options, callback) {
var self = this;
// check if deviceName is already persisted in storage
self.getDeviceName(function(err, deviceName) {
if (err) {
callback(err);
return;
}
requestDeviceRegistration(deviceName);
});
function requestDeviceRegistration(deviceName) {
// request device registration session key
self._privateKeyDao.requestDeviceRegistration({
userId: options.userId,
deviceName: deviceName
}, function(err, regSessionKey) {
if (err) {
callback(err);
return;
}
// generate random deviceKey
var deviceKey = util.random(256);
// persist deviceKey to local storage (in plaintext)
self._localDbDao.persist(DB_DEVICEKEY, deviceKey, function(err) {
if (!regSessionKey.encryptedRegSessionKey) {
callback(new Error('Invalid format for session key!'));
return;
}
decryptSessionKey(regSessionKey);
});
}
function decryptSessionKey(regSessionKey) {
// TODO: fetch public key for service to verify response
self.lookupPublicKey('WELL_KNOWN_SERVER_KEY_ID', function(err, serverPubkey) {
if (err) {
callback(err);
return;
}
// decrypt the session key
var ct = regSessionKey.encryptedRegSessionKey;
self._pgp.decrypt(ct, serverPubkey, function(err, decrypedSessionKey) {
if (err) {
callback(err);
return;
}
localCallback(deviceKey);
uploadDeviceSecret(decrypedSessionKey);
});
});
}
// encrypt: deviceSecret = Es(deviceKey, deviceName) -> callback
function generateDeciceSecret(deviceName, deviceKey) {
self._crypto.encrypt(deviceName, deviceKey, callback);
}
};
function uploadDeviceSecret(regSessionKey) {
// read device secret from local storage
self.getDeviceSecret(function(err, deviceSecret) {
if (err) {
callback(err);
return;
}
/**
* Register the device on the private key server. This will give the device access to upload an encrypted private key.
* @param {String} userId The user's email address
* @param {String} deviceName The device's memorable name e.g 'iPhone Work'
* @param {[type]} deviceSecret The device specific secret derived from the device key and the device name.
* @param {Function} callback(error)
*/
KeychainDAO.prototype.registerDevice = function(userId, deviceName, deviceSecret, callback) {
callback(new Error('Not yet implemented!'));
// generate deviceSecretIv
var deviceSecretIv = util.random(config.symIvSize);
// encrypt deviceSecret
self._crypto.encrypt(deviceSecret, regSessionKey, deviceSecretIv, function(err, encryptedDeviceSecret) {
if (err) {
callback(err);
return;
}
// upload encryptedDeviceSecret
self._privateKeyDao.uploadDeviceSecret({
userId: options.userId,
deviceName: options.deviceName,
encryptedDeviceSecret: encryptedDeviceSecret,
deviceSecretIv: deviceSecretIv
}, callback);
});
});
}
};
//
@ -335,60 +412,281 @@ define(function(require) {
/**
* Authenticate to the private key server (required before private PGP key upload).
* @param {String} userId The user's email address
* @param {Function} callback(error)
* @param {Function} callback(error, authSessionKey)
* @return {Object} {sessionId:String, sessionKey:[base64 encoded]}
*/
KeychainDAO.prototype.authenticateToPrivateKeyServer = function(userId, callback) {
callback(new Error('Not yet implemented!'));
KeychainDAO.prototype._authenticateToPrivateKeyServer = function(userId, callback) {
var self = this,
sessionId;
// request auth session key required for upload
self._privateKeyDao.requestAuthSessionKeys({
userId: userId
}, function(err, authSessionKey) {
if (err) {
callback(err);
return;
}
if (!authSessionKey.encryptedAuthSessionKey || !authSessionKey.encryptedChallenge || !authSessionKey.sessionId) {
callback(new Error('Invalid format for session key!'));
return;
}
// remember session id for verification
sessionId = authSessionKey.sessionId;
decryptSessionKey(authSessionKey);
});
function decryptSessionKey(authSessionKey) {
// TODO: fetch public key for service to verify response
self.lookupPublicKey('WELL_KNOWN_SERVER_KEY_ID', function(err, serverPubkey) {
if (err) {
callback(err);
return;
}
// decrypt the session key
var ct1 = authSessionKey.encryptedAuthSessionKey;
self._pgp.decrypt(ct1, serverPubkey.publicKey, function(err, decryptedSessionKey) {
if (err) {
callback(err);
return;
}
// decrypt the challenge
var ct2 = authSessionKey.encryptedAuthSessionKey;
self._pgp.decrypt(ct2, serverPubkey.publicKey, function(err, decryptedChallenge) {
if (err) {
callback(err);
return;
}
encryptChallenge(decryptedSessionKey, decryptedChallenge);
});
});
});
}
function encryptChallenge(sessionKey, challenge) {
// get device secret
self.getDeviceSecret(function(err, deviceSecret) {
if (err) {
callback(err);
return;
}
var iv = util.random(config.symIvSize);
// encrypt the challenge
self._crypto.encrypt(challenge, sessionKey, iv, function(err, encryptedChallenge) {
if (err) {
callback(err);
return;
}
// encrypt the device secret
self._crypto.encrypt(deviceSecret, sessionKey, iv, function(err, encryptedDeviceSecret) {
if (err) {
callback(err);
return;
}
replyChallenge({
encryptedChallenge: encryptedChallenge,
encryptedDeviceSecret: encryptedDeviceSecret,
iv: iv
}, sessionKey);
});
});
});
}
function replyChallenge(encryptedChallenge, sessionKey) {
// respond to challenge by uploading the with the session key encrypted challenge
self._privateKeyDao.verifyAuthentication({
userId: userId,
sessionId: sessionId,
encryptedChallenge: encryptedChallenge
}, function(err) {
if (err) {
callback(err);
return;
}
callback(null, {
sessionId: sessionId,
sessionKey: sessionKey
});
});
}
};
/**
* Encrypt and upload the private PGP key to the server.
* @param {Object} privkey
* @param {String} code The randomly generated or self selected code used to derive the key for the encryption of the private PGP key
* @param {Function} callback
* @param {String} options.userId The user's email address
* @param {String} options.code The randomly generated or self selected code used to derive the key for the encryption of the private PGP key
* @param {Function} callback(error)
*/
KeychainDAO.prototype.uploadPrivateKeyToServer = function(privkey, code, callback) {
// generate random salt
KeychainDAO.prototype.uploadPrivateKey = function(options, callback) {
var self = this,
keySize = config.symKeySize,
salt;
// derive key from the code using PBKDF2
if (!options.userId || !options.code) {
callback(new Error('Incomplete arguments!'));
return;
}
// encrypt the private key with the derived key (AES-GCM authenticated encryption)
deriveKey(options.code);
// upload the 'privatekey' {salt:[base64 encoded string], encryptedPrivateKey:[base64 encoded string]}
function deriveKey(code) {
// generate random salt
salt = util.random(keySize);
// derive key from the code using PBKDF2
self._crypto.deriveKey(code, salt, keySize, function(err, key) {
if (err) {
callback(err);
return;
}
callback(new Error('Not yet implemented!'));
encryptPrivateKey(key);
});
}
function encryptPrivateKey(encryptionKey) {
// get private key from local storage
self.getUserKeyPair(options.userId, function(err, keypair) {
if (err) {
callback(err);
return;
}
var privkeyId = keypair.privateKey._id,
pgpBlock = keypair.privateKey.encryptedKey;
// encrypt the private key with the derived key (AES-GCM authenticated encryption)
var iv = util.random(config.symIvSize);
self._crypto.encrypt(pgpBlock, encryptionKey, iv, function(err, ct) {
if (err) {
callback(err);
return;
}
var payload = {
_id: privkeyId,
encryptedPrivateKey: ct,
salt: salt,
iv: iv
};
uploadPrivateKey(payload);
});
});
}
function uploadPrivateKey(payload) {
// authenticate to server for upload
self._authenticateToPrivateKeyServer(options.userId, function(err, authSessionKey) {
if (err) {
callback(err);
return;
}
// encrypt encryptedPrivateKey again using authSessionKey
var pt = payload.encryptedPrivateKey,
iv = payload.iv,
key = authSessionKey.sessionKey;
self._crypto.encrypt(pt, key, iv, function(err, ct) {
if (err) {
callback(err);
return;
}
// replace the encryptedPrivateKey with the double wrapped ciphertext
payload.encryptedPrivateKey = ct;
// set sessionId
payload.sessionId = authSessionKey.sessionId;
// upload the encrypted priavet key
self._privateKeyDao.upload(payload, callback);
});
});
}
};
/**
* Request downloading the user's encrypted private key. This will initiate the server to send the recovery token via email/sms to the user.
* @param {[type]} userId The user's email address
* @param {String} userId The user's email address
* @param {Function} callback(error)
*/
KeychainDAO.prototype.requestPrivateKeyDownload = function(userId, callback) {
callback(new Error('Not yet implemented!'));
this._privateKeyDao.requestDownload({
userId: userId
}, callback);
};
/**
* Download the encrypted private PGP key from the server using the recovery token.
* @param {[type]} recoveryToken The recovery token acquired via email/sms from the key server
* @param {String} options.userId The user's email address
* @param {String} options.keyId The user's email address
* @param {String} options.recoveryToken The recovery token acquired via email/sms from the key server
* @param {Function} callback(error, encryptedPrivateKey)
*/
KeychainDAO.prototype.downloadPrivateKeyFromServer = function(recoveryToken, callback) {
callback(new Error('Not yet implemented!'));
KeychainDAO.prototype.downloadPrivateKey = function(options, callback) {
this._privateKeyDao.download(options, callback);
};
/**
* This is called after the encrypted private key has successfully been downloaded and it's ready to be decrypted and stored in localstorage.
* @param {[type]} code The randomly generated or self selected code used to derive the key for the decryption of the private PGP key
* @param {Object} encryptedPrivkey The encrypted private PGP key including the random salt {salt:[base64 encoded string], encryptedPrivateKey:[base64 encoded string]}
* @param {Function} callback(error, privateKey)
* @param {String} options.userId The user's email address
* @param {String} options.keyId The user's email address
* @param {String} options.code The randomly generated or self selected code used to derive the key for the decryption of the private PGP key
* @param {String} options.encryptedPrivkey The encrypted private PGP key
* @param {String} options.salt The salt required to derive the code derived key
* @param {String} options.iv The iv used to encrypt the private PGP key
* @param {Function} callback(error)
*/
KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(code, encryptedPrivkey, callback) {
KeychainDAO.prototype.decryptAndStorePrivateKeyLocally = function(options, callback) {
var self = this,
code = options.code,
salt = options.salt,
keySize = config.keySize;
if (!options.keyId || !options.userId) {
callback(new Error('Incomplete arguments!'));
return;
}
// derive key from the code and the salt using PBKDF2
self._crypto.deriveKey(code, salt, keySize, function(err, key) {
if (err) {
callback(err);
return;
}
// decrypt the private key with the derived key (AES-GCM authenticated decryption)
decryptAndStore(key);
});
callback(new Error('Not yet implemented!'));
function decryptAndStore(derivedKey) {
// decrypt the private key with the derived key
var pt = options.encryptedPrivkey,
iv = options.iv;
self._crypto.decrypt(pt, derivedKey, iv, function(err, pgpBlock) {
if (err) {
callback(err);
return;
}
// store private key locally
self.saveLocalPrivateKey({
_id: options.keyId,
userId: options.userId,
encryptedKey: pgpBlock
}, callback);
});
}
};
//

View File

@ -5,12 +5,145 @@ define(function() {
this._restDao = restDao;
};
//
// Device registration functions
//
/**
* Persist the user's private key on the server
* Request registration of a new device by fetching registration session key.
* @param {String} options.userId The user's email address
* @param {String} options.deviceName The device's memorable name
* @param {Function} callback(error, regSessionKey)
* @return {Object} {encryptedRegSessionKey:[base64]}
*/
PrivateKeyDAO.prototype.post = function(privkey, callback) {
var uri = '/privatekey/user/' + privkey.userId + '/key/' + privkey._id;
this._restDao.post(privkey, uri, callback);
PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback) {
var uri;
if (!options.userId || !options.deviceName) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
this._restDao.post(undefined, uri, callback);
};
/**
* Authenticate device registration by uploading the deviceSecret encrypted with the regSessionKeys.
* @param {String} options.userId The user's email address
* @param {String} options.deviceName The device's memorable name
* @param {Object} options.encryptedDeviceSecret {encryptedDeviceSecret:[base64 encoded]}
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.uploadDeviceSecret = function(options, callback) {
var uri;
if (!options.userId || !options.deviceName || !options.encryptedDeviceSecret) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/device/user/' + options.userId + '/devicename/' + options.deviceName;
this._restDao.put(options.encryptedDeviceSecret, uri, callback);
};
//
// Private key functions
//
/**
* Request authSessionKeys required for upload the encrypted private PGP key.
* @param {String} options.userId The user's email address
* @param {Function} callback(error, authSessionKey)
* @return {Object} {sessionId, encryptedAuthSessionKeys:[base64 encoded], encryptedChallenge:[base64 encoded]}
*/
PrivateKeyDAO.prototype.requestAuthSessionKeys = function(options, callback) {
var uri;
if (!options.userId) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/auth/user/' + options.userId;
this._restDao.post(undefined, uri, callback);
};
/**
* Verifiy authentication by uploading the challenge and deviceSecret encrypted with the authSessionKeys as a response.
* @param {String} options.userId The user's email address
* @param {Object} options.encryptedChallenge The server's challenge encrypted using the authSessionKey {encryptedChallenge:[base64 encoded], encryptedDeviceSecret:[base64 encoded], iv}
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.verifyAuthentication = function(options, callback) {
var uri;
if (!options.userId || !options.sessionId || !options.encryptedChallenge) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/auth/user/' + options.userId + '/session/' + options.sessionId;
this._restDao.put(options.encryptedChallenge, uri, callback);
};
/**
* Upload the encrypted private PGP key.
* @param {String} options.encryptedPrivateKey {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], sessionId: [base64 encoded]}
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.upload = function(options, callback) {
var uri,
key = options.encryptedPrivateKey;
if (!options.userId || !key || !key._id) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/privatekey/user/' + options.userId + '/key/' + key._id;
this._restDao.post(key, uri, callback);
};
/**
* Request download for the encrypted private PGP key.
* @param {[type]} options.userId The user's email address
* @param {Function} callback(error)
*/
PrivateKeyDAO.prototype.requestDownload = function(options, callback) {
var uri;
if (!options.userId || !options.keyId) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId;
this._restDao.get({
uri: uri
}, callback);
};
/**
* Verify the download request for the private PGP key using the recovery token sent via email. This downloads the actual encrypted private key.
* @param {String} options.userId The user's email address
* @param {String} options.keyId The private key id
* @param {String} options.recoveryToken The token proving the user own the email account
* @param {Function} callback(error, encryptedPrivateKey)
* @return {Object} {_id:[hex encoded capital 16 char key id], encryptedPrivateKey:[base64 encoded], encryptedUserId: [base64 encoded]}
*/
PrivateKeyDAO.prototype.download = function(options, callback) {
var uri;
if (!options.userId || !options.keyId || !options.recoveryToken) {
callback(new Error('Incomplete arguments!'));
return;
}
uri = '/privatekey/user/' + options.userId + '/key/' + options.keyId + '/recovery/' + options.recoveryToken;
this._restDao.get({
uri: uri
}, callback);
};
return PrivateKeyDAO;

View File

@ -17,7 +17,48 @@ define(function(require) {
* @param {String} options.type (optional) The type of data that you're expecting back from the server: json, xml, text. Default: json.
*/
RestDAO.prototype.get = function(options, callback) {
var xhr, acceptHeader;
options.method = 'GET';
this._processRequest(options, callback);
};
/**
* POST (create) request
*/
RestDAO.prototype.post = function(item, uri, callback) {
this._processRequest({
method: 'POST',
payload: item,
uri: uri
}, callback);
};
/**
* PUT (update) request
*/
RestDAO.prototype.put = function(item, uri, callback) {
this._processRequest({
method: 'PUT',
payload: item,
uri: uri
}, callback);
};
/**
* DELETE (remove) request
*/
RestDAO.prototype.remove = function(uri, callback) {
this._processRequest({
method: 'DELETE',
uri: uri
}, callback);
};
//
// helper functions
//
RestDAO.prototype._processRequest = function(options, callback) {
var xhr, format;
if (typeof options.uri === 'undefined') {
callback({
@ -30,11 +71,11 @@ define(function(require) {
options.type = options.type || 'json';
if (options.type === 'json') {
acceptHeader = 'application/json';
format = 'application/json';
} else if (options.type === 'xml') {
acceptHeader = 'application/xml';
format = 'application/xml';
} else if (options.type === 'text') {
acceptHeader = 'text/plain';
format = 'text/plain';
} else {
callback({
code: 400,
@ -44,14 +85,16 @@ define(function(require) {
}
xhr = new XMLHttpRequest();
xhr.open('GET', this._baseUri + options.uri);
xhr.setRequestHeader('Accept', acceptHeader);
xhr.open(options.method, this._baseUri + options.uri);
xhr.setRequestHeader('Accept', format);
xhr.setRequestHeader('Content-Type', format);
xhr.onload = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var res;
var res;
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201 || xhr.status === 304)) {
if (options.type === 'json') {
res = JSON.parse(xhr.responseText);
res = xhr.responseText ? JSON.parse(xhr.responseText) : xhr.responseText;
} else {
res = xhr.responseText;
}
@ -69,72 +112,11 @@ define(function(require) {
xhr.onerror = function() {
callback({
code: 42,
errMsg: 'Error calling GET on ' + options.uri
errMsg: 'Error calling ' + options.method + ' on ' + options.uri
});
};
xhr.send();
};
/**
* PUT (create/update) request
*/
RestDAO.prototype.put = function(item, uri, callback) {
var xhr;
xhr = new XMLHttpRequest();
xhr.open('PUT', this._baseUri + uri);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 201 || xhr.status === 304)) {
callback(null, xhr.responseText, xhr.status);
return;
}
callback({
code: xhr.status,
errMsg: xhr.statusText
});
};
xhr.onerror = function() {
callback({
errMsg: 'Error calling PUT on ' + uri
});
};
xhr.send(JSON.stringify(item));
};
/**
* DELETE (remove) request
*/
RestDAO.prototype.remove = function(uri, callback) {
var xhr;
xhr = new XMLHttpRequest();
xhr.open('DELETE', this._baseUri + uri);
xhr.onload = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
callback(null, xhr.responseText, xhr.status);
return;
}
callback({
code: xhr.status,
errMsg: xhr.statusText
});
};
xhr.onerror = function() {
callback({
errMsg: 'Error calling DELETE on ' + uri
});
};
xhr.send();
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined);
};
return RestDAO;

View File

@ -3,6 +3,7 @@ define(function(require) {
var Crypto = require('js/crypto/crypto'),
util = require('js/crypto/util'),
config = require('js/app-config').config,
expect = chai.expect;
describe('Crypto unit tests', function() {
@ -10,8 +11,8 @@ define(function(require) {
var crypto,
password = 'password',
keySize = 128,
ivSize = 128;
keySize = config.symKeySize,
ivSize = config.symIvSize;
beforeEach(function() {
crypto = new Crypto();

View File

@ -6,20 +6,22 @@ define(function(require) {
KeychainDAO = require('js/dao/keychain-dao'),
PrivateKeyDAO = require('js/dao/privatekey-dao'),
Crypto = require('js/crypto/crypto'),
PGP = require('js/crypto/pgp'),
expect = chai.expect;
var testUser = 'test@example.com';
describe('Keychain DAO unit tests', function() {
var keychainDao, lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub;
var keychainDao, lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub, pgpStub;
beforeEach(function() {
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
pubkeyDaoStub = sinon.createStubInstance(PublicKeyDAO);
privkeyDaoStub = new sinon.createStubInstance(PrivateKeyDAO);
cryptoStub = new sinon.createStubInstance(Crypto);
keychainDao = new KeychainDAO(lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub);
privkeyDaoStub = sinon.createStubInstance(PrivateKeyDAO);
cryptoStub = sinon.createStubInstance(Crypto);
pgpStub = sinon.createStubInstance(PGP);
keychainDao = new KeychainDAO(lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub, pgpStub);
});
afterEach(function() {});
@ -586,30 +588,42 @@ define(function(require) {
});
});
describe('getDeviceSecret', function() {
describe('getDeviceName', function() {
it('should fail when device name is not set', function(done) {
lawnchairDaoStub.read.withArgs('devicename').yields();
keychainDao.getDeviceSecret(function(err, deviceSecret) {
keychainDao.getDeviceName(function(err, deviceName) {
expect(err.message).to.equal('Device name not set!');
expect(deviceSecret).to.not.exist;
expect(deviceName).to.not.exist;
done();
});
});
it('should fail due to erorr when reading device name', function(done) {
it('should fail due to error when reading device name', function(done) {
lawnchairDaoStub.read.withArgs('devicename').yields(42);
keychainDao.getDeviceSecret(function(err, deviceSecret) {
keychainDao.getDeviceName(function(err, deviceName) {
expect(err).to.equal(42);
expect(deviceSecret).to.not.exist;
expect(deviceName).to.not.exist;
done();
});
});
it('should fail due to erorr when reading device key', function(done) {
it('should work', function(done) {
lawnchairDaoStub.read.withArgs('devicename').yields(null, 'iPhone');
lawnchairDaoStub.read.withArgs('devicekey').yields(42);
keychainDao.getDeviceName(function(err, deviceName) {
expect(err).to.not.exist;
expect(deviceName).to.equal('iPhone');
done();
});
});
});
describe('getDeviceSecret', function() {
it('should fail due to error when reading device secret', function(done) {
lawnchairDaoStub.read.withArgs('devicename').yields(null, 'iPhone');
lawnchairDaoStub.read.withArgs('devicesecret').yields(42);
keychainDao.getDeviceSecret(function(err, deviceSecret) {
expect(err).to.equal(42);
@ -618,10 +632,10 @@ define(function(require) {
});
});
it('should fail due to erorr when storing device key', function(done) {
it('should fail due to error when storing device secret', function(done) {
lawnchairDaoStub.read.withArgs('devicename').yields(null, 'iPhone');
lawnchairDaoStub.read.withArgs('devicekey').yields();
lawnchairDaoStub.persist.withArgs('devicekey').yields(42);
lawnchairDaoStub.read.withArgs('devicesecret').yields();
lawnchairDaoStub.persist.withArgs('devicesecret').yields(42);
keychainDao.getDeviceSecret(function(err, deviceSecret) {
expect(err).to.equal(42);
@ -630,11 +644,21 @@ define(function(require) {
});
});
it('should work when device key is not set', function(done) {
it('should work when device secret is not set', function(done) {
lawnchairDaoStub.read.withArgs('devicename').yields(null, 'iPhone');
lawnchairDaoStub.read.withArgs('devicekey').yields();
lawnchairDaoStub.persist.withArgs('devicekey').yields();
cryptoStub.encrypt.yields(null, 'secret');
lawnchairDaoStub.read.withArgs('devicesecret').yields();
lawnchairDaoStub.persist.withArgs('devicesecret').yields();
keychainDao.getDeviceSecret(function(err, deviceSecret) {
expect(err).to.not.exist;
expect(deviceSecret).to.exist;
done();
});
});
it('should work when device secret is set', function(done) {
lawnchairDaoStub.read.withArgs('devicename').yields(null, 'iPhone');
lawnchairDaoStub.read.withArgs('devicesecret').yields(null, 'secret');
keychainDao.getDeviceSecret(function(err, deviceSecret) {
expect(err).to.not.exist;
@ -642,15 +666,569 @@ define(function(require) {
done();
});
});
});
it('should work when device key is set', function(done) {
lawnchairDaoStub.read.withArgs('devicename').yields(null, 'iPhone');
lawnchairDaoStub.read.withArgs('devicekey').yields(null, 'key');
cryptoStub.encrypt.withArgs('iPhone', 'key').yields(null, 'secret');
describe('registerDevice', function() {
var getDeviceNameStub, lookupPublicKeyStub, getDeviceSecretStub;
keychainDao.getDeviceSecret(function(err, deviceSecret) {
beforeEach(function() {
getDeviceNameStub = sinon.stub(keychainDao, 'getDeviceName');
lookupPublicKeyStub = sinon.stub(keychainDao, 'lookupPublicKey');
getDeviceSecretStub = sinon.stub(keychainDao, 'getDeviceSecret');
});
afterEach(function() {
getDeviceNameStub.restore();
lookupPublicKeyStub.restore();
getDeviceSecretStub.restore();
});
it('should fail when reading devicename', function(done) {
getDeviceNameStub.yields(42);
keychainDao.registerDevice({}, function(err) {
expect(err).to.equal(42);
done();
});
});
it('should fail in requestDeviceRegistration', function(done) {
getDeviceNameStub.yields(null, 'iPhone');
privkeyDaoStub.requestDeviceRegistration.withArgs({
userId: testUser,
deviceName: 'iPhone'
}).yields(42);
keychainDao.registerDevice({
userId: testUser
}, function(err) {
expect(err).to.equal(42);
done();
});
});
it('should fail due to invalid requestDeviceRegistration return value', function(done) {
getDeviceNameStub.yields(null, 'iPhone');
privkeyDaoStub.requestDeviceRegistration.withArgs({
userId: testUser,
deviceName: 'iPhone'
}).yields(null, {});
keychainDao.registerDevice({
userId: testUser
}, function(err) {
expect(err.message).to.equal('Invalid format for session key!');
done();
});
});
it('should fail in lookupPublicKey', function(done) {
getDeviceNameStub.yields(null, 'iPhone');
privkeyDaoStub.requestDeviceRegistration.withArgs({
userId: testUser,
deviceName: 'iPhone'
}).yields(null, {
encryptedRegSessionKey: 'asdf'
});
lookupPublicKeyStub.yields(42);
keychainDao.registerDevice({
userId: testUser
}, function(err) {
expect(err).to.equal(42);
done();
});
});
it('should fail in decrypt', function(done) {
getDeviceNameStub.yields(null, 'iPhone');
privkeyDaoStub.requestDeviceRegistration.withArgs({
userId: testUser,
deviceName: 'iPhone'
}).yields(null, {
encryptedRegSessionKey: 'asdf'
});
lookupPublicKeyStub.yields(null, 'pubkey');
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(42);
keychainDao.registerDevice({
userId: testUser
}, function(err) {
expect(err).to.equal(42);
done();
});
});
it('should fail in getDeviceSecret', function(done) {
getDeviceNameStub.yields(null, 'iPhone');
privkeyDaoStub.requestDeviceRegistration.withArgs({
userId: testUser,
deviceName: 'iPhone'
}).yields(null, {
encryptedRegSessionKey: 'asdf'
});
lookupPublicKeyStub.yields(null, 'pubkey');
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
getDeviceSecretStub.yields(42);
keychainDao.registerDevice({
userId: testUser
}, function(err) {
expect(err).to.equal(42);
done();
});
});
it('should fail in encrypt', function(done) {
getDeviceNameStub.yields(null, 'iPhone');
privkeyDaoStub.requestDeviceRegistration.withArgs({
userId: testUser,
deviceName: 'iPhone'
}).yields(null, {
encryptedRegSessionKey: 'asdf'
});
lookupPublicKeyStub.yields(null, 'pubkey');
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
getDeviceSecretStub.yields(null, 'secret');
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(42);
keychainDao.registerDevice({
userId: testUser
}, function(err) {
expect(err).to.equal(42);
done();
});
});
it('should work', function(done) {
getDeviceNameStub.yields(null, 'iPhone');
privkeyDaoStub.requestDeviceRegistration.withArgs({
userId: testUser,
deviceName: 'iPhone'
}).yields(null, {
encryptedRegSessionKey: 'asdf'
});
lookupPublicKeyStub.yields(null, 'pubkey');
pgpStub.decrypt.withArgs('asdf', 'pubkey').yields(null, 'decrypted');
getDeviceSecretStub.yields(null, 'secret');
cryptoStub.encrypt.withArgs('secret', 'decrypted').yields(null, 'encryptedDeviceSecret');
privkeyDaoStub.uploadDeviceSecret.yields();
keychainDao.registerDevice({
userId: testUser
}, function(err) {
expect(err).not.exist;
expect(privkeyDaoStub.uploadDeviceSecret.calledOnce).to.be.true;
done();
});
});
});
describe('_authenticateToPrivateKeyServer', function() {
var lookupPublicKeyStub, getDeviceSecretStub;
beforeEach(function() {
lookupPublicKeyStub = sinon.stub(keychainDao, 'lookupPublicKey');
getDeviceSecretStub = sinon.stub(keychainDao, 'getDeviceSecret');
});
afterEach(function() {
lookupPublicKeyStub.restore();
getDeviceSecretStub.restore();
});
it('should fail due to privkeyDao.requestAuthSessionKeys', function(done) {
privkeyDaoStub.requestAuthSessionKeys.withArgs({
userId: testUser
}).yields(42);
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
expect(err).to.equal(42);
expect(authSessionKey).to.not.exist;
done();
});
});
it('should fail due to privkeyDao.requestAuthSessionKeys response', function(done) {
privkeyDaoStub.requestAuthSessionKeys.yields(null, {});
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
expect(err).to.exist;
expect(authSessionKey).to.not.exist;
done();
});
});
it('should fail due to lookupPublicKey', function(done) {
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
encryptedChallenge: 'encryptedChallenge',
sessionId: 'sessionId'
});
lookupPublicKeyStub.yields(42);
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
expect(err).to.exist;
expect(authSessionKey).to.not.exist;
done();
});
});
it('should fail due to pgp.decrypt', function(done) {
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
encryptedChallenge: 'encryptedChallenge',
sessionId: 'sessionId'
});
lookupPublicKeyStub.yields(null, {
publickKey: 'publicKey'
});
pgpStub.decrypt.yields(42);
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
expect(err).to.exist;
expect(authSessionKey).to.not.exist;
done();
});
});
it('should fail due to getDeviceSecret', function(done) {
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
encryptedChallenge: 'encryptedChallenge',
sessionId: 'sessionId'
});
lookupPublicKeyStub.yields(null, {
publickKey: 'publicKey'
});
pgpStub.decrypt.yields(null, 'decryptedStuff');
getDeviceSecretStub.yields(42);
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
expect(err).to.exist;
expect(authSessionKey).to.not.exist;
done();
});
});
it('should fail due to crypto.encrypt', function(done) {
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
encryptedChallenge: 'encryptedChallenge',
sessionId: 'sessionId'
});
lookupPublicKeyStub.yields(null, {
publickKey: 'publicKey'
});
pgpStub.decrypt.yields(null, 'decryptedStuff');
getDeviceSecretStub.yields(null, 'deviceSecret');
cryptoStub.encrypt.yields(42);
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
expect(err).to.exist;
expect(authSessionKey).to.not.exist;
done();
});
});
it('should fail due to privkeyDao.verifyAuthentication', function(done) {
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
encryptedChallenge: 'encryptedChallenge',
sessionId: 'sessionId'
});
lookupPublicKeyStub.yields(null, {
publickKey: 'publicKey'
});
pgpStub.decrypt.yields(null, 'decryptedStuff');
getDeviceSecretStub.yields(null, 'deviceSecret');
cryptoStub.encrypt.yields(null, 'encryptedStuff');
privkeyDaoStub.verifyAuthentication.yields(42);
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
expect(err).to.exist;
expect(authSessionKey).to.not.exist;
done();
});
});
it('should work', function(done) {
privkeyDaoStub.requestAuthSessionKeys.yields(null, {
encryptedAuthSessionKey: 'encryptedAuthSessionKey',
encryptedChallenge: 'encryptedChallenge',
sessionId: 'sessionId'
});
lookupPublicKeyStub.yields(null, {
publickKey: 'publicKey'
});
pgpStub.decrypt.yields(null, 'decryptedStuff');
getDeviceSecretStub.yields(null, 'deviceSecret');
cryptoStub.encrypt.yields(null, 'encryptedStuff');
privkeyDaoStub.verifyAuthentication.yields();
keychainDao._authenticateToPrivateKeyServer(testUser, function(err, authSessionKey) {
expect(err).to.not.exist;
expect(deviceSecret).to.equal('secret');
expect(authSessionKey).to.deep.equal({
sessionKey: 'decryptedStuff',
sessionId: 'sessionId'
});
done();
});
});
});
describe('uploadPrivateKey', function() {
var getUserKeyPairStub, _authenticateToPrivateKeyServerStub;
beforeEach(function() {
getUserKeyPairStub = sinon.stub(keychainDao, 'getUserKeyPair');
_authenticateToPrivateKeyServerStub = sinon.stub(keychainDao, '_authenticateToPrivateKeyServer');
});
afterEach(function() {
getUserKeyPairStub.restore();
_authenticateToPrivateKeyServerStub.restore();
});
it('should fail due to missing args', function(done) {
keychainDao.uploadPrivateKey({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should fail due to error in derive key', function(done) {
cryptoStub.deriveKey.yields(42);
keychainDao.uploadPrivateKey({
code: 'code',
userId: testUser
}, function(err) {
expect(err).to.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
done();
});
});
it('should fail due to error in getUserKeyPair', function(done) {
cryptoStub.deriveKey.yields(null, 'derivedKey');
getUserKeyPairStub.yields(42);
keychainDao.uploadPrivateKey({
code: 'code',
userId: testUser
}, function(err) {
expect(err).to.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
expect(getUserKeyPairStub.calledOnce).to.be.true;
done();
});
});
it('should fail due to error in crypto.encrypt', function(done) {
cryptoStub.deriveKey.yields(null, 'derivedKey');
getUserKeyPairStub.yields(null, {
privateKey: {
_id: 'pgpKeyId',
encryptedKey: 'pgpKey'
}
});
cryptoStub.encrypt.yields(42);
keychainDao.uploadPrivateKey({
code: 'code',
userId: testUser
}, function(err) {
expect(err).to.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
expect(getUserKeyPairStub.calledOnce).to.be.true;
expect(cryptoStub.encrypt.calledOnce).to.be.true;
done();
});
});
it('should fail due to error in _authenticateToPrivateKeyServer', function(done) {
cryptoStub.deriveKey.yields(null, 'derivedKey');
getUserKeyPairStub.yields(null, {
privateKey: {
_id: 'pgpKeyId',
encryptedKey: 'pgpKey'
}
});
cryptoStub.encrypt.yields(null, 'encryptedPgpKey');
_authenticateToPrivateKeyServerStub.yields(42);
keychainDao.uploadPrivateKey({
code: 'code',
userId: testUser
}, function(err) {
expect(err).to.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
expect(getUserKeyPairStub.calledOnce).to.be.true;
expect(cryptoStub.encrypt.calledOnce).to.be.true;
expect(_authenticateToPrivateKeyServerStub.calledOnce).to.be.true;
done();
});
});
it('should fail due to error in cryptoStub.encrypt', function(done) {
cryptoStub.deriveKey.yields(null, 'derivedKey');
getUserKeyPairStub.yields(null, {
privateKey: {
_id: 'pgpKeyId',
encryptedKey: 'pgpKey'
}
});
cryptoStub.encrypt.withArgs('pgpKey').yields(null, 'encryptedPgpKey');
_authenticateToPrivateKeyServerStub.yields(null, {
sessionId: 'sessionId',
sessionKey: 'sessionKey'
});
cryptoStub.encrypt.withArgs('encryptedPgpKey').yields(42);
keychainDao.uploadPrivateKey({
code: 'code',
userId: testUser
}, function(err) {
expect(err).to.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
expect(getUserKeyPairStub.calledOnce).to.be.true;
expect(cryptoStub.encrypt.calledTwice).to.be.true;
expect(_authenticateToPrivateKeyServerStub.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
cryptoStub.deriveKey.yields(null, 'derivedKey');
getUserKeyPairStub.yields(null, {
privateKey: {
_id: 'pgpKeyId',
encryptedKey: 'pgpKey'
}
});
cryptoStub.encrypt.withArgs('pgpKey').yields(null, 'encryptedPgpKey');
_authenticateToPrivateKeyServerStub.yields(null, {
sessionId: 'sessionId',
sessionKey: 'sessionKey'
});
cryptoStub.encrypt.withArgs('encryptedPgpKey').yields(null, 'doubleEncryptedPgpKey');
privkeyDaoStub.upload.yields();
keychainDao.uploadPrivateKey({
code: 'code',
userId: testUser
}, function(err) {
expect(err).to.not.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
expect(getUserKeyPairStub.calledOnce).to.be.true;
expect(cryptoStub.encrypt.calledTwice).to.be.true;
expect(_authenticateToPrivateKeyServerStub.calledOnce).to.be.true;
expect(privkeyDaoStub.upload.calledOnce).to.be.true;
done();
});
});
});
describe('requestPrivateKeyDownload', function() {
it('should work', function(done) {
privkeyDaoStub.requestDownload.withArgs({
userId: testUser
}).yields();
keychainDao.requestPrivateKeyDownload(testUser, done);
});
});
describe('downloadPrivateKey', function() {
it('should work', function(done) {
var options = {
recoveryToken: 'token'
};
privkeyDaoStub.download.withArgs(options).yields();
keychainDao.downloadPrivateKey(options, done);
});
});
describe('decryptAndStorePrivateKeyLocally', function() {
var saveLocalPrivateKeyStub;
beforeEach(function() {
saveLocalPrivateKeyStub = sinon.stub(keychainDao, 'saveLocalPrivateKey');
});
afterEach(function() {
saveLocalPrivateKeyStub.restore();
});
it('should fail due to invlaid args', function(done) {
keychainDao.decryptAndStorePrivateKeyLocally({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should fail due to crypto.deriveKey', function(done) {
cryptoStub.deriveKey.yields(42);
keychainDao.decryptAndStorePrivateKeyLocally({
userId: testUser,
keyId: 'keyId'
}, function(err) {
expect(err).to.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
done();
});
});
it('should fail due to crypto.decrypt', function(done) {
cryptoStub.deriveKey.yields(null, 'derivedKey');
cryptoStub.decrypt.yields(42);
keychainDao.decryptAndStorePrivateKeyLocally({
userId: testUser,
keyId: 'keyId'
}, function(err) {
expect(err).to.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
expect(cryptoStub.decrypt.calledOnce).to.be.true;
done();
});
});
it('should work', function(done) {
cryptoStub.deriveKey.yields(null, 'derivedKey');
cryptoStub.decrypt.yields(null, 'pgpBlock');
saveLocalPrivateKeyStub.yields();
keychainDao.decryptAndStorePrivateKeyLocally({
userId: testUser,
keyId: 'keyId'
}, function(err) {
expect(err).to.not.exist;
expect(cryptoStub.deriveKey.calledOnce).to.be.true;
expect(cryptoStub.decrypt.calledOnce).to.be.true;
expect(saveLocalPrivateKeyStub.calledOnce).to.be.true;
done();
});
});

View File

@ -36,6 +36,7 @@ function startTests() {
'test/unit/crypto-test',
'test/unit/rest-dao-test',
'test/unit/publickey-dao-test',
'test/unit/privatekey-dao-test',
'test/unit/lawnchair-dao-test',
'test/unit/keychain-dao-test',
'test/unit/devicestorage-dao-test',

View File

@ -0,0 +1,194 @@
define(function(require) {
'use strict';
var RestDAO = require('js/dao/rest-dao'),
PrivateKeyDAO = require('js/dao/privatekey-dao'),
expect = chai.expect;
describe('Private Key DAO unit tests', function() {
var privkeyDao, restDaoStub,
emailAddress = 'test@example.com',
deviceName = 'iPhone Work';
beforeEach(function() {
restDaoStub = sinon.createStubInstance(RestDAO);
privkeyDao = new PrivateKeyDAO(restDaoStub);
});
afterEach(function() {});
describe('requestDeviceRegistration', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestDeviceRegistration({}, function(err, sessionKey) {
expect(err).to.exist;
expect(sessionKey).to.not.exist;
done();
});
});
it('should work', function(done) {
restDaoStub.post.yields(null, {
encryptedRegSessionKey: 'asdf'
});
privkeyDao.requestDeviceRegistration({
userId: emailAddress,
deviceName: deviceName
}, function(err, sessionKey) {
expect(err).to.not.exist;
expect(sessionKey).to.exist;
done();
});
});
});
describe('uploadDeviceSecret', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.uploadDeviceSecret({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
restDaoStub.put.yields();
privkeyDao.uploadDeviceSecret({
userId: emailAddress,
deviceName: deviceName,
encryptedDeviceSecret: 'asdf'
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
describe('requestAuthSessionKeys', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestAuthSessionKeys({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
restDaoStub.post.withArgs(undefined, '/auth/user/' + emailAddress).yields();
privkeyDao.requestAuthSessionKeys({
userId: emailAddress
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
describe('verifyAuthentication', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.verifyAuthentication({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
var sessionId = '1';
restDaoStub.put.withArgs('asdf', '/auth/user/' + emailAddress + '/session/' + sessionId).yields();
privkeyDao.verifyAuthentication({
userId: emailAddress,
sessionId: sessionId,
encryptedChallenge: 'asdf'
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
describe('upload', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.upload({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
var key = {
_id: '12345'
};
restDaoStub.post.withArgs(key, '/privatekey/user/' + emailAddress + '/key/' + key._id).yields();
privkeyDao.upload({
userId: emailAddress,
encryptedPrivateKey: key
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
describe('requestDownload', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.requestDownload({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
var key = {
_id: '12345'
};
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + key._id
}).yields();
privkeyDao.requestDownload({
userId: emailAddress,
keyId: key._id
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
describe('download', function() {
it('should fail due to invalid args', function(done) {
privkeyDao.download({}, function(err) {
expect(err).to.exist;
done();
});
});
it('should work', function(done) {
var key = {
_id: '12345'
};
restDaoStub.get.withArgs({
uri: '/privatekey/user/' + emailAddress + '/key/' + key._id + '/recovery/token'
}).yields();
privkeyDao.download({
userId: emailAddress,
keyId: key._id,
recoveryToken: 'token'
}, function(err) {
expect(err).to.not.exist;
done();
});
});
});
});
});

View File

@ -145,6 +145,31 @@ define(function(require) {
});
});
describe('post', function() {
it('should fail', function() {
restDao.post('/asdf', {}, function(err) {
expect(err).to.exist;
expect(err.code).to.equal(500);
});
expect(requests.length).to.equal(1);
requests[0].respond(500, {
"Content-Type": "text/plain"
}, 'Internal error');
});
it('should work', function() {
restDao.post('/asdf', {}, function(err, res, status) {
expect(err).to.not.exist;
expect(res).to.equal('');
expect(status).to.equal(201);
});
expect(requests.length).to.equal(1);
requests[0].respond(201);
});
});
describe('put', function() {
it('should fail', function() {
restDao.put('/asdf', {}, function(err) {