WICKET-6559 new encrypting page store
Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/bf72e279 Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/bf72e279 Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/bf72e279 Branch: refs/heads/WICKET-6563 Commit: bf72e27980070572b57c8ad0130c83a7d024aa0f Parents: c43d3a3 Author: Sven Meier <[email protected]> Authored: Tue Oct 30 09:13:41 2018 +0100 Committer: Sven Meier <[email protected]> Committed: Tue Oct 30 13:56:24 2018 +0100 ---------------------------------------------------------------------- .../wicket/DefaultPageManagerProvider.java | 11 +- .../wicket/pageStore/CryptingPageStore.java | 146 +++++++++++++++++++ .../wicket/pageStore/crypt/DefaultCrypter.java | 108 ++++++++++++++ .../apache/wicket/pageStore/crypt/ICrypter.java | 28 ++++ .../apache/wicket/settings/StoreSettings.java | 17 ++- .../wicket/pageStore/CryptingPageStoreTest.java | 87 +++++++++++ 6 files changed, 393 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/bf72e279/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java b/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java index cf4f84a..36c1fac 100644 --- a/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java +++ b/wicket-core/src/main/java/org/apache/wicket/DefaultPageManagerProvider.java @@ -21,6 +21,7 @@ import java.io.File; import org.apache.wicket.page.IPageManager; import org.apache.wicket.page.PageManager; import org.apache.wicket.pageStore.AsynchronousPageStore; +import org.apache.wicket.pageStore.CryptingPageStore; import org.apache.wicket.pageStore.DiskPageStore; import org.apache.wicket.pageStore.FilePageStore; import org.apache.wicket.pageStore.GroupingPageStore; @@ -41,8 +42,8 @@ import org.apache.wicket.util.lang.Bytes; * <ol> * <li>{@link RequestPageStore} caching pages until end of the request</li> * <li>{@link InSessionPageStore} keeping the last accessed page in the session</li> - * <li>{@link AsynchronousPageStore} moving storage of pages to a worker thread (if enabled in - * {@link StoreSettings#isAsynchronous()})</li> + * <li>{@link AsynchronousPageStore} moving storage of pages to a worker thread (if enabled in {@link StoreSettings#isAsynchronous()})</li> + * <li>{@link CryptingPageStore} encrypting all pages (if enabled in {@link StoreSettings#isEncrypted()})</li> * <li>{@link DiskPageStore} keeping all pages, configured according to {@link StoreSettings}</li> * </ol> * An alternative chain with all pages held in-memory could be: @@ -166,6 +167,10 @@ public class DefaultPageManagerProvider implements IPageManagerProvider Bytes maxSizePerSession = storeSettings.getMaxSizePerSession(); File fileStoreFolder = storeSettings.getFileStoreFolder(); - return new DiskPageStore(application.getName(), fileStoreFolder, maxSizePerSession, getSerializer()); + if (storeSettings.isEncrypted()) { + return new SerializingPageStore(new CryptingPageStore(new DiskPageStore(application.getName(), fileStoreFolder, maxSizePerSession)), getSerializer()); + } else { + return new DiskPageStore(application.getName(), fileStoreFolder, maxSizePerSession, getSerializer()); + } } } http://git-wip-us.apache.org/repos/asf/wicket/blob/bf72e279/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java new file mode 100644 index 0000000..2b27e80 --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.pageStore; + +import java.io.Serializable; + +import org.apache.wicket.MetaDataKey; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.page.IManageablePage; +import org.apache.wicket.pageStore.crypt.DefaultCrypter; +import org.apache.wicket.pageStore.crypt.ICrypter; +import org.apache.wicket.serialize.ISerializer; + +/** + * A store that encrypts all pages before delegating and vice versa. + * <p> + * All pages passing through this store are restricted to be {@link SerializedPage}s. You can + * achieve this with + * <ul> + * <li>a {@link SerializingPageStore} delegating to this store and</li> + * <li>delegating to a store that does not deserialize its pages, e.g. a {@link DiskPageStore} + * without {@link ISerializer}</li>. + * </ul> + */ +public class CryptingPageStore extends DelegatingPageStore +{ + private static final MetaDataKey<SessionData> KEY = new MetaDataKey<SessionData>() + { + }; + + /** + * @param delegate + * store to delegate to + * @param applicationName + * name of application + */ + public CryptingPageStore(IPageStore delegate) + { + super(delegate); + } + + /** + * Supports asynchronous add if the delegate supports it. + */ + @Override + public boolean canBeAsynchronous(IPageContext context) + { + context.bind(); + + getSessionData(context); + + return getDelegate().canBeAsynchronous(context); + } + + private SessionData getSessionData(IPageContext context) + { + SessionData data = context.getSessionData(KEY); + if (data == null) + { + data = context.setSessionData(KEY, new SessionData(newCrypter(context))); + } + return data; + } + + /** + * Create a new {@link ICrypter} for the given context. + */ + protected ICrypter newCrypter(IPageContext context) { + return new DefaultCrypter(); + } + + @Override + public IManageablePage getPage(IPageContext context, int id) + { + IManageablePage page = super.getPage(context, id); + + if (page != null) + { + if (page instanceof SerializedPage == false) + { + throw new WicketRuntimeException("CryptingPageStore expects serialized pages"); + } + SerializedPage serializedPage = (SerializedPage)page; + + byte[] encrypted = serializedPage.getData(); + byte[] decrypted = getSessionData(context).decrypt(encrypted); + + page = new SerializedPage(page.getPageId(), serializedPage.getPageType(), decrypted); + } + + return page; + } + + @Override + public void addPage(IPageContext context, IManageablePage page) + { + if (page instanceof SerializedPage == false) + { + throw new WicketRuntimeException("CryptingPageStore works with serialized pages only"); + } + + SerializedPage serializedPage = (SerializedPage)page; + + byte[] decrypted = serializedPage.getData(); + byte[] encrypted = getSessionData(context).encrypt(decrypted); + + page = new SerializedPage(page.getPageId(), serializedPage.getPageType(), encrypted); + + super.addPage(context, page); + } + + private static class SessionData implements Serializable + { + + private ICrypter cypter; + + public SessionData(ICrypter crypter) + { + this.cypter= crypter; + } + + public byte[] encrypt(byte[] decrypted) + { + return cypter.encrypt(decrypted); + } + + public byte[] decrypt(byte[] encrypted) + { + return cypter.decrypt(encrypted); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/bf72e279/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/DefaultCrypter.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/DefaultCrypter.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/DefaultCrypter.java new file mode 100644 index 0000000..53c63be --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/DefaultCrypter.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.pageStore.crypt; + +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + +import org.apache.wicket.WicketRuntimeException; + +/** + * Default encryption and decryption implementation. + */ +public class DefaultCrypter implements ICrypter +{ + private final SecureRandom random; + + private final SecretKey key; + + public DefaultCrypter() + { + try + { + random = SecureRandom.getInstance("SHA1PRNG", "SUN"); + + KeyGenerator generator = KeyGenerator.getInstance("AES"); + generator.init(256, random); + key = generator.generateKey(); + } + catch (GeneralSecurityException ex) + { + throw new WicketRuntimeException(ex); + } + } + + protected Cipher getCipher() throws GeneralSecurityException + { + return Cipher.getInstance("AES/CBC/PKCS5Padding"); + } + + @Override + public byte[] encrypt(byte[] decrypted) + { + try + { + Cipher cipher = getCipher(); + cipher.init(Cipher.ENCRYPT_MODE, key, random); + + AlgorithmParameters params = cipher.getParameters(); + byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV(); + + byte[] ciphertext = cipher.doFinal(decrypted); + + byte[] encrypted = Arrays.copyOf(iv, iv.length + ciphertext.length); + System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length); + + return encrypted; + } + catch (GeneralSecurityException ex) + { + throw new WicketRuntimeException(ex); + } + } + + @Override + public byte[] decrypt(byte[] encrypted) + { + try + { + byte[] iv = new byte[16]; + byte[] ciphertext = new byte[encrypted.length - 16]; + System.arraycopy(encrypted, 0, iv, 0, iv.length); + System.arraycopy(encrypted, 16, ciphertext, 0, ciphertext.length); + + Cipher cipher = getCipher(); + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + byte[] decrypted = cipher.doFinal(ciphertext); + + return decrypted; + } + catch (GeneralSecurityException ex) + { + throw new WicketRuntimeException(ex); + } + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/bf72e279/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/ICrypter.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/ICrypter.java b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/ICrypter.java new file mode 100644 index 0000000..1cdbeaf --- /dev/null +++ b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/ICrypter.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.pageStore.crypt; + +import java.io.Serializable; + +/** + * An encrypter and decrypter of pages. + */ +public interface ICrypter extends Serializable { + byte[] encrypt(byte[] bytes); + + byte[] decrypt(byte[] bytes); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/bf72e279/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java b/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java index 7c32101..965258c 100644 --- a/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java +++ b/wicket-core/src/main/java/org/apache/wicket/settings/StoreSettings.java @@ -49,6 +49,8 @@ public class StoreSettings private boolean asynchronous = true; + private boolean encrypted = false; + /** * Construct. * @@ -179,4 +181,17 @@ public class StoreSettings { return asynchronous; } -} \ No newline at end of file + + public void setEncrypted(boolean encrypted) + { + this.encrypted = encrypted; + } + + /** + * @return {@code true} if the storing of page is encrypted + */ + public boolean isEncrypted() + { + return encrypted; + } +} http://git-wip-us.apache.org/repos/asf/wicket/blob/bf72e279/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java b/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java new file mode 100644 index 0000000..a4b547e --- /dev/null +++ b/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.wicket.pageStore; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.security.GeneralSecurityException; + +import org.apache.wicket.MockPage; +import org.apache.wicket.WicketRuntimeException; +import org.apache.wicket.mock.MockPageStore; +import org.apache.wicket.serialize.java.JavaSerializer; +import org.junit.jupiter.api.Test; + +/** + * Test for {@link CryptingPageStore}. + * + * @author svenmeier + */ +public class CryptingPageStoreTest +{ + + @Test + void test() + { + CryptingPageStore store = new CryptingPageStore(new MockPageStore()); + JavaSerializer serializer = new JavaSerializer("test"); + + IPageContext context = new DummyPageContext(); + + for (int p = 0; p < 10; p++) + { + MockPage add = new MockPage(p); + SerializedPage serializedAdd = new SerializedPage(p, "foo", serializer.serialize(add)); + store.addPage(context, serializedAdd); + + SerializedPage serializedGot = (SerializedPage)store.getPage(context, p); + MockPage got = (MockPage)serializer.deserialize(serializedGot.getData()); + assertEquals(p, got.getPageId()); + } + } + + @Test + void testFail() + { + CryptingPageStore store = new CryptingPageStore(new MockPageStore()); + JavaSerializer serializer = new JavaSerializer("test"); + + DummyPageContext context = new DummyPageContext(); + + int p = 42; + + MockPage add = new MockPage(p); + SerializedPage serializedAdd = new SerializedPage(p, "foo", serializer.serialize(add)); + store.addPage(context, serializedAdd); + + // remove key from session + context.clearSession(); + + try + { + SerializedPage serializedGot = (SerializedPage)store.getPage(context, p); + + MockPage got = (MockPage)serializer.deserialize(serializedGot.getData()); + assertEquals(p, got.getPageId()); + } + catch (WicketRuntimeException ex) + { + assertTrue("unable to decrypt with new key", ex.getCause() instanceof GeneralSecurityException); + } + } +}
