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