[FEATURE] recover blob and change password

This commit is contained in:
Matthew Fettig
2014-06-11 09:26:03 -07:00
parent 0a8d4ad587
commit 182e1863f4
3 changed files with 299 additions and 17 deletions

View File

@@ -1,8 +1,8 @@
var crypt = require('./crypt').Crypt;
var SignedRequest = require('./signedrequest').SignedRequest;
var request = require('superagent');
var extend = require("extend");
var async = require("async");
var request = require('superagent');
var extend = require("extend");
var async = require("async");
var BlobClient = {};
@@ -261,11 +261,12 @@ BlobObj.prototype.encryptBlobCrypt = function(secret, blobDecryptKey) {
* Decrypt recovery key
*
* @param {string} secret
* @param {string} encryptedKey
*/
BlobObj.prototype.decryptBlobCrypt = function(secret) {
function decryptBlobCrypt (secret, encryptedKey) {
var recoveryEncryptionKey = crypt.deriveRecoveryEncryptionKeyFromSecret(secret);
return crypt.decrypt(recoveryEncryptionKey, this.encrypted_blobdecrypt_key);
return crypt.decrypt(recoveryEncryptionKey, encryptedKey);
};
/**** Blob updating functions ****/
@@ -843,6 +844,7 @@ BlobClient.verify = function(url, username, token, fn) {
* ResendEmail
* resend verification email
*/
BlobClient.resendEmail = function (opts, fn) {
var config = {
method : 'POST',
@@ -875,6 +877,145 @@ BlobClient.resendEmail = function (opts, fn) {
});
};
/**
* RecoverBlob
* recover a blob using the account secret
* @param {object} opts
* @param {string} opts.url
* @param {string} opts.username
* @param {string} opts.masterkey
* @param {function} fn
*/
BlobClient.recoverBlob = function (opts, fn) {
var username = String(opts.username).trim();
var config = {
method : 'GET',
url : opts.url + '/v1/user/recov/' + username,
};
var signedRequest = new SignedRequest(config);
var signed = signedRequest.signAsymmetricRecovery(opts.masterkey, username);
request.get(signed.url)
.end(function(err, resp) {
if (err) {
fn(err);
} else if (resp.body && resp.body.result === 'success') {
handleRecovery(resp);
} else if (resp.body && resp.body.result === 'error') {
fn(new Error(resp.body.message));
} else {
fn(new Error('Could not recover blob'));
}
});
function handleRecovery (resp) {
//decrypt crypt key
var crypt = decryptBlobCrypt(opts.masterkey, resp.body.encrypted_blobdecrypt_key);
var blob = new BlobObj(opts.url, resp.body.blob_id, crypt);
blob.revision = resp.body.revision;
blob.encrypted_secret = resp.body.encrypted_secret;
if (!blob.decrypt(resp.body.blob)) {
return fn(new Error('Error while decrypting blob'));
}
//Apply patches
if (resp.body.patches && resp.body.patches.length) {
var successful = true;
resp.body.patches.forEach(function(patch) {
successful = successful && blob.applyEncryptedPatch(patch);
});
if (successful) {
blob.consolidate();
}
}
//return with newly decrypted blob
fn(null, blob);
};
};
/**
* updateKeys
* @param {object} opts
* @param {string} opts.username
* @param {string} opts.password
* @param {string} opts.masterkey
* @param {object} opts.pakdf
*/
BlobObj.prototype.updateKeys = function (opts, fn) {
var self = this;
var username = String(opts.username).trim();
var password = String(opts.password).trim();
function deriveKeys(callback) {
// derive unlock and login keys
var keys = { };
function deriveKey(keyType, callback) {
crypt.derive(opts.pakdf, keyType, username.toLowerCase(), password, function(err, key) {
if (err) {
callback(err);
} else {
keys[keyType] = key;
callback();
}
});
};
async.eachSeries([ 'login', 'unlock' ], deriveKey, function(err) {
if (err) {
callback(err);
} else {
callback(null, keys);
}
});
};
function updateBlob (keys, callback) {
var old_id = self.id;
self.id = keys.login.id;
self.key = keys.login.crypt;
self.encrypted_secret = self.encryptSecret(keys.unlock.unlock, opts.masterkey);
//post to the blob vault to create
var config = {
method : 'POST',
url : self.url + '/v1/user/' + username,
data : {
blob_id : self.id,
data : self.encrypt(),
revision : self.revision,
encrypted_blobdecrypt_key : self.encryptBlobCrypt(opts.masterkey, self.key),
encrypted_secret : self.encrypted_secret
}
};
var signedRequest = new SignedRequest(config);
var signed = signedRequest.signAsymmetric(opts.masterkey, self.data.account_id, old_id);
request.post(signed.url)
.send(signed.data)
.end(function(err, resp) {
if (err) {
fn(new Error('Updated blob could not be saved - XHR error'));
} else if (!resp.body || resp.body.result !== 'success') {
fn(new Error('Updated blob could not be saved - bad result'));
} else {
fn(null, resp.body);
}
});
};
async.waterfall([ deriveKeys, updateBlob ], fn);
};
/**
* Rename a ripple account
*
@@ -894,6 +1035,7 @@ BlobClient.rename = function (opts, fn) {
username: opts.new_username,
data: opts.blob.encrypt(),
encrypted_secret: opts.blob.encryptedSecret,
encrypted_blobdecrypt_key: opts.blob.encrypted_blobdecrypt_key,
revision: opts.blob.revision
}
};
@@ -978,7 +1120,7 @@ BlobClient.create = function(options, fn) {
if (err) {
fn(err);
} else if (resp.body && resp.body.result === 'success') {
fn(null, blob,resp.body);
fn(null, blob, resp.body);
} else if (resp.body && resp.body.result === 'error') {
fn(new Error(resp.body.message));
} else {
@@ -988,3 +1130,7 @@ BlobClient.create = function(options, fn) {
};
exports.BlobClient = BlobClient;

View File

@@ -8,6 +8,7 @@ var SignedRequest = function (config) {
// XXX Constructor should be generalized and constructing from an Angular.js
// $http config should be a SignedRequest.from... utility method.
this.config = extend(true, {}, config);
if (!this.config.data) this.config.data = {};
};
@@ -150,6 +151,34 @@ SignedRequest.prototype.signAsymmetric = function (secretKey, account, blob_id)
return config;
};
/**
* Asymmetric signed request for vault recovery
* @param {Object} config
* @param {Object} secretKey
* @param {Object} username
*/
SignedRequest.prototype.signAsymmetricRecovery = function (secretKey, username) {
var config = extend(true, {}, this.config);
// Parse URL
var parsed = parser.parse(config.url);
var date = dateAsIso8601();
var signatureType = 'RIPPLE1-ECDSA-SHA512';
var stringToSign = this.getStringToSign(parsed, date, signatureType);
var signature = Message.signMessage(stringToSign, secretKey);
var query = querystring.stringify({
signature: Crypt.base64ToBase64Url(signature),
signature_date: date,
signature_username: username,
signature_type: signatureType
});
config.url += (parsed.search ? '&' : '?') + query;
return config;
};
var dateAsIso8601 = (function () {
function pad(n) {
return (n < 0 || n > 9 ? "" : "0") + n;

View File

@@ -90,9 +90,9 @@ VaultClient.prototype.login = function(username, password, callback) {
self.infos[keys.id] = authInfo;
callback(null, {
blob: blob,
username: authInfo.username,
verified: authInfo.emailVerified
blob : blob,
username : authInfo.username,
verified : authInfo.emailVerified
});
});
};
@@ -203,11 +203,11 @@ VaultClient.prototype.loginAndUnlock = function(username, password, callback) {
}
callback(null, {
blob : blob,
unlock : keys.unlock,
secret : secret,
username : authInfo.username,
verified : authInfo.emailVerified
blob : blob,
unlock : keys.unlock,
secret : secret,
username : authInfo.username,
verified : authInfo.emailVerified
});
});
};
@@ -231,7 +231,7 @@ VaultClient.prototype.loginAndUnlock = function(username, password, callback) {
if (!authInfo) {
return callback(new Error('Unable to find authInfo'));
}
deriveUnlockKey(authInfo, resp.blob, callback);
});
};
@@ -281,10 +281,117 @@ VaultClient.prototype.verify = function(username, token, callback) {
* resendEmail
* send a new verification email
* @param {object} options
* @param {function} fn - Callback
*/
VaultClient.prototype.resendEmail = function (options, fn) {
blobClient.resendEmail(options, fn);
};
/**
* recoverBlob
* recover blob with account secret
* @param {object} options
* @param {string} options.url
* @param {string} options.username
* @param {string} options.masterkey
* @param {function}
*/
VaultClient.prototype.recoverBlob = function (options, fn) {
blobClient.recoverBlob(options, fn);
};
VaultClient.prototype.updateBlobKeys = function (options, fn) {
var username = String(options.username).trim();
this.authInfo.get(this.domain, username.toLowerCase(), function(err, authInfo) {
if (err) {
return callback(err);
}
options.pakdf = authInfo.pakdf;
options.blob.updateKeys(options, fn);
});
}
/**
* rename
* rename a ripple account
* @param {object} options
* @param {function} callback
*/
VaultClient.prototype.resendEmail = function (options, callback) {
blobClient.resendEmail(options, callback);
VaultClient.prototype.rename = function (options, callback) {
var self = this;
var new_username = options.new_username;
var password = options.password;
// TODO duplicate function
function getAuthInfo(callback) {
self.authInfo.get(self.domain, new_username, function(err, authInfo) {
if (err) {
return callback(err);
}
if (authInfo.version !== 3) {
return callback(new Error('This wallet is incompatible with this version of the vault-client.'));
}
if (!authInfo.pakdf) {
return callback(new Error('No settings for PAKDF in auth packet.'));
}
/*if (!authInfo.exists) {
return callback(new Error('User does not exist.'));
}*/
if (typeof authInfo.blobvault !== 'string') {
return callback(new Error('No blobvault specified in the authinfo.'));
}
callback(null, authInfo);
});
}
// TODO duplicate function
function deriveLoginKeys(authInfo, callback) {
//derive login keys
crypt.derive(authInfo.pakdf, 'login', new_username.toLowerCase(), password, function(err, keys) {
if (err) {
callback(err);
} else {
callback(null, authInfo, keys);
}
});
}
// TODO duplicate function
function deriveUnlockKey(authInfo, callback) {
//derive unlock key
crypt.derive(authInfo.pakdf, 'unlock', new_username.toLowerCase(), password, function(err, keys) {
if (err) {
console.log('Error',err);
return callback(err);
}
callback(null, keys.unlock);
});
}
getAuthInfo(function(err, authInfo){
deriveLoginKeys(authInfo, function(err, authInfo, loginKeys){
deriveUnlockKey(authInfo, function(err, unlockKeys){
if (err) {
console.log('Error', err);
return;
}
options.crypt = loginKeys.crypt;
options.new_blob_id = loginKeys.id;
options.unlock = unlockKeys;
blobClient.rename(options, callback);
})
})
});
};
/**