derive bits instead of derive key
This commit is contained in:
@@ -2,34 +2,41 @@
|
||||
|
||||
var Neeto = Neeto || {};
|
||||
|
||||
angular
|
||||
.module('app.frontend', [
|
||||
'ui.router',
|
||||
'ng-token-auth',
|
||||
'restangular',
|
||||
'ipCookie',
|
||||
'oc.lazyLoad',
|
||||
'angularLazyImg',
|
||||
'ngDialog'
|
||||
])
|
||||
// Configure path to API
|
||||
.config(function (RestangularProvider, apiControllerProvider) {
|
||||
RestangularProvider.setDefaultHeaders({"Content-Type": "application/json"});
|
||||
if(window.crypto.subtle) {
|
||||
console.log("using WebCrypto");
|
||||
Neeto.crypto = new SNCryptoWeb();
|
||||
} else {
|
||||
console.log("using CryptoJS");
|
||||
Neeto.crypto = new SNCryptoJS();
|
||||
}
|
||||
|
||||
var url = apiControllerProvider.defaultServerURL();
|
||||
RestangularProvider.setBaseUrl(url);
|
||||
angular.module('app.frontend', [
|
||||
'ui.router',
|
||||
'ng-token-auth',
|
||||
'restangular',
|
||||
'ipCookie',
|
||||
'oc.lazyLoad',
|
||||
'angularLazyImg',
|
||||
'ngDialog'
|
||||
])
|
||||
|
||||
RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
|
||||
var token = localStorage.getItem("jwt");
|
||||
if(token) {
|
||||
headers = _.extend(headers, {Authorization: "Bearer " + localStorage.getItem("jwt")});
|
||||
}
|
||||
.config(function (RestangularProvider, apiControllerProvider) {
|
||||
RestangularProvider.setDefaultHeaders({"Content-Type": "application/json"});
|
||||
|
||||
return {
|
||||
element: element,
|
||||
params: params,
|
||||
headers: headers,
|
||||
httpConfig: httpConfig
|
||||
};
|
||||
});
|
||||
})
|
||||
var url = apiControllerProvider.defaultServerURL();
|
||||
RestangularProvider.setBaseUrl(url);
|
||||
|
||||
RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
|
||||
var token = localStorage.getItem("jwt");
|
||||
if(token) {
|
||||
headers = _.extend(headers, {Authorization: "Bearer " + localStorage.getItem("jwt")});
|
||||
}
|
||||
|
||||
return {
|
||||
element: element,
|
||||
params: params,
|
||||
headers: headers,
|
||||
httpConfig: httpConfig
|
||||
};
|
||||
});
|
||||
})
|
||||
|
||||
@@ -85,65 +85,66 @@ angular.module('app.frontend')
|
||||
this.login = function(email, password, callback) {
|
||||
console.log("login with", email, password);
|
||||
this.getAuthParamsForEmail(email, function(authParams){
|
||||
var keys = Neeto.crypto.computeEncryptionKeysForUser(_.merge({email: email, password: password}, authParams));
|
||||
this.setGk(keys.gk);
|
||||
var request = Restangular.one("auth/sign_in");
|
||||
request.user = {password: keys.pw, email: email};
|
||||
request.post().then(function(response){
|
||||
localStorage.setItem("jwt", response.token);
|
||||
callback(response);
|
||||
})
|
||||
Neeto.crypto.computeEncryptionKeysForUser(_.merge({email: email, password: password}, authParams), function(keys){
|
||||
this.setGk(keys.gk);
|
||||
var request = Restangular.one("auth/sign_in");
|
||||
request.user = {password: keys.pw, email: email};
|
||||
request.post().then(function(response){
|
||||
localStorage.setItem("jwt", response.token);
|
||||
callback(response);
|
||||
})
|
||||
}.bind(this));
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
this.register = function(email, password, callback) {
|
||||
var keys = Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email});
|
||||
this.setGk(keys.gk);
|
||||
keys.gk = null;
|
||||
var request = Restangular.one("auth");
|
||||
request.user = _.merge({password: keys.pw, email: email}, keys);
|
||||
request.post().then(function(response){
|
||||
localStorage.setItem("jwt", response.token);
|
||||
callback(response);
|
||||
})
|
||||
Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys){
|
||||
this.setGk(keys.gk);
|
||||
keys.gk = null;
|
||||
var request = Restangular.one("auth");
|
||||
request.user = _.merge({password: keys.pw, email: email}, keys);
|
||||
request.post().then(function(response){
|
||||
localStorage.setItem("jwt", response.token);
|
||||
callback(response);
|
||||
})
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this.changePassword = function(user, current_password, new_password) {
|
||||
this.getAuthParamsForEmail(email, function(authParams){
|
||||
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: current_password, email: user.email}, authParams), function(currentKeys) {
|
||||
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: new_password, email: user.email}, authParams), function(newKeys){
|
||||
var data = {};
|
||||
data.current_password = currentKeys.pw;
|
||||
data.password = newKeys.pw;
|
||||
data.password_confirmation = newKeys.pw;
|
||||
|
||||
var current_keys = Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: current_password, email: user.email}, authParams));
|
||||
var new_keys = Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: new_password, email: user.email}, authParams));
|
||||
var user = this.user;
|
||||
|
||||
var data = {};
|
||||
data.current_password = current_keys.pw;
|
||||
data.password = new_keys.pw;
|
||||
data.password_confirmation = new_keys.pw;
|
||||
|
||||
var user = this.user;
|
||||
|
||||
this._performPasswordChange(current_keys, new_keys, function(response){
|
||||
if(response && !response.errors) {
|
||||
// this.showNewPasswordForm = false;
|
||||
// reencrypt data with new gk
|
||||
this.reencryptAllItemsAndSave(user, new_keys.gk, current_keys.gk, function(success){
|
||||
if(success) {
|
||||
this.setGk(new_keys.gk);
|
||||
alert("Your password has been changed and your data re-encrypted.");
|
||||
this._performPasswordChange(currentKeys, newKeys, function(response){
|
||||
if(response && !response.errors) {
|
||||
// this.showNewPasswordForm = false;
|
||||
// reencrypt data with new gk
|
||||
this.reencryptAllItemsAndSave(user, newKeys.gk, currentKeys.gk, function(success){
|
||||
if(success) {
|
||||
this.setGk(newKeys.gk);
|
||||
alert("Your password has been changed and your data re-encrypted.");
|
||||
} else {
|
||||
// rollback password
|
||||
this._performPasswordChange(newKeys, currentKeys, function(response){
|
||||
alert("There was an error changing your password. Your password has been rolled back.");
|
||||
window.location.reload();
|
||||
})
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
// rollback password
|
||||
this._performPasswordChange(new_keys, current_keys, function(response){
|
||||
alert("There was an error changing your password. Your password has been rolled back.");
|
||||
window.location.reload();
|
||||
})
|
||||
// this.showNewPasswordForm = false;
|
||||
alert("There was an error changing your password. Please try again.");
|
||||
}
|
||||
}.bind(this));
|
||||
} else {
|
||||
// this.showNewPasswordForm = false;
|
||||
alert("There was an error changing your password. Please try again.");
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
}.bind(this))
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this._performPasswordChange = function(email, current_keys, new_keys, callback) {
|
||||
|
||||
@@ -1,84 +1,66 @@
|
||||
var Neeto = Neeto || {};
|
||||
class SNCrypto {
|
||||
|
||||
Neeto.crypto = {
|
||||
generateRandomKey() {
|
||||
return CryptoJS.lib.WordArray.random(256/8).toString();
|
||||
}
|
||||
|
||||
generateRandomKey: function() {
|
||||
return CryptoJS.lib.WordArray.random(256/8).toString();
|
||||
},
|
||||
|
||||
generateUUID: function() {
|
||||
var d = new Date().getTime();
|
||||
if(window.performance && typeof window.performance.now === "function"){
|
||||
d += performance.now(); //use high-precision timer if available
|
||||
}
|
||||
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = (d + Math.random()*16)%16 | 0;
|
||||
d = Math.floor(d/16);
|
||||
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
|
||||
});
|
||||
return uuid;
|
||||
},
|
||||
|
||||
decryptText: function(encrypted_content, key) {
|
||||
return CryptoJS.AES.decrypt(encrypted_content, key).toString(CryptoJS.enc.Utf8);
|
||||
},
|
||||
|
||||
encryptText: function(text, key) {
|
||||
return CryptoJS.AES.encrypt(text, key).toString();
|
||||
},
|
||||
|
||||
generateRandomEncryptionKey: function() {
|
||||
var salt = Neeto.crypto.generateRandomKey();
|
||||
var passphrase = Neeto.crypto.generateRandomKey();
|
||||
return CryptoJS.PBKDF2(passphrase, salt, { keySize: 256/32 }).toString();
|
||||
},
|
||||
|
||||
sha256: function(text) {
|
||||
return CryptoJS.SHA256(text).toString();
|
||||
},
|
||||
|
||||
/** Generates two deterministic keys based on one input */
|
||||
generateSymmetricKeyPair: function({password, pw_salt, pw_func, pw_alg, pw_cost, pw_key_size} = {}) {
|
||||
var algMapping = {
|
||||
"sha256" : CryptoJS.algo.SHA256,
|
||||
"sha512" : CryptoJS.algo.SHA512
|
||||
}
|
||||
var fnMapping = {
|
||||
"pbkdf2" : CryptoJS.PBKDF2
|
||||
generateUUID() {
|
||||
var d = new Date().getTime();
|
||||
if(window.performance && typeof window.performance.now === "function"){
|
||||
d += performance.now(); //use high-precision timer if available
|
||||
}
|
||||
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = (d + Math.random()*16)%16 | 0;
|
||||
d = Math.floor(d/16);
|
||||
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
|
||||
});
|
||||
return uuid;
|
||||
}
|
||||
|
||||
var alg = algMapping[pw_alg];
|
||||
var kdf = fnMapping[pw_func];
|
||||
var output = kdf(password, pw_salt, { keySize: pw_key_size/32, hasher: alg, iterations: pw_cost }).toString();
|
||||
decryptText(encrypted_content, key) {
|
||||
return CryptoJS.AES.decrypt(encrypted_content, key).toString(CryptoJS.enc.Utf8);
|
||||
}
|
||||
|
||||
var outputLength = output.length;
|
||||
var firstHalf = output.slice(0, outputLength/2);
|
||||
var secondHalf = output.slice(outputLength/2, outputLength);
|
||||
return [firstHalf, secondHalf];
|
||||
},
|
||||
encryptText(text, key) {
|
||||
return CryptoJS.AES.encrypt(text, key).toString();
|
||||
}
|
||||
|
||||
computeEncryptionKeysForUser: function({email, password, pw_salt, pw_func, pw_alg, pw_cost, pw_key_size} = {}) {
|
||||
var keys = Neeto.crypto.generateSymmetricKeyPair({password: password, pw_salt: pw_salt,
|
||||
pw_func: pw_func, pw_alg: pw_alg, pw_cost: pw_cost, pw_key_size: pw_key_size});
|
||||
var pw = keys[0];
|
||||
var gk = keys[1];
|
||||
generateRandomEncryptionKey() {
|
||||
var salt = Neeto.crypto.generateRandomKey();
|
||||
var passphrase = Neeto.crypto.generateRandomKey();
|
||||
return CryptoJS.PBKDF2(passphrase, salt, { keySize: 256/32 }).toString();
|
||||
}
|
||||
|
||||
return {pw: pw, gk: gk};
|
||||
},
|
||||
sha256(text) {
|
||||
return CryptoJS.SHA256(text).toString();
|
||||
}
|
||||
|
||||
generateInitialEncryptionKeysForUser: function({email, password} = {}) {
|
||||
sha1(text) {
|
||||
return CryptoJS.SHA1(text).toString();
|
||||
}
|
||||
|
||||
computeEncryptionKeysForUser({email, password, pw_salt, pw_func, pw_alg, pw_cost, pw_key_size} = {}, callback) {
|
||||
this.generateSymmetricKeyPair({password: password, pw_salt: pw_salt,
|
||||
pw_func: pw_func, pw_alg: pw_alg, pw_cost: pw_cost, pw_key_size: pw_key_size}, function(keys){
|
||||
var pw = keys[0];
|
||||
var gk = keys[1];
|
||||
|
||||
callback({pw: pw, gk: gk});
|
||||
});
|
||||
}
|
||||
|
||||
generateInitialEncryptionKeysForUser({email, password} = {}, callback) {
|
||||
var defaults = this.defaultPasswordGenerationParams();
|
||||
var {pw_func, pw_alg, pw_key_size, pw_cost} = defaults;
|
||||
var pw_nonce = this.generateRandomKey();
|
||||
var pw_salt = CryptoJS.SHA1(email + "SN" + pw_nonce).toString();
|
||||
var keys = Neeto.crypto.generateSymmetricKeyPair(_.merge({email: email, password: password, pw_salt: pw_salt}, defaults));
|
||||
var pw = keys[0];
|
||||
var gk = keys[1];
|
||||
var pw_salt = this.sha1(email + "SN" + pw_nonce);
|
||||
this.generateSymmetricKeyPair(_.merge({email: email, password: password, pw_salt: pw_salt}, defaults), function(keys){
|
||||
var pw = keys[0];
|
||||
var gk = keys[1];
|
||||
|
||||
return _.merge({pw: pw, gk: gk, pw_nonce: pw_nonce}, defaults);
|
||||
},
|
||||
|
||||
defaultPasswordGenerationParams: function() {
|
||||
return {pw_func: "pbkdf2", pw_alg: "sha512", pw_key_size: 512, pw_cost: 3000};
|
||||
callback(_.merge({pw: pw, gk: gk, pw_nonce: pw_nonce}, defaults));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export { SNCrypto }
|
||||
|
||||
29
app/assets/javascripts/app/services/helpers/cryptojs.js
Normal file
29
app/assets/javascripts/app/services/helpers/cryptojs.js
Normal file
@@ -0,0 +1,29 @@
|
||||
class SNCryptoJS extends SNCrypto {
|
||||
|
||||
/** Generates two deterministic keys based on one input */
|
||||
generateSymmetricKeyPair({password, pw_salt, pw_func, pw_alg, pw_cost, pw_key_size} = {}, callback) {
|
||||
var algMapping = {
|
||||
"sha256" : CryptoJS.algo.SHA256,
|
||||
"sha512" : CryptoJS.algo.SHA512
|
||||
}
|
||||
var fnMapping = {
|
||||
"pbkdf2" : CryptoJS.PBKDF2
|
||||
}
|
||||
|
||||
var alg = algMapping[pw_alg];
|
||||
var kdf = fnMapping[pw_func];
|
||||
var output = kdf(password, pw_salt, { keySize: pw_key_size/32, hasher: alg, iterations: pw_cost }).toString();
|
||||
|
||||
var outputLength = output.length;
|
||||
var firstHalf = output.slice(0, outputLength/2);
|
||||
var secondHalf = output.slice(outputLength/2, outputLength);
|
||||
callback([firstHalf, secondHalf])
|
||||
}
|
||||
|
||||
defaultPasswordGenerationParams() {
|
||||
return {pw_func: "pbkdf2", pw_alg: "sha512", pw_key_size: 512, pw_cost: 3000};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export { SNCryptoJS }
|
||||
113
app/assets/javascripts/app/services/helpers/webcrypto.js
Normal file
113
app/assets/javascripts/app/services/helpers/webcrypto.js
Normal file
@@ -0,0 +1,113 @@
|
||||
var subtleCrypto = window.crypto.subtle;
|
||||
|
||||
class SNCryptoWeb extends SNCrypto {
|
||||
|
||||
/**
|
||||
Overrides
|
||||
*/
|
||||
defaultPasswordGenerationParams() {
|
||||
return {pw_func: "pbkdf2", pw_alg: "sha512", pw_key_size: 512, pw_cost: 5000};
|
||||
}
|
||||
|
||||
/** Generates two deterministic keys based on one input */
|
||||
generateSymmetricKeyPair({password, pw_salt, pw_func, pw_alg, pw_cost, pw_key_size} = {}, callback) {
|
||||
this.stretchPassword({password: password, pw_func: pw_func, pw_alg: pw_alg, pw_salt: pw_salt, pw_cost: pw_cost, pw_key_size: pw_key_size}, function(output){
|
||||
var outputLength = output.length;
|
||||
var firstHalf = output.slice(0, outputLength/2);
|
||||
var secondHalf = output.slice(outputLength/2, outputLength);
|
||||
callback([firstHalf, secondHalf]);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Internal
|
||||
*/
|
||||
|
||||
stretchPassword({password, pw_salt, pw_cost, pw_func, pw_alg, pw_key_size} = {}, callback) {
|
||||
|
||||
this.webCryptoImportKey(password, pw_func, function(key){
|
||||
console.log("Importing key", password);
|
||||
|
||||
if(!key) {
|
||||
console.log("Key is null, unable to continue");
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
this.webCryptoDeriveBits({key: key, pw_func: pw_func, pw_alg: pw_alg, pw_salt: pw_salt, pw_cost: pw_cost, pw_key_size: pw_key_size}, function(key){
|
||||
if(!key) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(key);
|
||||
|
||||
}.bind(this))
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
webCryptoImportKey(input, pw_func, callback) {
|
||||
subtleCrypto.importKey(
|
||||
"raw",
|
||||
this.stringToArrayBuffer(input),
|
||||
{name: pw_func.toUpperCase()},
|
||||
false,
|
||||
["deriveBits"]
|
||||
)
|
||||
.then(function(key){
|
||||
callback(key);
|
||||
})
|
||||
.catch(function(err){
|
||||
console.error(err);
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
webCryptoDeriveBits({key, pw_func, pw_alg, pw_salt, pw_cost, pw_key_size} = {}, callback) {
|
||||
var algMapping = {
|
||||
"sha256" : "SHA-256",
|
||||
"sha512" : "SHA-512",
|
||||
}
|
||||
var alg = algMapping[pw_alg];
|
||||
subtleCrypto.deriveBits(
|
||||
{
|
||||
"name": pw_func.toUpperCase(),
|
||||
salt: this.stringToArrayBuffer(pw_salt),
|
||||
iterations: pw_cost,
|
||||
hash: {name: alg},
|
||||
},
|
||||
key,
|
||||
pw_key_size
|
||||
)
|
||||
.then(function(bits){
|
||||
var key = this.arrayBufferToHexString(new Uint8Array(bits));
|
||||
callback(key);
|
||||
}.bind(this))
|
||||
.catch(function(err){
|
||||
console.error(err);
|
||||
callback(null);
|
||||
});
|
||||
}
|
||||
|
||||
stringToArrayBuffer(string) {
|
||||
var encoder = new TextEncoder("utf-8");
|
||||
return encoder.encode(string);
|
||||
}
|
||||
|
||||
arrayBufferToHexString(arrayBuffer) {
|
||||
var byteArray = new Uint8Array(arrayBuffer);
|
||||
var hexString = "";
|
||||
var nextHexByte;
|
||||
|
||||
for (var i=0; i<byteArray.byteLength; i++) {
|
||||
nextHexByte = byteArray[i].toString(16);
|
||||
if (nextHexByte.length < 2) {
|
||||
nextHexByte = "0" + nextHexByte;
|
||||
}
|
||||
hexString += nextHexByte;
|
||||
}
|
||||
return hexString;
|
||||
}
|
||||
}
|
||||
|
||||
export { SNCryptoWeb }
|
||||
Reference in New Issue
Block a user