Compare commits

...

20 commits

Author SHA1 Message Date
Brendon Duncan
d46c1def6a Merge commit '6c6d90ebfe' into wallet/task/351 2018-06-29 11:28:42 +12:00
Brendon Duncan
60c7a03d8b Scripts for running on Android emulator. 2018-06-29 08:43:43 +12:00
Brendon Duncan
9b20cb36fd Fixed incorrect case. 2018-06-29 08:13:13 +12:00
Jean-Baptiste Dominguez
f4f2a84fbb 351 - Improvement - Fix return was missing 2018-06-29 00:48:03 +09:00
Jean-Baptiste Dominguez
2e2012f978
Merge pull request #196 from Bitcoin-com/wallet/task/365
Wallet/task/365
2018-06-29 00:06:28 +09:00
Sebastiaan Pasma
6ef1bca911 small fix for key + exception message 2018-06-28 14:47:27 +02:00
Brendon Duncan
fb88b05463 Catching exception caused by using the wrong key to decrypt the profile. 2018-06-28 21:55:06 +12:00
Brendon Duncan
074e691cf9 Removing encryption key if it already exists from a previous installation. 2018-06-28 21:38:14 +12:00
Brendon Duncan
a388e6deac Some more clean up and documentation in encryptionService. 2018-06-28 21:13:37 +12:00
Brendon Duncan
63ddf545e4 Removed oboslete code from encryptionService and gave it a better layout. 2018-06-28 20:49:54 +12:00
Brendon Duncan
f04417bc39 Clarification of some items. 2018-06-28 20:39:54 +12:00
Brendon Duncan
52b9a206c3 Creation of new profile on startup works. 2018-06-28 20:31:22 +12:00
Brendon Duncan
ecaa13f6d4 Migrating profile and storing key, when getting profile. 2018-06-28 17:33:38 +12:00
Brendon Duncan
e215ecfb52 Initial encryption test is working on Chrome and iOS. 2018-06-28 08:32:45 +12:00
Brendon Duncan
35ba9bcb3c Test for merging profiles where each one contains a different copayer of the same shared wallet. 2018-06-17 16:02:51 +12:00
Brendon Duncan
6d90a0277a Fix equality test for credentials in profile merging, to match on something unique. 2018-06-15 21:12:25 +12:00
Sebastiaan Pasma
6c6d90ebfe Cleaner way without redundant code 2018-06-05 11:15:33 +02:00
Sebastiaan Pasma
1e1f5deb2a iOS 9.3 fix 2018-06-04 21:37:36 +02:00
Sebastiaan Pasma
ddbc969de2 Merge remote-tracking branch 'origin/wallet/sprint/17' into wallet/sprint/17 2018-06-04 21:04:02 +02:00
Sebastiaan Pasma
4c4c213786 Merge remote-tracking branch 'origin/wallet/sprint/17' into wallet/sprint/17
# Conflicts:
#	src/js/controllers/tab-scan.js
2018-05-28 13:48:26 +02:00
12 changed files with 449 additions and 55 deletions

View file

@ -175,6 +175,17 @@ module.exports = function(grunt) {
'src/js/trezor-url.js',
'bower_components/trezor-connect/connect.js',
'node_modules/bezier-easing/dist/bezier-easing.min.js',
'bower_components/crypto-js/core.js',
'bower_components/crypto-js/enc-base64.js',
'bower_components/crypto-js/hmac.js',
'bower_components/crypto-js/md5.js',
'bower_components/crypto-js/sha1.js',
'bower_components/crypto-js/evpkdf.js',
'bower_components/crypto-js/cipher-core.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'
],
dest: 'www/js/app.js'

View file

