mirror of
https://github.com/moparisthebest/mail
synced 2024-11-22 08:52:15 -05:00
Refactor REST dao
This commit is contained in:
parent
e720753779
commit
5244c5c2d7
@ -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');
|
||||
|
@ -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: {
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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',
|
||||
|
194
test/unit/privatekey-dao-test.js
Normal file
194
test/unit/privatekey-dao-test.js
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user