// Copyright 2011 The Closure Library Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS-IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. goog.provide('goog.storage.EncryptedStorageTest'); goog.setTestOnly('goog.storage.EncryptedStorageTest'); goog.require('goog.json'); goog.require('goog.storage.EncryptedStorage'); goog.require('goog.storage.ErrorCode'); goog.require('goog.storage.RichStorage'); goog.require('goog.storage.collectableStorageTester'); goog.require('goog.storage.storageTester'); goog.require('goog.testing.MockClock'); goog.require('goog.testing.PseudoRandom'); goog.require('goog.testing.jsunit'); goog.require('goog.testing.storage.FakeMechanism'); function getEncryptedWrapper(storage, key) { return goog.json.parse( storage.mechanism.get(storage.hashKeyWithSecret_(key))); } function getEncryptedData(storage, key) { return getEncryptedWrapper(storage, key)[goog.storage.RichStorage.DATA_KEY]; } function decryptWrapper(storage, key, wrapper) { return goog.json.parse( storage.decryptValue_( wrapper[goog.storage.EncryptedStorage.SALT_KEY], key, wrapper[goog.storage.RichStorage.DATA_KEY])); } function hammingDistance(a, b) { if (a.length != b.length) { throw Error('Lengths must be the same for Hamming distance'); } var distance = 0; for (var i = 0; i < a.length; ++i) { if (a.charAt(i) != b.charAt(i)) { ++distance; } } return distance; } function testBasicOperations() { var mechanism = new goog.testing.storage.FakeMechanism(); var storage = new goog.storage.EncryptedStorage(mechanism, 'secret'); goog.storage.storageTester.runBasicTests(storage); } function testExpiredKeyCollection() { var mechanism = new goog.testing.storage.FakeMechanism(); var clock = new goog.testing.MockClock(true); var storage = new goog.storage.EncryptedStorage(mechanism, 'secret'); goog.storage.collectableStorageTester.runBasicTests( mechanism, clock, storage); } function testEncryption() { var mechanism = new goog.testing.storage.FakeMechanism(); var clock = new goog.testing.MockClock(true); var storage = new goog.storage.EncryptedStorage(mechanism, 'secret'); var mallory = new goog.storage.EncryptedStorage(mechanism, 'guess'); // Simple Objects. storage.set('first', 'Hello world!'); storage.set('second', ['one', 'two', 'three'], 1000); storage.set('third', {'a': 97, 'b': 98}); // Wrong secret can't find keys. assertNull(mechanism.get('first')); assertNull(mechanism.get('second')); assertNull(mechanism.get('third')); assertUndefined(mallory.get('first')); assertUndefined(mallory.get('second')); assertUndefined(mallory.get('third')); // Wrong secret can't overwrite keys. mallory.set('first', 'Ho ho ho!'); assertObjectEquals('Ho ho ho!', mallory.get('first')); assertObjectEquals('Hello world!', storage.get('first')); mallory.remove('first'); // Correct key decrypts properly. assertObjectEquals('Hello world!', storage.get('first')); assertObjectEquals(['one', 'two', 'three'], storage.get('second')); assertObjectEquals({'a': 97, 'b': 98}, storage.get('third')); // Wrong secret can't decode values even if the key is revealed. var encryptedWrapper = getEncryptedWrapper(storage, 'first'); assertObjectEquals( 'Hello world!', decryptWrapper(storage, 'first', encryptedWrapper)); assertThrows(function() { decryptWrapper(mallory, 'first', encryptedWrapper); }); // If the value is overwritten, it can't be decrypted. encryptedWrapper[goog.storage.RichStorage.DATA_KEY] = 'kaboom'; mechanism.set( storage.hashKeyWithSecret_('first'), goog.json.serialize(encryptedWrapper)); assertEquals( goog.storage.ErrorCode.DECRYPTION_ERROR, assertThrows(function() { storage.get('first'); })); // Test garbage collection. storage.collect(); assertNotNull(getEncryptedWrapper(storage, 'first')); assertObjectEquals(['one', 'two', 'three'], storage.get('second')); assertObjectEquals({'a': 97, 'b': 98}, storage.get('third')); clock.tick(2000); storage.collect(); assertNotNull(getEncryptedWrapper(storage, 'first')); assertUndefined(storage.get('second')); assertObjectEquals({'a': 97, 'b': 98}, storage.get('third')); mechanism.set(storage.hashKeyWithSecret_('first'), '"kaboom"'); storage.collect(); assertNotNull(getEncryptedWrapper(storage, 'first')); assertObjectEquals({'a': 97, 'b': 98}, storage.get('third')); storage.collect(true); assertUndefined(storage.get('first')); assertObjectEquals({'a': 97, 'b': 98}, storage.get('third')); // Clean up. storage.remove('third'); assertUndefined(storage.get('third')); clock.uninstall(); } function testSalting() { var mechanism = new goog.testing.storage.FakeMechanism(); var randomMock = new goog.testing.PseudoRandom(0, true); var storage = new goog.storage.EncryptedStorage(mechanism, 'secret'); // Same value under two different keys should appear very different, // even with the same salt. storage.set('one', 'Hello world!'); randomMock.seed(0); // Reset the generator so we get the same salt. storage.set('two', 'Hello world!'); var golden = getEncryptedData(storage, 'one'); assertRoughlyEquals( 'Ciphertext did not change with keys', golden.length, hammingDistance(golden, getEncryptedData(storage, 'two')), 2); // Same key-value pair written second time should appear very different. storage.set('one', 'Hello world!'); assertRoughlyEquals( 'Salting seems to have failed', golden.length, hammingDistance(golden, getEncryptedData(storage, 'one')), 2); // Clean up. storage.remove('1'); storage.remove('2'); randomMock.uninstall(); }