[java-shib-shared] 02/02: JSSH-84 - Base64Support.decode issue with commons-codec 1.23.0

Codeberg noreply at shibboleth.net
Thu Oct 22 13:32:07 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/5b4802814f8b5f3fe6451be5c7220c6e60caac52

commit 5b4802814f8b5f3fe6451be5c7220c6e60caac52
Author: Scott Cantor <scott at restingparrotsoftware.com>
AuthorDate: Thu Oct 22 09:31:28 2026 -0400

    JSSH-84 - Base64Support.decode issue with commons-codec 1.23.0
    
    https://shibboleth.atlassian.net/browse/JSSH-84
    
    Add sanitize workaround for decode based on system property.
---
 .../net/shibboleth/shared/codec/Base64Support.java | 41 ++++++++++++++++++++--
 1 file changed, 39 insertions(+), 2 deletions(-)

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 19c3c99f..08095959 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
@@ -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,15 +31,31 @@ import org.apache.commons.codec.binary.Base64;
  * instances of the {@link Base64} 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 Base64 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 Base64Support {
 
+    /** 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.base64.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 Base64 CHUNKED_ENCODER = new Base64(76, new byte[] { '\n' },
             false, CodecPolicy.STRICT);
@@ -47,11 +64,16 @@ public final class Base64Support {
     @Nonnull private static final Base64 UNCHUNKED_ENCODER = new Base64(0, new byte[] { '\n' },
             false, CodecPolicy.STRICT);
 
+    static {
+        final String prop = System.getProperty(SANITIZE_WHITESPACE_PROPERTY_NAME);
+        sanitize = "1".equals(prop) || "true".equalsIgnoreCase(prop);
+    }
+    
     /** Constructor. */
     private Base64Support() {
 
     }
-
+    
     /**
      * Base64 encodes the given binary data.
      * 
@@ -96,7 +118,22 @@ public final class Base64Support {
     @Nonnull public static byte[] decode(@Nonnull final String data) throws DecodingException{
         Constraint.isNotNull(data, "Base64 encoded data can not 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("Base64 decoded data was null");

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


More information about the commits mailing list