diff --git a/src/providers/key/key.spec.ts b/src/providers/key/key.spec.ts index be936c835ed..6523e97dfcb 100644 --- a/src/providers/key/key.spec.ts +++ b/src/providers/key/key.spec.ts @@ -11,18 +11,14 @@ describe('KeyProvider', () => { let keys: any[]; let warnSpy; - // Just for test - function match(keya, keyb) { - return keya.id == keyb.id; - } - - beforeEach(() => { + beforeEach(async () => { const testBed = TestUtils.configureProviderTestingModule(); keyProvider = testBed.get(KeyProvider); persistenceProvider = testBed.get(PersistenceProvider); persistenceProvider.load(); logger = testBed.get(Logger); warnSpy = spyOn(logger, 'warn'); + await persistenceProvider.setKeys([]); }); describe('addKey', () => { @@ -30,17 +26,18 @@ describe('KeyProvider', () => { key = { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }; await keyProvider.load(); keyProvider .addKey(key) - .then(async () => { + .then(() => { persistenceProvider.getKeys().then((keys: any[]) => { expect(keys).toEqual([ { id: 'id1', - xPrivKey: 'xPrivKey1' + xPrivKey: 'xPrivKey1', + version: 1 } ]); }); @@ -55,12 +52,12 @@ describe('KeyProvider', () => { { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 } ]); @@ -69,25 +66,28 @@ describe('KeyProvider', () => { key = { id: 'id3', xPrivKey: 'xPrivKey3', - match + version: 1 }; keyProvider .addKey(key) - .then(async () => { + .then(() => { persistenceProvider.getKeys().then((keys: any[]) => { expect(keys).toEqual([ { id: 'id1', - xPrivKey: 'xPrivKey1' + xPrivKey: 'xPrivKey1', + version: 1 }, { id: 'id2', - xPrivKey: 'xPrivKey2' + xPrivKey: 'xPrivKey2', + version: 1 }, { id: 'id3', - xPrivKey: 'xPrivKey3' + xPrivKey: 'xPrivKey3', + version: 1 } ]); }); @@ -97,17 +97,17 @@ describe('KeyProvider', () => { }); }); - it('should not add provided key to storage if it was already added', async () => { + it('should replace with the provided key if it was already added', async () => { await persistenceProvider.setKeys([ { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 } ]); @@ -115,13 +115,13 @@ describe('KeyProvider', () => { key = { id: 'id1', - xPrivKey: 'xPrivKey1', - match + xPrivKeyEncrypted: 'xPrivKeyEncrypted', + version: 1 }; keyProvider .addKey(key) - .then(async () => { + .then(() => { persistenceProvider.getKeys().then(() => { expect().nothing(); }); @@ -132,11 +132,13 @@ describe('KeyProvider', () => { expect(keys).toEqual([ { id: 'id1', - xPrivKey: 'xPrivKey1' + xPrivKey: 'xPrivKey1', + version: 1 }, { id: 'id2', - xPrivKey: 'xPrivKey2' + xPrivKey: 'xPrivKey2', + version: 1 } ]); }); @@ -150,12 +152,12 @@ describe('KeyProvider', () => { { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 } ]; @@ -167,11 +169,13 @@ describe('KeyProvider', () => { expect(keys).toEqual([ { id: 'id1', - xPrivKey: 'xPrivKey1' + xPrivKey: 'xPrivKey1', + version: 1 }, { id: 'id2', - xPrivKey: 'xPrivKey2' + xPrivKey: 'xPrivKey2', + version: 1 } ]); }); @@ -181,17 +185,17 @@ describe('KeyProvider', () => { }); }); - it("should add provided keys to storage if doesn't already added", async () => { + it('should add provided keys to storage', async () => { await persistenceProvider.setKeys([ { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 } ]); @@ -199,12 +203,12 @@ describe('KeyProvider', () => { { id: 'id3', xPrivKey: 'xPrivKey3', - match + version: 1 }, { id: 'id4', xPrivKey: 'xPrivKey4', - match + version: 1 } ]; @@ -216,19 +220,23 @@ describe('KeyProvider', () => { expect(keys).toEqual([ { id: 'id1', - xPrivKey: 'xPrivKey1' + xPrivKey: 'xPrivKey1', + version: 1 }, { id: 'id2', - xPrivKey: 'xPrivKey2' + xPrivKey: 'xPrivKey2', + version: 1 }, { id: 'id3', - xPrivKey: 'xPrivKey3' + xPrivKey: 'xPrivKey3', + version: 1 }, { id: 'id4', - xPrivKey: 'xPrivKey4' + xPrivKey: 'xPrivKey4', + version: 1 } ]); }); @@ -244,12 +252,12 @@ describe('KeyProvider', () => { { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 } ]); @@ -257,27 +265,27 @@ describe('KeyProvider', () => { { id: 'id3', xPrivKey: 'xPrivKey3', - match + version: 1 }, { id: 'id4', xPrivKey: 'xPrivKey4', - match + version: 1 }, { id: 'id3', xPrivKey: 'xPrivKey3', - match + version: 1 }, { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id5', xPrivKey: 'xPrivKey5', - match + version: 1 } ]; @@ -289,23 +297,28 @@ describe('KeyProvider', () => { expect(keys).toEqual([ { id: 'id1', - xPrivKey: 'xPrivKey1' + xPrivKey: 'xPrivKey1', + version: 1 }, { id: 'id2', - xPrivKey: 'xPrivKey2' + xPrivKey: 'xPrivKey2', + version: 1 }, { id: 'id3', - xPrivKey: 'xPrivKey3' + xPrivKey: 'xPrivKey3', + version: 1 }, { id: 'id4', - xPrivKey: 'xPrivKey4' + xPrivKey: 'xPrivKey4', + version: 1 }, { id: 'id5', - xPrivKey: 'xPrivKey5' + xPrivKey: 'xPrivKey5', + version: 1 } ]); }); @@ -315,60 +328,95 @@ describe('KeyProvider', () => { expect(err).toBeUndefined(); }); }); - }); - describe('getKey', () => { - it("should get null if provided keyId doesn't match", async () => { + it('should not add new keys if are already added', async () => { await persistenceProvider.setKeys([ { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 } ]); + keys = [ + { + id: 'id1', + xPrivKey: 'xPrivKey3', + version: 1 + }, + { + id: 'id2', + xPrivKey: 'xPrivKey4', + version: 1 + } + ]; + await keyProvider.load(); keyProvider - .getKey('id3') - .then(key => { - expect(key).toBeNull(); + .addKeys(keys) + .then(() => { + persistenceProvider.getKeys().then((keys: any[]) => { + expect(keys).toEqual([ + { + id: 'id1', + xPrivKey: 'xPrivKey1', + version: 1 + }, + { + id: 'id2', + xPrivKey: 'xPrivKey2', + version: 1 + } + ]); + }); + expect(warnSpy).toHaveBeenCalledTimes(2); }) .catch(err => { expect(err).toBeUndefined(); }); }); + }); + + describe('getKey', () => { + it("should get null if provided keyId doesn't match", async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKey: 'xPrivKey1', + version: 1 + }, + { + id: 'id2', + xPrivKey: 'xPrivKey2', + version: 1 + } + ]); + + await keyProvider.load(); + expect(keyProvider.getKey('id3')).toBeNull(); + }); it('should get the correct key of a provided keyId', async () => { await persistenceProvider.setKeys([ { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 } ]); await keyProvider.load(); - keyProvider - .getKey('id2') - .then(key => { - expect(key).toEqual({ - id: 'id2', - xPrivKey: 'xPrivKey2' - }); - }) - .catch(err => { - expect(err).toBeUndefined(); - }); + expect(keyProvider.getKey('id2')).toBeDefined(); }); }); @@ -390,17 +438,17 @@ describe('KeyProvider', () => { { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 }, { id: 'id3', xPrivKey: 'xPrivKey3', - match + version: 1 } ]); @@ -412,11 +460,13 @@ describe('KeyProvider', () => { expect(keys).toEqual([ { id: 'id1', - xPrivKey: 'xPrivKey1' + xPrivKey: 'xPrivKey1', + version: 1 }, { id: 'id3', - xPrivKey: 'xPrivKey3' + xPrivKey: 'xPrivKey3', + version: 1 } ]); }); @@ -431,12 +481,12 @@ describe('KeyProvider', () => { { id: 'id1', xPrivKey: 'xPrivKey1', - match + version: 1 }, { id: 'id2', xPrivKey: 'xPrivKey2', - match + version: 1 } ]); @@ -452,15 +502,227 @@ describe('KeyProvider', () => { expect(keys).toEqual([ { id: 'id1', - xPrivKey: 'xPrivKey1' + xPrivKey: 'xPrivKey1', + version: 1 }, { id: 'id2', - xPrivKey: 'xPrivKey2' + xPrivKey: 'xPrivKey2', + version: 1 } ]); }); }); }); }); + + describe('Function: encrypt new key', () => { + it('Should call askPassword to encrypt key', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKey: 'xPrivKey', + version: 1 + } + ]); + await keyProvider.load(); + let spyAskPassword = spyOn(keyProvider, 'askPassword'); + spyAskPassword.and.returnValue(Promise.resolve('password1')); + const key = keyProvider.getKey('id1'); + keyProvider.encryptNewKey(key).catch(err => { + expect(err).toBeUndefined(); + }); + expect(spyAskPassword).toHaveBeenCalled(); + }); + }); + + describe('Function: encrypt', () => { + it('Should call askPassword to encrypt key', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKey: 'xPrivKey1', + version: 1 + } + ]); + await keyProvider.load(); + let spyAskPassword = spyOn(keyProvider, 'askPassword'); + spyAskPassword.and.returnValue(Promise.resolve('password1')); + keyProvider.encrypt('id1').catch(err => { + expect(err).toBeUndefined(); + }); + expect(spyAskPassword).toHaveBeenCalled(); + }); + + it('Should reject the promise if password is an empty string', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKey: 'xPrivKey1', + version: 1 + } + ]); + await keyProvider.load(); + spyOn(keyProvider, 'askPassword').and.returnValue( + Promise.resolve('') + ); + keyProvider.encrypt('id1').catch(err => { + expect(err).toBeDefined(); // 'No password' + }); + }); + }); + + describe('Function: decrypt', () => { + it('Should call askPassword to decrypt key', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKeyEncrypted: + '{"iv":"E/QaP6isnNYkBk51tiLlfw==","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"iQTE8Be+cN8=","ct":"1AvFHKxOQjzIIJ5/JxfDe/BtbITnskr/Uw=="}', + version: 1 + } + ]); + await keyProvider.load(); + const spyAskPassword = spyOn( + keyProvider, + 'askPassword' + ).and.returnValue(Promise.resolve('1')); + keyProvider.decrypt('id1'); + expect(spyAskPassword).toHaveBeenCalled(); + }); + + it('Should reject the promise if password is an empty string', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKeyEncrypted: 'xPrivKeyEncrypted', + version: 1 + } + ]); + await keyProvider.load(); + spyOn(keyProvider, 'askPassword').and.returnValue( + Promise.resolve('') + ); + keyProvider.decrypt('id1').catch(err => { + expect(err).toBeDefined(); // 'No password' + }); + }); + + it('Should reject the promise if password is not correct', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKeyEncrypted: 'xPrivKeyEncrypted', + version: 1 + } + ]); + await keyProvider.load(); + spyOn(keyProvider, 'askPassword').and.returnValue( + Promise.resolve('wrong_password') + ); + keyProvider.decrypt('id1').catch(err => { + expect(err).toBeDefined(); // wrong password' + }); + }); + }); + + describe('Function: handleEncryptedWallet', () => { + it('Should call askPassword and resolve with the correct password', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKeyEncrypted: + '{"iv":"E/QaP6isnNYkBk51tiLlfw==","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"iQTE8Be+cN8=","ct":"1AvFHKxOQjzIIJ5/JxfDe/BtbITnskr/Uw=="}', + version: 1 + } + ]); + await keyProvider.load(); + + const spyAskPassword = spyOn( + keyProvider, + 'askPassword' + ).and.returnValue(Promise.resolve('1')); + keyProvider + .handleEncryptedWallet('id1') + .then(pass => { + expect(pass).toEqual('1'); + }) + .catch(err => { + expect(err).toBeUndefined(); + }); + expect(spyAskPassword).toHaveBeenCalled(); + }); + + it('Should resolve if priv key is not encrypted', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKey: 'xPrivKey', + version: 1 + } + ]); + await keyProvider.load(); + + keyProvider.handleEncryptedWallet('id1').catch(err => { + expect(err).toBeUndefined(); + }); + }); + + it('Should reject the promise if password is wrong', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKeyEncrypted: + '{"iv":"E/QaP6isnNYkBk51tiLlfw==","v":1,"iter":1000,"ks":128,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"iQTE8Be+cN8=","ct":"1AvFHKxOQjzIIJ5/JxfDe/BtbITnskr/Uw=="}', + version: 1 + } + ]); + await keyProvider.load(); + + const spyAskPassword = spyOn( + keyProvider, + 'askPassword' + ).and.returnValue(Promise.resolve('2')); + keyProvider.handleEncryptedWallet('id1').catch(err => { + expect(err).toEqual(new Error('WRONG_PASSWORD')); + }); + expect(spyAskPassword).toHaveBeenCalled(); + }); + + it('Should reject the promise if password is an empty string', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKeyEncrypted: 'xPrivKeyEncrypted', + version: 1 + } + ]); + await keyProvider.load(); + + spyOn(keyProvider, 'askPassword').and.returnValue( + Promise.resolve('') + ); + keyProvider.handleEncryptedWallet('id1').catch(err => { + expect(err).toEqual(new Error('NO_PASSWORD')); + }); + }); + + it('Should reject the promise if password prompt is cancelled', async () => { + await persistenceProvider.setKeys([ + { + id: 'id1', + xPrivKeyEncrypted: 'xPrivKeyEncrypted', + version: 1 + } + ]); + await keyProvider.load(); + + spyOn(keyProvider, 'askPassword').and.returnValue( + Promise.resolve(null) + ); + keyProvider.handleEncryptedWallet('id1').catch(err => { + expect(err).toEqual(new Error('PASSWORD_CANCELLED')); + }); + }); + }); }); diff --git a/src/providers/key/key.ts b/src/providers/key/key.ts index 2cb2025aca7..44fd8affb0b 100644 --- a/src/providers/key/key.ts +++ b/src/providers/key/key.ts @@ -34,20 +34,20 @@ export class KeyProvider { }); } - public storeKeysIfDirty(): Promise { - if (this.isDirty) { - const keysToAdd = []; - this.keys.forEach(k => { - keysToAdd.push(k.toObj(k)); - }); - return this.persistenceProvider.setKeys(keysToAdd).then(() => { - this.isDirty = false; - Promise.resolve(); - }); - } else { + private storeKeysIfDirty(): Promise { + if (!this.isDirty) { this.logger.debug('The keys have not been saved. Not dirty'); return Promise.resolve(); } + + const keysToAdd = []; + this.keys.forEach(k => { + keysToAdd.push(k.toObj(k)); + }); + return this.persistenceProvider.setKeys(keysToAdd).then(() => { + this.isDirty = false; + return Promise.resolve(); + }); } public addKey(keyToAdd): Promise { @@ -128,10 +128,14 @@ export class KeyProvider { } title = this.translate.instant('Confirm your new encrypt password'); return this.askPassword(warnMsg, title).then((password2: string) => { - if (password != password2 || _.isNull(password2)) + if (password != password2 || _.isNull(password2)) { return Promise.reject(this.translate.instant('Password mismatch')); - - this.encryptPrivateKey(key, password); + } + try { + this.encryptPrivateKey(key, password); + } catch (error) { + return Promise.reject(error); + } return Promise.resolve(); }); }); @@ -158,7 +162,11 @@ export class KeyProvider { if (!password2 || password != password2) { return this.encryptNewKey(key); } else { - this.encryptPrivateKey(key, password); + try { + this.encryptPrivateKey(key, password); + } catch (error) { + return Promise.reject(error); + } return Promise.resolve(); } }); @@ -205,13 +213,13 @@ export class KeyProvider { ).then((password: string) => { if (_.isNull(password)) { return Promise.reject(new Error('PASSWORD_CANCELLED')); - } - if (password == '') { + } else if (password == '') { return Promise.reject(new Error('NO_PASSWORD')); - } - if (!key.checkPassword(password)) + } else if (!key.checkPassword(password)) { return Promise.reject(new Error('WRONG_PASSWORD')); - return Promise.resolve(password); + } else { + return Promise.resolve(password); + } }); }