[java-support] 16/26: JSPT-116 - Give DataSealer ability to prefix wrapped results

Scott Cantor cantor.2 at osu.edu
Tue Apr 12 14:38:41 UTC 2022


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch dev/JSPT-111
in repository java-support.

View the commit online:
http://git.shibboleth.net/view/?p=java-support.git;a=commit;h=e68b05ef7d062e7f14dca6baec51b8a9e3094975

commit e68b05ef7d062e7f14dca6baec51b8a9e3094975
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Mar 2 13:39:43 2022 -0500

    JSPT-116 - Give DataSealer ability to prefix wrapped results
    
    https://shibboleth.atlassian.net/browse/JSPT-116
---
 .../java/support/security/DataSealer.java          | 53 ++++++++++++++++++++--
 .../java/support/security/DataSealerTest.java      | 37 ++++++++++++---
 2 files changed, 80 insertions(+), 10 deletions(-)

diff --git a/src/main/java/net/shibboleth/utilities/java/support/security/DataSealer.java b/src/main/java/net/shibboleth/utilities/java/support/security/DataSealer.java
index c74d0ec..1440cd7 100644
--- a/src/main/java/net/shibboleth/utilities/java/support/security/DataSealer.java
+++ b/src/main/java/net/shibboleth/utilities/java/support/security/DataSealer.java
@@ -45,6 +45,7 @@ import net.shibboleth.utilities.java.support.component.ComponentInitializationEx
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.logic.ConstraintViolationException;
+import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 import org.apache.commons.codec.BinaryDecoder;
 import org.apache.commons.codec.BinaryEncoder;
