Browse Source

Add tests for TrustedKeyStoreService

Danilo Bargen 7 years ago
parent
commit
5c98d60ca2
3 changed files with 160 additions and 9 deletions
  1. 13 9
      src/services/keystore.ts
  2. 146 0
      tests/service/keystore.js
  3. 1 0
      tests/testsuite.html

+ 13 - 9
src/services/keystore.ts

@@ -39,7 +39,7 @@ import {stringToUtf8a, utf8aToString} from '../helpers';
  *
  */
 export class TrustedKeyStoreService {
-    private static STORAGE_KEY = 'trusted';
+    private STORAGE_KEY = 'trusted';
 
     private logTag: string = '[TrustedKeyStoreService]';
 
@@ -120,14 +120,14 @@ export class TrustedKeyStoreService {
         data.set(token, 96);
         const encrypted: Uint8Array = nacl.secretbox(data, nonce, this.pwToKey(password));
         this.$log.debug(this.logTag, 'Storing trusted key');
-        this.storage.setItem(TrustedKeyStoreService.STORAGE_KEY, u8aToHex(nonce) + ':' + u8aToHex(encrypted));
+        this.storage.setItem(this.STORAGE_KEY, u8aToHex(nonce) + ':' + u8aToHex(encrypted));
     }
 
     /**
      * Return whether or not a trusted key is stored in local storage.
      */
     public hasTrustedKey(): boolean {
-        const item: string = this.storage.getItem(TrustedKeyStoreService.STORAGE_KEY);
+        const item: string = this.storage.getItem(this.STORAGE_KEY);
         return item !== null && item.length > 96 && item.indexOf(':') !== -1;
     }
 
@@ -136,7 +136,7 @@ export class TrustedKeyStoreService {
      * the provided password.
      */
     public retrieveTrustedKey(password: string): threema.TrustedKeyStoreData | null {
-        const storedValue: string = this.storage.getItem(TrustedKeyStoreService.STORAGE_KEY);
+        const storedValue: string = this.storage.getItem(this.STORAGE_KEY);
         if (storedValue === null) {
             return null;
         }
@@ -156,9 +156,9 @@ export class TrustedKeyStoreService {
         // Parse push token
         const tokenBytes = (decrypted as Uint8Array).slice(96);
         const tokenString: string | null = tokenBytes.byteLength > 0 ? utf8aToString(tokenBytes) : null;
-        let tokenType = threema.PushTokenType.Gcm;
-        let token: string;
-        if (tokenString[1] === ':') {
+        let tokenType: threema.PushTokenType = null;
+        let token: string = null;
+        if (tokenString !== null && tokenString[1] === ':') {
             switch (tokenString[0]) {
                 case threema.PushTokenPrefix.Gcm:
                     tokenType = threema.PushTokenType.Gcm;
@@ -166,11 +166,15 @@ export class TrustedKeyStoreService {
                 case threema.PushTokenPrefix.Apns:
                     tokenType = threema.PushTokenType.Apns;
                     break;
+                default:
+                    this.$log.error(this.logTag, 'Invalid push token type:', tokenString[0]);
+                    return null;
             }
             token = tokenString.slice(2);
-        } else {
+        } else if (tokenString !== null) {
             // Compat
             token = tokenString;
+            tokenType = threema.PushTokenType.Gcm;
         }
 
         return {
@@ -187,6 +191,6 @@ export class TrustedKeyStoreService {
      */
     public clearTrustedKey(): void {
         this.$log.debug(this.logTag, 'Clearing trusted key');
-        this.storage.removeItem(TrustedKeyStoreService.STORAGE_KEY);
+        this.storage.removeItem(this.STORAGE_KEY);
     }
 }

+ 146 - 0
tests/service/keystore.js

@@ -0,0 +1,146 @@
+describe('TrustedKeyStoreService', function() {
+
+    let $service;
+
+    const STORAGE_KEY = 'trusted-test';
+
+    // Ignoring page reload request
+    beforeAll(() => window.onbeforeunload = () => null);
+
+    beforeEach(function() {
+
+        module(($provide) => {
+            $provide.constant('PROTOCOL_VERSION', 1337);
+            $provide.constant('CONFIG', {
+                'SELF_HOSTED': false,
+            });
+        });
+
+        module('3ema.services');
+
+        // Inject the service
+        inject(function(TrustedKeyStore) {
+            $service = TrustedKeyStore;
+            foo = $service;
+            $service.STORAGE_KEY = STORAGE_KEY;
+        });
+
+    });
+
+    it('localstorage is not blocked', () => {
+        expect($service.blocked).toBe(false);
+    });
+
+    it('stringToBytes', () => {
+        expect($service.stringToBytes('asdf')).toEqual(Uint8Array.of(0x61, 0x73, 0x64, 0x66));
+    });
+
+    it('pwToKey', () => {
+        expect($service.pwToKey('mehmetefendi')).toEqual(Uint8Array.of(
+            132, 95, 3, 249, 12, 29, 57, 169,
+            54, 146, 171, 111, 43, 69, 133, 87,
+            26, 207, 236, 120, 176, 94, 2, 231,
+            72, 177, 74, 104, 102, 109, 50, 13,
+        ));
+    });
+
+    function roundtrip(tokenType) {
+        const pubkey = Uint8Array.of(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8);
+        const seckey = Uint8Array.of(2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9);
+        const peerkey = Uint8Array.of(3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,10,10,10,10);
+        const token = 'yaytoken';
+        const password = 'baristapro';
+
+        $service.clearTrustedKey();
+
+        expect($service.hasTrustedKey()).toBe(false);
+        expect($service.retrieveTrustedKey(password)).toEqual(null);
+
+        $service.storeTrustedKey(pubkey, seckey, peerkey, token, tokenType, password);
+
+        expect($service.hasTrustedKey()).toBe(true);
+        expect($service.retrieveTrustedKey(password)).toEqual({
+            ownPublicKey: pubkey,
+            ownSecretKey: seckey,
+            peerPublicKey: peerkey,
+            pushToken: token,
+            pushTokenType: tokenType,
+        });
+
+        // Clean up
+        $service.clearTrustedKey();
+        expect($service.hasTrustedKey()).toBe(false);
+    }
+
+    it('roundtripGcm', () => {
+        roundtrip('gcm');
+    });
+
+    it('roundtripApns', () => {
+        roundtrip('apns');
+    });
+
+    // When no push token is set, then both the token and the token type retrieved should be null.
+    it('roundtripNoToken', () => {
+        const pubkey = Uint8Array.of(1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8);
+        const seckey = Uint8Array.of(2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9);
+        const peerkey = Uint8Array.of(3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,9,10,10,10,10);
+        const password = 'baristapro';
+
+        $service.clearTrustedKey();
+
+        expect($service.hasTrustedKey()).toBe(false);
+        expect($service.retrieveTrustedKey(password)).toEqual(null);
+
+        $service.storeTrustedKey(pubkey, seckey, peerkey, null, null, password);
+
+        expect($service.hasTrustedKey()).toBe(true);
+        const keyinfo = $service.retrieveTrustedKey(password);
+        expect(keyinfo.ownPublicKey).toEqual(pubkey);
+        expect(keyinfo.ownSecretKey).toEqual(seckey);
+        expect(keyinfo.peerPublicKey).toEqual(peerkey);
+        expect(keyinfo.pushToken).toEqual(null);
+        expect(keyinfo.pushTokenType).toEqual(null);
+
+        // Clean up
+        $service.clearTrustedKey();
+    });
+
+    it('decryptGcm', () => {
+        $service.clearTrustedKey();
+        window.localStorage.setItem(STORAGE_KEY, 'dfc0583350096ad02080f11113824fd65bd916ec295bd43f:33813017f6a7cecc9d6e1be858f9d5217496e2023de8fe0227a616f9d78339800da20bb1b83765ca83289f3f1ecfd9293934a15ac31127e91f186205c310c9bfefeccab9f58155930ab5e19804d4d304c4c61cc54e4f9dd695a5d222ab9af48dab1e2beccf7dbacdf82e144220f50b25b41f009c24296da86bddd83e48cb6719377d99dbb2fda2cc56b2c2c86df2ab27b7053b1c01cec2ec');
+        const keyinfo = $service.retrieveTrustedKey('espressodopio');
+    bar = keyinfo;
+        expect(keyinfo.pushToken).toEqual('asdfasdfjklo:DFLKJALKDJFKLJDASLKFJLDJF');
+        expect(keyinfo.pushTokenType).toEqual('gcm');
+        $service.clearTrustedKey();
+    });
+
+    it('decryptApns', () => {
+        $service.clearTrustedKey();
+        window.localStorage.setItem(STORAGE_KEY, 'e52061adf1093236b698876be67476015f8846f01060eb28:80e4ec31bb0390ef331c402937e45177c3450551ca29e8e7211a136c93a7cd70fe94de60212cb7b645149803e89681147a7fef9d3386064246aab8f85609835f38cc46a333267a20657076862aae653ff9bbe9f009b68d59cd7a8cf60a6f4cc4dfad43e6328132de0bd99a0b8555f7a93db7f7269f49dbdba0b29d0a7a810798bb314878b759944ebbb5e1dc7bc0d5597a3063875d0ba3c2');
+        const keyinfo = $service.retrieveTrustedKey('espressodopio');
+        expect(keyinfo.pushToken).toEqual('asdfasdfjklo:DFLKJALKDJFKLJDASLKFJLDJF');
+        expect(keyinfo.pushTokenType).toEqual('apns');
+        $service.clearTrustedKey();
+    });
+
+    it('decryptInvalidPushTokenType', () => {
+        $service.clearTrustedKey();
+        window.localStorage.setItem(STORAGE_KEY, '8a139834b5e6505b5ce8f2ee4243884e61cc9a928024dc9ad7764a8468af03f6a68a9fced7bb40c9a4fb3bb73eb5edba70253dfc90f3900d0135f7db8af77d9182b417bddcfd3fe7beb2e7f35fa17926b1cfc9fdf074798ca63c579667f95d9707fe35d87493bf46a07103daeee6199e9d7bda3fd170ee75490db79cf805406ff02e5afd47ced502451e69dcac5233b9a52f280b09147e73');
+        const keyinfo = $service.retrieveTrustedKey('espressodopio');
+        expect(keyinfo).toEqual(null);
+        $service.clearTrustedKey();
+    });
+
+    // Legacy push tokens don't have a type prefix
+    it('decryptLegacy', () => {
+        $service.clearTrustedKey();
+        window.localStorage.setItem(STORAGE_KEY, 'e6a7a40344361184b9d66bb658a6cc8d0fd4df2ab86affdd:2ee05bf4833f26ecbcac1f0301884d8137aeb2d98f485690bc73dbec943d20e9ad51e851a8568a88d880213d77095a3ec195298b1577804537c5000e6e4182348ed919e74e83b3e7a552ced2b6d52bbeb7ef661fd64111d1c5875af2e96cea828a69dbec71f50702cf3df629dc502c6f76c8e16e8f41127601031d8c20e22939e54585c8db58c7383e3e412368892cef700884fe7582');
+        const keyinfo = $service.retrieveTrustedKey('espressodopio');
+        expect(keyinfo.pushToken).toEqual('asdfasdfjklo:DFLKJALKDJFKLJDASLKFJLDJF');
+        expect(keyinfo.pushTokenType).toEqual('gcm');
+        $service.clearTrustedKey();
+    });
+
+});

+ 1 - 0
tests/testsuite.html

@@ -29,6 +29,7 @@
         <script src="service/webclient.js"></script>
         <script src="service/string.js"></script>
         <script src="service/browser.js"></script>
+        <script src="service/keystore.js"></script>
         <script src="helpers.js"></script>
     </head>
     <body>