You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/03/22 11:23:43 UTC
[isis] branch master updated: ISIS-2981: adds special crypto code to handle prototyping scenario
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/master by this push:
new 658dd90 ISIS-2981: adds special crypto code to handle prototyping scenario
658dd90 is described below
commit 658dd9032a995d8be611abde444478defd29f912
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Mar 22 12:23:34 2022 +0100
ISIS-2981: adds special crypto code to handle prototyping scenario
---
.../apache/isis/commons/internal/os/_OsUtil.java | 37 +++++++++++++
.../viewer/wicketapp/IsisWicketApplication.java | 3 +-
.../wicket/viewer/wicketapp/_CryptFactory.java | 49 ++++++++++++++++--
.../wicket/viewer/wicketapp/CryptFactoryTest.java | 60 ++++++++++++++++++----
4 files changed, 136 insertions(+), 13 deletions(-)
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/os/_OsUtil.java b/commons/src/main/java/org/apache/isis/commons/internal/os/_OsUtil.java
index 330f977..0f534ab 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/os/_OsUtil.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/os/_OsUtil.java
@@ -20,10 +20,14 @@ package org.apache.isis.commons.internal.os;
import java.io.File;
import java.io.FileWriter;
+import java.net.NetworkInterface;
import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.OptionalLong;
import org.springframework.lang.Nullable;
+import org.apache.isis.commons.internal.base._NullSafe;
import org.apache.isis.commons.internal.base._Strings;
import org.apache.isis.commons.internal.base._Text;
import org.apache.isis.commons.internal.exceptions._Exceptions;
@@ -124,4 +128,37 @@ public class _OsUtil {
rt.exec(cmd);
}
+ /**
+ * Optionally returns a machine specific unique number, based on whether
+ * the algorithm was able to generate one.
+ */
+ public OptionalLong machineId() {
+ try {
+
+ long hash = 5381L;
+ boolean valid = false;
+
+ for (NetworkInterface netint : Collections.list(NetworkInterface.getNetworkInterfaces())) {
+ val hwAddr = netint.getHardwareAddress();
+ if(_NullSafe.size(hwAddr)<6) {
+ continue;
+ }
+ for(byte b:hwAddr) {
+ hash = hash*33L + b;
+ }
+ valid = true;
+ }
+
+ if(valid) {
+ return OptionalLong.of(hash);
+ }
+
+ // fallback to empty
+
+ } catch (Throwable e) {
+ // fallback to empty
+ }
+ return OptionalLong.empty();
+ }
+
}
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/IsisWicketApplication.java b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/IsisWicketApplication.java
index e744f4e..718f655 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/IsisWicketApplication.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/IsisWicketApplication.java
@@ -336,6 +336,7 @@ implements
val rememberMe = configuration.getViewer().getWicket().getRememberMe();
val cookieKey = rememberMe.getCookieKey();
val encryptionKey = rememberMe.getEncryptionKey().orElse(defaultEncryptionKey());
+ System.err.printf("encryptionKey: %s%n", encryptionKey);
return new DefaultAuthenticationStrategy(cookieKey, _CryptFactory.sunJceCrypt(encryptionKey));
}
@@ -347,7 +348,7 @@ implements
*/
String defaultEncryptionKey() {
return systemEnvironment.isPrototyping()
- ? "PrototypingEncryptionKey"
+ ? _CryptFactory.FIXED_SALT_FOR_PROTOTYPING
: UUID.randomUUID().toString();
}
diff --git a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/_CryptFactory.java b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/_CryptFactory.java
index cbd27a5..2694371 100644
--- a/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/_CryptFactory.java
+++ b/viewers/wicket/viewer/src/main/java/org/apache/isis/viewer/wicket/viewer/wicketapp/_CryptFactory.java
@@ -19,6 +19,7 @@
package org.apache.isis.viewer.wicket.viewer.wicketapp;
import java.security.spec.KeySpec;
+import java.util.Random;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
@@ -29,8 +30,11 @@ import org.apache.wicket.core.random.DefaultSecureRandomSupplier;
import org.apache.wicket.core.random.ISecureRandomSupplier;
import org.apache.wicket.core.util.crypt.AESCrypt;
import org.apache.wicket.util.crypt.ICrypt;
+import org.apache.wicket.util.crypt.NoCrypt;
import org.apache.wicket.util.crypt.SunJceCrypt;
+import org.apache.isis.commons.internal.os._OsUtil;
+
import lombok.SneakyThrows;
import lombok.val;
import lombok.experimental.UtilityClass;
@@ -38,8 +42,10 @@ import lombok.experimental.UtilityClass;
@UtilityClass
class _CryptFactory {
+ static final String FIXED_SALT_FOR_PROTOTYPING = "PrototypingEncryptionKey";
+
ICrypt sunJceCrypt(final String encryptionKey) {
- final byte[] salt = SunJceCrypt.randomSalt();
+ final byte[] salt = getSalt(8, encryptionKey);
val crypt = new SunJceCrypt(salt, 1000);
crypt.setKey(encryptionKey);
return crypt;
@@ -48,7 +54,7 @@ class _CryptFactory {
@SneakyThrows
ICrypt aesCrypt(final String encryptionKey) {
- final byte[] salt = SunJceCrypt.randomSalt();
+ final byte[] salt = getSalt(8, encryptionKey);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(encryptionKey.toCharArray(), salt, 65536, 256);
@@ -60,6 +66,43 @@ class _CryptFactory {
return new AESCrypt(secret, rGen);
}
- //XXX BCryptPasswordEncoder (Spring);
+ ICrypt noCrypt(final String encryptionKey) {
+ return new NoCrypt();
+ }
+
+ //XXX what about BCryptPasswordEncoder (Spring);
+
+ // -- HELPER
+
+ /**
+ * @param size
+ * must be 8 bytes - for anything else PBES1Core throws
+ * InvalidAlgorithmParameterException: Salt must be 8 bytes long
+ */
+ private byte[] getSalt(final int size, final String encryptionKey) {
+ final byte[] salt = FIXED_SALT_FOR_PROTOTYPING.equals(encryptionKey)
+ ? machineFixedSalt(size)
+ : randomSalt(size);
+ return salt;
+ }
+
+ /**
+ * cloned from {@link SunJceCrypt#randomSalt()}
+ */
+ private byte[] randomSalt(final int size) {
+ val salt = new byte[size];
+ new Random().nextBytes(salt);
+ return salt;
+ }
+
+ private byte[] machineFixedSalt(final int size) {
+ val machineFixedSeed = _OsUtil.machineId();
+ if(machineFixedSeed.isEmpty()){
+ return randomSalt(size);
+ }
+ val salt = new byte[size];
+ new Random(machineFixedSeed.getAsLong()).nextBytes(salt);
+ return salt;
+ }
}
diff --git a/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/wicketapp/CryptFactoryTest.java b/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/wicketapp/CryptFactoryTest.java
index 65dc4d0..e63cef1 100644
--- a/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/wicketapp/CryptFactoryTest.java
+++ b/viewers/wicket/viewer/src/test/java/org/apache/isis/viewer/wicket/viewer/wicketapp/CryptFactoryTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.isis.viewer.wicket.viewer.wicketapp;
+import java.util.function.Function;
import java.util.stream.Stream;
import javax.servlet.http.Cookie;
@@ -41,6 +42,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
import lombok.val;
@@ -84,16 +86,49 @@ class CryptFactoryTest {
@ParameterizedTest(name = "{index} {0}")
@MethodSource("provideCryptoCandidates")
- void authenticationStrategyRoundtrip(
+ void authenticationStrategyRoundtrip_whenProduction(
final String displayName,
- final ICrypt crypto) {
+ final Function<String, ICrypt> cryptFactory) {
+
+ val encryptionKey = "any other than FIXED_SALT_FOR_PROTOTYPING";
+
+ val strategy =
+ new DefaultAuthenticationStrategy("cookieKey", cryptFactory.apply(encryptionKey));
+ strategy.save("hello", "world", "appendix");
+
+ val data = strategy.load();
+
+ assertNotNull(data);
+ assertEquals(2, data.length);
+ assertEquals("hello", data[0]);
+ assertEquals("world", data[1]);
+
+ // simulated application restart
+
+ val strategy2 =
+ new DefaultAuthenticationStrategy("cookieKey", cryptFactory.apply(encryptionKey));
+ val data2 = strategy2.load();
+
+ assertNull(data2);
+
+ }
+
+ @ParameterizedTest(name = "{index} {0}")
+ @MethodSource("provideCryptoCandidates")
+ void authenticationStrategyRoundtrip_whenPrototyping(
+ final String displayName,
+ final Function<String, ICrypt> cryptFactory) {
+
+ val encryptionKey = _CryptFactory.FIXED_SALT_FOR_PROTOTYPING;
val strategy1 =
- new DefaultAuthenticationStrategy("cookieKey", crypto);
+ new DefaultAuthenticationStrategy("cookieKey", cryptFactory.apply(encryptionKey));
strategy1.save("hello", "world", "appendix");
+ // simulated application restart
+
val strategy2 =
- new DefaultAuthenticationStrategy("cookieKey", crypto);
+ new DefaultAuthenticationStrategy("cookieKey", cryptFactory.apply(encryptionKey));
val data = strategy2.load();
assertNotNull(data);
@@ -102,17 +137,24 @@ class CryptFactoryTest {
assertEquals("world", data[1]);
}
- private static Stream<Arguments> provideCryptoCandidates() {
+ // -- HELPER
+ private static Stream<Arguments> provideCryptoCandidates() {
return Stream.of(
- Arguments.of(
+ scenario(
"sunJceCrypt",
- _CryptFactory.sunJceCrypt("encryptionKey")),
- Arguments.of(
+ _CryptFactory::sunJceCrypt),
+ scenario(
"aesCrypt",
- _CryptFactory.aesCrypt("encryptionKey"))
+ _CryptFactory::aesCrypt)
);
}
+ private static Arguments scenario(
+ final String displayName,
+ final Function<String, ICrypt> cryptFactory) {
+ return Arguments.of(
+ displayName, cryptFactory);
+ }
}