This is an automated email from the ASF dual-hosted git repository.

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new 67d1cef188 Reject BASIC authorization header with null password
67d1cef188 is described below

commit 67d1cef1887659c60c2804e153078c0edfaca2f3
Author: remm <[email protected]>
AuthorDate: Wed Jun 24 21:57:08 2026 +0200

    Reject BASIC authorization header with null password
    
    RFC 7617 mandates that a ':' must be present.
    As there does not seem to be any adverse effects, I will leave it as
    Tomcat 12 only.
---
 .../catalina/authenticator/BasicAuthenticator.java     | 10 ++++------
 .../catalina/authenticator/LocalStrings.properties     |  1 +
 .../catalina/authenticator/TestBasicAuthParser.java    | 18 ++++++++----------
 webapps/docs/changelog.xml                             |  4 ++++
 4 files changed, 17 insertions(+), 16 deletions(-)

diff --git a/java/org/apache/catalina/authenticator/BasicAuthenticator.java 
b/java/org/apache/catalina/authenticator/BasicAuthenticator.java
index a22d5779f4..eb93d5e4f8 100644
--- a/java/org/apache/catalina/authenticator/BasicAuthenticator.java
+++ b/java/org/apache/catalina/authenticator/BasicAuthenticator.java
@@ -188,8 +188,7 @@ public class BasicAuthenticator extends AuthenticatorBase {
         /**
          * Trivial accessor.
          *
-         * @return the decoded password token as a String, or 
<code>null</code> if no password was found in the
-         *             credentials.
+         * @return the decoded password token as a String, which is never 
<code>null</code>, but can be empty.
          */
         public String getPassword() {
             return password;
@@ -228,7 +227,7 @@ public class BasicAuthenticator extends AuthenticatorBase {
         }
 
         /*
-         * Extract the mandatory username token and separate it from the 
optional password token. Tolerate surplus
+         * Extract the mandatory username and password tokens separated by a 
colon. Tolerate surplus
          * surrounding white space.
          */
         private void parseCredentials(byte[] decoded) throws 
IllegalArgumentException {
@@ -241,10 +240,9 @@ public class BasicAuthenticator extends AuthenticatorBase {
                 }
             }
 
-            // Tomcat allows a null password
+            // Null password is not allowed according to RFC 7617
             if (colon < 0) {
-                username = new String(decoded, charset);
-                // password will remain null!
+                throw new 
IllegalArgumentException(sm.getString("basicAuthenticator.noColon"));
             } else {
                 username = new String(decoded, 0, colon, charset);
                 password = new String(decoded, colon + 1, decoded.length - 
colon - 1, charset);
diff --git a/java/org/apache/catalina/authenticator/LocalStrings.properties 
b/java/org/apache/catalina/authenticator/LocalStrings.properties
index b1f1e68c89..22ba239883 100644
--- a/java/org/apache/catalina/authenticator/LocalStrings.properties
+++ b/java/org/apache/catalina/authenticator/LocalStrings.properties
@@ -41,6 +41,7 @@ authenticator.userPermissionFail=User [{0}] does not have 
authorization to acces
 
 basicAuthenticator.invalidAuthorization=Invalid Authorization header
 basicAuthenticator.invalidCharset=The only permitted values are null, the 
empty string or UTF-8
+basicAuthenticator.noColon=Basic Authorization credentials do not contain a 
colon
 basicAuthenticator.notBase64=Basic Authorization credentials are not Base64
 basicAuthenticator.notBasic=Authorization header method is not ''Basic''
 
diff --git a/test/org/apache/catalina/authenticator/TestBasicAuthParser.java 
b/test/org/apache/catalina/authenticator/TestBasicAuthParser.java
index feb95f0f77..9d3e6aba92 100644
--- a/test/org/apache/catalina/authenticator/TestBasicAuthParser.java
+++ b/test/org/apache/catalina/authenticator/TestBasicAuthParser.java
@@ -46,13 +46,12 @@ public class TestBasicAuthParser {
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
 
-    @Test
-    public void testGoodCredentialsNoPassword() throws Exception {
+    @Test(expected = IllegalArgumentException.class)
+    public void testBadCredentialsNoPassword() throws Exception {
         final BasicAuthHeader AUTH_HEADER = new BasicAuthHeader(NICE_METHOD, 
USER_NAME, null);
+        @SuppressWarnings("unused")
         BasicAuthenticator.BasicCredentials credentials =
                 new 
BasicAuthenticator.BasicCredentials(AUTH_HEADER.getHeader(), 
StandardCharsets.UTF_8);
-        Assert.assertEquals(USER_NAME, credentials.getUsername());
-        Assert.assertNull(credentials.getPassword());
     }
 
     @Test
@@ -65,14 +64,13 @@ public class TestBasicAuthParser {
         Assert.assertEquals(PASSWORD, credentials.getPassword());
     }
 
-    @Test
-    public void testGoodCribUserOnly() throws Exception {
+    @Test(expected = IllegalArgumentException.class)
+    public void testBadCribUserOnly() throws Exception {
         final String BASE64_CRIB = "dXNlcmlk";
         final BasicAuthHeader AUTH_HEADER = new BasicAuthHeader(NICE_METHOD, 
BASE64_CRIB);
+        @SuppressWarnings("unused")
         BasicAuthenticator.BasicCredentials credentials =
                 new 
BasicAuthenticator.BasicCredentials(AUTH_HEADER.getHeader(), 
StandardCharsets.UTF_8);
-        Assert.assertEquals(USER_NAME, credentials.getUsername());
-        Assert.assertNull(credentials.getPassword());
     }
 
     @Test
@@ -108,8 +106,8 @@ public class TestBasicAuthParser {
         // Our decoder accepts a long token without complaint.
         // 80 characters
         final String USER_LONG = 
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/AAAABBBBCCCCDDDD";
-        final String BASE64_CRIB = 
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0" +
-                "NTY3ODkrL0FBQUFCQkJCQ0NDQ0REREQ="; // no new line
+        final String BASE64_CRIB = 
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5e" +
+                "jAxMjM0NTY3ODkrL0FBQUFCQkJCQ0NDQ0REREQ6"; // no new line
         final BasicAuthHeader AUTH_HEADER = new BasicAuthHeader(NICE_METHOD, 
BASE64_CRIB);
         BasicAuthenticator.BasicCredentials credentials =
                 new 
BasicAuthenticator.BasicCredentials(AUTH_HEADER.getHeader(), 
StandardCharsets.UTF_8);
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index d5d2bceef3..cbc17e7130 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -217,6 +217,10 @@
         implementations), along with version compatibility warnings and
         third-party library version information. (csutherl)
       </add>
+      <fix>
+        Reject BASIC authorization with no password, to comply with RFC 7617
+        strictly. (remm)
+      </fix>
       <!-- Entries for backport and removal before 12.0.0-M1 below this line 
-->
       <fix>
         Avoid a race condition with concurrent lookups for a singleton JNDI


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to