From 1698546ba40c8ac9bf98ad2d6248d78fadc14f98 Mon Sep 17 00:00:00 2001 From: Darran A Lofthouse Date: Sat, 30 Nov 2024 19:42:58 +0000 Subject: [PATCH 1/3] [ELY-2866] Add a new test specifically for external mode of the KeyStoreCredentialStore. --- .../ExternalKeyStoreCredentialStoreTest.java | 223 ++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 credential/store/src/test/java/org/wildfly/security/credential/store/impl/ExternalKeyStoreCredentialStoreTest.java diff --git a/credential/store/src/test/java/org/wildfly/security/credential/store/impl/ExternalKeyStoreCredentialStoreTest.java b/credential/store/src/test/java/org/wildfly/security/credential/store/impl/ExternalKeyStoreCredentialStoreTest.java new file mode 100644 index 00000000000..0d39c158415 --- /dev/null +++ b/credential/store/src/test/java/org/wildfly/security/credential/store/impl/ExternalKeyStoreCredentialStoreTest.java @@ -0,0 +1,223 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2024 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * 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. + */ + +package org.wildfly.security.credential.store.impl; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.security.KeyStore; +import java.security.KeyStore.ProtectionParameter; +import java.security.Provider; +import java.security.Security; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.wildfly.security.auth.server.IdentityCredentials; +import org.wildfly.security.credential.Credential; +import org.wildfly.security.credential.PasswordCredential; +import org.wildfly.security.credential.SecretKeyCredential; +import org.wildfly.security.credential.source.CredentialSource; +import org.wildfly.security.credential.store.CredentialStore; +import org.wildfly.security.credential.store.CredentialStore.CredentialSourceProtectionParameter; +import org.wildfly.security.credential.store.CredentialStoreSpi; +import org.wildfly.security.encryption.SecretKeyUtil; +import org.wildfly.security.password.Password; +import org.wildfly.security.password.PasswordFactory; +import org.wildfly.security.password.WildFlyElytronPasswordProvider; +import org.wildfly.security.password.interfaces.ClearPassword; +import org.wildfly.security.password.spec.ClearPasswordSpec; + +/** + * /** + * Test case to test the {@code KeyStoreCredentialStore} implementation when + * configured to persist to an external file. + * + * When running in this mode a KeyStore is used to obtain a SecretKey instance + * and the credentials are encrypted using this SecretKey before being written + * to the external file. + * + * @author Darran Lofthouse + */ +public class ExternalKeyStoreCredentialStoreTest { + + private static final String KEY_KEY_STORE_NAME = "secret.pkcs12"; + private static final String SECRET_KEY_ALIAS = "secret"; + private final char[] keyStorePassword = "The quick brown fox jumped over the lazy dog".toCharArray(); + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + private File keyKeyStoreFile; + private PasswordFactory passwordFactory; + private String providerName; + + private char[] secretPassword; + private PasswordCredential storedPasswordCredential; + private SecretKeyCredential storedSecretKeyCredential; + + private CredentialSourceProtectionParameter storeProtection; + + @Before + public void prepareTest() throws Exception { + /* + * Step 1 - Create a KeyStore containing a SecretKey to be used for encryption. + */ + byte[] rawKey = new byte[16]; // 16 bytes = 128 bits + Arrays.fill(rawKey, (byte) 0x00); // This is a test, we don't need a random key. + SecretKey secretKey = new SecretKeySpec(rawKey, "AES"); + + KeyStore keyKeyStore = KeyStore.getInstance("PKCS12"); + ProtectionParameter keyStoreProtection = new KeyStore.PasswordProtection(keyStorePassword); + + keyKeyStore.load(null, keyStorePassword); + keyKeyStore.setEntry(SECRET_KEY_ALIAS, new KeyStore.SecretKeyEntry(secretKey), keyStoreProtection); + + keyKeyStoreFile = temporaryFolder.newFile(KEY_KEY_STORE_NAME); + + try (FileOutputStream fos = new FileOutputStream(keyKeyStoreFile)) { + keyKeyStore.store(fos, keyStorePassword); + } + /* + * Step 2 - Create the PasswordFactory and SecretKeyCredential to be stored. + */ + final Provider provider = WildFlyElytronPasswordProvider.getInstance(); + + providerName = provider.getName(); + + Security.addProvider(provider); + + passwordFactory = PasswordFactory.getInstance(ClearPassword.ALGORITHM_CLEAR); + final Password password = passwordFactory.generatePassword(new ClearPasswordSpec(keyStorePassword)); + final Credential credential = new PasswordCredential(password); + final CredentialSource credentialSource = IdentityCredentials.NONE.withCredential(credential); + + storeProtection = new CredentialStore.CredentialSourceProtectionParameter(credentialSource); + + secretPassword = "this is a password".toCharArray(); + + final Password secret = passwordFactory.generatePassword(new ClearPasswordSpec(secretPassword)); + + storedPasswordCredential = new PasswordCredential(secret); + storedSecretKeyCredential = new SecretKeyCredential(SecretKeyUtil.generateSecretKey(256)); + } + + @After + public void removeWildFlyElytronProvider() { + Security.removeProvider(providerName); + } + + private CredentialStoreSpi getCredentialStore(final File location) throws Exception { + final KeyStoreCredentialStore keyStoreCredentialStore = new KeyStoreCredentialStore(); + + final Map attributes = new HashMap<>(); + attributes.put("keyStoreType", "PKCS12"); + attributes.put("keyAlias", SECRET_KEY_ALIAS); + attributes.put("create", Boolean.TRUE.toString()); + attributes.put("location", keyKeyStoreFile.getAbsolutePath()); + attributes.put("external", Boolean.TRUE.toString()); + attributes.put("externalPath", location.getAbsolutePath()); + + keyStoreCredentialStore.initialize(attributes, storeProtection, null); + + return keyStoreCredentialStore; + } + + @Test + public void testPasswordCredential() throws Exception { + final File credentialStoreFile = new File(temporaryFolder.getRoot(), "test.credential.store"); + + CredentialStoreSpi credentialStore = getCredentialStore(credentialStoreFile); + + credentialStore.store("testAlias", storedPasswordCredential, null); + credentialStore.flush(); + + assertTrue("Credential Store File Created", credentialStoreFile.exists()); + + credentialStore = getCredentialStore(credentialStoreFile); + + final PasswordCredential retrievedCredential = credentialStore.retrieve("testAlias", PasswordCredential.class, null, + null, null); + + final ClearPasswordSpec retrievedPassword = passwordFactory.getKeySpec(retrievedCredential.getPassword(), + ClearPasswordSpec.class); + + assertArrayEquals(secretPassword, retrievedPassword.getEncodedPassword()); + } + + @Test + public void testSecretKeyCredential() throws Exception { + final File credentialStoreFile = new File(temporaryFolder.getRoot(), "test.credential.store"); + + CredentialStoreSpi credentialStore = getCredentialStore(credentialStoreFile); + + credentialStore.store("testAlias", storedSecretKeyCredential, null); + credentialStore.flush(); + + assertTrue("Credential Store File Created", credentialStoreFile.exists()); + + credentialStore = getCredentialStore(credentialStoreFile); + + final SecretKeyCredential retrievedSecretKeyCredential = credentialStore.retrieve("testAlias",SecretKeyCredential.class, null, + null, null); + assertEquals("Expect SecretKeys to match", storedSecretKeyCredential.getSecretKey(), retrievedSecretKeyCredential.getSecretKey()); + } + + @Test + public void testBothCredentials() throws Exception { + final File credentialStoreFile = new File(temporaryFolder.getRoot(), "test.credential.store"); + + CredentialStoreSpi credentialStore = getCredentialStore(credentialStoreFile); + + credentialStore.store("testAlias", storedPasswordCredential, null); + credentialStore.store("testAlias", storedSecretKeyCredential, null); + credentialStore.flush(); + + assertTrue("Credential Store File Created", credentialStoreFile.exists()); + + credentialStore = getCredentialStore(credentialStoreFile); + + Set aliases = credentialStore.getAliases(); + assertEquals("Expected alias count", 1, aliases.size()); + assertTrue("Expected alias 'testAlias'", aliases.contains("testalias")); + + final PasswordCredential retrievedCredential = credentialStore.retrieve("testAlias", PasswordCredential.class, null, + null, null); + + final ClearPasswordSpec retrievedPassword = passwordFactory.getKeySpec(retrievedCredential.getPassword(), + ClearPasswordSpec.class); + assertArrayEquals(secretPassword, retrievedPassword.getEncodedPassword()); + + final SecretKeyCredential retrievedSecretKeyCredential = credentialStore.retrieve("testAlias",SecretKeyCredential.class, null, + null, null); + assertEquals("Expect SecretKeys to match", storedSecretKeyCredential.getSecretKey(), retrievedSecretKeyCredential.getSecretKey()); + } +} From a4efa34b23f52930e0f863846d005d87e796dd26 Mon Sep 17 00:00:00 2001 From: Darran A Lofthouse Date: Sun, 1 Dec 2024 13:25:04 +0000 Subject: [PATCH 2/3] [ELY-2867] Where SecretKeyCredentials are stored when using external storage persist the algorithm. --- .../store/impl/KeyStoreCredentialStore.java | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/credential/store/src/main/java/org/wildfly/security/credential/store/impl/KeyStoreCredentialStore.java b/credential/store/src/main/java/org/wildfly/security/credential/store/impl/KeyStoreCredentialStore.java index 5c20ff92525..d9043de06ba 100644 --- a/credential/store/src/main/java/org/wildfly/security/credential/store/impl/KeyStoreCredentialStore.java +++ b/credential/store/src/main/java/org/wildfly/security/credential/store/impl/KeyStoreCredentialStore.java @@ -44,6 +44,7 @@ import java.security.PublicKey; import java.security.Security; import java.security.UnrecoverableEntryException; +import java.security.KeyStore.SecretKeyEntry; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; @@ -1172,7 +1173,14 @@ int getHashCode() { private final class ExternalStorage { // version of external storage file, can be used later to enhance functionality and keep backward compatibility - private int VERSION = 1; + private int VERSION_ONE = 1; + /* + * If the file being read is version 2 the algorithm of each SecretKey is written after the encoded form. + * + * On writing version 2 will only be selected if at lease one SecretKeyEntry has an algorithm other than + * the DATA_OID value. + */ + private int VERSION_TWO = 2; private int SECRET_KEY_ENTRY_TYPE = 100; @@ -1237,11 +1245,12 @@ void load(InputStream inputStream) throws IOException, GeneralSecurityException dataKeyStore.load(null, null); ObjectInputStream ois = new ObjectInputStream(inputStream); int fileVersion = ois.readInt(); - if (fileVersion == VERSION) { + if (fileVersion == VERSION_ONE || fileVersion == VERSION_TWO) { + boolean readAlgorithm = fileVersion == VERSION_TWO; while (ois.available() > 0) { int entryType = ois.readInt(); if (entryType == SECRET_KEY_ENTRY_TYPE) { - loadSecretKey(ois); + loadSecretKey(ois, readAlgorithm); } else { throw log.unrecognizedEntryType(Integer.toString(entryType)); } @@ -1252,7 +1261,7 @@ void load(InputStream inputStream) throws IOException, GeneralSecurityException ois.close(); } - private void loadSecretKey(ObjectInputStream ois) throws IOException, GeneralSecurityException { + private void loadSecretKey(ObjectInputStream ois, boolean readAlgorithm) throws IOException, GeneralSecurityException { byte[] encryptedData = readBytes(ois); byte[] iv = readBytes(ois); @@ -1262,7 +1271,8 @@ private void loadSecretKey(ObjectInputStream ois) throws IOException, GeneralSec ObjectInputStream entryOis = new ObjectInputStream(new ByteArrayInputStream(unPadded)); String ksAlias = entryOis.readUTF(); byte[] encodedSecretKey = readBytes(entryOis); - KeyStore.Entry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(encodedSecretKey, DATA_OID)); + String algorithm = readAlgorithm ? entryOis.readUTF() : DATA_OID; + KeyStore.Entry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(encodedSecretKey, algorithm)); dataKeyStore.setEntry(ksAlias, entry, convertParameter(protectionParameter)); } @@ -1289,13 +1299,15 @@ private int writeBytes(byte[] data, ObjectOutputStream oos) throws IOException { */ void store(OutputStream outputStream) throws IOException, GeneralSecurityException { ObjectOutputStream oos = new ObjectOutputStream(outputStream); - oos.writeInt(VERSION); + final boolean writeVersionTwo = isWriteUsingVersionTwo(); + + oos.writeInt(writeVersionTwo ? VERSION_TWO : VERSION_ONE); Enumeration ksAliases = dataKeyStore.aliases(); while(ksAliases.hasMoreElements()) { String alias = ksAliases.nextElement(); KeyStore.Entry entry = dataKeyStore.getEntry(alias, convertParameter(protectionParameter)); if (entry instanceof KeyStore.SecretKeyEntry) { - saveSecretKey(alias, oos, (KeyStore.SecretKeyEntry)entry); + saveSecretKey(alias, oos, (KeyStore.SecretKeyEntry)entry, writeVersionTwo); } else { throw log.unrecognizedEntryType(entry != null ? entry.getClass().getCanonicalName() : "null"); } @@ -1304,11 +1316,31 @@ void store(OutputStream outputStream) throws IOException, GeneralSecurityExcepti oos.close(); } - private void saveSecretKey(String ksAlias, ObjectOutputStream oos, KeyStore.SecretKeyEntry entry) throws IOException, GeneralSecurityException { + private boolean isWriteUsingVersionTwo() throws GeneralSecurityException { + Enumeration aliases = dataKeyStore.aliases(); + while(aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + KeyStore.Entry entry = dataKeyStore.getEntry(alias, convertParameter(protectionParameter)); + if (entry instanceof KeyStore.SecretKeyEntry) { + SecretKeyEntry secretKeyEntry = (SecretKeyEntry) entry; + if (!DATA_OID.equals(secretKeyEntry.getSecretKey().getAlgorithm())) { + // It just takes one. + return true; + } + } + } + return false; + } + + private void saveSecretKey(String ksAlias, ObjectOutputStream oos, KeyStore.SecretKeyEntry entry, boolean writeAlgorithm) throws IOException, GeneralSecurityException { ByteArrayOutputStream entryData = new ByteArrayOutputStream(1024); ObjectOutputStream entryOos = new ObjectOutputStream(entryData); entryOos.writeUTF(ksAlias); - writeBytes(entry.getSecretKey().getEncoded(), entryOos); + SecretKey secretKey = entry.getSecretKey(); + writeBytes(secretKey.getEncoded(), entryOos); + if (writeAlgorithm) { + entryOos.writeUTF(secretKey.getAlgorithm()); + } entryOos.flush(); encrypt.init(Cipher.ENCRYPT_MODE, storageSecretKey, (AlgorithmParameterSpec) null); // ELY-1308: third param need to workaround BouncyCastle bug From 91903207f45ae5543b96bec09361c7ebcc89b9de Mon Sep 17 00:00:00 2001 From: Darran A Lofthouse Date: Sun, 1 Dec 2024 13:56:15 +0000 Subject: [PATCH 3/3] [ELY-2866] Where the KeyStore is used as an in-memory cache only use a HashMap instead. --- .../store/impl/KeyStoreCredentialStore.java | 142 +++++++++++++++--- 1 file changed, 120 insertions(+), 22 deletions(-) diff --git a/credential/store/src/main/java/org/wildfly/security/credential/store/impl/KeyStoreCredentialStore.java b/credential/store/src/main/java/org/wildfly/security/credential/store/impl/KeyStoreCredentialStore.java index d9043de06ba..4995e49c48d 100644 --- a/credential/store/src/main/java/org/wildfly/security/credential/store/impl/KeyStoreCredentialStore.java +++ b/credential/store/src/main/java/org/wildfly/security/credential/store/impl/KeyStoreCredentialStore.java @@ -44,6 +44,8 @@ import java.security.PublicKey; import java.security.Security; import java.security.UnrecoverableEntryException; +import java.security.KeyStore.Entry; +import java.security.KeyStore.ProtectionParameter; import java.security.KeyStore.SecretKeyEntry; import java.security.cert.Certificate; import java.security.cert.CertificateException; @@ -172,7 +174,7 @@ public final class KeyStoreCredentialStore extends CredentialStoreSpi { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final HashMap cache = new HashMap<>(); private volatile boolean modifiable; - private KeyStore keyStore; + private InMemoryStore inMemoryStore; private Path location; private Path externalPath; private boolean create; @@ -389,7 +391,7 @@ public void store(final String credentialAlias, final Credential credential, fin // now, store it under a unique alias final String ksAlias = calculateNewAlias(credentialAlias, credentialClass, algorithmName, parameterSpec); try (Hold hold = lockForWrite()) { - keyStore.setEntry(ksAlias, entry, convertParameter(protectionParameter)); + inMemoryStore.setEntry(ksAlias, entry, convertParameter(protectionParameter)); final TopEntry topEntry = cache.computeIfAbsent(toLowercase(credentialAlias), TopEntry::new); final MidEntry midEntry = topEntry.getMap().computeIfAbsent(credentialClass, c -> new MidEntry(topEntry, c)); final BottomEntry bottomEntry; @@ -406,7 +408,7 @@ public void store(final String credentialAlias, final Credential credential, fin } if (oldAlias != null && ! oldAlias.equals(ksAlias)) { // unlikely but possible - keyStore.deleteEntry(oldAlias); + inMemoryStore.deleteEntry(oldAlias); } } } catch (KeyStoreException | NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException | CertificateException e) { @@ -474,7 +476,7 @@ public C retrieve(final String credentialAlias, final Cla log.tracef("KeyStoreCredentialStore: no entry for parameterSpec %s", parameterSpec); return null; } - entry = keyStore.getEntry(ksAlias, convertParameter(protectionParameter)); + entry = inMemoryStore.getEntry(ksAlias, convertParameter(protectionParameter)); } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) { throw log.cannotAcquireCredentialFromStore(e); } @@ -776,7 +778,7 @@ private void remove(final BottomEntry bottomEntry, final AlgorithmParameterSpec private void remove(final String ksAlias) throws KeyStoreException { if (ksAlias != null) { - keyStore.deleteEntry(ksAlias); + inMemoryStore.deleteEntry(ksAlias); } } @@ -791,7 +793,7 @@ public void flush() throws CredentialStoreException { if (useExternalStorage) { externalStorage.store(os); } else { - keyStore.store(os, storePassword); + inMemoryStore.store(os, storePassword); } } catch (Throwable t) { try { @@ -844,13 +846,14 @@ private void load(String type) throws CredentialStoreException { setupExternalStorage(type, location); } else { dataLocation = location; - keyStore = getKeyStoreInstance(type); + KeyStore keyStore = getKeyStoreInstance(type); + inMemoryStore = new KeyStoreWrapper(keyStore); } if (create) { log.tracef("KeyStoreCredentialStore: creating empty backing KeyStore dataLocation = %s external = %b", dataLocation, useExternalStorage); if (dataLocation == null) { try { - keyStore.load(null, null); + inMemoryStore.load(null, null); } catch (CertificateException | IOException | NoSuchAlgorithmException e) { throw log.cannotInitializeCredentialStore(e); } @@ -869,12 +872,12 @@ private void load(String type) throws CredentialStoreException { if (useExternalStorage) { externalStorage.load(fileStream); } else { - keyStore.load(fileStream, password); + inMemoryStore.load(fileStream, password); } } - enumeration = keyStore.aliases(); + enumeration = inMemoryStore.aliases(); } else { - keyStore.load(null, null); + inMemoryStore.load(null, null); enumeration = Collections.emptyEnumeration(); } } catch (GeneralSecurityException e) { @@ -963,7 +966,7 @@ private KeyStore getKeyStoreInstance(String type) throws CredentialStoreExceptio */ private void setupExternalStorage(final String keyContainingKeyStoreType, final Path keyContainingKeyStoreLocation) throws CredentialStoreException { KeyStore keyContainingKeyStore = getKeyStoreInstance(keyContainingKeyStoreType); - keyStore = getKeyStoreInstance("JCEKS"); + inMemoryStore = new HashMapStore(); externalStorage = new ExternalStorage(); try { final char[] storePassword = getStorePassword(protectionParameter); @@ -977,7 +980,7 @@ private void setupExternalStorage(final String keyContainingKeyStoreType, final keyContainingKeyStore.load(null, storePassword); } } - externalStorage.init(cryptographicAlgorithm, encryptionKeyAlias, keyContainingKeyStore, storePassword, keyStore); + externalStorage.init(cryptographicAlgorithm, encryptionKeyAlias, keyContainingKeyStore, storePassword, inMemoryStore); } catch(IOException | GeneralSecurityException e) { throw log.cannotInitializeCredentialStore(e); } @@ -1170,6 +1173,101 @@ int getHashCode() { } } + /** + * Interface to represent the in-memory form of either the direct KeyStore + * or the store used for external mode. + */ + private interface InMemoryStore { + + public Enumeration aliases() throws KeyStoreException; + public Entry getEntry(String alias, ProtectionParameter protParam) throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException; + public void setEntry(String alias, Entry entry, ProtectionParameter protParam) throws KeyStoreException; + + public void deleteEntry(String alias) throws KeyStoreException; + + public void load(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException; + public void store(OutputStream stream, char[] password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException; + } + + static final class KeyStoreWrapper implements InMemoryStore { + + private final KeyStore wrapped; + + KeyStoreWrapper(final KeyStore toWrap) { + this.wrapped = toWrap; + } + + @Override + public Enumeration aliases() throws KeyStoreException { + return wrapped.aliases(); + } + + @Override + public Entry getEntry(String alias, ProtectionParameter protParam) + throws NoSuchAlgorithmException, UnrecoverableEntryException, KeyStoreException { + return wrapped.getEntry(alias, protParam); + } + + @Override + public void setEntry(String alias, Entry entry, ProtectionParameter protParam) throws KeyStoreException { + wrapped.setEntry(alias, entry, protParam); + } + + @Override + public void deleteEntry(String alias) throws KeyStoreException { + wrapped.deleteEntry(alias); + } + + @Override + public void load(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { + wrapped.load(stream, password); + } + + @Override + public void store(OutputStream stream, char[] password) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + wrapped.store(stream, password); + } + + } + + static final class HashMapStore implements InMemoryStore { + + private final Map map = new HashMap<>(); + + @Override + public Enumeration aliases() throws KeyStoreException { + return Collections.enumeration(map.keySet()); + } + + @Override + public Entry getEntry(String alias, ProtectionParameter protParam) { + return map.get(alias); + } + + @Override + public void setEntry(String alias, Entry entry, ProtectionParameter protParam) { + map.put(alias, entry); + } + + @Override + public void deleteEntry(String alias) { + map.remove(alias); + } + + @Override + public void load(InputStream stream, char[] password) throws IOException, NoSuchAlgorithmException, CertificateException { + map.clear(); + } + + @Override + public void store(OutputStream stream, char[] password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + // This should not be reachable so not converted to an error coded message. + throw new UnsupportedOperationException("Cannot store in-memory store"); + } + + } + private final class ExternalStorage { // version of external storage file, can be used later to enhance functionality and keep backward compatibility @@ -1189,19 +1287,19 @@ private final class ExternalStorage { private Cipher encrypt; private Cipher decrypt; - private KeyStore dataKeyStore; + private InMemoryStore inMemoryStore; private KeyStore storageSecretKeyStore; private SecretKey storageSecretKey; private ExternalStorage() {} - void init(String cryptographicAlgorithm, String keyAlias, KeyStore keyStore, char[] keyPassword, KeyStore dataKeyStore) throws CredentialStoreException { + void init(String cryptographicAlgorithm, String keyAlias, KeyStore keyStore, char[] keyPassword, InMemoryStore inMemoryStore) throws CredentialStoreException { if (cryptographicAlgorithm == null) cryptographicAlgorithm = DEFAULT_CRYPTOGRAPHIC_ALGORITHM; storageSecretKeyStore = keyStore; - this.dataKeyStore = dataKeyStore; + this.inMemoryStore = inMemoryStore; try { fetchStorageSecretKey(keyAlias, keyPassword); @@ -1242,7 +1340,7 @@ private void fetchStorageSecretKey(String keyAlias, char[] keyPassword) throws C * @throws IOException if something goes wrong */ void load(InputStream inputStream) throws IOException, GeneralSecurityException { - dataKeyStore.load(null, null); + inMemoryStore.load(null, null); ObjectInputStream ois = new ObjectInputStream(inputStream); int fileVersion = ois.readInt(); if (fileVersion == VERSION_ONE || fileVersion == VERSION_TWO) { @@ -1273,7 +1371,7 @@ private void loadSecretKey(ObjectInputStream ois, boolean readAlgorithm) throws byte[] encodedSecretKey = readBytes(entryOis); String algorithm = readAlgorithm ? entryOis.readUTF() : DATA_OID; KeyStore.Entry entry = new KeyStore.SecretKeyEntry(new SecretKeySpec(encodedSecretKey, algorithm)); - dataKeyStore.setEntry(ksAlias, entry, convertParameter(protectionParameter)); + inMemoryStore.setEntry(ksAlias, entry, convertParameter(protectionParameter)); } private byte[] readBytes(ObjectInputStream ois) throws IOException { @@ -1302,10 +1400,10 @@ void store(OutputStream outputStream) throws IOException, GeneralSecurityExcepti final boolean writeVersionTwo = isWriteUsingVersionTwo(); oos.writeInt(writeVersionTwo ? VERSION_TWO : VERSION_ONE); - Enumeration ksAliases = dataKeyStore.aliases(); + Enumeration ksAliases = inMemoryStore.aliases(); while(ksAliases.hasMoreElements()) { String alias = ksAliases.nextElement(); - KeyStore.Entry entry = dataKeyStore.getEntry(alias, convertParameter(protectionParameter)); + KeyStore.Entry entry = inMemoryStore.getEntry(alias, convertParameter(protectionParameter)); if (entry instanceof KeyStore.SecretKeyEntry) { saveSecretKey(alias, oos, (KeyStore.SecretKeyEntry)entry, writeVersionTwo); } else { @@ -1317,10 +1415,10 @@ void store(OutputStream outputStream) throws IOException, GeneralSecurityExcepti } private boolean isWriteUsingVersionTwo() throws GeneralSecurityException { - Enumeration aliases = dataKeyStore.aliases(); + Enumeration aliases = inMemoryStore.aliases(); while(aliases.hasMoreElements()) { String alias = aliases.nextElement(); - KeyStore.Entry entry = dataKeyStore.getEntry(alias, convertParameter(protectionParameter)); + KeyStore.Entry entry = inMemoryStore.getEntry(alias, convertParameter(protectionParameter)); if (entry instanceof KeyStore.SecretKeyEntry) { SecretKeyEntry secretKeyEntry = (SecretKeyEntry) entry; if (!DATA_OID.equals(secretKeyEntry.getSecretKey().getAlgorithm())) {