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);
+               }
+       }
+}

Reply via email to