Skip to content

BASIC Authentication scheme assume that crdential are encoded with ISO-8859-1 #1455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
arcadmlafon opened this issue Mar 6, 2025 · 5 comments

Comments

@arcadmlafon
Copy link

I do not know if this is a deliberated choice or not, but we have discovered that the BASIC Authenticate HTTP header is decoded with the ISO-8859-1 charset avoiding the usage of non Latin character in the login and password when using this schema, in the org.restlet.engine.security.HttpBasicHelper class.

The code is coherent because the same charset is used to encode too, but it appears to give some problems to non-european people... As I said I do not know if this issue should be viewed as a bug or an enhancement request, but there is a optional parameter allowing for the server to indicate a preference for an UTF-8 encoding:

https://www.rfc-editor.org/rfc/rfc7617.html#section-2.1

It could be great if some parameter could be retrieved by HttpBasicHelper to insert this parameter and then use UTF-8 to encode and decode the header...

@arcadmlafon
Copy link
Author

As a turn around, we can inject the charset preference with this turn around when generating the ChallengeRequest in the Authenticator class:

ChallengeRequest cr = new ChallengeRequest(ChallengeScheme.HTTP_BASIC, myRealm);
// Force the inclusion of the UTF-8 charset preference.
cr.setRawValue("Basic realm=\"" + myRealm + "\", charset=\"UTF-8\"");

By the way it appears thet HttpBasicHelper ignore the parameters added to the ChallengeRequest, the following code is useless:

cr.getParameters().add("charset", "UTF-8");

(I know that except "charset" and "realm" there is no other parameter existing for a BASIC but this could have been useful to manage them.)

And to correctly decode the credential we can add the following code after retreiving the ChallengeResponse:

ChallengeResponse challenge = request.getChallengeResponse();
byte[] credentialsEncoded = Base64.getDecoder().decode(challenge.getRawValue());
// Use UTF-8 as the default charset used for credential in BASIC HTTP authenticate header.
String credentials = new String(credentialsEncoded, StandardCharsets.UTF_8);
int separator = credentials.indexOf(':');
// Restlet already log an information about missing separator... 
if (separator > 0) {
  challenge.setIdentifier(credentials.substring(0, separator));
  challenge.setSecret(credentials.substring(separator + 1));
} else {
  // Assume that a missing separator imply that the password is empty... but we should throw a ResourceException here...
  challenge.setIdentifier(credentials);
  challenge.setSecret(new char[0]);
}

@jlouvel
Copy link
Collaborator

jlouvel commented Mar 16, 2025

Thanks Marc for the detailled issue. This chartset parameters was added in 2015 with this RFC 7617 and never supported by Restlet Framework.

As part of a branch that I'm working on right now, I have enhanced the code to support charset="UTF-8":
https://github.com/restlet/restlet-framework-java/blob/2.6-multipart-jetty/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java

Could you test this revised HttpBasicHelper.java class and let me know if it works as expected?

Added @thboileau for visibility.

@arcadmlafon
Copy link
Author

Sorry for the delay... I am testing it today !

@arcadmlafon
Copy link
Author

I am afraid that it does not work completely,

On a request without challenge the server add correctly the charset parameter.

On the client side the Client encode the credential in utf-8 correctly to.

The problem come when the server get the request with the correct credential... the RFC 7617 is... let say... tricky, because the client request does not contain any information about the charset used to encode the raw value of the Authorization... so the HttpBasicHelper class the legacy charset.

the AuthenticatorUtils.parseResponse method create the ChallenceResponse with the schema and the raw value and call the helper in a raw, so there is no way to add the parameter to the it before it is pass to the helper.

@arcadmlafon
Copy link
Author

Here is the test code I used, for the Client part:

package testsbasicunicode;

import java.io.IOException;

import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Status;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;

public class ClientTest {

	public static void main(String[] args) {
		// Prepare the request
		ClientResource resource = new ClientResource("http://localhost:8182/");

		// Add the client authentication to the call
		ChallengeResponse response = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "unicode", "aaa£€§");
		response.getParameters().add("charset", "UTF-8");
		resource.setChallengeResponse(response);

