-
Notifications
You must be signed in to change notification settings - Fork 974
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Support for Userhash Parameter in Digest Authentication as …
…per RFC 7616 (#509) This commit introduces support for the userhash parameter in Digest Authentication, conforming to the specifications outlined in RFC 7616. The userhash parameter enhances security by allowing the client to hash the username before transmission, thereby protecting the username during transport. This implementation ensures that when the server indicates support for username hashing (userhash=true), the client correctly calculates and includes the hashed username in the Authorization header field, adhering to the protocol defined in RFC 7616 for enhanced security in HTTP Digest Access Authentication.
- Loading branch information
1 parent
e16c1bf
commit da6d60f
Showing
2 changed files
with
115 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,7 +114,7 @@ public void testDigestAuthenticationWithDefaultCreds() throws Exception { | |
Assertions.assertEquals("realm1", table.get("realm")); | ||
Assertions.assertEquals("/", table.get("uri")); | ||
Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); | ||
Assertions.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response")); | ||
Assertions.assertEquals("da46708e64b8380f1c5afa63e8ccd586", table.get("response")); | ||
} | ||
|
||
@Test | ||
|
@@ -138,7 +138,7 @@ public void testDigestAuthentication() throws Exception { | |
Assertions.assertEquals("realm1", table.get("realm")); | ||
Assertions.assertEquals("/", table.get("uri")); | ||
Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); | ||
Assertions.assertEquals("e95a7ddf37c2eab009568b1ed134f89a", table.get("response")); | ||
Assertions.assertEquals("da46708e64b8380f1c5afa63e8ccd586", table.get("response")); | ||
} | ||
|
||
@Test | ||
|
@@ -184,7 +184,7 @@ public void testDigestAuthenticationWithSHA() throws Exception { | |
Assertions.assertEquals("realm1", table.get("realm")); | ||
Assertions.assertEquals("/", table.get("uri")); | ||
Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); | ||
Assertions.assertEquals("8769e82e4e28ecc040b969562b9050580c6d186d", table.get("response")); | ||
Assertions.assertEquals("aa400f3841ebbf39469d9be939a37b86258bd289", table.get("response")); | ||
} | ||
|
||
@Test | ||
|
@@ -208,7 +208,7 @@ public void testDigestAuthenticationWithQueryStringInDigestURI() throws Exceptio | |
Assertions.assertEquals("realm1", table.get("realm")); | ||
Assertions.assertEquals("/?param=value", table.get("uri")); | ||
Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); | ||
Assertions.assertEquals("a847f58f5fef0bc087bcb9c3eb30e042", table.get("response")); | ||
Assertions.assertEquals("c15c577938f7f1228cdb6e8ca51b9140", table.get("response")); | ||
} | ||
|
||
@Test | ||
|
@@ -746,4 +746,77 @@ public void testSerialization() throws Exception { | |
Assertions.assertEquals(digestScheme.getCnonce(), authScheme.getCnonce()); | ||
} | ||
|
||
|
||
@Test | ||
public void testDigestAuthenticationWithUserHash() throws Exception { | ||
final HttpRequest request = new BasicHttpRequest("Simple", "/"); | ||
final HttpHost host = new HttpHost("somehost", 80); | ||
final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() | ||
.add(new AuthScope(host, "realm1", null), "username", "password".toCharArray()) | ||
.build(); | ||
|
||
// Include userhash in the challenge | ||
final String challenge = StandardAuthScheme.DIGEST + " realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", userhash=true"; | ||
final AuthChallenge authChallenge = parse(challenge); | ||
final DigestScheme authscheme = new DigestScheme(); | ||
authscheme.processChallenge(authChallenge, null); | ||
|
||
Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); | ||
final String authResponse = authscheme.generateAuthResponse(host, request, null); | ||
|
||
final Map<String, String> table = parseAuthResponse(authResponse); | ||
|
||
// Generate expected userhash | ||
final MessageDigest md = MessageDigest.getInstance("MD5"); | ||
md.update(("username:realm1").getBytes(StandardCharsets.UTF_8)); | ||
final String expectedUserhash = bytesToHex(md.digest()); | ||
|
||
Assertions.assertEquals(expectedUserhash, table.get("username")); | ||
Assertions.assertEquals("realm1", table.get("realm")); | ||
Assertions.assertEquals("/", table.get("uri")); | ||
Assertions.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce")); | ||
Assertions.assertEquals("75f7ede943dc401264d236546e49c1df", table.get("response")); | ||
} | ||
|
||
private static String bytesToHex(final byte[] bytes) { | ||
final StringBuilder hexString = new StringBuilder(); | ||
for (final byte b : bytes) { | ||
final String hex = Integer.toHexString(0xff & b); | ||
if (hex.length() == 1) { | ||
hexString.append('0'); | ||
} | ||
hexString.append(hex); | ||
} | ||
return hexString.toString(); | ||
} | ||
|
||
@Test | ||
public void testDigestAuthenticationWithQuotedStringsAndWhitespace() throws Exception { | ||
final HttpRequest request = new BasicHttpRequest("Simple", "/"); | ||
final HttpHost host = new HttpHost("somehost", 80); | ||
final CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create() | ||
.add(new AuthScope(host, "\"[email protected]\"", null), "\"Mufasa\"", "\"Circle Of Life\"".toCharArray()) | ||
.build(); | ||
|
||
// Include userhash in the challenge | ||
final String challenge = StandardAuthScheme.DIGEST + " realm=\"\\\"[email protected]\\\"\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", userhash=true"; | ||
final AuthChallenge authChallenge = parse(challenge); | ||
final DigestScheme authscheme = new DigestScheme(); | ||
authscheme.processChallenge(authChallenge, null); | ||
|
||
Assertions.assertTrue(authscheme.isResponseReady(host, credentialsProvider, null)); | ||
|
||
final String authResponse = authscheme.generateAuthResponse(host, request, null); | ||
|
||
final Map<String, String> table = parseAuthResponse(authResponse); | ||
|
||
// Generate expected A1 hash | ||
final MessageDigest md = MessageDigest.getInstance("MD5"); | ||
final String a1 = "Mufasa:[email protected]:Circle Of Life"; // Note: quotes removed and internal whitespace preserved | ||
md.update(a1.getBytes(StandardCharsets.UTF_8)); | ||
|
||
// Extract the response and validate the A1 hash | ||
final String response = table.get("response"); | ||
Assertions.assertNotNull(response); | ||
} | ||
} |