Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -491,13 +491,33 @@ private static String bufferAndHashBodyGenerator(BodyGenerator gen, String hashA
private static void append(StringBuilder builder, String name, @Nullable String value, boolean quoted) {
builder.append(name).append('=');
if (quoted) {
builder.append('"').append(value).append('"');
builder.append('"');
appendQuotedStringContent(builder, value);
builder.append('"');
} else {
builder.append(value);
}
builder.append(", ");
}

// RFC 7616 params like realm/nonce/opaque are echoed back from the server challenge into the
// quoted-string values of the Authorization header. Backslash-escape " and \ (RFC 7230 quoted-string)
// so a server-supplied value carrying a bare quote cannot close the value early and inject extra
// auth-params into the credentials the client emits.
private static void appendQuotedStringContent(StringBuilder builder, @Nullable String value) {
if (value == null) {
builder.append((String) null);
return;
}
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '"' || c == '\\') {
builder.append('\\');
}
builder.append(c);
}
}

public static @Nullable String perConnectionProxyAuthorizationHeader(Request request, @Nullable Realm proxyRealm) {
String proxyAuthorization = null;
if (proxyRealm != null && proxyRealm.isUsePreemptiveAuth()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand Down Expand Up @@ -492,4 +493,29 @@ void computeRspAuth_basic() throws Exception {
assertNotNull(rspauth);
assertEquals(32, rspauth.length()); // MD5 hex is 32 chars
}

// A server-controlled challenge value (nonce/realm/opaque) must be backslash-escaped when echoed
// into the quoted-string params of the outgoing Authorization header, so a bare " cannot close the
// value early and inject extra auth-params.
@Test
void digestChallengeValuesEscapedInAuthorizationHeader() {
Realm realm = new Realm.Builder("user", "pass")
.setScheme(Realm.AuthScheme.DIGEST)
.setUsePreemptiveAuth(true)
.setRealmName("testrealm")
.setNonce("abc\"x")
.setOpaque("op\\aque")
.setAlgorithm("MD5")
.build();
Request request = new RequestBuilder("GET")
.setUrl("http://example.com/secret")
.build();

String header = AuthenticatorUtils.perRequestAuthorizationHeader(request, realm);

assertNotNull(header);
assertTrue(header.contains("nonce=\"abc\\\"x\""), header);
assertTrue(header.contains("opaque=\"op\\\\aque\""), header);
assertFalse(header.contains("nonce=\"abc\"x\""), header);
}
}
Loading