		try {
			// Send the HTTP GET request
			resource.get();
			// Manage the response
			if (resource.getStatus().isSuccess()) {
			    // Output the response entity on the JVM console...
			    resource.getResponseEntity().write(System.out);
			} else {
			    System.out.println("An unexpected status: " + resource.getStatus());
			}
		} catch (ResourceException e) {
			if (Status.CLIENT_ERROR_UNAUTHORIZED.equals(e.getStatus())) {
				System.out.println("Access unauthorized by the server !");
			} else {
			    System.out.println("An unexpected status: " + e.getStatus());
			}
		} catch (IOException e) {
			System.out.println("Broken connection: " + e.getLocalizedMessage());
		}
	}
}

Adding the charset parameter to the challenge response allow to the HttpBasicHelper to encode the crendential in UTF-8. (But the parameter is not sent through the HTTP call, as the RFC decribe it...)

And for the server side:

package testsbasicunicode;

import org.restlet.Application;
import org.restlet.Component;
import org.restlet.Restlet;
import org.restlet.data.ChallengeRequest;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Protocol;
import org.restlet.resource.Get;
import org.restlet.resource.ServerResource;
import org.restlet.security.ChallengeAuthenticator;
import org.restlet.security.MapVerifier;

public class ServerTest {

	public static class RootResource extends ServerResource {

		@Get("txt")
	    public String toString() {
	        return "Successful authentication !";
	    }
	}
	
	public static class GuardedApplication extends Application {
		
		@Override
	    public Restlet createInboundRoot() {
			// Create a simple password verifier with two users (one with unicode characters)
			MapVerifier verifier = new MapVerifier();
			verifier.getLocalSecrets().put("unicode", "aaa£€§".toCharArray());
			verifier.getLocalSecrets().put("ascii", "abcd".toCharArray());
			// Create a Guard
			ChallengeAuthenticator guard = new ChallengeAuthenticator(getContext(), ChallengeScheme.HTTP_BASIC, "test") {

				@Override
				protected ChallengeRequest createChallengeRequest(boolean stale) {
					ChallengeRequest challengeRequest = super.createChallengeRequest(stale);
					// Add the charset requirement.
					challengeRequest.getParameters().add("charset", "UTF-8");
					return challengeRequest;
				}
			};
			guard.setVerifier(verifier);
			guard.setNext(RootResource.class);
            return guard;
	    }
	}
	
	public static void main(String[] args) throws Exception {
		Component component = new Component();
		component.getServers().add(Protocol.HTTP, 8182);
		component.getDefaultHost().attach(new GuardedApplication());
		component.start();
	}
}

thboileau added a commit that referenced this issue Apr 4, 2025
* Added MultiPartFormDataRepresentation

Added MultiPartFormDataRepresentation to Jetty extension to support generation and parsing.

* Added support for the "charset" parameter in HTTP BASIC challenges

See issue #1455

* Removed unused imports

* Update changes.md

* Update MultiPartFormDataRepresentation.java

Added "location" parameter to help set a useful MultiPartConfig in addition to the default Jetty values

* HttpBasic test refacto

* HttpBasicHelper: fix potential NPE

* HttpBasicHelper: fix potential NPE

* Added tests cases for multipart

* Added logic to duplicate MediaType along with the addition of parameter

* Enhanced MultiPartFormDataRepresentation

Cleanup behavior to generate random boundary just before its usage and only when needed.
Added method to create a Part based on a Representation

* Update MultiPartFormDataRepresentation.java

Cleanup behavior to generate random boundary just before its usage and only when needed.
Added method to create a Part based on a Representation

* Fixed boundary setting issue and adjusted test case

* Renamed MultiPartFormDataRep to MultiPartRep

Eventually the logic could be reused for related media types

* Update MultiPartRepresentation.java

* Fixed multipart parsing logic

Also added related unit test

* Update changes.md

* Update org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restlet/ext/jetty/MultiPartRepresentation.java

Co-authored-by: Thierry Boileau <thboileau@gmail.com>

* Update org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restlet/ext/jetty/MultiPartRepresentation.java

Co-authored-by: Thierry Boileau <thboileau@gmail.com>

* Update org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restlet/ext/jetty/MultiPartRepresentation.java

Co-authored-by: Thierry Boileau <thboileau@gmail.com>

* Update org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restlet/ext/jetty/MultiPartRepresentation.java

Co-authored-by: Thierry Boileau <thboileau@gmail.com>

---------

Co-authored-by: Jerome Louvel <374450+jlouvel@users.noreply.github.com>
Co-authored-by: Thierry Boileau <thboileau@gmaiil.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants