[java-shib-shared] branch main updated: JSSH-84 - Base64Support decode rejects input with commons-codec 1.23.0

Codeberg noreply at shibboleth.net
Thu Oct 22 15:38:10 UTC 2026


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

codeberg pushed a commit to branch main
in repository java-shib-shared.

View the commit online:
https://codeberg.org/Shibboleth/java-shib-shared/commit/d7d970c8c8eb1d910916cf68c3e7219945ac21c7

The following commit(s) were added to refs/heads/main by this push:
     new d7d970c8 JSSH-84 - Base64Support decode rejects input with commons-codec 1.23.0
d7d970c8 is described below

commit d7d970c8c8eb1d910916cf68c3e7219945ac21c7
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Thu Oct 22 11:37:58 2026 -0400

    JSSH-84 - Base64Support decode rejects input with commons-codec 1.23.0
    
    https://shibboleth.atlassian.net/browse/JSSH-84
    
    Add parallel workaround to Base32Support class.
---
 .../net/shibboleth/shared/codec/Base32Support.java | 43 ++++++++++++++++++++--
 .../net/shibboleth/shared/codec/Base64Support.java |  4 +-
 .../shibboleth/shared/codec/Base32SupportTest.java |  9 ++++-
 3 files changed, 50 insertions(+), 6 deletions(-)

diff --git a/shib-support/src/main/java/net/shibboleth/shared/codec/Base32Support.java b/shib-support/src/main/java/net/shibboleth/shared/codec/Base32Support.java
index 242ac253..90427892 100644
--- a/shib-support/src/main/java/net/shibboleth/shared/codec/Base32Support.java
+++ b/shib-support/src/main/java/net/shibboleth/shared/codec/Base32Support.java
@@ -16,6 +16,7 @@ package net.shibboleth.shared.codec;
 
 import javax.annotation.Nonnull;
 
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.StringSupport;
 
@@ -30,14 +31,30 @@ import org.apache.commons.codec.binary.Base32;
  * instances of the {@link Base32} for every operation. It also provides the helper method to produce both chunked and
  * unchunked encoded content as strings.
  * </p>