@ -87,6 +87,7 @@
"start": "npm run build:www && ionic serve --nolivereload --nogulp -s --address 0.0.0.0",
"start:ios": "npm run build:www && npm run build:ios && npm run open:ios",
"start:android": "npm run build:www && npm run build:android && npm run run:android",
"start:android-emulator": "npm run build:www && npm run build:android && npm run run:android-emulator",
"start:windows": "npm run build:www && npm run build:windows",
"start:desktop": "npm start",
"watch": "grunt watch",
@ -99,7 +100,7 @@
"build:android-release": "cordova prepare android && cordova build android --release",
"build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"ARM\"",
"build:desktop": "grunt desktop",
"build:osx": "grunt osx",
"build:osx": "grunt osx",
"open:ios": "open platforms/ios/*.xcodeproj",
"open:android": "open -a open -a /Applications/Android\\ Studio.app platforms/android",
"final:www": "npm run build:www-release",
@ -108,7 +109,8 @@
"final:windows": "npm run final:www && npm run build:windows-release",
"final:desktop": "npm run build:desktop && npm run build:osx",
"run:android": "cordova run android --device",
"run:android-release": "cordova run android --device --release",
"run:android-emulator": "cordova run android --emulator",
"run:android-release": "cordova run android --device --release",
"log:android": "adb logcat | grep chromium",
"sign:android": "rm -f platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../bitcoin-com-release-key.jks -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk bitcoin-com && $ANDROID_HOME/build-tools/27.0.1/zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned.apk",
"apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare",

View file

@ -11,6 +11,7 @@
"angular-gettext": "2.2.1",
"angular-moment": "0.10.1",
"angular-qrcode": "bitpay/angular-qrcode#~6.3.0",
"crypto-js": "^3.1.9",
"ionic": "https://github.com/ionic-team/ionic-v1.git",
"moment": "2.10.3",
"ng-lodash": "0.2.3",

View file

