WICKET-6864 updated crypt configuration (#462)
* WICKET-6864 updated crypt configuration
Co-authored-by: Sven Meier <svenmeier@apache.org>
Co-authored-by: Andrea Del Bene <adelbene@apache.org>
diff --git a/wicket-core/src/main/java/org/apache/wicket/authentication/strategy/DefaultAuthenticationStrategy.java b/wicket-core/src/main/java/org/apache/wicket/authentication/strategy/DefaultAuthenticationStrategy.java
index d51efbc..105db6a 100644
--- a/wicket-core/src/main/java/org/apache/wicket/authentication/strategy/DefaultAuthenticationStrategy.java
+++ b/wicket-core/src/main/java/org/apache/wicket/authentication/strategy/DefaultAuthenticationStrategy.java
@@ -16,12 +16,13 @@
*/
package org.apache.wicket.authentication.strategy;
-import org.apache.wicket.Application;
+import java.util.UUID;
+
import org.apache.wicket.authentication.IAuthenticationStrategy;
import org.apache.wicket.util.cookies.CookieDefaults;
import org.apache.wicket.util.cookies.CookieUtils;
-import org.apache.wicket.util.crypt.CachingSunJceCryptFactory;
import org.apache.wicket.util.crypt.ICrypt;
+import org.apache.wicket.util.crypt.SunJceCrypt;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
@@ -30,6 +31,9 @@
/**
* Wicket's default implementation of an authentication strategy. It'll concatenate username and
* password, encrypt it and put it into one Cookie.
+ * <p>
+ * Note: To support automatic authentication across application restarts you have to use
+ * the constructor {@link DefaultAuthenticationStrategy#DefaultAuthenticationStrategy(String, ICrypt)}.
*
* @author Juergen Donnerstag
*/
@@ -40,8 +44,11 @@
/** The cookie name to store the username and password */
protected final String cookieKey;
- /** The key to use for encrypting/decrypting the cookie value */
- protected final String encryptionKey;
+ /**
+ * @deprecated no longer used TODO remove in Wicket 10
+ */
+ @Deprecated(forRemoval = true)
+ protected final String encryptionKey = null;
/** The separator used to concatenate the username and password */
protected final String VALUE_SEPARATOR = "-sep-";
@@ -57,25 +64,51 @@
*
* @param cookieKey
* The name of the cookie
+ *
+ * @deprecated supply a crypt instead TODO remove in Wicket 10
*/
+ @Deprecated(forRemoval = true)
public DefaultAuthenticationStrategy(final String cookieKey)
{
- this(cookieKey, defaultEncryptionKey(cookieKey));
+ this(cookieKey, defaultEncryptionKey());
}
- private static String defaultEncryptionKey(String cookieKey)
+ private static String defaultEncryptionKey()
{
- if (Application.exists())
- {
- return Application.get().getName();
- }
- return cookieKey;
+ return UUID.randomUUID().toString();
}
+ /**
+ * @deprecated supply a crypt instead TODO remove in Wicket 10
+ */
+ @Deprecated(forRemoval = true)
public DefaultAuthenticationStrategy(final String cookieKey, final String encryptionKey)
{
+ this(cookieKey, defaultCrypt(encryptionKey));
+ }
+
+ private static ICrypt defaultCrypt(String encryptionKey)
+ {
+ byte[] salt = SunJceCrypt.randomSalt();
+
+ SunJceCrypt crypt = new SunJceCrypt(salt, 1000);
+ crypt.setKey(encryptionKey);
+ return crypt;
+ }
+
+ /**
+ * This is the recommended constructor to be used, which allows automatic authentication across
+ * application restarts.
+ *
+ * @param cookieKey
+ * The name of the cookie
+ * @param crypt
+ * the crypt
+ */
+ public DefaultAuthenticationStrategy(final String cookieKey, ICrypt crypt)
+ {
this.cookieKey = Args.notEmpty(cookieKey, "cookieKey");
- this.encryptionKey = Args.notEmpty(encryptionKey, "encryptionKey");
+ this.crypt = Args.notNull(crypt, "crypt");
}
/**
@@ -99,11 +132,6 @@
*/
protected ICrypt getCrypt()
{
- if (crypt == null)
- {
- CachingSunJceCryptFactory cryptFactory = new CachingSunJceCryptFactory(encryptionKey);
- crypt = cryptFactory.newCrypt();
- }
return crypt;
}
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AESCrypt.java b/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AESCrypt.java
new file mode 100644
index 0000000..59d1d48
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AESCrypt.java
@@ -0,0 +1,132 @@
+/*
+ * 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.core.util.crypt;
+
+import org.apache.wicket.core.random.ISecureRandomSupplier;
+import org.apache.wicket.util.crypt.ICrypt;
+import org.apache.wicket.util.lang.Args;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+
+/**
+ * AES based {@link ICrypt} encrypt and decrypt strings such as passwords or URL segments.
+ * Based on http://stackoverflow.com/a/992413
+ *
+ * @see ICrypt
+ */
+public class AESCrypt extends AbstractJceCrypt
+{
+
+ private final SecretKey secretKey;
+ private final String algorithm;
+ private final ISecureRandomSupplier randomSupplier;
+
+
+ /**
+ * Constructor
+ *
+ * @param secretKey
+ * The {@link SecretKey} to use to initialize the {@link Cipher}.
+ * @param randomSupplier
+ * The {@link ISecureRandomSupplier} to use to generate random values.
+ */
+ public AESCrypt(SecretKey secretKey, ISecureRandomSupplier randomSupplier)
+ {
+ this(secretKey, "AES/CBC/PKCS5Padding", randomSupplier);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param secretKey
+ * The {@link SecretKey} to use to initialize the {@link Cipher}.
+ * @param algorithm
+ * The cipher algorithm to use, for example "AES/CBC/PKCS5Padding".
+ * @param randomSupplier
+ * The {@link ISecureRandomSupplier} to use to generate random values.
+ */
+ public AESCrypt(SecretKey secretKey, String algorithm,
+ ISecureRandomSupplier randomSupplier)
+ {
+ Args.notNull(secretKey, "secretKey");
+ Args.notNull(algorithm, "algorithm");
+ Args.notNull(randomSupplier, "randomSupplier");
+
+ this.secretKey = secretKey;
+ this.algorithm = algorithm;
+ this.randomSupplier = randomSupplier;
+ }
+
+ @Override
+ protected byte[] decrypt(byte[] encrypted)
+ {
+ try
+ {
+ Cipher cipher = Cipher.getInstance(algorithm);
+
+ int ivSize = cipher.getBlockSize();
+ byte[] iv = new byte[ivSize];
+ byte[] ciphertext = new byte[encrypted.length - ivSize];
+
+ System.arraycopy(encrypted, 0, iv, 0, ivSize);
+ System.arraycopy(encrypted, ivSize, ciphertext, 0, ciphertext.length);
+
+ cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
+ return cipher.doFinal(ciphertext);
+ }
+ catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidKeyException | InvalidAlgorithmParameterException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected byte[] encrypt(byte[] plainBytes)
+ {
+ try
+ {
+ Cipher cipher = Cipher.getInstance(algorithm);
+ int ivSize = cipher.getBlockSize();
+ byte[] iv = randomSupplier.getRandomBytes(ivSize);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
+
+ byte[] ciphertext = cipher.doFinal(plainBytes);
+ byte[] finalRes = new byte[ciphertext.length + ivSize];
+
+ System.arraycopy(iv, 0, finalRes, 0, ivSize);
+ System.arraycopy(ciphertext, 0, finalRes, ivSize, ciphertext.length);
+
+ return finalRes;
+ }
+ catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException
+ | InvalidKeyException | InvalidAlgorithmParameterException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AbstractJceCrypt.java b/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AbstractJceCrypt.java
new file mode 100644
index 0000000..1f8ca90
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AbstractJceCrypt.java
@@ -0,0 +1,101 @@
+/*
+ * 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.core.util.crypt;
+
+import org.apache.wicket.util.crypt.ICrypt;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.util.Base64;
+
+
+/**
+ * Base class for JCE based ICrypt implementations.
+ *
+ */
+public abstract class AbstractJceCrypt implements ICrypt
+{
+ /** Log. */
+ private static final Logger log = LoggerFactory.getLogger(AbstractJceCrypt.class);
+
+ /**
+ * Decrypts a string into a string.
+ *
+ * @param text
+ * text to decrypt
+ * @return the decrypted text
+ */
+ @Override
+ public final String decryptUrlSafe(final String text)
+ {
+ try
+ {
+ byte[] decoded = java.util.Base64.getUrlDecoder().decode(text);
+ return new String(decrypt(decoded), StandardCharsets.UTF_8);
+ }
+ catch (Exception ex)
+ {
+ log.debug("Error decoding text: {}", text, ex);
+ return null;
+ }
+ }
+
+ /**
+ * Encrypt a string into a string using URL safe Base64 encoding.
+ *
+ * @param plainText
+ * text to encrypt
+ * @return encrypted string
+ */
+ @Override
+ public final String encryptUrlSafe(final String plainText)
+ {
+ byte[] encrypted = encrypt(plainText.getBytes(StandardCharsets.UTF_8));
+ Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
+ byte[] encoded = encoder.encode(encrypted);
+ return new String(encoded, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Decrypts an encrypted byte array.
+ *
+ * @param encrypted
+ * byte array to decrypt
+ * @return the decrypted text
+ */
+ abstract protected byte[] decrypt(final byte[] encrypted);
+
+
+ /**
+ * Encrypts the given text into a byte array.
+ *
+ * @param plainText
+ * text to encrypt
+ * @return the string encrypted
+ * @throws GeneralSecurityException
+ */
+ abstract protected byte[] encrypt(final byte[] plainBytes);
+
+
+ @Override
+ final public void setKey(String key)
+ {
+ throw new UnsupportedOperationException("This method has been deprecated in ICrypt and will be removed.");
+ }
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AbstractKeyInSessionCryptFactory.java b/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AbstractKeyInSessionCryptFactory.java
new file mode 100644
index 0000000..b406582
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/AbstractKeyInSessionCryptFactory.java
@@ -0,0 +1,84 @@
+/*
+ * 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.core.util.crypt;
+
+import org.apache.wicket.MetaDataKey;
+import org.apache.wicket.Session;
+import org.apache.wicket.util.crypt.ICrypt;
+import org.apache.wicket.util.crypt.ICryptFactory;
+import org.apache.wicket.util.io.IClusterable;
+
+
+/**
+ * Base class to implement crypt factories that store crypt into user session. Note that the use of
+ * this crypt factory will result in an immediate creation of a http session.
+ *
+ * @author andrea del bene
+ *
+ * @param <T>
+ * the type for the secret key.
+ */
+public abstract class AbstractKeyInSessionCryptFactory<T extends IClusterable>
+ implements
+ ICryptFactory
+{
+ /** metadata-key used to store crypto-key in session metadata */
+ private final MetaDataKey<T> KEY = new MetaDataKey<T>()
+ {
+ private static final long serialVersionUID = 1L;
+ };
+
+ /**
+ * Creates a new crypt for the current user session. If no user session is available, a new one
+ * is created.
+ *
+ * @return
+ */
+ @Override
+ public ICrypt newCrypt()
+ {
+ Session session = Session.get();
+ session.bind();
+
+ // retrieve or generate encryption key from session
+ T key = session.getMetaData(KEY);
+ if (key == null)
+ {
+ // generate new key
+ key = generateKey(session);
+ session.setMetaData(KEY, key);
+ }
+
+ // build the crypt based on session key
+ ICrypt crypt = createCrypt(key);
+ return crypt;
+ }
+
+ /**
+ * Generates the secret key for a new crypt.
+ *
+ * @param session
+ * the current user session where crypt will be stored
+ * @return the secret key for a new crypt
+ */
+ protected abstract T generateKey(Session session);
+
+ /**
+ * @return the {@link org.apache.wicket.util.crypt.ICrypt} to use
+ */
+ protected abstract ICrypt createCrypt(T key);
+}
\ No newline at end of file
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/KeyInSessionSunJceCryptFactory.java b/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/KeyInSessionSunJceCryptFactory.java
index 556c1ff..2ad0592 100644
--- a/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/KeyInSessionSunJceCryptFactory.java
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/crypt/KeyInSessionSunJceCryptFactory.java
@@ -16,17 +16,17 @@
*/
package org.apache.wicket.core.util.crypt;
+import org.apache.wicket.Session;
+import org.apache.wicket.core.util.crypt.KeyInSessionSunJceCryptFactory.CryptData;
+import org.apache.wicket.util.crypt.ICrypt;
+import org.apache.wicket.util.crypt.SunJceCrypt;
+import org.apache.wicket.util.io.IClusterable;
+import org.apache.wicket.util.lang.Args;
+
import java.security.Provider;
import java.security.Security;
import java.util.UUID;
-import org.apache.wicket.MetaDataKey;
-import org.apache.wicket.Session;
-import org.apache.wicket.util.crypt.ICrypt;
-import org.apache.wicket.util.crypt.ICryptFactory;
-import org.apache.wicket.util.crypt.SunJceCrypt;
-import org.apache.wicket.util.lang.Args;
-
/**
* Crypt factory that produces {@link SunJceCrypt} instances based on session-specific
* encryption key. This allows each user to have his own encryption key, hardening against CSRF
@@ -36,13 +36,8 @@
*
* @author igor.vaynberg
*/
-public class KeyInSessionSunJceCryptFactory implements ICryptFactory
+public class KeyInSessionSunJceCryptFactory extends AbstractKeyInSessionCryptFactory<CryptData>
{
- /** metadata-key used to store crypto-key in session metadata */
- private static final MetaDataKey<String> KEY = new MetaDataKey<>()
- {
- private static final long serialVersionUID = 1L;
- };
private final String cryptMethod;
@@ -82,32 +77,50 @@
}
}
- @Override
- public ICrypt newCrypt()
- {
- Session session = Session.get();
- session.bind();
-
- // retrieve or generate encryption key from session
- String key = session.getMetaData(KEY);
- if (key == null)
- {
- // generate new key
- key = session.getId() + "." + UUID.randomUUID().toString();
- session.setMetaData(KEY, key);
- }
-
- // build the crypt based on session key
- ICrypt crypt = createCrypt();
- crypt.setKey(key);
- return crypt;
- }
-
/**
* @return the {@link org.apache.wicket.util.crypt.ICrypt} to use
+ *
+ * @deprecated this method is no longer called TODO remove in Wicket 10
*/
+ @Deprecated(forRemoval = true)
protected ICrypt createCrypt()
{
- return new SunJceCrypt(cryptMethod);
+ return null;
+ }
+
+ @Override
+ protected CryptData generateKey(Session session)
+ {
+ // generate new salt
+ byte[] salt = SunJceCrypt.randomSalt();
+
+ // generate new key
+ String key = session.getId() + "." + UUID.randomUUID().toString();
+
+ return new CryptData(key, salt);
+ }
+
+ @Override
+ protected ICrypt createCrypt(CryptData keyParams)
+ {
+ SunJceCrypt crypt = new SunJceCrypt(cryptMethod, keyParams.salt, 1000);
+ crypt.setKey(keyParams.key);
+
+ return crypt;
+ }
+
+ static final class CryptData implements IClusterable
+ {
+ private static final long serialVersionUID = 1L;
+
+ final String key;
+
+ final byte[] salt;
+
+ CryptData(String key, byte[] salt)
+ {
+ this.key = key;
+ this.salt = salt;
+ }
}
}
diff --git a/wicket-core/src/main/java/org/apache/wicket/settings/SecuritySettings.java b/wicket-core/src/main/java/org/apache/wicket/settings/SecuritySettings.java
index a618ee2..dbcba20 100644
--- a/wicket-core/src/main/java/org/apache/wicket/settings/SecuritySettings.java
+++ b/wicket-core/src/main/java/org/apache/wicket/settings/SecuritySettings.java
@@ -49,8 +49,11 @@
public class SecuritySettings
{
/**
- * encryption key used by default crypt factory
+ * encryption key is no longer used
+ *
+ * @deprecated
*/
+ @Deprecated(forRemoval = true)
public static final String DEFAULT_ENCRYPTION_KEY = "WiCkEt-FRAMEwork";
/** The authorization strategy. */
@@ -121,8 +124,8 @@
}
/**
- * Note: Prints a warning to stderr if no factory was set and {@link #DEFAULT_ENCRYPTION_KEY} is
- * used instead.
+ * Returns the {@link ICryptFactory}. If no factory is set, a {@link KeyInSessionSunJceCryptFactory}
+ * is used.
*
* @return crypt factory used to generate crypt objects
*/
@@ -270,6 +273,7 @@
*
* @return Returns the authentication strategy.
*/
+ @SuppressWarnings("deprecation")
public IAuthenticationStrategy getAuthenticationStrategy()
{
if (authenticationStrategy == null)
diff --git a/wicket-core/src/test/java/org/apache/wicket/core/request/mapper/CryptoMapperTest.java b/wicket-core/src/test/java/org/apache/wicket/core/request/mapper/CryptoMapperTest.java
index 17d2273..3a89b26 100644
--- a/wicket-core/src/test/java/org/apache/wicket/core/request/mapper/CryptoMapperTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/core/request/mapper/CryptoMapperTest.java
@@ -24,8 +24,6 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import java.util.function.Supplier;
-
import org.apache.wicket.MockPage;
import org.apache.wicket.core.request.handler.BookmarkableListenerRequestHandler;
import org.apache.wicket.core.request.handler.ListenerRequestHandler;
@@ -50,10 +48,8 @@
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.UrlResourceReference;
-import org.apache.wicket.settings.SecuritySettings;
-import org.apache.wicket.util.crypt.CachingSunJceCryptFactory;
import org.apache.wicket.util.crypt.ICrypt;
-import org.apache.wicket.util.crypt.ICryptFactory;
+import org.apache.wicket.util.crypt.SunJceCrypt;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.tester.WicketTester;
@@ -100,23 +96,11 @@
WebApplication application = tester.getApplication();
application.mountPage(MOUNTED_URL, Page1.class);
- /**
- * Use explicit crypt provider to prevent crypt warning output, see
- * SecuritySettings#getCryptFactory()
- */
- Supplier<ICrypt> cryptProvider = new Supplier<ICrypt>()
- {
- private ICryptFactory cryptFactory = new CachingSunJceCryptFactory(
- SecuritySettings.DEFAULT_ENCRYPTION_KEY);
+ ICrypt crypt = new SunJceCrypt(new byte[]{ (byte)0x15, (byte)0x8c, (byte)0xa3, (byte)0x4a,
+ (byte)0x66, (byte)0x51, (byte)0x2a, (byte)0xbc }, 17);
+ crypt.setKey("WiCkEt-FRAMEwork");
- @Override
- public ICrypt get()
- {
- return cryptFactory.newCrypt();
- }
- };
-
- mapper = new CryptoMapper(application.getRootRequestMapper(), cryptProvider);
+ mapper = new CryptoMapper(application.getRootRequestMapper(), () -> crypt);
}
/**
diff --git a/wicket-core/src/test/java/org/apache/wicket/core/util/crypt/AESCryptTest.java b/wicket-core/src/test/java/org/apache/wicket/core/util/crypt/AESCryptTest.java
new file mode 100644
index 0000000..c88de9c
--- /dev/null
+++ b/wicket-core/src/test/java/org/apache/wicket/core/util/crypt/AESCryptTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.core.util.crypt;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.wicket.core.random.DefaultSecureRandomSupplier;
+import org.apache.wicket.util.crypt.CipherUtils;
+import org.junit.jupiter.api.Test;
+
+import java.security.GeneralSecurityException;
+
+import javax.crypto.SecretKey;
+
+public class AESCryptTest
+{
+ @Test
+ public void encrypDecrypt() throws GeneralSecurityException
+ {
+ DefaultSecureRandomSupplier randomSupplier = new DefaultSecureRandomSupplier();
+
+ SecretKey secretKey = CipherUtils.generatePBEKey(
+ "myWeakPassword", "PBKDF2WithHmacSHA1", "AES",
+ randomSupplier.getRandomBytes(16), 65536, 256);
+
+ AbstractJceCrypt crypt = new AESCrypt(secretKey, randomSupplier);
+
+ String inputTest = "inputTest";
+ String encrypted = crypt.encryptUrlSafe(inputTest);
+
+ String japFlowerBirdsWindMoon = "花鳥風月";
+ String encrypted2 = crypt.encryptUrlSafe(japFlowerBirdsWindMoon);
+
+ String decrypted = crypt.decryptUrlSafe(encrypted);
+ assertEquals(decrypted, inputTest);
+
+ String decrypted2 = crypt.decryptUrlSafe(encrypted2);
+ assertEquals(decrypted2, japFlowerBirdsWindMoon);
+ }
+}
diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java b/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
index aa083c3..dc85c39 100644
--- a/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
+++ b/wicket-examples/src/main/java/org/apache/wicket/examples/WicketExampleApplication.java
@@ -22,8 +22,6 @@
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.http.WebResponse;
import org.apache.wicket.resource.CssUrlReplacer;
-import org.apache.wicket.settings.SecuritySettings;
-import org.apache.wicket.util.crypt.ClassCryptFactory;
import org.apache.wicket.util.crypt.NoCrypt;
@@ -57,8 +55,7 @@
// has the java security classes required by Crypt installed
// and we want them to be able to run the examples out of the
// box.
- getSecuritySettings().setCryptFactory(
- new ClassCryptFactory(NoCrypt.class, SecuritySettings.DEFAULT_ENCRYPTION_KEY));
+ getSecuritySettings().setCryptFactory(NoCrypt::new);
getDebugSettings().setDevelopmentUtilitiesEnabled(true);
diff --git a/wicket-user-guide/src/main/asciidoc/internals/pagestoring.adoc b/wicket-user-guide/src/main/asciidoc/internals/pagestoring.adoc
index 273a874..4bb85b9 100644
--- a/wicket-user-guide/src/main/asciidoc/internals/pagestoring.adoc
+++ b/wicket-user-guide/src/main/asciidoc/internals/pagestoring.adoc
@@ -9,12 +9,12 @@
=== IPageManager
_org.apache.wicket.page.IPageManager_'s task is to manage which pages have been used in a request and store their last state in the backing stores, namely _IPageStore_.
-The default implementation _org.apache.wicket.page.PageStoreManager_ collects all stateful pages which have been used in the request cycle (more than one page can be used in a single request if for example _setResponsePage()_ or _RestartResponseException_ is used).
-At the end of the request all collected page instances are being stored in the first level cache - http session. They are stored in http session attribute named "_wicket:persistentPageManagerData-APPLICATION_NAME_" and passed to the underlying _IPageStore_.
-When the next http request comes _IPageProvider_ will ask for page with specific id and _PageStoreManager_ will look first in the http session and if no match is found then it will delegate to the IPageStore. At the end of the second request the http session based cache is being overwritten completely with the newly used page instances.
+The default implementation _org.apache.wicket.page.PageManager_ uses a chaing of page stores to collect all stateful pages which have been used in the request cycle (more than one page can be used in a single request if for example _setResponsePage()_ or _RestartResponseException_ is used).
+At the end of the request all collected page instances are being stored in the first level cache - http session. They are stored as metadata in the http session and passed to an underlying _IPageStore_.
+When the next http request is handled, _IPageProvider_ will ask for page with specific id and _PageManager_ will look first in the http session and if no match is found then it will delegate to any further IPageStore. At the end of the second request the http session based cache is being overwritten completely with the newly used page instances.
To setup another _IPageManager_ implementation use _org.apache.wicket.Application.setPageManagerProvider(IPageManagerProvider)_.
-The custom _IPageManager_ implementation may or may not use _IPageStore/IDataStore_.
+The custom _IPageManager_ implementation may use a custom chain of _IPageStore_s as needed.
=== IPageStore
diff --git a/wicket-util/src/main/java/org/apache/wicket/util/crypt/CachingSunJceCryptFactory.java b/wicket-util/src/main/java/org/apache/wicket/util/crypt/CachingSunJceCryptFactory.java
index 65db9e4..8edf89e 100644
--- a/wicket-util/src/main/java/org/apache/wicket/util/crypt/CachingSunJceCryptFactory.java
+++ b/wicket-util/src/main/java/org/apache/wicket/util/crypt/CachingSunJceCryptFactory.java
@@ -21,6 +21,8 @@
* all further invocations of {@link #newCrypt()}.
*
* @author Igor Vaynberg (ivaynberg)
+ *
+ * @deprecated use a lambda expression instead TODO remove in Wicket 10
*/
public class CachingSunJceCryptFactory extends CryptFactoryCachingDecorator
{
diff --git a/wicket-util/src/main/java/org/apache/wicket/util/crypt/CipherUtils.java b/wicket-util/src/main/java/org/apache/wicket/util/crypt/CipherUtils.java
new file mode 100644
index 0000000..3f4cbc8
--- /dev/null
+++ b/wicket-util/src/main/java/org/apache/wicket/util/crypt/CipherUtils.java
@@ -0,0 +1,93 @@
+/*
+ * 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.util.crypt;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Utility class meant to help building {@link Cipher}.
+ */
+public class CipherUtils
+{
+ /**
+ * Generate a new {@link SecretKey} based on the given algorithm and with the given length.
+ *
+ * @param algorithm
+ * the algorithm that will be used to build the key.
+ * @param keyLength
+ * the key length
+ * @return a new {@link SecretKey}
+ */
+ public static SecretKey generateKey(String algorithm, int keyLength, SecureRandom secureRandom)
+ {
+ try
+ {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
+ keyGenerator.init(keyLength, secureRandom);
+ SecretKey key = keyGenerator.generateKey();
+ return key;
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ *
+ *
+ * @param password
+ * the password that will be used to build the key.
+ * @param pbeAlgorithm
+ * the password-based algorithm that will be used to build the key.
+ * @param keyAlgorithm
+ * the algorithm that will be used to build the key.
+ * @param salt
+ * salt for encryption.
+ * @param iterationCount
+ * iteration count.
+ * @param keyLength
+ * the key length.
+ * @return a new {@link SecretKey}
+ */
+ public static SecretKey generatePBEKey(String password, String pbeAlgorithm,
+ String keyAlgorithm, byte[] salt, int iterationCount, int keyLength)
+ {
+ try
+ {
+ SecretKeyFactory factory = SecretKeyFactory.getInstance(pbeAlgorithm);
+ KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength);
+ SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(),
+ keyAlgorithm);
+ return secret;
+ }
+ catch (NoSuchAlgorithmException | InvalidKeySpecException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/wicket-util/src/main/java/org/apache/wicket/util/crypt/ClassCryptFactory.java b/wicket-util/src/main/java/org/apache/wicket/util/crypt/ClassCryptFactory.java
index 5216c3b..e74d758 100644
--- a/wicket-util/src/main/java/org/apache/wicket/util/crypt/ClassCryptFactory.java
+++ b/wicket-util/src/main/java/org/apache/wicket/util/crypt/ClassCryptFactory.java
@@ -28,6 +28,8 @@
* must implement {@link ICrypt}.
*
* @author Igor Vaynberg (ivaynberg)
+ *
+ * @deprecated use a lambda expression instead TODO remove in Wicket 10
*/
public class ClassCryptFactory implements ICryptFactory
{
diff --git a/wicket-util/src/main/java/org/apache/wicket/util/crypt/ICrypt.java b/wicket-util/src/main/java/org/apache/wicket/util/crypt/ICrypt.java
index 7f28ad3..cb90a04 100644
--- a/wicket-util/src/main/java/org/apache/wicket/util/crypt/ICrypt.java
+++ b/wicket-util/src/main/java/org/apache/wicket/util/crypt/ICrypt.java
@@ -61,6 +61,10 @@
*
* @param key
* private key
+ *
+ *
+ * @deprecated TODO remove in Wicket 10
*/
+ @Deprecated(forRemoval = true)
void setKey(final String key);
}
\ No newline at end of file
diff --git a/wicket-util/src/main/java/org/apache/wicket/util/crypt/SunJceCrypt.java b/wicket-util/src/main/java/org/apache/wicket/util/crypt/SunJceCrypt.java
index b702851..09dce34 100644
--- a/wicket-util/src/main/java/org/apache/wicket/util/crypt/SunJceCrypt.java
+++ b/wicket-util/src/main/java/org/apache/wicket/util/crypt/SunJceCrypt.java
@@ -21,6 +21,7 @@
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
+import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
@@ -44,39 +45,79 @@
/**
* Iteration count used in combination with the salt to create the encryption key.
*/
- private final static int COUNT = 17;
+ private final static int DEFAULT_ITERATION_COUNT = 17;
/** Name of the default encryption method */
public static final String DEFAULT_CRYPT_METHOD = "PBEWithMD5AndDES";
- /** Salt */
+ /**
+ * Default salt.
+ *
+ * @deprecated TODO remove in Wicket 10
+ */
+ @Deprecated
public final static byte[] SALT = { (byte)0x15, (byte)0x8c, (byte)0xa3, (byte)0x4a,
(byte)0x66, (byte)0x51, (byte)0x2a, (byte)0xbc };
- private static final PBEParameterSpec PARAMETER_SPEC = new PBEParameterSpec(SALT, COUNT);
-
/** The name of encryption method (cipher) */
private final String cryptMethod;
-
+
+ private final int iterationCount;
+
+ private final byte[] salt;
+
/**
* Constructor
+ *
+ * @deprecated TODO remove in Wicket 10
*/
+ @Deprecated(forRemoval = true)
public SunJceCrypt()
{
this(DEFAULT_CRYPT_METHOD);
}
/**
+ * Constructor.
+ *
+ * @param salt
+ * salt for encryption
+ * @param iterationCount
+ * iteration count
+ */
+ public SunJceCrypt(byte[] salt, int iterationCount)
+ {
+ this(DEFAULT_CRYPT_METHOD, salt, iterationCount);
+ }
+
+ /**
+ * Constructor
+ *
+ * @deprecated TODO remove in Wicket 10
+ */
+ @Deprecated(forRemoval = true)
+ public SunJceCrypt(String cryptMethod)
+ {
+ this(cryptMethod, SALT, DEFAULT_ITERATION_COUNT);
+ }
+
+ /**
* Constructor that uses a custom encryption method (cipher).
* You may need to override {@link #createKeySpec()} and/or
* {@link #createParameterSpec()} for the custom cipher.
*
* @param cryptMethod
* the name of encryption method (the cipher)
+ * @param salt
+ * salt for encryption
+ * @param iterationCount
+ * iteration count
*/
- public SunJceCrypt(String cryptMethod)
+ public SunJceCrypt(String cryptMethod, byte[] salt, int iterationCount)
{
this.cryptMethod = Args.notNull(cryptMethod, "Crypt method");
+ this.salt = Args.notNull(salt, "salt");
+ this.iterationCount = Args.withinRange(1, Integer.MAX_VALUE, iterationCount, "iterationCount");
}
/**
@@ -137,13 +178,13 @@
KeySpec spec = createKeySpec();
return keyFactory.generateSecret(spec);
}
-
+
/**
* @return the parameter spec to be used for the configured crypt method
*/
protected AlgorithmParameterSpec createParameterSpec()
{
- return PARAMETER_SPEC;
+ return new PBEParameterSpec(salt, iterationCount);
}
/**
@@ -153,4 +194,18 @@
{
return new PBEKeySpec(getKey().toCharArray());
}
+
+ /**
+ * Create a random salt to be used for this crypt.
+ *
+ * @return salt, always 8 bytes long
+ */
+ public static byte[] randomSalt()
+ {
+ // must be 8 bytes - for anything else PBES1Core throws
+ // InvalidAlgorithmParameterException: Salt must be 8 bytes long
+ byte[] salt = new byte[8];
+ new Random().nextBytes(salt);
+ return salt;
+ }
}
diff --git a/wicket-util/src/test/java/org/apache/wicket/util/crypt/SunJceCryptTest.java b/wicket-util/src/test/java/org/apache/wicket/util/crypt/SunJceCryptTest.java
index fdd3164..a4b0c39 100644
--- a/wicket-util/src/test/java/org/apache/wicket/util/crypt/SunJceCryptTest.java
+++ b/wicket-util/src/test/java/org/apache/wicket/util/crypt/SunJceCryptTest.java
@@ -16,14 +16,15 @@
*/
package org.apache.wicket.util.crypt;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
-import javax.crypto.Cipher;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import javax.crypto.Cipher;
@SuppressWarnings("javadoc")
public class SunJceCryptTest
@@ -34,6 +35,7 @@
@Test
public void defaultEncryption() throws GeneralSecurityException
{
+ @SuppressWarnings("deprecation")
SunJceCrypt crypt = new SunJceCrypt();
String input = "input";
byte[] encrypted = crypt.crypt(input.getBytes(), Cipher.ENCRYPT_MODE);
@@ -51,6 +53,7 @@
boolean unlimitedStrengthJurisdictionPolicyInstalled = isUnlimitedStrengthJurisdictionPolicyInstalled();
Assumptions.assumeTrue(unlimitedStrengthJurisdictionPolicyInstalled);
+ @SuppressWarnings("deprecation")
SunJceCrypt crypt = new SunJceCrypt("PBEWithMD5AndTripleDES");
String input = "input";
byte[] encrypted = crypt.crypt(input.getBytes(), Cipher.ENCRYPT_MODE);
@@ -66,7 +69,7 @@
* @return {@code true} if Unlimited Strength Jurisdiction Policy is installed
* @throws NoSuchAlgorithmException
*/
- static boolean isUnlimitedStrengthJurisdictionPolicyInstalled() throws NoSuchAlgorithmException
+ public static boolean isUnlimitedStrengthJurisdictionPolicyInstalled() throws NoSuchAlgorithmException
{
return Cipher.getMaxAllowedKeyLength("AES") == Integer.MAX_VALUE;
}
diff --git a/wicket-util/src/test/java/org/apache/wicket/util/crypt/UnlimitedStrengthJurisdictionPolicyTest.java b/wicket-util/src/test/java/org/apache/wicket/util/crypt/UnlimitedStrengthJurisdictionPolicyTest.java
deleted file mode 100644
index f36e4b8..0000000
--- a/wicket-util/src/test/java/org/apache/wicket/util/crypt/UnlimitedStrengthJurisdictionPolicyTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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.util.crypt;
-
-import org.junit.jupiter.api.Assumptions;
-import org.junit.jupiter.api.Test;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.PBEKeySpec;
-import javax.crypto.spec.SecretKeySpec;
-import java.security.AlgorithmParameters;
-import java.security.GeneralSecurityException;
-import java.security.spec.KeySpec;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-/**
- * A demo how to create {@link org.apache.wicket.util.crypt.ICrypt} implementation that
- * uses <em>PBKDF2WithHmacSHA1</em> for encryption
- */
-@SuppressWarnings("javadoc")
-public class UnlimitedStrengthJurisdictionPolicyTest
-{
- @Test
- public void unlimitedStrengthJurisdictionEncryption() throws GeneralSecurityException
- {
- boolean unlimitedStrengthJurisdictionPolicyInstalled = SunJceCryptTest.isUnlimitedStrengthJurisdictionPolicyInstalled();
- Assumptions.assumeTrue(unlimitedStrengthJurisdictionPolicyInstalled);
-
- AbstractCrypt crypt = new UnlimitedStrenghtJurisdictionPolicyCrypt();
-
- String input1 = "input1";
- byte[] encrypted = crypt.crypt(input1.getBytes(), Cipher.ENCRYPT_MODE);
-
- String input2 = "input2";
- byte[] encrypted2 = crypt.crypt(input2.getBytes(), Cipher.ENCRYPT_MODE);
-
- byte[] decrypted = crypt.crypt(encrypted, Cipher.DECRYPT_MODE);
- assertEquals(new String(decrypted), input1);
-
- byte[] decrypted2 = crypt.crypt(encrypted2, Cipher.DECRYPT_MODE);
- assertEquals(new String(decrypted2),input2);
- }
-
- /**
- * Based on http://stackoverflow.com/a/992413
- */
- private static class UnlimitedStrenghtJurisdictionPolicyCrypt extends AbstractCrypt
- {
- private final Cipher crypter;
- private final Cipher decrypter;
-
- private UnlimitedStrenghtJurisdictionPolicyCrypt() throws GeneralSecurityException
- {
- SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
- KeySpec spec = new PBEKeySpec(getKey().toCharArray(), SunJceCrypt.SALT, 65536, 256);
- SecretKey tmp = factory.generateSecret(spec);
- SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
-
- String transformation = "AES/CBC/PKCS5Padding";
- crypter = Cipher.getInstance(transformation);
- crypter.init(Cipher.ENCRYPT_MODE, secret);
- AlgorithmParameters params = crypter.getParameters();
- byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
-
- decrypter = Cipher.getInstance(transformation);
- decrypter.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
- }
-
- @Override
- protected byte[] crypt(byte[] input, int mode) throws GeneralSecurityException
- {
- byte[] result;
- switch (mode)
- {
- case Cipher.ENCRYPT_MODE:
- result = crypter.doFinal(input);
- break;
- case Cipher.DECRYPT_MODE:
- result = decrypter.doFinal(input);
- break;
- default:
- throw new RuntimeException("Wrong crypt mode: " + mode);
- }
- return result;
- }
- }
-}