This is an automated email from the ASF dual-hosted git repository.

papegaaij pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/wicket.git


The following commit(s) were added to refs/heads/master by this push:
     new 210525f0ec WICKET-7016: Add support for AES-GCM-SIV as cipher for page 
store encryption
210525f0ec is described below

commit 210525f0ecd30794532b5ebebfbe677daf244517
Author: Emond Papegaaij <emond.papega...@topicus.nl>
AuthorDate: Fri Nov 18 11:59:31 2022 +0100

    WICKET-7016: Add support for AES-GCM-SIV as cipher for page store encryption
---
 pom.xml                                            |   6 ++
 wicket-core/pom.xml                                |   5 +
 wicket-core/src/main/java/module-info.java         |   1 +
 .../apache/wicket/pageStore/CryptingPageStore.java |   2 +-
 .../wicket/pageStore/crypt/GCMSIVCrypter.java      | 107 +++++++++++++++++++++
 .../org/apache/wicket/settings/StoreSettings.java  |  29 ++++++
 .../wicket/pageStore/CryptingPageStoreTest.java    |  49 ++++++++--
 7 files changed, 188 insertions(+), 11 deletions(-)

diff --git a/pom.xml b/pom.xml
index d4dadc97f1..f07d75e49b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -138,6 +138,7 @@
                <asm.version>9.2</asm.version>
                <aspectj.version>1.9.7</aspectj.version>
                <assertj-core.version>3.21.0</assertj-core.version>
+               <bouncycastle.version>1.72</bouncycastle.version>
                <byte-buddy.version>1.12.2</byte-buddy.version>
                <cdi-unit.version>4.1.0</cdi-unit.version>
                <commons-collections.version>3.2.2</commons-collections.version>
@@ -480,6 +481,11 @@
                                <artifactId>aspectjrt</artifactId>
                                <version>${aspectj.version}</version>
                        </dependency>
+                       <dependency>
+                               <groupId>org.bouncycastle</groupId>
+                               <artifactId>bcprov-jdk18on</artifactId>
+                               <version>${bouncycastle.version}</version>
+                       </dependency>
                        <dependency>
                                <groupId>org.danekja</groupId>
                                
<artifactId>jdk-serializable-functional</artifactId>
diff --git a/wicket-core/pom.xml b/wicket-core/pom.xml
index a699761976..7a606c8bf6 100644
--- a/wicket-core/pom.xml
+++ b/wicket-core/pom.xml
@@ -169,6 +169,11 @@ org.apache.wicket.validation.validator;-noimport:=true
                        <groupId>org.apache.wicket</groupId>
                        <artifactId>wicket-util</artifactId>
                </dependency>
+               <dependency>
+                       <groupId>org.bouncycastle</groupId>
+                       <artifactId>bcprov-jdk18on</artifactId>
+                       <optional>true</optional>
+               </dependency>
                <dependency>
                        <groupId>org.danekja</groupId>
                        <artifactId>jdk-serializable-functional</artifactId>
