Migrating profile and storing key, when getting profile.
This commit is contained in:
parent
e215ecfb52
commit
ecaa13f6d4
4 changed files with 279 additions and 61 deletions
|
|
@ -184,6 +184,7 @@ module.exports = function(grunt) {
|
||||||
'bower_components/crypto-js/evpkdf.js',
|
'bower_components/crypto-js/evpkdf.js',
|
||||||
'bower_components/crypto-js/cipher-core.js',
|
'bower_components/crypto-js/cipher-core.js',
|
||||||
'bower_components/crypto-js/aes.js',
|
'bower_components/crypto-js/aes.js',
|
||||||
|
'bower_components/crypto-js/pbkdf2.js',
|
||||||
|
|
||||||
'node_modules/cordova-plugin-qrscanner/dist/cordova-plugin-qrscanner-lib.min.js'
|
'node_modules/cordova-plugin-qrscanner/dist/cordova-plugin-qrscanner-lib.min.js'
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,72 +1,106 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.services').factory('encryptionService', function($log) {
|
angular.module('copayApp.services').factory('encryptionService', function($log, secureStorageService) {
|
||||||
var root = {};
|
var root = {};
|
||||||
|
|
||||||
//lazy creation of cipher and decipher?
|
var keySize = 512;
|
||||||
|
var iterations = 1500;
|
||||||
|
var storageKey = 'encryptionKey';
|
||||||
|
|
||||||
// need a function to get the key
|
// need a function to get the key
|
||||||
var password = 'password';
|
var password = 'password';
|
||||||
|
|
||||||
function _getGetOrCreateKey() {
|
function _generateKey() {
|
||||||
|
var salt = CryptoJS.lib.WordArray.random(128/8);
|
||||||
|
var passphrase = CryptoJS.lib.WordArray.random(128/8);
|
||||||
|
var key = CryptoJS.PBKDF2(passphrase, salt, { keySize: keySize/32, iterations: iterations });
|
||||||
|
|
||||||
//crytpo.scrypt()
|
$log.debug('Generated key: ' + key);
|
||||||
//crypto.createCipheriv()
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {*} cb
|
||||||
|
*/
|
||||||
|
function _getOrCreateKey(cb) {
|
||||||
|
// TODO: Get from secure storage
|
||||||
|
secureStorageService.get(storageKey, function onKeyRetrieved(keyErr, keyHex) {
|
||||||
|
if (keyErr) {
|
||||||
|
cb(keyErr, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyHex) {
|
||||||
|
var key = CryptoJS.enc.Hex.parse(keyHex);
|
||||||
|
cb(null, key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = _generateKey();
|
||||||
|
var keyHex = CryptoJS.enc.Hex.stringify(key);
|
||||||
|
secureStorageService.set(storageKey, keyHex, function onKeyStored(storeErr) {
|
||||||
|
if (storeErr) {
|
||||||
|
$log.error('Error storing key.', storeErr);
|
||||||
|
cb(storeErr, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, key);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function encryptUsingcrypto(str) {
|
function _decryptUsingCryptoJS(str, key, iv) {
|
||||||
var cipher = crypto.createCipher('aes256', password);
|
var plaintext = CryptoJS.AES.decrypt(str, key, { iv: iv});
|
||||||
|
return plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
cipher.on('readable', () => {
|
|
||||||
var data = cipher.read();
|
function _encryptUsingCryptoJS(str, key) {
|
||||||
if (data) {
|
var iv = CryptoJS.lib.WordArray.random(16);
|
||||||
encrypted += data.toString('hex');
|
|
||||||
|
var cipherParams = CryptoJS.AES.encrypt(str, key, { iv: iv });
|
||||||
|
$log.debug('cipherText: ' + cipherParams.ciphertext);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ciphertext: cipherParams.ciphertext.toString(CryptoJS.enc.Base64),
|
||||||
|
opts: {
|
||||||
|
iv: iv.toString(CryptoJS.enc.Hex)
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
cipher.on('end', () => {
|
|
||||||
console.log('Encrypted 1: ' + encrypted);
|
|
||||||
//cb();
|
|
||||||
});
|
|
||||||
|
|
||||||
//cipher.write(str);
|
|
||||||
cipher.write(str);
|
|
||||||
cipher.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function encryptUsingCryptoJS(str) {
|
root.decrypt = function(str, opts, cb) {
|
||||||
var ciphertext = CryptoJS.AES.encrypt(str, password);
|
_getOrCreateKey(function onKey(err, key) {
|
||||||
$log.debug('cipherText: ' + ciphertext);
|
if (err) {
|
||||||
}
|
$log.error('Failed to get or create key.', err);
|
||||||
|
cb(err, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var decrypted = _decryptUsingCryptoJS(str, key, opts.iv);
|
||||||
|
cb(null, decrypted);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
root.encrypt = function(str, cb) {
|
root.encrypt = function(str, cb) {
|
||||||
$log.debug('*** crypto exists: ' + !!crypto);
|
$log.debug('*** crypto exists: ' + !!crypto);
|
||||||
$log.debug('*** CryptoJS exists: ' + !!CryptoJS);
|
$log.debug('*** CryptoJS exists: ' + !!CryptoJS);
|
||||||
|
|
||||||
encryptUsingCryptoJS('I am a secret.');
|
_getOrCreateKey(function onKey(err, key){
|
||||||
|
if (err) {
|
||||||
/*
|
cb(err, null);
|
||||||
// var ciphertext = CryptoJS.AES.encrypt(str, password);
|
return;
|
||||||
var cipher = crypto.createCipher('aes256', password);
|
|
||||||
|
|
||||||
cipher.on('readable', () => {
|
|
||||||
var data = cipher.read();
|
|
||||||
if (data) {
|
|
||||||
encrypted += data.toString('hex');
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
cipher.on('end', () => {
|
|
||||||
console.log('Encrypted: ' + encrypted);
|
|
||||||
//cb();
|
|
||||||
});
|
|
||||||
|
|
||||||
//cipher.write(str);
|
var encrypted = _encryptUsingCryptoJS(str, key);
|
||||||
cipher.write('I am secret');
|
cb(null, encrypted);
|
||||||
cipher.end();
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
root.encryptedObjectFromString = function(str) {
|
root.encryptedObjectFromString = function(str) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -82,5 +116,52 @@ angular.module('copayApp.services').factory('encryptionService', function($log)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var JsonFormatter = {
|
||||||
|
stringify: function (cipherParams) {
|
||||||
|
// create json object with ciphertext
|
||||||
|
var jsonObj = {
|
||||||
|
ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64)
|
||||||
|
};
|
||||||
|
// optionally add iv and salt
|
||||||
|
if (cipherParams.iv) {
|
||||||
|
jsonObj.iv = cipherParams.iv.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipherParams.salt) {
|
||||||
|
jsonObj.s = cipherParams.salt.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringify json object
|
||||||
|
return JSON.stringify(jsonObj);
|
||||||
|
},
|
||||||
|
parse: function (jsonStr) {
|
||||||
|
// parse json string
|
||||||
|
var jsonObj = JSON.parse(jsonStr);
|
||||||
|
// extract ciphertext from json object, and create cipher params object
|
||||||
|
var cipherParams = CryptoJS.lib.CipherParams.create({
|
||||||
|
ciphertext: CryptoJS.enc.Base64.parse(jsonObj.ct)
|
||||||
|
});
|
||||||
|
|
||||||
|
// optionally extract iv and salt
|
||||||
|
if (jsonObj.iv) {
|
||||||
|
cipherParams.iv = CryptoJS.enc.Hex.parse(jsonObj.iv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jsonObj.s) {
|
||||||
|
cipherParams.salt = CryptoJS.enc.Hex.parse(jsonObj.s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cipherParams;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
var encrypted = CryptoJS.AES.encrypt("Message", "Secret Passphrase", { format: JsonFormatter });
|
||||||
|
alert(encrypted); // {"ct":"tZ4MsEnfbcDOwqau68aOrQ==","iv":"8a8c8fd8fe33743d3638737ea4a00698","s":"ba06373c8f57179c"}
|
||||||
|
|
||||||
|
var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase", { format: JsonFormatter });
|
||||||
|
alert(decrypted.toString(CryptoJS.enc.Utf8)); // Message
|
||||||
|
*/
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
});
|
});
|
||||||
75
src/js/services/jsonEncryptionService.js
Normal file
75
src/js/services/jsonEncryptionService.js
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('copayApp.services')
|
||||||
|
.factory('jsonEncryptionService', jsonEncryptionService);
|
||||||
|
|
||||||
|
function jsonEncryptionService($log) {
|
||||||
|
var currentVersion = 1;
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
isEncrypted: isEncrypted,
|
||||||
|
parse: parse,
|
||||||
|
stringify: stringify
|
||||||
|
};
|
||||||
|
return service;
|
||||||
|
|
||||||
|
function isEncrypted(jsonStr) {
|
||||||
|
try {
|
||||||
|
var jsonObj = JSON.parse(jsonStr);
|
||||||
|
} catch (e) {
|
||||||
|
$log.error('Failed to parse JSON when looking for encypted data.', e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return jsonObj.version && jsonObj.encryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(jsonStr) {
|
||||||
|
|
||||||
|
var jsonObj = JSON.parse(jsonStr);
|
||||||
|
|
||||||
|
if (!(jsonObj.version && jsonObj.version === currentVersion)) {
|
||||||
|
throw new Error('Incompatible version.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptedData = jsonObj.encryptedData;
|
||||||
|
// extract ciphertext from json object, and create cipher params object
|
||||||
|
var ciphertext = CryptoJS.enc.Base64.parse(encryptedData.ciphertext)
|
||||||
|
var iv = CryptoJS.enc.Hex.parse(encryptedData.iv);
|
||||||
|
|
||||||
|
// TODO: Need to convert iv into WordArray?
|
||||||
|
|
||||||
|
return {
|
||||||
|
ciphertext: ciphertext,
|
||||||
|
opts: {
|
||||||
|
iv: iv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string, Base64 encoded} ciphertext
|
||||||
|
* @param {*} opts So it flexible enough to handle other schemes in future.
|
||||||
|
* @throws If cipherParams does not include the iv.
|
||||||
|
*/
|
||||||
|
function stringify(ciphertext, opts) {
|
||||||
|
var iv = opts.iv;
|
||||||
|
|
||||||
|
if(!iv) {
|
||||||
|
throw new Error('Must include iv.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var encryptedData = {
|
||||||
|
ciphertext: ciphertext,
|
||||||
|
iv: iv
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.stringify({
|
||||||
|
version: currentVersion,
|
||||||
|
encryptedData: encryptedData
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
angular.module('copayApp.services')
|
angular.module('copayApp.services')
|
||||||
.factory('storageService', function(appConfigService, encryptionService, logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo, secureStorageService, $timeout) {
|
.factory('storageService', function(appConfigService, encryptionService, jsonEncryptionService, logHeader, fileStorageService, localStorageService, sjcl, $log, lodash, platformInfo, secureStorageService, $timeout) {
|
||||||
|
|
||||||
var root = {};
|
var root = {};
|
||||||
var storage;
|
var storage;
|
||||||
|
|
@ -200,6 +200,50 @@ angular.module('copayApp.services')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function _migrateUnencryptedProfile(profileStr, cb) {
|
||||||
|
copayDecryptOnMobile(profileStr, function(decryptErr, decryptedStr) {
|
||||||
|
if (decryptErr) return cb(decryptErr, null);
|
||||||
|
var profile;
|
||||||
|
try {
|
||||||
|
profile = Profile.fromString(decryptedStr);
|
||||||
|
} catch (e) {
|
||||||
|
$log.error('Could not read profile:', e);
|
||||||
|
return cb(new Error('Could not read profile.'), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the only change to the contents of the profile.
|
||||||
|
profile.setAppVersion(appConfigService.version);
|
||||||
|
var newProfileStr = profile.toObj();
|
||||||
|
|
||||||
|
encryptionService.encrypt(newProfileStr, function onProfileEncrypted(encryptErr, encryptedProfile){
|
||||||
|
if (encryptErr) {
|
||||||
|
$log.error('Failed to encrypt profile.', encryptErr);
|
||||||
|
cb(encryptErr, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var persistentProfileStr;
|
||||||
|
try {
|
||||||
|
persistentProfileStr = jsonEncryptionService.stringify(encryptedProfile.ciphertext, encryptedProfile.opts);
|
||||||
|
} catch(e) {
|
||||||
|
$log.error('Failed to stringify to encrypted profile.', e);
|
||||||
|
cb(e, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.set('profile', persistentProfileStr, function onEncryptedProfileStored(setErr) {
|
||||||
|
if (setErr) {
|
||||||
|
$log.error('Failed to store encrypted profile.', setErr);
|
||||||
|
cb(setErr, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cb (null, profile);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {getProfileCallback} cb
|
* @param {getProfileCallback} cb
|
||||||
|
|
@ -217,26 +261,43 @@ angular.module('copayApp.services')
|
||||||
return cb(null, null);
|
return cb(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var encryptedProfile = encryptionService.encryptedObjectFromString(profileStr);
|
var isEncrypted = jsonEncryptionService.isEncrypted(profileStr);
|
||||||
if (!encryptedProfile) {
|
if (isEncrypted) {
|
||||||
|
$log.debug('profile was encrypted.');
|
||||||
|
|
||||||
|
var encryptedProfileObject;
|
||||||
|
try {
|
||||||
|
encryptedProfileObject = jsonEncryptionService.parse(profileStr);
|
||||||
|
} catch (e) {
|
||||||
|
$log.error('Failed to parse encrypted profile.', e);
|
||||||
|
cb(e, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
copayDecryptOnMobile(profileStr, function(decryptErr, decryptedStr) {
|
encryptionService.decrypt(
|
||||||
if (decryptErr) return cb(decryptErr, null);
|
encryptedProfileObject.ciphertext,
|
||||||
var profile;
|
encryptedProfileObject.opts,
|
||||||
try {
|
function onDecrypted(decryptionError, decryptedProfile) {
|
||||||
profile = Profile.fromString(decryptedStr);
|
if (decryptionError) {
|
||||||
} catch (e) {
|
$log.error('Failed to decrypt profile');
|
||||||
$log.debug('Could not read profile:', e);
|
cb(decryptionError, null);
|
||||||
return cb(new Error('Could not read profile.'), null);
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var profileObj = Profile.fromString(decryptedProfile);
|
||||||
|
cb(null, profileObj);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
_migrateUnencryptedProfile(profileStr, function onProfileMigrated(migrationErr, migratedProfile){
|
||||||
|
if (migrationErr) {
|
||||||
|
$log.error('Failed to migrate the profile.', migrationErr);
|
||||||
|
cb(migraionErr, null);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedProfile = encryptionService.encrypt(profile);
|
cb(null, migratedProfile);
|
||||||
$log.debug('encryptedProfile');
|
|
||||||
|
|
||||||
//cb(null, profile)
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
$log.debug('profile was encrypted.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue