Skip to content

Commit

Permalink
Merge pull request #2095 from fjuma/ELY-2717
Browse files Browse the repository at this point in the history
[ELY-2717] Ensure that the credential algorithm for the DIGEST mechanism is set to digest-md5
  • Loading branch information
darranl committed Feb 12, 2024
2 parents 71c92fb + cf5b055 commit 93c6b1d
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 3 deletions.
Expand Up @@ -23,6 +23,7 @@
import static org.wildfly.security.http.HttpConstants.AUTHORIZATION;
import static org.wildfly.security.http.HttpConstants.BAD_REQUEST;
import static org.wildfly.security.http.HttpConstants.CNONCE;
import static org.wildfly.security.http.HttpConstants.DIGEST_NAME;
import static org.wildfly.security.http.HttpConstants.NC;
import static org.wildfly.security.http.HttpConstants.QOP;
import static org.wildfly.security.http.HttpConstants.URI;
Expand Down Expand Up @@ -69,6 +70,7 @@
import org.wildfly.security.mechanism.AuthenticationMechanismException;
import org.wildfly.security.mechanism.digest.DigestQuote;
import org.wildfly.security.mechanism.digest.PasswordDigestObtainer;
import org.wildfly.security.password.interfaces.DigestPassword;

/**
* Implementation of the HTTP DIGEST authentication mechanism as defined in RFC 7616.
Expand Down Expand Up @@ -326,10 +328,19 @@ private byte[] calculateResponseDigest(MessageDigest messageDigest, byte[] hA1,
}

private byte[] getH_A1(final MessageDigest messageDigest, final String username, final String messageRealm) throws AuthenticationMechanismException {
PasswordDigestObtainer obtainer = new PasswordDigestObtainer(callbackHandler, username, messageRealm, httpDigest, getMechanismName().toLowerCase(Locale.ROOT), messageDigest, providers, null, true, false);
PasswordDigestObtainer obtainer = new PasswordDigestObtainer(callbackHandler, username, messageRealm, httpDigest, getCredentialAlgorithm(getMechanismName()), messageDigest, providers, null, true, false);
return obtainer.handleUserRealmPasswordCallbacks();
}

private String getCredentialAlgorithm(String mechanismName) {
switch (mechanismName) {
case DIGEST_NAME:
return DigestPassword.ALGORITHM_DIGEST_MD5;
default:
return mechanismName.toLowerCase(Locale.ROOT);
}
}

private String convertToken(final String name, final byte[] value) throws AuthenticationMechanismException {
if (value == null) {
throw httpDigest.mechMissingDirective(name);
Expand Down
Expand Up @@ -259,6 +259,38 @@ public void testSha256WithDigestPassword() throws Exception {
Assert.assertEquals(Status.COMPLETE, request2.getResult());
}

@Test
public void testDigestMD5Password() throws Exception {
mockDigestNonce("5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK");
Map<String, Object> props = new HashMap<>();
props.put(CONFIG_REALM, "api@example.org");
props.put("org.wildfly.security.http.validate-digest-uri", "false");
HttpServerAuthenticationMechanism mechanism = digestFactory.createAuthenticationMechanism(DIGEST_NAME, props, getCallbackHandler("J\u00E4s\u00F8n Doe", "api@example.org", "Secret, or not?", true));

TestingHttpServerRequest request1 = new TestingHttpServerRequest(null);
mechanism.evaluateRequest(request1);
Assert.assertEquals(Status.NO_AUTH, request1.getResult());
TestingHttpServerResponse response = request1.getResponse();
Assert.assertEquals(UNAUTHORIZED, response.getStatusCode());
Assert.assertEquals("Digest realm=\"api@example.org\", nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\", opaque=\"00000000000000000000000000000000\", algorithm=MD5, qop=auth", response.getAuthenticateHeader());

TestingHttpServerRequest request2 = new TestingHttpServerRequest(new String[] {
"Digest username*=UTF-8''J%C3%A4s%C3%B8n%20Doe,\n" +
" realm=\"api@example.org\",\n" +
" uri=\"/doe.json\",\n" +
" algorithm=MD5,\n" +
" nonce=\"5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK\",\n" +
" nc=00000001,\n" +
" cnonce=\"NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v\",\n" +
" qop=auth,\n" +
" response=\"" + computeDigest("/doe.json", "5TsQWLVdgBdmrQ0XsxbDODV+57QdFR34I9HAbC/RVvkK", "NTg6RKcb9boFIAS3KrFK9BGeh+iDa/sm6jUMp2wds69v", "00000001", "J\u00E4s\u00F8n Doe", "Secret, or not?", "MD5", "api@example.org", "auth", "GET") + "\",\n" +
" opaque=\"00000000000000000000000000000000\",\n" +
" userhash=false"
});
mechanism.evaluateRequest(request2);
Assert.assertEquals(Status.COMPLETE, request2.getResult());
}

private String computeDigest(String uri, String nonce, String cnonce, String nc, String username, String password, String algorithm, String realm, String qop, String method) throws NoSuchAlgorithmException {
String A1, HashA1, A2, HashA2;
MessageDigest md = MessageDigest.getInstance(algorithm);
Expand Down
Expand Up @@ -430,11 +430,13 @@ protected CallbackHandler getCallbackHandler(String username, String realm, Stri
Assert.assertEquals(username, ((NameCallback) callback).getDefaultName());
} else if (callback instanceof CredentialCallback) {
if (useDigestPassword) {
if (! DigestPassword.ALGORITHM_DIGEST_SHA_256.equals(((CredentialCallback) callback).getAlgorithm())) {
String credentialAlgorithm = ((CredentialCallback) callback).getAlgorithm();
if (! DigestPassword.ALGORITHM_DIGEST_SHA_256.equals(credentialAlgorithm) &&
! DigestPassword.ALGORITHM_DIGEST_MD5.equals(credentialAlgorithm)) {
throw new UnsupportedCallbackException(callback);
}
try {
PasswordFactory factory = PasswordFactory.getInstance(DigestPassword.ALGORITHM_DIGEST_SHA_256, ELYTRON_PASSWORD_PROVIDERS);
PasswordFactory factory = PasswordFactory.getInstance(credentialAlgorithm, ELYTRON_PASSWORD_PROVIDERS);
DigestPasswordAlgorithmSpec algorithmSpec = new DigestPasswordAlgorithmSpec(username, realm);
EncryptablePasswordSpec encryptableSpec = new EncryptablePasswordSpec(password.toCharArray(), algorithmSpec);
DigestPassword digestPassword = (DigestPassword) factory.generatePassword(encryptableSpec);
Expand Down

0 comments on commit 93c6b1d

Please sign in to comment.