diff --git a/wicket-core/src/main/java/module-info.java 
b/wicket-core/src/main/java/module-info.java
index 3ab3c7f0ed..04cc5f45c1 100644
--- a/wicket-core/src/main/java/module-info.java
+++ b/wicket-core/src/main/java/module-info.java
@@ -29,6 +29,7 @@ module org.apache.wicket.core {
     requires org.danekja.jdk.serializable.functional;
     requires com.github.openjson;
     requires org.junit.jupiter.api;
+    requires static org.bouncycastle.provider;
 
     provides org.apache.wicket.IInitializer with org.apache.wicket.Initializer;
     provides org.apache.wicket.resource.FileSystemPathService with 
org.apache.wicket.resource.FileSystemJarPathService;
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
index b8e26ac9e9..32185a620b 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/CryptingPageStore.java
@@ -96,7 +96,7 @@ public class CryptingPageStore extends DelegatingPageStore
         */
        protected ICrypter newCrypter()
        {
-               return new DefaultCrypter();
+               return application.getStoreSettings().getCrypter().get();
        }
 
        @Override
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/GCMSIVCrypter.java
 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/GCMSIVCrypter.java
new file mode 100644
index 0000000000..e4fff7bcc8
--- /dev/null
+++ 
b/wicket-core/src/main/java/org/apache/wicket/pageStore/crypt/GCMSIVCrypter.java
@@ -0,0 +1,107 @@
+/*
+ * 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.SecureRandom;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.apache.wicket.WicketRuntimeException;
+import org.bouncycastle.jcajce.spec.AEADParameterSpec;
+
+/**
+ * Encryption and decryption implementation using AES-256-GCM-SIV 
authenticated encryption.
+ * 
+ * This implementation requires Bouncy Castle. It is more secure than the 
{@link DefaultCrypter},
+ * but also more expensive. Simple measurements have shown {@link 
DefaultCrypter} to be about 10 to
+ * 15 times faster than this implementation. This is likely caused by 
not-so-optimal implementation
+ * of the algorithm in Java by BC. When the JDK gets support for GCM-SIV
+ * (https://bugs.openjdk.org/browse/JDK-8256530), this implementation will 
likely be faster than or
+ * about as fast as CBC.
+ */
+public class GCMSIVCrypter implements ICrypter
+{
+       protected Cipher getCipher() throws GeneralSecurityException
+       {
+               return Cipher.getInstance("AES/GCM-SIV/NoPadding");
+       }
+
+       @Override
+       public SecretKey generateKey(SecureRandom random)
+       {
+               try
+               {
+                       KeyGenerator generator = 
KeyGenerator.getInstance("AES");
+                       generator.init(256, random);
+                       return generator.generateKey();
+               }
+               catch (GeneralSecurityException ex)
+               {
+                       throw new WicketRuntimeException(ex);
+               }
+       }
+
+       @Override
+       public byte[] encrypt(byte[] decrypted, SecretKey key, SecureRandom 
random)
+       {
+               try
+               {
+                       Cipher cipher = getCipher();
+                       cipher.init(Cipher.ENCRYPT_MODE, key, random);
+
+                       AlgorithmParameters params = cipher.getParameters();
+                       byte[] nonce = 
params.getParameterSpec(AEADParameterSpec.class).getNonce();
+                       byte[] ciphertext = cipher.doFinal(decrypted);
+
+                       byte[] encrypted = Arrays.copyOf(nonce, nonce.length + 
ciphertext.length);
+                       System.arraycopy(ciphertext, 0, encrypted, 
nonce.length, ciphertext.length);
+
+                       return encrypted;
+               }
+               catch (GeneralSecurityException ex)
+               {
+                       throw new WicketRuntimeException(ex);
+               }
+       }
+
+       @Override
+       public byte[] decrypt(byte[] encrypted, SecretKey key)
+       {
+               try
+               {
+                       byte[] nonce = new byte[12];
+                       byte[] ciphertext = new byte[encrypted.length - 
nonce.length];
+                       System.arraycopy(encrypted, 0, nonce, 0, nonce.length);
+                       System.arraycopy(encrypted, nonce.length, ciphertext, 
0, ciphertext.length);
+
+                       Cipher cipher = getCipher();
+                       cipher.init(Cipher.DECRYPT_MODE, key, new 
AEADParameterSpec(nonce, 128));
+                       byte[] decrypted = cipher.doFinal(ciphertext);
+
+                       return decrypted;
+               }
+               catch (GeneralSecurityException ex)
+               {
+                       throw new WicketRuntimeException(ex);
+               }
+       }
+}
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 1778055a0a..a6ab0cb5aa 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
@@ -18,9 +18,12 @@ package org.apache.wicket.settings;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.function.Supplier;
 
 import org.apache.wicket.Application;
 import org.apache.wicket.WicketRuntimeException;
+import org.apache.wicket.pageStore.crypt.DefaultCrypter;
+import org.apache.wicket.pageStore.crypt.ICrypter;
 import org.apache.wicket.protocol.http.WebApplication;
 import org.apache.wicket.util.lang.Args;
 import org.apache.wicket.util.lang.Bytes;
@@ -50,6 +53,8 @@ public class StoreSettings
        private boolean asynchronous = true;
        
        private boolean encrypted = false;
+       
+       private Supplier<ICrypter> crypter = DefaultCrypter::new;
 
        /**
         * Construct.
@@ -203,4 +208,28 @@ public class StoreSettings
        {
                return encrypted;
        }
+       
+       /**
+        * Sets the supplier for the {@link ICrypter} used by a
+        * {@link org.apache.wicket.pageStore.CryptingPageStore}.
+        * 
+        * @param crypter
+        *            The new supplier for an {@link ICrypter}.
+        * @return {@code this} object for chaining
+        */
+       public StoreSettings setCrypter(Supplier<ICrypter> crypter)
+       {
+               this.crypter = crypter;
+               return this;
+       }
+
+       /**
+        * @return the supplier used to create a {@link ICrypter} for a
+        *         {@link org.apache.wicket.pageStore.CryptingPageStore}. The 
default is
+        *         {@link DefaultCrypter}.
+        */
+       public Supplier<ICrypter> getCrypter()
+       {
+               return crypter;
+       }
 }
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
index 970f9fee1a..10f4d735f4 100644
--- 
a/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java
+++ 
b/wicket-core/src/test/java/org/apache/wicket/pageStore/CryptingPageStoreTest.java
@@ -21,14 +21,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.io.StreamCorruptedException;
 import java.security.GeneralSecurityException;
