This is an automated email from the ASF dual-hosted git repository.
reschke pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new 386223e315 OAK-12259: oak-http: OakServlet mis-parses HTTP Basic
credentials
386223e315 is described below
commit 386223e315e968de6233721cad0f7855c7a853ab
Author: Julian Reschke <[email protected]>
AuthorDate: Wed Jul 1 06:22:09 2026 +0200
OAK-12259: oak-http: OakServlet mis-parses HTTP Basic credentials
---
.../jackrabbit/oak/http/AuthorizationField.java | 34 ++++++++---
.../oak/http/AuthorizationFieldTest.java | 68 ++++++++++++++++++++--
2 files changed, 89 insertions(+), 13 deletions(-)
diff --git
a/oak-http/src/main/java/org/apache/jackrabbit/oak/http/AuthorizationField.java
b/oak-http/src/main/java/org/apache/jackrabbit/oak/http/AuthorizationField.java
index c572e4a3ed..13eda1a1d9 100644
---
a/oak-http/src/main/java/org/apache/jackrabbit/oak/http/AuthorizationField.java
+++
b/oak-http/src/main/java/org/apache/jackrabbit/oak/http/AuthorizationField.java
@@ -16,11 +16,12 @@
*/
package org.apache.jackrabbit.oak.http;
-import org.apache.jackrabbit.util.Base64;
-
import javax.jcr.SimpleCredentials;
import javax.security.auth.login.LoginException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
import java.util.Enumeration;
+import java.util.Locale;
import java.util.NoSuchElementException;
public class AuthorizationField {
@@ -44,11 +45,30 @@ public class AuthorizationField {
return parseCredentials(field);
}
- private static SimpleCredentials parseCredentials(String fieldValue)
throws LoginException {
- if (fieldValue.startsWith("Basic ")) {
- String[] basic =
- Base64.decode(fieldValue.substring("Basic
".length())).split(":");
- return new SimpleCredentials(basic[0], basic[1].toCharArray());
+ private static SimpleCredentials parseCredentials(String rawFieldValue)
throws LoginException {
+ boolean hasControls = rawFieldValue.chars().anyMatch(c -> c < ' ');
+ if (hasControls) {
+ throw new LoginException("Control characters are not allowed");
+ }
+
+ String fieldValue = rawFieldValue.trim().replaceAll(" +", " ");
+
+ if (fieldValue.toLowerCase(Locale.ENGLISH).startsWith("basic ")) {
+ String token68 = fieldValue.substring("basic ".length());
+ try {
+ String decoded = new
String(Base64.getDecoder().decode(token68), StandardCharsets.UTF_8);
+ int colon = decoded.indexOf(':');
+ if (colon < 0) {
+ throw new LoginException(
+ "Malformed Basic credentials: missing ':'
separator");
+ }
+ String userId = decoded.substring(0, colon);
+ String password = decoded.substring(colon + 1);
+
+ return new SimpleCredentials(userId, password.toCharArray());
+ } catch (IllegalArgumentException ex) {
+ throw new LoginException(ex.getMessage());
+ }
} else {
throw new LoginException("Only Basic Authentication supported");
}
diff --git
a/oak-http/src/test/java/org/apache/jackrabbit/oak/http/AuthorizationFieldTest.java
b/oak-http/src/test/java/org/apache/jackrabbit/oak/http/AuthorizationFieldTest.java
index e2a3ab214d..4579db6c56 100644
---
a/oak-http/src/test/java/org/apache/jackrabbit/oak/http/AuthorizationFieldTest.java
+++
b/oak-http/src/test/java/org/apache/jackrabbit/oak/http/AuthorizationFieldTest.java
@@ -47,12 +47,10 @@ public class AuthorizationFieldTest {
assertEquals("bar", new String(credentials.getPassword()));
}
- @Test(expected = ArrayIndexOutOfBoundsException.class) // BUG: OAK-12259
+ @Test(expected = LoginException.class)
public void testInvalidBase64() throws LoginException {
String b64 =
Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
- SimpleCredentials credentials =
AuthorizationField.valueOf(Collections.enumeration(List.of("Basic dksjdkj" +
b64)));
- assertEquals("foo", credentials.getUserID());
- assertEquals("bar", new String(credentials.getPassword()));
+ AuthorizationField.valueOf(Collections.enumeration(List.of("Basic
dksjdkj" + b64)));
}
@Test(expected = LoginException.class)
@@ -64,9 +62,67 @@ public class AuthorizationFieldTest {
@Test
public void testColonInPassword() throws LoginException {
String b64 = "Basic " +
Base64.getEncoder().encodeToString("foo:bar:qux".getBytes(StandardCharsets.UTF_8));
- SimpleCredentials credentials
=AuthorizationField.valueOf(Collections.enumeration(List.of(b64)));
+ SimpleCredentials credentials =
AuthorizationField.valueOf(Collections.enumeration(List.of(b64)));
+ assertEquals("foo", credentials.getUserID());
+ assertEquals("bar:qux", new String(credentials.getPassword()));
+ }
+
+ @Test(expected = LoginException.class)
+ public void testMissingColon() throws LoginException {
+ String b64 = "Basic " +
Base64.getEncoder().encodeToString("foobarqux".getBytes(StandardCharsets.UTF_8));
+ AuthorizationField.valueOf(Collections.enumeration(List.of(b64)));
+ }
+
+ @Test
+ public void testSchemeCase() throws LoginException {
+ String b64 =
Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
+ SimpleCredentials credentials =
AuthorizationField.valueOf(Collections.enumeration(List.of("BaSiC " + b64)));
+ assertEquals("foo", credentials.getUserID());
+ assertEquals("bar", new String(credentials.getPassword()));
+ }
+
+ @Test
+ public void testMoreWhitespace() throws LoginException {
+ String b64 =
Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
+ SimpleCredentials credentials =
AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " + b64)));
+ assertEquals("foo", credentials.getUserID());
+ assertEquals("bar", new String(credentials.getPassword()));
+ }
+
+ @Test(expected = LoginException.class)
+ public void testNonSpWhitespace() throws LoginException {
+ String b64 =
Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
+ AuthorizationField.valueOf(Collections.enumeration(List.of("Basic \t "
+ b64)));
+ }
+
+ @Test(expected = LoginException.class)
+ public void testBrokenBase64() throws LoginException {
+ String b64 =
Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
+ // insert a single SP into the base64 sequence
+ b64 = b64.substring(0,5) + " " + b64.substring(5);
+ AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " +
b64)));
+ }
+
+ @Test(expected = LoginException.class)
+ public void testMoreBrokenBase64() throws LoginException {
+ String b64 =
Base64.getEncoder().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
+ b64 = b64.substring(0,5) + "=" + b64.substring(5);
+ AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " +
b64)));
+ }
+
+ @Test
+ public void testMoreNonAscii() throws LoginException {
+ String b64 =
Base64.getEncoder().encodeToString("test:123\u00a3".getBytes(StandardCharsets.UTF_8));
+ SimpleCredentials credentials =
AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " + b64)));
+ assertEquals("test", credentials.getUserID());
+ assertEquals("123\u00a3", new String(credentials.getPassword()));
+ }
+
+ @Test
+ public void testBasic64NoPadding() throws LoginException {
+ String b64 =
Base64.getEncoder().withoutPadding().encodeToString("foo:bar".getBytes(StandardCharsets.UTF_8));
+ SimpleCredentials credentials =
AuthorizationField.valueOf(Collections.enumeration(List.of("Basic " + b64)));
assertEquals("foo", credentials.getUserID());
- // BUG: OAK-12259
assertEquals("bar", new String(credentials.getPassword()));
}
}
\ No newline at end of file