mirror of
https://github.com/moparisthebest/mail
synced 2025-02-19 20:31:48 -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)
|
// Load the plugin(s)
|
||||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||||
grunt.loadNpmTasks('grunt-contrib-qunit');
|
|
||||||
grunt.loadNpmTasks('grunt-mocha');
|
grunt.loadNpmTasks('grunt-mocha');
|
||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||||
grunt.loadNpmTasks('grunt-csso');
|
grunt.loadNpmTasks('grunt-csso');
|
||||||
|
@ -25,8 +25,8 @@ define(function(require) {
|
|||||||
*/
|
*/
|
||||||
app.config = {
|
app.config = {
|
||||||
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
|
cloudUrl: cloudUrl || 'https://keys.whiteout.io',
|
||||||
symKeySize: 128,
|
symKeySize: 256,
|
||||||
symIvSize: 128,
|
symIvSize: 96,
|
||||||
asymKeySize: 2048,
|
asymKeySize: 2048,
|
||||||
workerPath: 'js',
|
workerPath: 'js',
|
||||||
gmail: {
|
gmail: {
|
||||||
|
@ -6,18 +6,20 @@ define(function(require) {
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var _ = require('underscore'),
|
var _ = require('underscore'),
|
||||||
util = require('js/crypto/util');
|
util = require('js/crypto/util'),
|
||||||
|
config = require('js/app-config').config;
|
||||||
|
|
||||||
var DB_PUBLICKEY = 'publickey',
|
var DB_PUBLICKEY = 'publickey',
|
||||||
DB_PRIVATEKEY = 'privatekey',
|
DB_PRIVATEKEY = 'privatekey',
|
||||||
DB_DEVICENAME = 'devicename',
|
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._localDbDao = localDbDao;
|
||||||
this._publicKeyDao = publicKeyDao;
|
this._publicKeyDao = publicKeyDao;
|
||||||
this._privateKeyDao = privateKeyDao;
|
this._privateKeyDao = privateKeyDao;
|
||||||
this._crypto = crypto;
|
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.
|
* Get the device' memorable name from local storage. Throws an error if not set
|
||||||
* @param {Function} callback(error, deviceSecret:[base64 encoded string])
|
* @param {Function} callback(error, deviceName)
|
||||||
|
* @return {String} The device name
|
||||||
*/
|
*/
|
||||||
KeychainDAO.prototype.getDeviceSecret = function(callback) {
|
KeychainDAO.prototype.getDeviceName = function(callback) {
|
||||||
var self = this;
|
// check if deviceName is already persisted in storage
|
||||||
|
this._localDbDao.read(DB_DEVICENAME, function(err, deviceName) {
|
||||||
// check if deviceName is already persisted in storage and if not return an error
|
|
||||||
self._localDbDao.read(DB_DEVICENAME, function(err, deviceName) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
@ -278,54 +279,130 @@ define(function(require) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
readOrGenerateDeviceKey(function(deviceKey) {
|
callback(null, deviceName);
|
||||||
generateDeciceSecret(deviceName, deviceKey);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// generate random deviceKey or get from storage
|
/**
|
||||||
function readOrGenerateDeviceKey(localCallback) {
|
* Geneate a device specific key and secret to authenticate to the private key service.
|
||||||
self._localDbDao.read(DB_DEVICEKEY, function(err, storedDevKey) {
|
* @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) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storedDevKey) {
|
callback(null, deviceSecret);
|
||||||
// a device key is already available locally
|
});
|
||||||
localCallback(storedDevKey);
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate random deviceKey
|
if (!regSessionKey.encryptedRegSessionKey) {
|
||||||
var deviceKey = util.random(256);
|
callback(new Error('Invalid format for session key!'));
|
||||||
// persist deviceKey to local storage (in plaintext)
|
return;
|
||||||
self._localDbDao.persist(DB_DEVICEKEY, deviceKey, function(err) {
|
}
|
||||||
|
|
||||||
|
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) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
localCallback(deviceKey);
|
uploadDeviceSecret(decrypedSessionKey);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// encrypt: deviceSecret = Es(deviceKey, deviceName) -> callback
|
function uploadDeviceSecret(regSessionKey) {
|
||||||
function generateDeciceSecret(deviceName, deviceKey) {
|
// read device secret from local storage
|
||||||
self._crypto.encrypt(deviceName, deviceKey, callback);
|
self.getDeviceSecret(function(err, deviceSecret) {
|
||||||
}
|
if (err) {
|
||||||
};
|
callback(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// generate deviceSecretIv
|
||||||
* Register the device on the private key server. This will give the device access to upload an encrypted private key.
|
var deviceSecretIv = util.random(config.symIvSize);
|
||||||
* @param {String} userId The user's email address
|
// encrypt deviceSecret
|
||||||
* @param {String} deviceName The device's memorable name e.g 'iPhone Work'
|
self._crypto.encrypt(deviceSecret, regSessionKey, deviceSecretIv, function(err, encryptedDeviceSecret) {
|
||||||
* @param {[type]} deviceSecret The device specific secret derived from the device key and the device name.
|
if (err) {
|
||||||
* @param {Function} callback(error)
|
callback(err);
|
||||||
*/
|
return;
|
||||||
KeychainDAO.prototype.registerDevice = function(userId, deviceName, deviceSecret, callback) {
|
}
|
||||||
callback(new Error('Not yet implemented!'));
|
|
||||||
|
// 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).
|
* Authenticate to the private key server (required before private PGP key upload).
|
||||||
* @param {String} userId The user's email address
|
* @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) {
|
KeychainDAO.prototype._authenticateToPrivateKeyServer = function(userId, callback) {
|
||||||
callback(new Error('Not yet implemented!'));
|
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.
|
* Encrypt and upload the private PGP key to the server.
|
||||||
* @param {Object} privkey
|
* @param {String} options.userId The user's email address
|
||||||
* @param {String} code The randomly generated or self selected code used to derive the key for the encryption of the private PGP key
|
* @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
|
* @param {Function} callback(error)
|
||||||
*/
|
*/
|
||||||
KeychainDAO.prototype.uploadPrivateKeyToServer = function(privkey, code, callback) {
|
KeychainDAO.prototype.uploadPrivateKey = function(options, callback) {
|
||||||
// generate random salt
|
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.
|
* 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)
|
* @param {Function} callback(error)
|
||||||
*/
|
*/
|
||||||
KeychainDAO.prototype.requestPrivateKeyDownload = function(userId, callback) {
|
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.
|
* 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)
|
* @param {Function} callback(error, encryptedPrivateKey)
|
||||||
*/
|
*/
|
||||||
KeychainDAO.prototype.downloadPrivateKeyFromServer = function(recoveryToken, callback) {
|
KeychainDAO.prototype.downloadPrivateKey = function(options, callback) {
|
||||||
callback(new Error('Not yet implemented!'));
|
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.
|
* 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 {String} options.userId The user's email address
|
||||||
* @param {Object} encryptedPrivkey The encrypted private PGP key including the random salt {salt:[base64 encoded string], encryptedPrivateKey:[base64 encoded string]}
|
* @param {String} options.keyId The user's email address
|
||||||
* @param {Function} callback(error, privateKey)
|
* @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
|
// 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;
|
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) {
|
PrivateKeyDAO.prototype.requestDeviceRegistration = function(options, callback) {
|
||||||
var uri = '/privatekey/user/' + privkey.userId + '/key/' + privkey._id;
|
var uri;
|
||||||
this._restDao.post(privkey, uri, callback);
|
|
||||||
|
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;
|
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.
|
* @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) {
|
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') {
|
if (typeof options.uri === 'undefined') {
|
||||||
callback({
|
callback({
|
||||||
@ -30,11 +71,11 @@ define(function(require) {
|
|||||||
options.type = options.type || 'json';
|
options.type = options.type || 'json';
|
||||||
|
|
||||||
if (options.type === 'json') {
|
if (options.type === 'json') {
|
||||||
acceptHeader = 'application/json';
|
format = 'application/json';
|
||||||
} else if (options.type === 'xml') {
|
} else if (options.type === 'xml') {
|
||||||
acceptHeader = 'application/xml';
|
format = 'application/xml';
|
||||||
} else if (options.type === 'text') {
|
} else if (options.type === 'text') {
|
||||||
acceptHeader = 'text/plain';
|
format = 'text/plain';
|
||||||
} else {
|
} else {
|
||||||
callback({
|
callback({
|
||||||
code: 400,
|
code: 400,
|
||||||
@ -44,14 +85,16 @@ define(function(require) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
xhr = new XMLHttpRequest();
|
xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', this._baseUri + options.uri);
|
xhr.open(options.method, this._baseUri + options.uri);
|
||||||
xhr.setRequestHeader('Accept', acceptHeader);
|
xhr.setRequestHeader('Accept', format);
|
||||||
|
xhr.setRequestHeader('Content-Type', format);
|
||||||
|
|
||||||
xhr.onload = function() {
|
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') {
|
if (options.type === 'json') {
|
||||||
res = JSON.parse(xhr.responseText);
|
res = xhr.responseText ? JSON.parse(xhr.responseText) : xhr.responseText;
|
||||||
} else {
|
} else {
|
||||||
res = xhr.responseText;
|
res = xhr.responseText;
|
||||||
}
|
}
|
||||||
@ -69,72 +112,11 @@ define(function(require) {
|
|||||||
xhr.onerror = function() {
|
xhr.onerror = function() {
|
||||||
callback({
|
callback({
|
||||||
code: 42,
|
code: 42,
|
||||||
errMsg: 'Error calling GET on ' + options.uri
|
errMsg: 'Error calling ' + options.method + ' on ' + options.uri
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send();
|
xhr.send(options.payload ? JSON.stringify(options.payload) : undefined);
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return RestDAO;
|
return RestDAO;
|
||||||
|
@ -3,6 +3,7 @@ define(function(require) {
|
|||||||
|
|
||||||
var Crypto = require('js/crypto/crypto'),
|
var Crypto = require('js/crypto/crypto'),
|
||||||
util = require('js/crypto/util'),
|
util = require('js/crypto/util'),
|
||||||
|
config = require('js/app-config').config,
|
||||||
expect = chai.expect;
|
expect = chai.expect;
|
||||||
|
|
||||||
describe('Crypto unit tests', function() {
|
describe('Crypto unit tests', function() {
|
||||||
@ -10,8 +11,8 @@ define(function(require) {
|
|||||||
|
|
||||||
var crypto,
|
var crypto,
|
||||||
password = 'password',
|
password = 'password',
|
||||||
keySize = 128,
|
keySize = config.symKeySize,
|
||||||
ivSize = 128;
|
ivSize = config.symIvSize;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
crypto = new Crypto();
|
crypto = new Crypto();
|
||||||
|
@ -6,20 +6,22 @@ define(function(require) {
|
|||||||
KeychainDAO = require('js/dao/keychain-dao'),
|
KeychainDAO = require('js/dao/keychain-dao'),
|
||||||
PrivateKeyDAO = require('js/dao/privatekey-dao'),
|
PrivateKeyDAO = require('js/dao/privatekey-dao'),
|
||||||
Crypto = require('js/crypto/crypto'),
|
Crypto = require('js/crypto/crypto'),
|
||||||
|
PGP = require('js/crypto/pgp'),
|
||||||
expect = chai.expect;
|
expect = chai.expect;
|
||||||
|
|
||||||
var testUser = 'test@example.com';
|
var testUser = 'test@example.com';
|
||||||
|
|
||||||
describe('Keychain DAO unit tests', function() {
|
describe('Keychain DAO unit tests', function() {
|
||||||
|
|
||||||
var keychainDao, lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub;
|
var keychainDao, lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub, pgpStub;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
|
lawnchairDaoStub = sinon.createStubInstance(LawnchairDAO);
|
||||||
pubkeyDaoStub = sinon.createStubInstance(PublicKeyDAO);
|
pubkeyDaoStub = sinon.createStubInstance(PublicKeyDAO);
|
||||||
privkeyDaoStub = new sinon.createStubInstance(PrivateKeyDAO);
|
privkeyDaoStub = sinon.createStubInstance(PrivateKeyDAO);
|
||||||
cryptoStub = new sinon.createStubInstance(Crypto);
|
cryptoStub = sinon.createStubInstance(Crypto);
|
||||||
keychainDao = new KeychainDAO(lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub);
|
pgpStub = sinon.createStubInstance(PGP);
|
||||||
|
keychainDao = new KeychainDAO(lawnchairDaoStub, pubkeyDaoStub, privkeyDaoStub, cryptoStub, pgpStub);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function() {});
|
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) {
|
it('should fail when device name is not set', function(done) {
|
||||||
lawnchairDaoStub.read.withArgs('devicename').yields();
|
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(err.message).to.equal('Device name not set!');
|
||||||
expect(deviceSecret).to.not.exist;
|
expect(deviceName).to.not.exist;
|
||||||
done();
|
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);
|
lawnchairDaoStub.read.withArgs('devicename').yields(42);
|
||||||
|
|
||||||
keychainDao.getDeviceSecret(function(err, deviceSecret) {
|
keychainDao.getDeviceName(function(err, deviceName) {
|
||||||
expect(err).to.equal(42);
|
expect(err).to.equal(42);
|
||||||
expect(deviceSecret).to.not.exist;
|
expect(deviceName).to.not.exist;
|
||||||
done();
|
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('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) {
|
keychainDao.getDeviceSecret(function(err, deviceSecret) {
|
||||||
expect(err).to.equal(42);
|
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('devicename').yields(null, 'iPhone');
|
||||||
lawnchairDaoStub.read.withArgs('devicekey').yields();
|
lawnchairDaoStub.read.withArgs('devicesecret').yields();
|
||||||
lawnchairDaoStub.persist.withArgs('devicekey').yields(42);
|
lawnchairDaoStub.persist.withArgs('devicesecret').yields(42);
|
||||||
|
|
||||||
keychainDao.getDeviceSecret(function(err, deviceSecret) {
|
keychainDao.getDeviceSecret(function(err, deviceSecret) {
|
||||||
expect(err).to.equal(42);
|
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('devicename').yields(null, 'iPhone');
|
||||||
lawnchairDaoStub.read.withArgs('devicekey').yields();
|
lawnchairDaoStub.read.withArgs('devicesecret').yields();
|
||||||
lawnchairDaoStub.persist.withArgs('devicekey').yields();
|
lawnchairDaoStub.persist.withArgs('devicesecret').yields();
|
||||||
cryptoStub.encrypt.yields(null, 'secret');
|
|
||||||
|
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) {
|
keychainDao.getDeviceSecret(function(err, deviceSecret) {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
@ -642,15 +666,569 @@ define(function(require) {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should work when device key is set', function(done) {
|
describe('registerDevice', function() {
|
||||||
lawnchairDaoStub.read.withArgs('devicename').yields(null, 'iPhone');
|
var getDeviceNameStub, lookupPublicKeyStub, getDeviceSecretStub;
|
||||||
lawnchairDaoStub.read.withArgs('devicekey').yields(null, 'key');
|
|
||||||
cryptoStub.encrypt.withArgs('iPhone', 'key').yields(null, 'secret');
|
|
||||||
|
|
||||||
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(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();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -36,6 +36,7 @@ function startTests() {
|
|||||||
'test/unit/crypto-test',
|
'test/unit/crypto-test',
|
||||||
'test/unit/rest-dao-test',
|
'test/unit/rest-dao-test',
|
||||||
'test/unit/publickey-dao-test',
|
'test/unit/publickey-dao-test',
|
||||||
|
'test/unit/privatekey-dao-test',
|
||||||
'test/unit/lawnchair-dao-test',
|
'test/unit/lawnchair-dao-test',
|
||||||
'test/unit/keychain-dao-test',
|
'test/unit/keychain-dao-test',
|
||||||
'test/unit/devicestorage-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() {
|
describe('put', function() {
|
||||||
it('should fail', function() {
|
it('should fail', function() {
|
||||||
restDao.put('/asdf', {}, function(err) {
|
restDao.put('/asdf', {}, function(err) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user