From 63bac1481af22885d62c8091811b0cfaddeab8b7 Mon Sep 17 00:00:00 2001 From: dhairya05 Date: Thu, 12 Nov 2026 14:55:59 +0530 Subject: [PATCH 1/2] Encode HTTP Basic credentials as UTF-8, not in the platform default charset Five places build an Authorization/Proxy-Authorization header by Base64-encoding "user:password" through String.getBytes() with no argument, which uses the JVM's default charset: camel-as2 AS2HeaderUtils.addAuthorizationHeader camel-keycloak KeycloakProducer (token revocation) camel-keycloak KeycloakTokenIntrospector camel-smpp SmppConnectionFactory.connectProxy camel-wordpress WordpressBasicAuthentication RFC 7617 says the credentials are encoded with UTF-8 unless the server asks for another charset, and that is what Keycloak, WordPress and ordinary HTTP proxies expect. The default charset is only UTF-8 when nothing overrides it: -Dfile.encoding, file.encoding=COMPAT, or a JVM before 18 following the platform locale, all give something else. A user name or password holding a non-ASCII character then produces a different Authorization header on different machines, and the server rejects it with 401 even though the credentials are right. Since the bytes are Base64-encoded, nothing in the error points at the encoding. All five now pass StandardCharsets.UTF_8 explicitly. Credentials made only of ASCII characters encode to exactly the same bytes as before. camel-smpp's connectProxy also wrote the CONNECT request line, the proxy headers and the line separators, and read the proxy's response, in the default charset; those are converted too, so the whole exchange is built one way. --- .../as2/api/util/AS2HeaderUtils.java | 4 +++- .../as2/api/util/AS2HeaderUtilsTest.java | 19 +++++++++++++++++++ .../component/keycloak/KeycloakProducer.java | 3 ++- .../security/KeycloakTokenIntrospector.java | 3 ++- .../component/smpp/SmppConnectionFactory.java | 17 +++++++++-------- .../auth/WordpressBasicAuthentication.java | 7 +++++-- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2HeaderUtils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2HeaderUtils.java index d65009ccded23..31678a99e029c 100644 --- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2HeaderUtils.java +++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2HeaderUtils.java @@ -16,6 +16,7 @@ */ package org.apache.camel.component.as2.api.util; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -167,7 +168,8 @@ public static String getParameterValue(Header[] headers, String headerName, Stri public static void addAuthorizationHeader(HttpMessage message, String userName, String password, String accessToken) { if (userName != null && password != null) { message.addHeader(AS2Header.AUTHORIZATION, - ("Basic " + Base64.getEncoder().encodeToString((userName + ":" + password).getBytes()))); + ("Basic " + Base64.getEncoder() + .encodeToString((userName + ":" + password).getBytes(StandardCharsets.UTF_8)))); } else if (accessToken != null) { message.addHeader(AS2Header.AUTHORIZATION, "Bearer " + accessToken); } diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java index aa26051a06996..9657009dc0373 100644 --- a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java +++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java @@ -16,7 +16,13 @@ */ package org.apache.camel.component.as2.api.util; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.apache.camel.component.as2.api.AS2Header; import org.apache.camel.component.as2.api.util.AS2HeaderUtils.Parameter; +import org.apache.hc.core5.http.HttpMessage; +import org.apache.hc.core5.http.message.BasicHttpRequest; import org.apache.hc.core5.http.message.ParserCursor; import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Test; @@ -55,4 +61,17 @@ public void parseNameValuePairTest() { assertArrayEquals(SIGNED_RECEIPT_MICALG_VALUES, parameter.getValues(), "Unexpected value for parameter values"); } + @Test + public void addAuthorizationHeaderEncodesCredentialsAsUtf8() { + // a password outside US-ASCII: "pa(diaeresis)ssword" + final String password = "p\u00e4ssword"; + final HttpMessage message = new BasicHttpRequest("POST", "/"); + + AS2HeaderUtils.addAuthorizationHeader(message, "user", password, null); + + // RFC 7617 asks for UTF-8, and the platform default charset must not decide what goes on the wire + final String expected + = "Basic " + Base64.getEncoder().encodeToString(("user:" + password).getBytes(StandardCharsets.UTF_8)); + assertEquals(expected, message.getFirstHeader(AS2Header.AUTHORIZATION).getValue()); + } } diff --git a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java index a54cb4e17b970..b9c41747fbc70 100644 --- a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java +++ b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/KeycloakProducer.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -1414,7 +1415,7 @@ private void revokeToken(Exchange exchange, String defaultTypeHint) throws Excep // Add Basic authentication header String auth = clientId + ":" + clientSecret; - String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes()); + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8)); request.setHeader("Authorization", "Basic " + encodedAuth); request.setHeader("Content-Type", "application/x-www-form-urlencoded"); diff --git a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakTokenIntrospector.java b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakTokenIntrospector.java index f95617613d8da..46a1900dd15e2 100644 --- a/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakTokenIntrospector.java +++ b/components/camel-keycloak/src/main/java/org/apache/camel/component/keycloak/security/KeycloakTokenIntrospector.java @@ -17,6 +17,7 @@ package org.apache.camel.component.keycloak.security; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; import java.util.Map; @@ -142,7 +143,7 @@ public IntrospectionResult introspect(String token) throws IOException { // Add Basic authentication header String auth = clientId + ":" + clientSecret; - String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes()); + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8)); request.setHeader("Authorization", "Basic " + encodedAuth); request.setHeader("Content-Type", "application/x-www-form-urlencoded"); diff --git a/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppConnectionFactory.java b/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppConnectionFactory.java index 0bbbbbb7e7969..9b7db82dccaed 100644 --- a/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppConnectionFactory.java +++ b/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppConnectionFactory.java @@ -53,6 +53,7 @@ import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; +import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Map; @@ -171,33 +172,33 @@ private void connectProxy(String host, int port, Socket socket) throws IOExcepti InputStream in = socket.getInputStream(); String connectString = "CONNECT " + host + ":" + port + " HTTP/1.0\r\n"; - out.write(connectString.getBytes()); + out.write(connectString.getBytes(StandardCharsets.UTF_8)); String username = config.getHttpProxyUsername(); String password = config.getHttpProxyPassword(); if (username != null && password != null) { String usernamePassword = username + ":" + password; - byte[] code = Base64.getEncoder().encode(usernamePassword.getBytes()); - out.write("Proxy-Authorization: Basic ".getBytes()); + byte[] code = Base64.getEncoder().encode(usernamePassword.getBytes(StandardCharsets.UTF_8)); + out.write("Proxy-Authorization: Basic ".getBytes(StandardCharsets.UTF_8)); out.write(code); - out.write("\r\n".getBytes()); + out.write("\r\n".getBytes(StandardCharsets.UTF_8)); } Map proxyHeaders = config.getProxyHeaders(); if (proxyHeaders != null) { for (Map.Entry entry : proxyHeaders.entrySet()) { - out.write((entry.getKey() + ": " + entry.getValue()).getBytes()); - out.write("\r\n".getBytes()); + out.write((entry.getKey() + ": " + entry.getValue()).getBytes(StandardCharsets.UTF_8)); + out.write("\r\n".getBytes(StandardCharsets.UTF_8)); } } - out.write("\r\n".getBytes()); + out.write("\r\n".getBytes(StandardCharsets.UTF_8)); out.flush(); int ch = 0; - BufferedReader reader = IOHelper.buffered(new InputStreamReader(in)); + BufferedReader reader = IOHelper.buffered(new InputStreamReader(in, StandardCharsets.UTF_8)); String response = reader.readLine(); if (response == null) { throw new RuntimeCamelException("Empty response to CONNECT request to host " + host + ":" + port); diff --git a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/api/auth/WordpressBasicAuthentication.java b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/api/auth/WordpressBasicAuthentication.java index 80ee479178324..275d5da52d5b5 100644 --- a/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/api/auth/WordpressBasicAuthentication.java +++ b/components/camel-wordpress/src/main/java/org/apache/camel/component/wordpress/api/auth/WordpressBasicAuthentication.java @@ -16,6 +16,8 @@ */ package org.apache.camel.component.wordpress.api.auth; +import java.nio.charset.StandardCharsets; + import org.apache.cxf.common.util.Base64Utility; import org.apache.cxf.jaxrs.client.ClientConfiguration; import org.apache.cxf.jaxrs.client.WebClient; @@ -51,8 +53,9 @@ public WordpressBasicAuthentication(String username, String password) { @Override public void configureAuthentication(Object api) { if (isCredentialsSet()) { - final String authorizationHeader = String.format("Basic %s", - Base64Utility.encode(String.format("%s:%s", this.username, this.password).getBytes())); + final String credentials = String.format("%s:%s", this.username, this.password); + final String authorizationHeader + = String.format("Basic %s", Base64Utility.encode(credentials.getBytes(StandardCharsets.UTF_8))); LOGGER.info("Credentials set for user {}", username); WebClient.client(api).header("Authorization", authorizationHeader); } else { From bc390a77910547a533cb2da9b13a9ae5e60062f2 Mon Sep 17 00:00:00 2001 From: dhairya05 Date: Thu, 12 Nov 2026 16:02:26 +0530 Subject: [PATCH 2/2] camel-as2: assert the Basic header against a fixed value Recomputing the expectation the way the code does makes the assertion vacuous on a JVM whose default charset is already UTF-8, which is every JVM Camel CI runs. The expected header is now written out, and the ISO-8859-1 encoding of the same credentials is asserted to differ, so the test says what has to go on the wire rather than agreeing with whatever the code did. --- .../component/as2/api/util/AS2HeaderUtilsTest.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java index 9657009dc0373..507c02214d131 100644 --- a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java +++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/AS2HeaderUtilsTest.java @@ -16,7 +16,6 @@ */ package org.apache.camel.component.as2.api.util; -import java.nio.charset.StandardCharsets; import java.util.Base64; import org.apache.camel.component.as2.api.AS2Header; @@ -27,8 +26,10 @@ import org.apache.hc.core5.util.CharArrayBuffer; import org.junit.jupiter.api.Test; +import static java.nio.charset.StandardCharsets.ISO_8859_1; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; public class AS2HeaderUtilsTest { @@ -69,9 +70,12 @@ public void addAuthorizationHeaderEncodesCredentialsAsUtf8() { AS2HeaderUtils.addAuthorizationHeader(message, "user", password, null); - // RFC 7617 asks for UTF-8, and the platform default charset must not decide what goes on the wire - final String expected - = "Basic " + Base64.getEncoder().encodeToString(("user:" + password).getBytes(StandardCharsets.UTF_8)); - assertEquals(expected, message.getFirstHeader(AS2Header.AUTHORIZATION).getValue()); + // the expected value is written out rather than recomputed the way the code does it, so the assertion says + // what has to go on the wire whatever the JVM's default charset is: RFC 7617 asks for UTF-8 + assertEquals("Basic dXNlcjpww6Rzc3dvcmQ=", message.getFirstHeader(AS2Header.AUTHORIZATION).getValue()); + + // what the default charset would have produced instead, had it not been UTF-8 + assertNotEquals("Basic " + Base64.getEncoder().encodeToString(("user:" + password).getBytes(ISO_8859_1)), + message.getFirstHeader(AS2Header.AUTHORIZATION).getValue()); } }