+ * 
+ * <p>Most importantly this insulates calling code so we can swap in a different Base32 implementation, as commons-codec
+ * and many if not most others have a very inconvenient stance on whitespace, which is absolutely necessary to ignore, so
+ * changes to the implementation are likely to continue.</p>
+ * 
+ * <p>As of 9.3, a new system property has been introduced ({@link #SANITIZE_WHITESPACE_PROPERTY_NAME}) to enable
+ * compatibility with newer commons-codec versions being used under this library at the cost of a whitespace removal scan.
+ * We do not support use of the library with that version, but the property provides a workaround for that case without
+ * incurring additional overhead for the project's own use. A future major update may obviate the need for that workaround.</p>
  */
 public final class Base32Support {
 
+    /** System property name to enable whitespace sanitization on decode for commons-codec 1.23 compatibility. */
+    @Nonnull @NotEmpty public static final String SANITIZE_WHITESPACE_PROPERTY_NAME =
+            "net.shibboleth.shared.codec.base32.sanitizeWhitespace";
+    
     /** Chunk the encoded data into 76-character lines broken by CRLF characters. */
     public static final boolean CHUNKED = true;
 
     /** Do not chunk encoded data. */
     public static final boolean UNCHUNKED = false;
+    
+    /** Triggers whitespace sanitization of input when decoding, per JSSH-84. */
+    public static final boolean sanitize;
 
     /** Encoder used to produce chunked output. */
     @Nonnull private static final Base32 CHUNKED_ENCODER = new Base32(76, new byte[] { '\n' },
@@ -47,6 +64,11 @@ public final class Base32Support {
     @Nonnull private static final Base32 UNCHUNKED_ENCODER = new Base32(0, new byte[] { '\n' },
             false, (byte)'=', CodecPolicy.STRICT);
 
+    static {
+        final String prop = System.getProperty(SANITIZE_WHITESPACE_PROPERTY_NAME);
+        sanitize = "1".equals(prop) || "true".equalsIgnoreCase(prop);
+    }
+    
     /** Constructor. */
     private Base32Support() {
 
@@ -63,7 +85,7 @@ public final class Base32Support {
      *                                  or the output is null.
      */
     @Nonnull public static String encode(@Nonnull final byte[] data, final boolean chunked) throws EncodingException {
-        Constraint.isNotNull(data, "Binary data to be encoded can not be null");
+        Constraint.isNotNull(data, "Binary data to be encoded cannot be null");
         
         try {
             String encoded = null;
@@ -94,9 +116,24 @@ public final class Base32Support {
      *                              the underlying decoder, or the output is null.
      */
     @Nonnull public static byte[] decode(@Nonnull final String data) throws DecodingException {
-        Constraint.isNotNull(data, "Base32 encoded data can not be null");
+        Constraint.isNotNull(data, "Base32 encoded data cannot be null");
         try {
-            final byte[] decoded = CHUNKED_ENCODER.decode(data);
+            final byte[] decoded;
+            if (sanitize) {
+                // This is per JSSH-84, to provide compatibility when third parties use
+                // commons-codec >= 1.23 with this library (though not supported by us).
+                // The STRICT unchunked encoder "works" but will reject whitespace so a
+                // regex is applied to remove any from the input first.
+                decoded = UNCHUNKED_ENCODER.decode(data.replaceAll("\\s+", ""));
+            } else {
+                // This probably should be using the unchunked encoder, but in order to
+                // prevent any unexpected changes when using the existing, supported
+                // commons-codec version, I'm leaving it as is until such time as we
+                // replace the implementation outright to control the behavior.
+                // The older decoder even in STRICT mode ignored whitespace (as it
+                // should), and tolerated the trim() our encode methods perform.
+                decoded = CHUNKED_ENCODER.decode(data);
+            }
             //TODO: can this ever be null, do we need to check for null?
             if (null == decoded) {
                 throw new DecodingException("Base32 decoded data was null");
diff --git a/shib-support/src/main/java/net/shibboleth/shared/codec/Base64Support.java b/shib-support/src/main/java/net/shibboleth/shared/codec/Base64Support.java
index 08095959..4ec502a9 100644
--- a/shib-support/src/main/java/net/shibboleth/shared/codec/Base64Support.java
+++ b/shib-support/src/main/java/net/shibboleth/shared/codec/Base64Support.java
@@ -85,7 +85,7 @@ public final class Base64Support {
      *                                  or the output is null.
      */
     @Nonnull public static String encode(@Nonnull final byte[] data, final boolean chunked) throws EncodingException{
-        Constraint.isNotNull(data, "Binary data to be encoded can not be null");
+        Constraint.isNotNull(data, "Binary data to be encoded cannot be null");
         
         try {
             String encoded = null;
@@ -116,7 +116,7 @@ public final class Base64Support {
      *                              the underlying decoder, or the output is null.
      */
     @Nonnull public static byte[] decode(@Nonnull final String data) throws DecodingException{
-        Constraint.isNotNull(data, "Base64 encoded data can not be null");
+        Constraint.isNotNull(data, "Base64 encoded data cannot be null");
         try {
             final byte[] decoded;
             if (sanitize) {
diff --git a/shib-support/src/test/java/net/shibboleth/shared/codec/Base32SupportTest.java b/shib-support/src/test/java/net/shibboleth/shared/codec/Base32SupportTest.java
index c824b6fc..acf161d2 100644
--- a/shib-support/src/test/java/net/shibboleth/shared/codec/Base32SupportTest.java
+++ b/shib-support/src/test/java/net/shibboleth/shared/codec/Base32SupportTest.java
@@ -37,7 +37,6 @@ public class Base32SupportTest {
     /** Invalid base32, should not produce a result.*/
     private final static String INVALID_BASE32="MC======";
     
-    
     /**
      * Test that an invalid base32 input string does not return a response, instead throwing a {@link DecodingException}. 
      * 
@@ -68,4 +67,12 @@ public class Base32SupportTest {
         Assert.assertEquals(ENCODED_TEXT,  Base32Support.encode(PLAIN_TEXT.getBytes(), false));
     }
   
+    /** Test decoding content. 
+     *  
+     * @throws DecodingException on decoding failure.
+     */
+    @Test public void testDecode() throws DecodingException {
+        Assert.assertEquals(new String(Base32Support.decode(ENCODED_TEXT)), PLAIN_TEXT);
+    }
+
 }
\ No newline at end of file

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


More information about the commits mailing list