Author: markt Date: Fri Sep 1 19:51:42 2017 New Revision: 1807004 URL: http://svn.apache.org/viewvc?rev=1807004&view=rev Log: Fix https://bz.apache.org/bugzilla/show_bug.cgi?id=61280 Add RFC 7617 support to the BasicAuthenticator
Modified: tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java tomcat/trunk/webapps/docs/changelog.xml tomcat/trunk/webapps/docs/config/valve.xml Modified: tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java?rev=1807004&r1=1807003&r2=1807004&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java (original) +++ tomcat/trunk/java/org/apache/catalina/authenticator/BasicAuthenticator.java Fri Sep 1 19:51:42 2017 @@ -20,6 +20,7 @@ package org.apache.catalina.authenticato import java.io.IOException; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.Principal; @@ -43,10 +44,30 @@ import org.apache.tomcat.util.codec.bina * @author Craig R. McClanahan */ public class BasicAuthenticator extends AuthenticatorBase { + private static final Log log = LogFactory.getLog(BasicAuthenticator.class); + private Charset charset = StandardCharsets.UTF_8; + private String charsetString = "UTF-8"; + + + public String getCharset() { + return charsetString; + } + + + public void setCharset(String charsetString) { + // Only acceptable options are null, "" or "UTF-8" (case insensitive) + if (charsetString == null || charsetString.isEmpty()) { + charset = StandardCharsets.ISO_8859_1; + } else if ("UTF-8".equalsIgnoreCase(charsetString)) { + charset = StandardCharsets.UTF_8; + } else { + throw new IllegalArgumentException(sm.getString("basicAuthenticator.invalidCharset")); + } + this.charsetString = charsetString; + } - // --------------------------------------------------------- Public Methods @Override protected boolean doAuthenticate(Request request, HttpServletResponse response) @@ -66,7 +87,7 @@ public class BasicAuthenticator extends ByteChunk authorizationBC = authorization.getByteChunk(); BasicCredentials credentials = null; try { - credentials = new BasicCredentials(authorizationBC); + credentials = new BasicCredentials(authorizationBC, charset); String username = credentials.getUsername(); String password = credentials.getPassword(); @@ -89,6 +110,10 @@ public class BasicAuthenticator extends value.append("Basic realm=\""); value.append(getRealmName(context)); value.append('\"'); + if (charsetString != null && !charsetString.isEmpty()) { + value.append(", charset="); + value.append(charsetString); + } response.setHeader(AUTH_HEADER_NAME, value.toString()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; @@ -112,28 +137,31 @@ public class BasicAuthenticator extends // note: we include single white space as its delimiter private static final String METHOD = "basic "; - private ByteChunk authorization; - private int initialOffset; + private final Charset charset; + private final ByteChunk authorization; + private final int initialOffset; private int base64blobOffset; private int base64blobLength; private String username = null; private String password = null; - /** * Parse the HTTP Authorization header for BASIC authentication * as per RFC 2617 section 2, and the Base64 encoded credentials * as per RFC 2045 section 6.8. * - * @param input The header value to parse in-place + * @param input The header value to parse in-place + * @param charset The character set to use to convert the bytes to a + * string * * @throws IllegalArgumentException If the header does not conform * to RFC 2617 */ - public BasicCredentials(ByteChunk input) - throws IllegalArgumentException { + public BasicCredentials(ByteChunk input, Charset charset) throws IllegalArgumentException { authorization = input; initialOffset = input.getOffset(); + this.charset = charset; + parseMethod(); byte[] decoded = parseBase64(); parseCredentials(decoded); @@ -210,15 +238,12 @@ public class BasicAuthenticator extends } if (colon < 0) { - username = new String(decoded, StandardCharsets.ISO_8859_1); + username = new String(decoded, charset); // password will remain null! } else { - username = new String( - decoded, 0, colon, StandardCharsets.ISO_8859_1); - password = new String( - decoded, colon + 1, decoded.length - colon - 1, - StandardCharsets.ISO_8859_1); + username = new String(decoded, 0, colon, charset); + password = new String(decoded, colon + 1, decoded.length - colon - 1, charset); // tolerate surplus white space around credentials if (password.length() > 1) { password = password.trim(); Modified: tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties?rev=1807004&r1=1807003&r2=1807004&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties (original) +++ tomcat/trunk/java/org/apache/catalina/authenticator/LocalStrings.properties Fri Sep 1 19:51:42 2017 @@ -32,6 +32,8 @@ authenticator.sessionExpired=The time al authenticator.unauthorized=Cannot authenticate with the provided credentials authenticator.tomcatPrincipalLogoutFail=Logout with TomcatPrincipal instance has failed +basicAuthenticator.invalidCharset=The only permitted values are null, the empty string or UTF-8 + digestAuthenticator.cacheRemove=A valid entry has been removed from client nonce cache to make room for new entries. A replay attack is now possible. To prevent the possibility of replay attacks, reduce nonceValidity or increase cnonceCacheSize. Further warnings of this type will be suppressed for 5 minutes. formAuthenticator.forwardErrorFail=Unexpected error forwarding to error page Modified: tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java?rev=1807004&r1=1807003&r2=1807004&view=diff ============================================================================== --- tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java (original) +++ tomcat/trunk/test/org/apache/catalina/authenticator/TestBasicAuthParser.java Fri Sep 1 19:51:42 2017 @@ -45,7 +45,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -56,7 +56,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, null); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertNull(credentials.getPassword()); } @@ -68,7 +68,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -80,7 +80,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertNull(credentials.getPassword()); } @@ -93,7 +93,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD1, credentials.getPassword()); } @@ -119,7 +119,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_LONG, credentials.getUsername()); } @@ -141,7 +141,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_LONG, credentials.getUsername()); } @@ -157,7 +157,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(METHOD, USER_NAME, PASSWORD); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -174,7 +174,7 @@ public class TestBasicAuthParser { BasicAuthenticator.BasicCredentials credentials = null; try { credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.fail("IllegalArgumentException expected"); } catch (Exception e) { @@ -197,7 +197,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD + " ", USER_NAME, PASSWORD); final BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -213,7 +213,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, PWD_WRONG); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertNotSame(PASSWORD, credentials.getPassword()); } @@ -225,7 +225,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, EMPTY_USER_NAME, PASSWORD); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(EMPTY_USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -237,7 +237,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, SHORT_USER_NAME, PASSWORD); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(SHORT_USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -249,7 +249,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, SHORT_PASSWORD); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(SHORT_PASSWORD, credentials.getPassword()); } @@ -261,7 +261,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD_SPACE); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD_SPACE, credentials.getPassword()); } @@ -273,7 +273,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD_COLON); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD_COLON, credentials.getPassword()); } @@ -285,7 +285,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD_COLON); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD_COLON, credentials.getPassword()); } @@ -297,7 +297,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD_COLON); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD_COLON, credentials.getPassword()); } @@ -315,7 +315,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, PASSWORD, " "); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -334,7 +334,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, " " + USER_NAME + " ", PASSWORD); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -353,7 +353,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, USER_NAME, " " + PASSWORD + " "); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -378,7 +378,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertNotSame(PASSWORD, credentials.getPassword()); Assert.assertEquals(TRUNCATED_PWD, credentials.getPassword()); @@ -396,7 +396,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertNotSame(PASSWORD, credentials.getPassword()); } @@ -416,7 +416,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(POSSIBLY_DAMAGED_PWD, credentials.getPassword()); } @@ -433,7 +433,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } @@ -452,7 +452,7 @@ public class TestBasicAuthParser { new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( - AUTH_HEADER.getHeader()); + AUTH_HEADER.getHeader(), StandardCharsets.UTF_8); Assert.assertEquals(USER_NAME, credentials.getUsername()); Assert.assertEquals(PASSWORD, credentials.getPassword()); } Modified: tomcat/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1807004&r1=1807003&r2=1807004&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/changelog.xml (original) +++ tomcat/trunk/webapps/docs/changelog.xml Fri Sep 1 19:51:42 2017 @@ -77,6 +77,10 @@ added in Java 9 to only disable the caching for JAR URL connections. (markt) </add> + <add> + <bug>61280</bug>: Add RFC 7617 support to the + <code>BasicAuthenticator</code>. (markt) + </add> </changelog> </subsection> <subsection name="Coyote"> Modified: tomcat/trunk/webapps/docs/config/valve.xml URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/valve.xml?rev=1807004&r1=1807003&r2=1807004&view=diff ============================================================================== --- tomcat/trunk/webapps/docs/config/valve.xml (original) +++ tomcat/trunk/webapps/docs/config/valve.xml Fri Sep 1 19:51:42 2017 @@ -1098,6 +1098,19 @@ used.</p> </attribute> + <attribute name="charset" required="false"> + <p>Controls if the <code>WWW-Authenticate</code> HTTP header includes a + <code>charset</code> authentication parameter as per RFC 7617. The only + permitted options are <code>null</code>, the empty string and + <code>UTF-8</code>. If <code>UTF-8</code> is specified then the + <code>charset</code> authentication parameter will be sent with that + value and the provided user name and optional password will be converted + from bytes to characters using UTF-8. Otherwise, no <code>charset</code> + authentication parameter will be sent and the provided user name and + optional password will be converted from bytes to characters using + ISO-8859-1. The default value is <code>UTF-8</code></p> + </attribute> + <attribute name="className" required="true"> <p>Java class name of the implementation to use. This MUST be set to <strong>org.apache.catalina.authenticator.BasicAuthenticator</strong>.</p> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org