From 27aef851ee098fc18f946716821564f3dfa16a40 Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Sun, 11 Oct 2026 11:58:39 +0530 Subject: [PATCH] require scram server nonce to extend the client nonce --- .../client5/http/impl/auth/ScramScheme.java | 5 ++-- .../http/impl/auth/TestScramScheme.java | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/ScramScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/ScramScheme.java index 3cf3978581..f843782bcc 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/ScramScheme.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/ScramScheme.java @@ -289,9 +289,10 @@ public void processChallenge( this.state = State.FAILED; throw new MalformedChallengeException("SCRAM server-first missing r/s/i"); } - if (this.clientNonce == null || !r.startsWith(this.clientNonce)) { + if (this.clientNonce == null || !r.startsWith(this.clientNonce) + || r.length() <= this.clientNonce.length()) { this.state = State.FAILED; - throw new AuthenticationException("SCRAM server nonce does not start with client nonce"); + throw new AuthenticationException("SCRAM server nonce does not extend client nonce"); } try { diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestScramScheme.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestScramScheme.java index c2f15cb783..8ef68cd29c 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestScramScheme.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestScramScheme.java @@ -257,6 +257,36 @@ void strictScram_invalidServerNonce_rejectedAt401() throws Exception { assertTrue(scheme.isChallengeComplete()); } + @Test + void strictScram_serverNonceNotExtended_rejectedAt401() throws Exception { + final ScramScheme scheme = new ScramScheme(); + final BasicCredentialsProvider creds = new BasicCredentialsProvider(); + creds.setCredentials(new AuthScope(HOST, REALM, scheme.getName()), + new UsernamePasswordCredentials(USER, PASS.toCharArray())); + final HttpClientContext ctx = HttpClientContext.create(); + + scheme.processChallenge(HOST, true, + new AuthChallenge(ChallengeType.TARGET, scheme.getName(), + new BasicNameValuePair("realm", REALM)), + ctx); + assertTrue(scheme.isResponseReady(HOST, creds, ctx)); + + final String authz1 = scheme.generateAuthResponse(HOST, null, ctx); + final String clientFirstBare = deb64s(splitHeader(authz1).get("data")).substring("n,,".length()); + final String clientNonce = parseCsvAttrs(clientFirstBare).get("r"); + + // Server echoes the client nonce verbatim, contributing no server nonce of its own. + final String serverFirst = "r=" + clientNonce + ",s=" + b64("salt".getBytes(StandardCharsets.UTF_8)) + ",i=4096"; + final AuthenticationException ex = assertThrows(AuthenticationException.class, () -> + scheme.processChallenge(HOST, true, + new AuthChallenge(ChallengeType.TARGET, scheme.getName(), + new BasicNameValuePair("sid", SID), + new BasicNameValuePair("data", b64(serverFirst.getBytes(StandardCharsets.UTF_8)))), + ctx)); + assertTrue(ex.getMessage().toLowerCase(Locale.ROOT).contains("nonce")); + assertTrue(scheme.isChallengeComplete()); + } + @Test void strictScram_minIterations_enforced() throws Exception { final ScramScheme scheme = new ScramScheme(4096, 4096, 100000, null);