+import java.security.Security;
+import java.util.List;
 
 import org.apache.wicket.MockPage;
 import org.apache.wicket.mock.MockPageContext;
 import org.apache.wicket.mock.MockPageStore;
+import org.apache.wicket.pageStore.crypt.DefaultCrypter;
+import org.apache.wicket.pageStore.crypt.GCMSIVCrypter;
+import org.apache.wicket.pageStore.crypt.ICrypter;
 import org.apache.wicket.serialize.java.JavaSerializer;
 import org.apache.wicket.util.tester.WicketTestCase;
-import org.apache.wicket.util.tester.WicketTester;
-import org.junit.jupiter.api.Test;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * Test for {@link CryptingPageStore}.
@@ -37,12 +44,22 @@ import org.junit.jupiter.api.Test;
  */
 public class CryptingPageStoreTest extends WicketTestCase
 {
+       @BeforeAll
+       public static void init()
+       {
+               Security.addProvider(new BouncyCastleProvider());
+       }
+       
+       static List<ICrypter> crypters()
+       {
+               return List.of(new DefaultCrypter(), new GCMSIVCrypter());
+       }
 
-       @Test
-       void test()
+       @ParameterizedTest
+       @MethodSource("crypters")
+       void test(ICrypter crypter)
        {
-               CryptingPageStore store =
-                       new CryptingPageStore(new MockPageStore(), 
tester.getApplication());
+               CryptingPageStore store = buildPageStore(crypter);
                JavaSerializer serializer = new JavaSerializer("test");
 
                IPageContext context = new MockPageContext();
@@ -59,11 +76,11 @@ public class CryptingPageStoreTest extends WicketTestCase
                }
        }
 
-       @Test
-       void testFail()
+       @ParameterizedTest
+       @MethodSource("crypters")
+       void testFail(ICrypter crypter)
        {
-               CryptingPageStore store =
-                       new CryptingPageStore(new MockPageStore(), 
tester.getApplication());
+               CryptingPageStore store = buildPageStore(crypter);
                JavaSerializer serializer = new JavaSerializer("test");
 
                MockPageContext context = new MockPageContext();
@@ -92,4 +109,16 @@ public class CryptingPageStoreTest extends WicketTestCase
                                "unable to decrypt with new key");
                }
        }
+       
+       private CryptingPageStore buildPageStore(ICrypter crypter)
+       {
+               return new CryptingPageStore(new MockPageStore(), 
tester.getApplication())
+               {
+                       @Override
+                       protected ICrypter newCrypter()
+                       {
+                               return crypter;
+                       }
+               };
+       }
 }

Reply via email to