@@ -61,6 +62,9 @@ import org.slf4j.LoggerFactory;
  */
 public class DataSealer extends AbstractInitializableComponent {
 
+    /** Magic string to signal use of per-node prefix feature. */
+    @Nonnull @NotEmpty public static final String MAGIC_STRING = "PXR5";
+    
     /** Size of UTF-8 data chunks to read/write. */
     private static final int CHUNK_SIZE = 60000;
     
@@ -82,6 +86,9 @@ public class DataSealer extends AbstractInitializableComponent {
     /** Decodes encrypted string to bytes. */
     @Nonnull private BinaryDecoder decoder;
     
+    /** Optional per-server node prefix to affix to encoded values. */
+    @Nullable private String nodePrefix;
+    
     /** Constructor. */
     public DataSealer() {
         encoder = new Base64(0, new byte[] { '\n' });
@@ -148,6 +155,22 @@ public class DataSealer extends AbstractInitializableComponent {
         
         decoder = Constraint.isNotNull(d, "Decoder cannot be null");
     }
+    
+    /**
+     * Set a prefix to affix to wrapped values to support signaling to load balancers, etc.
+     * 
+     * <p>The prefix will itself be prefixed by {@link #MAGIC_STRING} for compatibility with
+     * unprefixed values.</p>
+     * 
+     * @param prefix node prefix
+     * 
+     * @since 8.3.0
+     */
+    public void setNodePrefix(@Nullable @NotEmpty final String prefix) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        nodePrefix = StringSupport.trimOrNull(prefix);
+    }
 
     /** {@inheritDoc} */
     @Override
@@ -167,7 +190,11 @@ public class DataSealer extends AbstractInitializableComponent {
                 // Before we finish initialization, make sure that things are working.
                 testEncryption(keyStrategy.getDefaultKey().getSecond());
             }
-
+            
+            if (nodePrefix != null) {
+                log.info("DataSealer will attach prefix of {} + {} to wrapped values", MAGIC_STRING, nodePrefix);
+            }
+            
         } catch (final KeyException e) {
             log.error(e.getMessage());
             throw new ComponentInitializationException("Exception loading the keystore", e);
@@ -204,7 +231,17 @@ public class DataSealer extends AbstractInitializableComponent {
         ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
         
         try {
-            final byte[] in = decoder.decode(wrapped.getBytes(StandardCharsets.UTF_8));
+            final byte[] in;
+            if (wrapped.startsWith(MAGIC_STRING)) {
+                if (wrapped.regionMatches(MAGIC_STRING.length(), nodePrefix, 0, nodePrefix.length())) {
+                    in = decoder.decode(wrapped.substring(MAGIC_STRING.length() +
+                            nodePrefix.length()).getBytes(StandardCharsets.UTF_8));
+                } else {
+                    throw new DataSealerException("Data was node-prefixed but prefix did not match the expected value");
+                }
+            } else {
+                in = decoder.decode(wrapped.getBytes(StandardCharsets.UTF_8));
+            }
 
             // Note: we don't technically need try-with-resources here b/c BAIS close() is a no-op
             // and DIS close() just calls close() on the wrapped stream. But do for consistency.
@@ -308,6 +345,7 @@ public class DataSealer extends AbstractInitializableComponent {
         return wrap(data, null);
     }
     
+// Checkstyle: MethodLength OFF
     /**
      * Encodes data into an AEAD-encrypted blob, gzip(exp|data)
      * 
@@ -380,6 +418,14 @@ public class DataSealer extends AbstractInitializableComponent {
                     finalDataStream.flush();
                     finalByteStream.flush();
 
+                    if (nodePrefix != null) {
+                        final StringBuilder builder = new StringBuilder(MAGIC_STRING)
+                                .append(nodePrefix)
+                                .append(new String(encoder.encode(finalByteStream.toByteArray()),
+                                        StandardCharsets.UTF_8));
+                        return builder.toString();
+                    }
+                    
                     return new String(encoder.encode(finalByteStream.toByteArray()), StandardCharsets.UTF_8);
                 }
             }
@@ -390,7 +436,8 @@ public class DataSealer extends AbstractInitializableComponent {
         }
 
     }
-
+// Checkstyle: MethodLength ON
+    
     /**
      * Run a test over the configured bean properties.
      * 
diff --git a/src/test/java/net/shibboleth/utilities/java/support/security/DataSealerTest.java b/src/test/java/net/shibboleth/utilities/java/support/security/DataSealerTest.java
index 1ba3c26..925efed 100644
--- a/src/test/java/net/shibboleth/utilities/java/support/security/DataSealerTest.java
+++ b/src/test/java/net/shibboleth/utilities/java/support/security/DataSealerTest.java
@@ -28,6 +28,7 @@ import java.time.Instant;
 import java.util.Arrays;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import org.springframework.core.io.ClassPathResource;
 import org.testng.Assert;
@@ -67,7 +68,8 @@ public class DataSealerTest {
         version2Resource = TestResourceConverter.of(resource);
     }
 
-    private DataSealer createDataSealer() throws DataSealerException, ComponentInitializationException {
+    private DataSealer createDataSealer(@Nullable @NotEmpty final String nodePrefix)
+            throws DataSealerException, ComponentInitializationException {
         final BasicKeystoreKeyStrategy strategy = new BasicKeystoreKeyStrategy();
         
         strategy.setKeyAlias("secret");
@@ -82,6 +84,7 @@ public class DataSealerTest {
         
         final DataSealer sealer = new DataSealer();
         sealer.setKeyStrategy(strategy);
+        sealer.setNodePrefix(nodePrefix);
         sealer.initialize();
         return sealer;
     }
@@ -106,7 +109,7 @@ public class DataSealerTest {
     }
 
     @Test public void encodeDecode() throws DataSealerException, ComponentInitializationException {
-        final DataSealer sealer = createDataSealer();
+        final DataSealer sealer = createDataSealer(null);
 
         final String encoded = sealer.wrap(THE_DATA);
         final StringBuffer alias = new StringBuffer(); 
@@ -114,8 +117,28 @@ public class DataSealerTest {
         Assert.assertEquals(alias.toString(), "secret1");
     }
 
+
+    @Test public void encodeDecodePrefixed() throws DataSealerException, ComponentInitializationException {
+        final DataSealer sealer = createDataSealer("serverA");
+
+        final String encoded = sealer.wrap(THE_DATA);
+        final StringBuffer alias = new StringBuffer(); 
+        Assert.assertEquals(sealer.unwrap(encoded, alias), THE_DATA);
+        Assert.assertEquals(alias.toString(), "secret1");
+    }
+
+
+    @Test(expectedExceptions=DataSealerException.class)
+    public void encodeDecodePrefixedWrong() throws DataSealerException, ComponentInitializationException {
+        final DataSealer sealer = createDataSealer("serverA");
+
+        final String encoded = sealer.wrap(THE_DATA);
+        final StringBuffer alias = new StringBuffer(); 
+        sealer.unwrap(encoded.replaceFirst("serverA", "serverB"), alias);
+    }
+
     @Test public void encodeDecodeWithExp() throws DataSealerException, ComponentInitializationException {
-        final DataSealer sealer = createDataSealer();
+        final DataSealer sealer = createDataSealer(null);
 
         final String encoded = sealer.wrap(THE_DATA, Instant.now().plusSeconds(50));
         final StringBuffer alias = new StringBuffer(); 
@@ -124,7 +147,7 @@ public class DataSealerTest {
     }
 
     @Test public void encodeDecodeSecondKey() throws DataSealerException, ComponentInitializationException {
-        final DataSealer sealer = createDataSealer();
+        final DataSealer sealer = createDataSealer(null);
         final DataSealer sealer2 = createDataSealer2();
 
         final StringBuffer alias = new StringBuffer(); 
@@ -137,7 +160,7 @@ public class DataSealerTest {
     }
     
     @Test public void timeOut() throws DataSealerException, InterruptedException, ComponentInitializationException {
-        final DataSealer sealer = createDataSealer();
+        final DataSealer sealer = createDataSealer(null);
 
         String encoded = sealer.wrap(THE_DATA, Instant.now().plus(THE_DELAY));
         Thread.sleep(THE_DELAY.toMillis() + 150);
@@ -150,7 +173,7 @@ public class DataSealerTest {
     }
 
     @Test public void encodeDecodeLong() throws DataSealerException, ComponentInitializationException {
-        final DataSealer sealer = createDataSealer();
+        final DataSealer sealer = createDataSealer(null);
         
         char[] buffer = new char[1000000];
         Arrays.fill(buffer, 'x');
@@ -171,7 +194,7 @@ public class DataSealerTest {
 
         }
 
-        sealer = createDataSealer();
+        sealer = createDataSealer(null);
 
         try {
             sealer.unwrap("");

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list