@ -104,12 +104,12 @@ angular.module('copayApp.controllers').controller('searchController', function($
};
$scope.searchOnBlockchain = function(searchTerm) {
const url = 'https://explorer.bitcoin.com/'+$scope.wallet.coin+'/search/' + searchTerm;
const optIn = true;
const title = null;
const message = gettextCatalog.getString('Search on Explorer.Bitcoin.com');
const okText = gettextCatalog.getString('Open Explorer');
const cancelText = gettextCatalog.getString('Go Back');
var url = 'https://explorer.bitcoin.com/'+$scope.wallet.coin+'/search/' + searchTerm;
var optIn = true;
var title = null;
var message = gettextCatalog.getString('Search on Explorer.Bitcoin.com');
var okText = gettextCatalog.getString('Open Explorer');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};

View file

@ -76,7 +76,7 @@ Profile.prototype.merge = function(other) {
other.credentials.forEach(function(otherCredential) {
var credentialExists = false;
thisProfile.credentials.forEach(function(thisCredential) {
if (otherCredential.walletId === thisCredential.walletId) {
if (otherCredential.mnemonic === thisCredential.mnemonic) {
credentialExists = true;
}
});

View file

@ -0,0 +1,32 @@
describe('Profiles', function(){
var profileNew,
profileNewWithCopayer1String = '{"version":"1.0.0","appVersion":"4.11.0","createdOn":1529205102152,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2NCcpNJx1AeP4KQpdX32YdQZYbCTDaggotfSPR3vMKHyATyHdnPz5Qi4kDKUEuQPFGa6imiihby8GYkWThAcG3KjffT1HeU","xPubKey":"xpub6C9CWjLy5XasVGNtadbMwq51ZyW3crgHMA7VMbcQcb6thzisFFzeAKrKQhW1RD6u1DBhQPJb9Az8NjJ9PYFPyw37C6J6HTB7NxBjMBttUC7","requestPrivKey":"c1dc4bd23639e6058835e6039526d7cc7876ef5d7a8ace787606921973b8ae2f","requestPubKey":"0208aea4fcbed1511924e4af167915c9e5f8638e88f5904c3ed97dbae87000644c","copayerId":"00adbea27e4eaba8ba4da39ebc1ad966bf1f10f786eab4caa918f645bea72629","publicKeyRing":[{"xPubKey":"xpub6C9CWjLy5XasVGNtadbMwq51ZyW3crgHMA7VMbcQcb6thzisFFzeAKrKQhW1RD6u1DBhQPJb9Az8NjJ9PYFPyw37C6J6HTB7NxBjMBttUC7","requestPubKey":"0208aea4fcbed1511924e4af167915c9e5f8638e88f5904c3ed97dbae87000644c"}],"walletId":"9c49c70d-f18e-4b98-9d2f-1cd7663b7814","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"a5fd054f4445c5f3a46ba83977348cbeff59cfcaffa912c3550c6855ad86bd6c","personalEncryptingKey":"VEk6iRC6VUxDOV+PcwfgGQ==","sharedEncryptingKey":"gyp7GQjDeRUoa0VYDpAZ/g==","copayerName":"me","mnemonic":"art ability taxi tennis scheme cage room bunker gentle degree peasant juice","entropySource":"a0d41b5f876335bd61def4ad5277beefadc5981d15e23c488836c91c0add864a","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2NCcpNJx1AeP4KQpdX32YdQZYbCTDaggotfSPR3vMKHyATyHdnPz5Qi4kDKUEuQPFGa6imiihby8GYkWThAcG3KjffT1HeU","xPubKey":"xpub6C9CWjLy5XasVGNtadbMwq51ZyW3crgHMA7VMbcQcb6thzisFFzeAKrKQhW1RD6u1DBhQPJb9Az8NjJ9PYFPyw37C6J6HTB7NxBjMBttUC7","requestPrivKey":"c1dc4bd23639e6058835e6039526d7cc7876ef5d7a8ace787606921973b8ae2f","requestPubKey":"0208aea4fcbed1511924e4af167915c9e5f8638e88f5904c3ed97dbae87000644c","copayerId":"b7e068e7f01a84be25383037bdded240565a97897791b034e390201c411b1ff8","publicKeyRing":[{"xPubKey":"xpub6C9CWjLy5XasVGNtadbMwq51ZyW3crgHMA7VMbcQcb6thzisFFzeAKrKQhW1RD6u1DBhQPJb9Az8NjJ9PYFPyw37C6J6HTB7NxBjMBttUC7","requestPubKey":"0208aea4fcbed1511924e4af167915c9e5f8638e88f5904c3ed97dbae87000644c"}],"walletId":"8180ad5e-8e0a-40e2-88b6-507badee3de4","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"4dcc3e1ce1fb8e22e14dfd1501d5e96aca05d43f8aef01bcc761a8a3d6916068","personalEncryptingKey":"VEk6iRC6VUxDOV+PcwfgGQ==","sharedEncryptingKey":"1WeeJVxGFB7wzS4w4S+4Tw==","copayerName":"me","mnemonic":"art ability taxi tennis scheme cage room bunker gentle degree peasant juice","entropySource":"a0d41b5f876335bd61def4ad5277beefadc5981d15e23c488836c91c0add864a","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2ZcybcyUf3bAfq3SAvBZbMPz7sMp8E8baXLc9GAgoVDho6iqPSAwvf124mabfWqPntRB93KPccmywNCQLmn6ukoGKqYWB1r","xPubKey":"xpub6DQPHX7xWx8EcAAtWAwmE5a4U4LoHCZHSkdcmgZpRn1A1LvupaWNiDf5pWEK2t4PdnxXWrGYS7uzjWWsmhXJgpkMfKRB3seQXk2KZngUyPc","requestPrivKey":"cdf993dded8c8d5d45006852f56624efc6637120225509c351c32b973bf21f5d","requestPubKey":"0260197aa6c1e7a1889d3686bd4e903931134c949bd8d85347eb7464d2ed66818b","copayerId":"449732a920b1315d730edb83fd4d4751f3f99a396844a91a2dd2bd981effeafb","publicKeyRing":[{"xPubKey":"xpub6DQPHX7xWx8EcAAtWAwmE5a4U4LoHCZHSkdcmgZpRn1A1LvupaWNiDf5pWEK2t4PdnxXWrGYS7uzjWWsmhXJgpkMfKRB3seQXk2KZngUyPc","requestPubKey":"0260197aa6c1e7a1889d3686bd4e903931134c949bd8d85347eb7464d2ed66818b","copayerName":"Alice"},{"xPubKey":"xpub6D94BNBSMLvAfsUs6p7Nt64zdeno4vMYWEAU8d7WJ7X44FaeWjZcuR9LyDtUHeJGKx475vfNwhgc8rAsFYN37JFb9ojavAhUas4deTeJ3bB","requestPubKey":"022d2938551609c30b5640c202ddf07cecd828e204647846fc3dc1c2fa7299aea1","copayerName":"Bob"}],"walletId":"4e5f4de5-20fd-4b69-9337-ef05ea204b8b","walletName":"Alice and Bob\'s Wallet","m":2,"n":2,"walletPrivKey":"a624db620778bb9c3b6645dfe734c0c55c1b8946d5c474904d10ed4c227f2574","personalEncryptingKey":"58lHfKK/gfwg6uGk6fqi7g==","sharedEncryptingKey":"IrVSZuuVr0q0CjJ+mdOM8A==","copayerName":"Alice","mnemonic":"enrich change conduct popular angle hover easy left month demand parade game","entropySource":"50c019f295dd3bc8ae22327c96cb2535809dbbdd2496b2946b64c81ff2b8c3c2","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2SH"}],"disclaimerAccepted":true,"checked":{"9c49c70d-f18e-4b98-9d2f-1cd7663b7814":true,"8180ad5e-8e0a-40e2-88b6-507badee3de4":true,"4e5f4de5-20fd-4b69-9337-ef05ea204b8b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36"}',
profileOld,
profileOldWithCopayer2String = '{"version":"1.0.0","createdOn":1529205124796,"credentials":[{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2GSFBcThSsdmTXUVKwHaifwLh5CY4swQGE1PSM5ESg8mjiXZg3U8SUfvQsyzTeWSTxCwPwvqwJHzsPDPPGZ781yswvPeCJL","xPubKey":"xpub6C27o1AABpLovccSoVJUG8KCH9EiR6949LTtsLY8zLd6GXaAaFmQPkJrfm5QCHQRT7nkJKRAMAZrXtPbEixHTPMrf48Q8gVbAQ25odupVCW","requestPrivKey":"6be2636e3a5523e34c0804e775f72fdcb2253f30e02275c1da9fae57bcdfc6d8","requestPubKey":"036c2c1c3ecf63de8e8aaa8e63720348df437ba9267b209c83daa1e5b16a37620c","copayerId":"01543b105191e2d29b9cbba7b1a9464360bc6dd1adcbcd6de326ca6d67caf33f","publicKeyRing":[{"xPubKey":"xpub6C27o1AABpLovccSoVJUG8KCH9EiR6949LTtsLY8zLd6GXaAaFmQPkJrfm5QCHQRT7nkJKRAMAZrXtPbEixHTPMrf48Q8gVbAQ25odupVCW","requestPubKey":"036c2c1c3ecf63de8e8aaa8e63720348df437ba9267b209c83daa1e5b16a37620c"}],"walletId":"ccf23d6d-bb4e-484f-b237-8da0299166a1","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"6f4a51d9b4753eaf90fbe25a27b59c12b548a7f294b2212f68684c9e2343e1a4","personalEncryptingKey":"wzVaz18pLOhMrWHXSifjyw==","sharedEncryptingKey":"u1818GdNZi8FE4MAWLKqpA==","copayerName":"me","mnemonic":"protect bike roof thunder dilemma gas online mask sleep blush kit follow","entropySource":"e1530f3b3d2eaea95a689f75e203ffc8ef3e7dda51c7600336a5aac297806293","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"btc","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2GSFBcThSsdmTXUVKwHaifwLh5CY4swQGE1PSM5ESg8mjiXZg3U8SUfvQsyzTeWSTxCwPwvqwJHzsPDPPGZ781yswvPeCJL","xPubKey":"xpub6C27o1AABpLovccSoVJUG8KCH9EiR6949LTtsLY8zLd6GXaAaFmQPkJrfm5QCHQRT7nkJKRAMAZrXtPbEixHTPMrf48Q8gVbAQ25odupVCW","requestPrivKey":"6be2636e3a5523e34c0804e775f72fdcb2253f30e02275c1da9fae57bcdfc6d8","requestPubKey":"036c2c1c3ecf63de8e8aaa8e63720348df437ba9267b209c83daa1e5b16a37620c","copayerId":"aaa253bffbc6265bec18672a0410b4dea693ef28d3b0241cea360a17f6448e77","publicKeyRing":[{"xPubKey":"xpub6C27o1AABpLovccSoVJUG8KCH9EiR6949LTtsLY8zLd6GXaAaFmQPkJrfm5QCHQRT7nkJKRAMAZrXtPbEixHTPMrf48Q8gVbAQ25odupVCW","requestPubKey":"036c2c1c3ecf63de8e8aaa8e63720348df437ba9267b209c83daa1e5b16a37620c"}],"walletId":"3843bee7-955f-4621-9b8d-c000afa1da1d","walletName":"Personal Wallet","m":1,"n":1,"walletPrivKey":"a9b6a00180b9d7be05aa5562575d46b2ebe1130b5899796d9b2d340917cb105d","personalEncryptingKey":"wzVaz18pLOhMrWHXSifjyw==","sharedEncryptingKey":"lmjMQWWJdIuW4qikpuZlFQ==","copayerName":"me","mnemonic":"protect bike roof thunder dilemma gas online mask sleep blush kit follow","entropySource":"e1530f3b3d2eaea95a689f75e203ffc8ef3e7dda51c7600336a5aac297806293","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2PKH"},{"coin":"bch","network":"livenet","xPrivKey":"xprv9s21ZrQH143K2GysbnXofDJh5UmhqvqvnGEw3raagcHr7qP8bSkFB33hEnAXWkxzg2VNF7ttkMVuU8zAnQ7rJHDvYaCB1T15fNek2fw62XC","xPubKey":"xpub6D94BNBSMLvAfsUs6p7Nt64zdeno4vMYWEAU8d7WJ7X44FaeWjZcuR9LyDtUHeJGKx475vfNwhgc8rAsFYN37JFb9ojavAhUas4deTeJ3bB","requestPrivKey":"f81b5dad3a83bcdb44a589791448e7c4acc83adce692432e26c57d4ad689ee25","requestPubKey":"022d2938551609c30b5640c202ddf07cecd828e204647846fc3dc1c2fa7299aea1","copayerId":"774e71854e43c6806d900b76f7b801aebe2f061b2da2aa42cdba2ffbdff64737","publicKeyRing":[{"xPubKey":"xpub6DQPHX7xWx8EcAAtWAwmE5a4U4LoHCZHSkdcmgZpRn1A1LvupaWNiDf5pWEK2t4PdnxXWrGYS7uzjWWsmhXJgpkMfKRB3seQXk2KZngUyPc","requestPubKey":"0260197aa6c1e7a1889d3686bd4e903931134c949bd8d85347eb7464d2ed66818b","copayerName":"Alice"},{"xPubKey":"xpub6D94BNBSMLvAfsUs6p7Nt64zdeno4vMYWEAU8d7WJ7X44FaeWjZcuR9LyDtUHeJGKx475vfNwhgc8rAsFYN37JFb9ojavAhUas4deTeJ3bB","requestPubKey":"022d2938551609c30b5640c202ddf07cecd828e204647846fc3dc1c2fa7299aea1","copayerName":"Bob"}],"walletId":"4e5f4de5-20fd-4b69-9337-ef05ea204b8b","walletName":"Alice and Bob\'s Wallet","m":2,"n":2,"walletPrivKey":"a624db620778bb9c3b6645dfe734c0c55c1b8946d5c474904d10ed4c227f2574","personalEncryptingKey":"VU5HJrwLAAZyz+npJ9V9Jw==","sharedEncryptingKey":"IrVSZuuVr0q0CjJ+mdOM8A==","copayerName":"Bob","mnemonic":"raccoon degree refuse night result tag elbow game mule board quantum october","entropySource":"c575a2f4ce7a5962ca2819907625a8844ce7ab867376279fa4dbe1c0fdf360b5","mnemonicHasPassphrase":false,"derivationStrategy":"BIP44","account":0,"compliantDerivation":true,"addressType":"P2SH"}],"disclaimerAccepted":true,"checked":{"ccf23d6d-bb4e-484f-b237-8da0299166a1":true,"3843bee7-955f-4621-9b8d-c000afa1da1d":true,"4e5f4de5-20fd-4b69-9337-ef05ea204b8b":true},"checkedUA":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0"}';
beforeEach(function(){
module('copayApp.services');
profileNew = Profile.fromString(profileNewWithCopayer1String);
profileOld = Profile.fromString(profileOldWithCopayer2String);
});
it('merge() one copayer for same shared wallet in each profile.', function() {
expect(profileNew.credentials[2].mnemonic).toBe('enrich change conduct popular angle hover easy left month demand parade game');
expect(profileNew.credentials[2].walletId).toBe('4e5f4de5-20fd-4b69-9337-ef05ea204b8b');
expect(profileOld.credentials[2].mnemonic).toBe('raccoon degree refuse night result tag elbow game mule board quantum october');
expect(profileOld.credentials[2].walletId).toBe('4e5f4de5-20fd-4b69-9337-ef05ea204b8b');
expect(profileNew.credentials.length).toBe(3);
expect(profileOld.credentials.length).toBe(3);
profileNew.merge(profileOld);
expect(profileNew.credentials.length).toBe(6);
expect(profileNew.credentials[2].mnemonic).toBe('enrich change conduct popular angle hover easy left month demand parade game');
expect(profileNew.credentials[2].walletId).toBe('4e5f4de5-20fd-4b69-9337-ef05ea204b8b');
expect(profileNew.credentials[5].mnemonic).toBe('raccoon degree refuse night result tag elbow game mule board quantum october');
expect(profileNew.credentials[5].walletId).toBe('4e5f4de5-20fd-4b69-9337-ef05ea204b8b');
});
});

View file

@ -0,0 +1,164 @@
(function() {
'use strict';
angular.module('copayApp.services').factory('encryptionService', function($log, secureStorageService) {
var keySize = 512;
var iterations = 1500;
var storageKey = 'encryptionKey';
var service = {
decrypt: decrypt,
encrypt: encrypt,
removeKeyIfExists: removeKeyIfExists
};
return service;
/**
* Returns a CryptoJS.WordArray
*/
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 });
$log.debug('Generated key: ' + key);
return key;
}
/**
*
* @param {*} cb
*/
function _getOrCreateKey(cb) {
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);
});
});
};
/**
*
* @param {string, Base64 encoded} str
* @param {CryptoJS.WordArray} key
* @param {string, hex} iv
*/
function _decryptUsingCryptoJS(str, key, iv) {
$log.debug('decrypt() str: ' + str);
$log.debug('decrypt() using iv:' + iv + ', key: ' + JSON.stringify(key));
var ivWords = CryptoJS.enc.Hex.parse(iv);
var plaintext = CryptoJS.AES.decrypt(str, key, { iv: ivWords });
$log.debug('plaintext', JSON.stringify(plaintext));
var plaintextWords = CryptoJS.lib.WordArray.create();
plaintextWords.init(plaintext.words, plaintext.sigBytes);
$log.debug('plaintextWords', JSON.stringify(plaintextWords));
var plaintextString = plaintextWords.toString(CryptoJS.enc.Utf8);
$log.debug('plaintextString: ', JSON.stringify(plaintextString));
return plaintextString;
}
/**
* Generates its own Initialization Vector, which is returned.
* @param {string, Base64 encoded} str
* @param {CryptoJS.WordArray} key
* @returns {*} The ciphertext created, and the IV used.
*/
function _encryptUsingCryptoJS(str, key) {
var iv = CryptoJS.lib.WordArray.random(16);
var cipherParams = CryptoJS.AES.encrypt(str, key, { iv: iv });
var ciphertextWords = cipherParams.ciphertext.toString(CryptoJS.enc.Base64);
var ivHex = iv.toString(CryptoJS.enc.Hex);
// Just for testing - do we get back what we put in?
/*
decrypt(ciphertext, {iv: ivHex}, function onDecryptionTest(err, decrypted){
if (err) {
$log.error('Failed to decrypt encrypted.', err);
} else {
$log.debug('Freshly decrypted:', JSON.stringify(decrypted));
}
});
*/
return {
ciphertext: ciphertextWords.toString(CryptoJS.enc.Base64),
opts: {
iv: ivHex
}
};
}
function decrypt(str, opts, cb) {
_getOrCreateKey(function onKey(err, key) {
if (err) {
$log.error('Failed to get or create key.', err);
cb(err, null);
return;
}
var decrypted;
try {
decrypted = _decryptUsingCryptoJS(str, key, opts.iv);
} catch (e) {
// Can get this when using the wrong key: Malformed UTF-8 data
$log.error('Error when decrypting.', e);
cb(e, null);
return;
}
cb(null, decrypted);
});
};
function encrypt(str, cb) {
_getOrCreateKey(function onKey(err, key){
if (err) {
cb(err, null);
return;
}
var encrypted = _encryptUsingCryptoJS(str, key);
cb(null, encrypted);
});
};
function removeKeyIfExists() {
secureStorageService.remove(storageKey, function onKeyRemoved(err){
if (err) {
$log.error('Error removing key.', err);
return;
}
$log.debug('Key removed.');
});
}
});
})();

