/******************************************************************************* * Copyright (c) 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.equinox.internal.security.tests.storage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; import org.eclipse.core.runtime.FileLocator; import org.eclipse.equinox.internal.security.storage.friends.InternalExchangeUtils; import org.eclipse.equinox.internal.security.tests.SecurityTestsActivator; import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.StorageException; import org.junit.Test; import org.osgi.framework.BundleContext; abstract public class SecurePreferencesTest extends StorageAbstractTest { final private static String sampleLocation = "/SecurePrefsSample/1/secure_storage.equinox"; final private static String path1 = "/test/abc"; final private static String path2 = "/test/cvs/eclipse.org"; final private static String path3 = "/test/cvs/eclipse.org/account1"; final private static String key = "password"; final private static String unassignedKey = "unknown"; final private static String value = "p[[pkknb#"; final private static String defaultValue = "default"; final private static String secondKey = "/ sdfdsf / sf"; final private static String secondValue = "one"; final private static String clearTextKey = "data"; final private static String clearTextValue = "-> this should not be encrypted <-"; final private static String unicodeKey = "unicodeKey"; final private static String unicodeValue = "va\u0432lue\u0433"; protected Map<String, Object> getOptions() { // Note that if the default password value below is modified, // the sample storage file needs to be regenerated. return getOptions("password1"); } private void fill(ISecurePreferences preferences) throws StorageException { preferences.put(key, value, true); // puts entry at the root node ISecurePreferences node1 = preferences.node(path1); // puts entry at the root node + 1 node1.put(key, value, true); node1.put(clearTextKey, clearTextValue, false); ISecurePreferences node2 = preferences.node(path2); // puts entry at the root node + 2 node2.put(key, value, true); node2.put(secondKey, secondValue, true); ISecurePreferences node3 = preferences.node(path3); // puts entry at the root node + 3 node3.put(key, value, true); node3.put(secondKey, secondValue, true); node3.put(clearTextKey, clearTextValue, false); node3.put(unicodeKey, unicodeValue, true); node2.remove(secondKey); assertTrue(isModified(node2)); assertTrue(isModified(preferences)); } /** * The method reaches into internal classes to check if modified flag is set on secure * preference data. */ private boolean isModified(ISecurePreferences node) { return InternalExchangeUtils.isModified(node); } private void check(ISecurePreferences preferences) throws StorageException { assertFalse(isModified(preferences)); assertEquals(value, preferences.get(key, defaultValue)); // checks entry at the root node assertEquals(defaultValue, preferences.get(unassignedKey, defaultValue)); ISecurePreferences node1 = preferences.node(path1); // checks entry at the root node + 1 assertFalse(isModified(node1)); assertEquals(value, node1.get(key, defaultValue)); assertEquals(defaultValue, node1.get(unassignedKey, defaultValue)); assertEquals(clearTextValue, node1.get(clearTextKey, defaultValue)); ISecurePreferences node2 = preferences.node(path2); // checks entry at the root node + 2 assertFalse(isModified(node2)); assertEquals(value, node2.get(key, defaultValue)); assertNull(node2.get(secondKey, null)); assertEquals(defaultValue, node2.get(unassignedKey, defaultValue)); ISecurePreferences node3 = preferences.node(path3); // checks entry at the root node + 3 assertFalse(isModified(node3)); assertEquals(value, node3.get(key, defaultValue)); assertEquals(secondValue, node3.get(secondKey, defaultValue)); assertEquals(defaultValue, node3.get(unassignedKey, defaultValue)); assertEquals(clearTextValue, node3.get(clearTextKey, defaultValue)); assertEquals(unicodeValue, node3.get(unicodeKey, defaultValue)); String[] leafKeys = node3.keys(); assertNotNull(leafKeys); assertEquals(leafKeys.length, 4); findAll(new String[] {clearTextKey, key, secondKey, unicodeKey}, leafKeys); } /** * Basic test to fill / read Preferences implementation. Also tests removal of a value * and Preferences#keys(). */ @Test public void testPreferences() throws IOException, StorageException { URL location = getStorageLocation(); assertNotNull(location); { // block1: fill and save ISecurePreferences preferences = newPreferences(getStorageLocation(), getOptions()); fill(preferences); preferences.flush(); closePreferences(preferences); } { // block2: re-load and check ISecurePreferences preferences = newPreferences(getStorageLocation(), getOptions()); check(preferences); } } /** * Test relative names, absolute names, and children names * @throws StorageException */ @Test public void testNames() throws IOException, StorageException { ISecurePreferences preferences = newPreferences(getStorageLocation(), getOptions()); fill(preferences); // check names for the root node assertNull(preferences.name()); assertEquals("/", preferences.absolutePath()); String[] childrenNames = preferences.node("test").childrenNames(); assertNotNull(childrenNames); boolean order1 = "abc".equals(childrenNames[0]) && "cvs".equals(childrenNames[1]); boolean order2 = "abc".equals(childrenNames[1]) && "cvs".equals(childrenNames[0]); assertTrue(order1 || order2); assertEquals(childrenNames.length, 2); // check names for the root node + 1 ISecurePreferences node1 = preferences.node("test/cvs"); assertEquals("cvs", node1.name()); assertEquals("/test/cvs", node1.absolutePath()); String[] childrenNames1 = node1.childrenNames(); assertNotNull(childrenNames1); assertEquals(childrenNames1.length, 1); assertEquals("eclipse.org", childrenNames1[0]); // check names for the root node + 2 ISecurePreferences node2 = node1.node("eclipse.org"); assertEquals("eclipse.org", node2.name()); assertEquals("/test/cvs/eclipse.org", node2.absolutePath()); String[] childrenNames2 = node2.childrenNames(); assertNotNull(childrenNames2); assertEquals(childrenNames2.length, 1); assertEquals("account1", childrenNames2[0]); // check names for the leaf node ISecurePreferences node3 = node2.node("account1"); assertEquals("account1", node3.name()); assertEquals("/test/cvs/eclipse.org/account1", node3.absolutePath()); String[] childrenNames3 = node3.childrenNames(); assertNotNull(childrenNames3); assertEquals(childrenNames3.length, 0); } /** * Test node existence, resolution: parent -> child; child -> parent, * compare absolute and relative paths. */ @Test public void testNodeResolution() throws IOException, StorageException { ISecurePreferences preferences = newPreferences(getStorageLocation(), getOptions()); fill(preferences); // absolute paths and node existence: assertTrue(preferences.nodeExists(null)); ISecurePreferences nodeRoot = preferences.node(null); assertNotNull(nodeRoot); assertTrue(preferences.nodeExists("/test/cvs")); assertFalse(preferences.nodeExists("/test/nonExistent")); ISecurePreferences node1 = preferences.node("/test/cvs"); assertNotNull(node1); assertTrue(preferences.nodeExists("/test/cvs/eclipse.org")); assertFalse(preferences.nodeExists("/test/nonExistent/cvs")); assertFalse(preferences.nodeExists("/test/cvs/nonExistent")); ISecurePreferences node2 = preferences.node("/test/cvs/eclipse.org"); assertNotNull(node2); assertTrue(preferences.nodeExists("/test/cvs/eclipse.org/account1")); assertFalse(preferences.nodeExists("/test/cvs/nonExistent/cvs")); ISecurePreferences node3 = preferences.node("/test/cvs/eclipse.org/account1"); assertNotNull(node3); // relative paths, parents and compare to results from absolute paths: assertNull(preferences.parent()); assertEquals(nodeRoot, preferences); assertTrue(nodeRoot.nodeExists("test/cvs")); assertFalse(nodeRoot.nodeExists("test/nonExistent")); ISecurePreferences relativeNode1 = nodeRoot.node("test/cvs"); assertNotNull(relativeNode1); assertEquals(node1, relativeNode1); assertEquals(nodeRoot, relativeNode1.parent().parent()); assertTrue(relativeNode1.nodeExists("eclipse.org")); assertFalse(relativeNode1.nodeExists("nonExistent")); ISecurePreferences relativeNode2 = relativeNode1.node("eclipse.org"); assertNotNull(relativeNode2); assertEquals(node2, relativeNode2); assertEquals(node1, relativeNode2.parent()); assertTrue(relativeNode2.nodeExists("account1")); assertFalse(relativeNode2.nodeExists("nonExistent")); ISecurePreferences relativeNode3 = relativeNode2.node("account1"); assertNotNull(relativeNode3); assertEquals(node3, relativeNode3); assertEquals(relativeNode2, relativeNode3.parent()); //check contents to make sure that traversing did not add and new children preferences.flush(); check(preferences); } /** * Tests node removal. * @throws StorageException */ @Test public void testNodeRemoval() throws IOException, StorageException { URL location = getStorageLocation(); assertNotNull(location); { // block1: initial fill and check ISecurePreferences preferences = newPreferences(location, getOptions()); fill(preferences); ISecurePreferences nodeToRemove = preferences.node("/test/cvs/eclipse.org"); assertNotNull(nodeToRemove); nodeToRemove.removeNode(); assertFalse(preferences.nodeExists("/test/cvs/eclipse.org/account1")); assertFalse(preferences.nodeExists("/test/cvs/eclipse.org")); preferences.flush(); closePreferences(preferences); } { // block2: reload ISecurePreferences preferences = newPreferences(location, getOptions()); assertTrue(preferences.nodeExists(null)); assertFalse(preferences.nodeExists("/test/cvs/eclipse.org/account1")); assertFalse(preferences.nodeExists("/test/cvs/eclipse.org")); ISecurePreferences node = preferences.node("/test/cvs"); String[] children = node.childrenNames(); assertNotNull(children); assertEquals(children.length, 0); // test in-memory removal ISecurePreferences node2 = preferences.node(null).node("test"); String[] children2 = node2.childrenNames(); assertNotNull(children2); assertEquals(children2.length, 2); ISecurePreferences nodeToRemove2 = node2.node("/test/cvs"); assertNotNull(nodeToRemove2); nodeToRemove2.removeNode(); String[] children3 = node2.childrenNames(); assertNotNull(children3); assertEquals(children3.length, 1); assertEquals("abc", children3[0]); preferences.removeNode(); // check the special case - removal of the root node boolean exception = false; try { preferences.nodeExists(null); } catch (IllegalStateException e) { exception = true; } assertTrue(exception); } } /** * Tests validation of node paths. * @throws Throwable */ @Test public void testPathValidation() throws Throwable { ISecurePreferences preferences = newPreferences(getStorageLocation(), getOptions()); boolean exception = false; try { preferences.node("/test/cvs/eclipse.org//account1"); } catch (IllegalArgumentException e) { exception = true; } assertTrue(exception); exception = false; try { preferences.node("/test/cvs/eclipse.org/"); } catch (IllegalArgumentException e) { exception = true; } assertTrue(exception); exception = false; try { preferences.node("/test/cvs/eclipse.org//"); } catch (IllegalArgumentException e) { exception = true; } assertTrue(exception); } /** * Tests URL validation. */ @Test public void testLocation() throws MalformedURLException { URL invalidURL = new URL("http", "eclipse.org", "testEquinoxFile"); boolean exception = false; try { newPreferences(invalidURL, getOptions()); } catch (IOException e) { exception = true; } assertTrue(exception); } /** * Tests data types * @throws StorageException * @throws IOException * @throws MalformedURLException */ @Test public void testDataTypes() throws StorageException, MalformedURLException, IOException { ISecurePreferences preferences = newPreferences(getStorageLocation(), getOptions()); ISecurePreferences node = preferences.node("/test"); byte[] testArray = new byte[] {0, 4, 12, 75, 84, 12, 1, (byte) 0xFF}; boolean encrypt = true; node.putBoolean("trueBoolean", true, encrypt); node.putBoolean("falseBoolean", false, encrypt); node.putInt("oneInteger", 1, encrypt); node.putLong("twoLong", 2l, encrypt); node.putFloat("threeFloat", 3.12f, encrypt); node.putDouble("fourDouble", 4.1d, encrypt); assertTrue(node.getBoolean("trueBoolean", false)); assertFalse(node.getBoolean("falseBoolean", true)); assertTrue(node.getBoolean("unknownBoolean", true)); assertFalse(node.getBoolean("unknownBoolean", false)); assertEquals(node.getInt("oneInteger", 0), 1); assertEquals(node.getInt("unknownInteger", 5), 5); assertEquals(node.getLong("twoLong", 0), 2l); assertEquals(node.getLong("unknownLong", 5), 5l); assertEquals(node.getFloat("threeFloat", 0f), 3.12f, 0); assertEquals(node.getFloat("unknownFloat", 1.23f), 1.23f, 0); assertEquals(node.getDouble("fourDouble", 0), 4.1d, 0); assertEquals(node.getDouble("unknownDouble", 1.23d), 1.23d, 0); node.putByteArray("fiveArray", testArray, encrypt); byte[] array = node.getByteArray("fiveArray", null); compareArrays(testArray, array); } /** * Tests corrupted encrypted data. */ @Test public void testIncorrectData() throws IOException { URL location = getFilePath(sampleLocation); // Same default password as in the SecurePreferencesTest.getOptions() - same note // on regenerating data file. ISecurePreferences preferences = newPreferences(location, getOptions("password1")); try { ISecurePreferences node = preferences.node("/abc"); boolean exception = false; try { node.get("password1", "default"); } catch (StorageException e) { assertEquals(StorageException.DECRYPTION_ERROR, e.getErrorCode()); exception = true; } assertTrue(exception); exception = false; try { node.get("password2", "default"); } catch (StorageException e) { assertEquals(StorageException.DECRYPTION_ERROR, e.getErrorCode()); exception = true; } assertTrue(exception); } finally { // make sure we won't try to delete it closePreferences(preferences); } } /** * Tests incorrect passwords */ @Test public void testIncorrectPassword() throws IOException { URL location = getFilePath(sampleLocation); Map<String, Object> options = getOptions("wrong"); ISecurePreferences preferences = newPreferences(location, options); try { ISecurePreferences node = preferences.node("/cvs/eclipse.org"); boolean exception = false; try { node.get("password", "default"); } catch (StorageException e) { assertEquals(StorageException.DECRYPTION_ERROR, e.getErrorCode()); exception = true; } assertTrue(exception); } finally { // make sure we won't try to delete it closePreferences(preferences); } } /** * Tests incorrect or unexpected module specifications */ @Test public void testModules() throws IOException { URL location = getFilePath(sampleLocation); ISecurePreferences preferences = newPreferences(location, getOptions(null)); try { ISecurePreferences node = preferences.node("/cvs/eclipse.org/account1"); // non-existent module boolean exception = false; try { node.get("password1", "default"); } catch (StorageException e) { assertEquals(StorageException.NO_SECURE_MODULE, e.getErrorCode()); exception = true; } assertTrue(exception); // empty module and no default password exception = false; try { node.get("password2", "default"); } catch (StorageException e) { assertEquals(StorageException.DECRYPTION_ERROR, e.getErrorCode()); exception = true; } assertTrue(exception); } finally { // make sure we won't try to delete it closePreferences(preferences); } } /** * Tests edge cases for data (nulls, empty strings, and so on). */ @Test public void testEdgeCases() throws StorageException, MalformedURLException, IOException { byte[] expectedEmptyArray = new byte[0]; byte[] wrongArray = new byte[] {1, 2, 3}; { // block1: fill, check, and save ISecurePreferences preferences = newPreferences(getStorageLocation(), getOptions()); ISecurePreferences node = preferences.node("/testEdge"); node.put("emptyString1", "", true); node.put("emptyString2", "", false); node.put("nullString1", null, true); node.put("nullString2", null, false); node.putByteArray("emptyArray1", new byte[0], true); node.putByteArray("emptyArray2", new byte[0], false); node.putByteArray("nullArray1", null, true); node.putByteArray("nullArray2", null, false); assertEquals("", node.get("emptyString1", "wrong")); assertEquals("", node.get("emptyString2", "wrong")); assertNull(node.get("nullString1", "wrong")); assertNull(node.get("nullString2", "wrong")); compareArrays(expectedEmptyArray, node.getByteArray("emptyArray1", wrongArray)); compareArrays(expectedEmptyArray, node.getByteArray("emptyArray2", wrongArray)); assertNull(node.getByteArray("nullString1", wrongArray)); assertNull(node.getByteArray("nullString2", wrongArray)); preferences.flush(); closePreferences(preferences); } { // block2: re-load and check ISecurePreferences preferences = newPreferences(getStorageLocation(), getOptions()); ISecurePreferences node = preferences.node("/testEdge"); assertEquals("", node.get("emptyString1", "wrong")); assertEquals("", node.get("emptyString2", "wrong")); assertNull(node.get("nullString1", "wrong")); assertNull(node.get("nullString2", "wrong")); compareArrays(expectedEmptyArray, node.getByteArray("emptyArray1", wrongArray)); compareArrays(expectedEmptyArray, node.getByteArray("emptyArray2", wrongArray)); assertNull(node.getByteArray("nullString1", wrongArray)); assertNull(node.getByteArray("nullString2", wrongArray)); } } // assumes all entries are unique and array1 has no null elements private void findAll(String[] array1, String[] array2) { assertNotNull(array1); assertNotNull(array2); assertEquals(array1.length, array2.length); for (int i = 0; i < array1.length; i++) { boolean found = false; for (int j = 0; j < array2.length; j++) { if (array1[i].equals(array2[j])) { found = true; break; } } assertTrue(found); } } private void compareArrays(byte[] array1, byte[] array2) { assertNotNull(array1); assertNotNull(array2); assertEquals(array1.length, array2.length); for (int i = 0; i < array1.length; i++) assertEquals(array1[i], array2[i]); } private URL getFilePath(String path) throws IOException { BundleContext bundleContext = SecurityTestsActivator.getDefault().getBundleContext(); URL url = bundleContext.getBundle().getEntry(path); return FileLocator.toFileURL(url); } }