View 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);
var ciphertext = encryptedData.ciphertext;
var iv = encryptedData.iv;
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
});
}
};
})();

View file

@ -47,7 +47,7 @@ angular.module('copayApp.services').factory('mobileSecureStorageService', functi
function (error) {
$log.debug('mss get failed. ' + error);
if (error.message === 'Failure in SecureStorage.get() - The specified item could not be found in the keychain' || // iOS
error.message === 'Key [_SS_profile] not found.') { // Android
error.message === 'Key [_SS_' + key + '] not found.') { // Android
// The callback expects no error, but also no value, if it cannot be found.
cb(null, null);
} else {
@ -57,10 +57,37 @@ angular.module('copayApp.services').factory('mobileSecureStorageService', functi
key);
};
root.remove = function(key, cb) {
if (!platformInfo.isMobile) {
cb(new Error('mobileSecureStorageService is only available on mobile.'));
}
if (!isReady) {
if (initialisationFailed) {
cb(new Error('mobileSecureStorageService initialisation failed.'));
} else {
pending.push(function(){ root.remove(key, cb); });
}
return;
}
storage.remove(
function (value) {
cb();
},
function (error) {
cb(new Error(error));
},
key);
};
root.set = function(key, value, cb) {
if (!platformInfo.isMobile) {
cb(new Error('mobileSecureStorageService is only available on mobile.'));
return;
}
if (!isReady) {

View file

@ -16,7 +16,17 @@ angular.module('copayApp.services').factory('secureStorageService', function(des
} else { // Browser
localStorageService.get(alteredKeyIndicatingDesireForSecureStorage(k), cb);
}
}
};
root.remove = function(k, cb) {
if (platformInfo.isMobile) {
mobileSecureStorageService.remove(k, cb);
} else if (platformInfo.isNW) {
desktopSecureStorageService.remove(k, cb);
} else { // Browser
localStorageService.remove(alteredKeyIndicatingDesireForSecureStorage(k), cb);
}
};
root.set = function(k, v, cb) {
if (platformInfo.isMobile) {
@ -26,7 +36,7 @@ angular.module('copayApp.services').factory('secureStorageService', function(des
} else { // Browser
localStorageService.set(alteredKeyIndicatingDesireForSecureStorage(k), v, cb);
}
}
};
return root;
});

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services')
.factory('storageService', function(appConfigService, 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 storage;
@ -32,7 +32,7 @@ angular.module('copayApp.services')
// This is only used in Copay, we used to encrypt profile
// using device's UUID.
var decryptOnMobile = function(text, cb) {
var copayDecryptOnMobile = function(text, cb) {
var json;
try {
json = JSON.parse(text);
@ -121,11 +121,22 @@ angular.module('copayApp.services')
root.storeProfile = function(profile, cb) {
var profileString = profile.toObj();
if (platformInfo.isNW) {
storage.set('profile', profileString, cb);
} else {
secureStorageService.set('profile', profileString, cb);
}
encryptionService.encrypt(profileString, function onProfileEncrypted(encryptionErr, encryptedProfile){
if (encryptionErr) {
$log.error('Failed to encrypt profile.', encryptionErr);
cb(encryptionErr, null);
return;
}
$log.debug('storing profile ciphertext:', JSON.stringify(encryptedProfile.ciphertext));
var persistentProfileStr = jsonEncryptionService.stringify(
encryptedProfile.ciphertext,
encryptedProfile.opts
);
storage.set('profile', persistentProfileStr, cb);
});
};
/**
@ -150,7 +161,7 @@ angular.module('copayApp.services')
return cb(null, null);
}
decryptOnMobile(profileStr, function(decryptErr, decryptedStr) {
copayDecryptOnMobile(profileStr, function(decryptErr, decryptedStr) {
if (decryptErr) return cb(decryptErr, null);
var profile;
try {
@ -200,53 +211,113 @@ 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
*/
root.getProfile = function(cb) {
if (platformInfo.isNW) {
storage.get('profile', function(getErr, getStr) {
_onOldProfileRetrieved(getErr, getStr, cb);
});
return
}
secureStorageService.get('profile', function(secureErr, secureStr) {
var secureProfile;
var oldProfile;
if (secureErr) {
return cb(secureErr, null);
$log.debug('getProfile()');
storage.get('profile', function onProfileRetrieved(getErr, profileStr){
if (getErr) {
$log.error(getErr);
return cb(getErr, null);
}
if (secureStr) {
if (!profileStr) {
$log.debug('No string loaded, returning nothing.');
// Don't want to use the same key as a previous installation
encryptionService.removeKeyIfExists();
return cb(null, null);
}
var isEncrypted = jsonEncryptionService.isEncrypted(profileStr);
if (isEncrypted) {
$log.debug('profile was encrypted.');
$log.debug('profileStr: ', profileStr);
var encryptedProfileObject;
try {
secureProfile = Profile.fromString(secureStr);
$log.debug('profile: ' + JSON.stringify(secureProfile));
encryptedProfileObject = jsonEncryptionService.parse(profileStr);
} catch (e) {
$log.error(e);
return cb(e, null);
$log.error('Failed to parse encrypted profile.', e);
cb(e, null);
return;
}
$log.debug('profileStr after JSON: ', JSON.stringify(encryptedProfileObject));
encryptionService.decrypt(
encryptedProfileObject.ciphertext,
encryptedProfileObject.opts,
function onDecrypted(decryptionError, decryptedProfile) {
if (decryptionError) {
$log.error('Failed to decrypt profile');
cb(decryptionError, null);
return
}
$log.debug('Decrypted profile:', JSON.stringify(decryptedProfile));
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;
}
cb(null, migratedProfile);
});
}
storage.get('profile', function(getErr, getStr) {
_onOldProfileRetrieved(getErr, getStr, function(oldErr, oldProfile){
if (oldErr) {
return cb(oldErr, null);
}
if (!oldProfile) {
if (secureProfile) {
return cb(null, secureProfile);
} else {
// No profiles found. No errors either.
return cb(null, null);
}
}
_migrateProfiles(oldProfile, secureProfile, cb);
});
});
});
};

View file

@ -1179,12 +1179,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (signedTxp.status == 'accepted') {
ongoingProcess.set('broadcastingTx', true, customStatusHandler);
function handleBroadcastTx(err, broadcastedTxp) {
var handleBroadcastTx = function(err, broadcastedTxp) {
ongoingProcess.set('broadcastingTx', false, customStatusHandler);
if (err) return cb(bwcError.msg(err));
if (err) return cb(bwcError.msg(err));
$rootScope.$emit('Local/TxAction', wallet.id);
return cb(null, broadcastedTxp);
}
};
if (signedTxp.payProUrl && signedTxp.coin == 'bch') {
payproService.broadcastBchTx(signedTxp, handleBroadcastTx);