org.wildfly.security.sasl.digest.DigestSaslServer Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
The newest version!
/*
* JBoss, Home of Professional Open Source
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.wildfly.security.sasl.digest;
import static org.wildfly.security.mechanism._private.ElytronMessages.saslDigest;
import static org.wildfly.security.mechanism.digest.DigestUtil.parseResponse;
import static org.wildfly.security.sasl.digest._private.DigestUtil.H_A1;
import static org.wildfly.security.sasl.digest._private.DigestUtil.QOP_AUTH;
import static org.wildfly.security.sasl.digest._private.DigestUtil.QOP_AUTH_CONF;
import static org.wildfly.security.sasl.digest._private.DigestUtil.QOP_VALUES;
import static org.wildfly.security.sasl.digest._private.DigestUtil.digestResponse;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.Provider;
import java.util.HashMap;
import java.util.Locale;
import java.util.function.Predicate;
import java.util.function.Supplier;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.AuthorizeCallback;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import org.wildfly.common.Assert;
import org.wildfly.common.bytes.ByteStringBuilder;
import org.wildfly.security.mechanism.AuthenticationMechanismException;
import org.wildfly.security.mechanism.digest.DigestQuote;
/**
* A server implementation of RFC 2831 {@code DIGEST} SASL mechanism.
*
* @author Peter Skopek
*/
final class DigestSaslServer extends AbstractDigestMechanism implements SaslServer {
private final Predicate digestUriProtocolAccepted;
private final boolean defaultRealm; // realms not defined, server name used as fallback
DigestSaslServer(String[] realms, final boolean defaultRealm, String mechanismName, String protocol, String serverName, CallbackHandler callbackHandler, Charset charset, String[] qops, String[] ciphers, Predicate digestUriProtocolAccepted, Supplier providers) throws SaslException {
super(mechanismName, protocol, serverName, callbackHandler, FORMAT.SERVER, charset, ciphers, providers);
this.realms = realms;
this.defaultRealm = defaultRealm;
this.supportedCiphers = getSupportedCiphers(ciphers);
this.qops = qops;
this.digestUriProtocolAccepted = digestUriProtocolAccepted;
}
private static final byte STEP_ONE = 1;
private static final byte STEP_THREE = 3;
private String[] realms;
private String supportedCiphers;
private int receivingMaxBuffSize = DEFAULT_MAXBUF;
private String[] qops;
private int nonceCount = -1;
private String receivedClientUri;
private String boundServerName = null;
/**
* Generates a digest challenge
*
* digest-challenge =
* 1#( realm | nonce | qop-options | stale | maxbuf | charset
* algorithm | cipher-opts | auth-param )
*
* realm = "realm" "=" <"> realm-value <">
* realm-value = qdstr-val
* nonce = "nonce" "=" <"> nonce-value <">
* nonce-value = qdstr-val
* qop-options = "qop" "=" <"> qop-list <">
* qop-list = 1#qop-value
* qop-value = "auth" | "auth-int" | "auth-conf" |
* token
* stale = "stale" "=" "true"
* maxbuf = "maxbuf" "=" maxbuf-value
* maxbuf-value = 1*DIGIT
* charset = "charset" "=" "utf-8"
* algorithm = "algorithm" "=" "md5-sess"
* cipher-opts = "cipher" "=" <"> 1#cipher-value <">
* cipher-value = "3des" | "des" | "rc4-40" | "rc4" |
* "rc4-56" | token
* auth-param = token "=" ( token | quoted-string )
* @return
*/
private byte[] generateChallenge() {
ByteStringBuilder challenge = new ByteStringBuilder();
// realms
StringBuilder sb = new StringBuilder();
for (String realm: this.realms) {
sb.append("realm=\"").append(DigestQuote.quote(realm)).append("\"").append(DELIMITER);
}
challenge.append(sb.toString().getBytes(getCharset()));
// nonce
assert nonce == null;
nonce = generateNonce();
challenge.append("nonce=\"");
challenge.append(DigestQuote.quote(nonce));
challenge.append("\"").append(DELIMITER);
// qop
if (qops != null) {
challenge.append("qop=\"");
boolean first = true;
for(String qop : qops){
if(!first) challenge.append(DELIMITER);
first = false;
challenge.append(DigestQuote.quote(qop));
}
challenge.append("\"").append(DELIMITER);
}
// maxbuf
if (receivingMaxBuffSize != DEFAULT_MAXBUF) {
challenge.append("maxbuf=");
challenge.append(String.valueOf(receivingMaxBuffSize));
challenge.append(DELIMITER);
}
// charset
if (StandardCharsets.UTF_8.equals(getCharset())) {
challenge.append("charset=");
challenge.append("utf-8");
challenge.append(DELIMITER);
}
// cipher
if (supportedCiphers != null && qops != null && arrayContains(qops, QOP_AUTH_CONF)) {
challenge.append("cipher=\"");
challenge.append(supportedCiphers);
challenge.append("\"").append(DELIMITER);
}
challenge.append("algorithm=md5-sess"); // only for backwards compatibility with HTTP Digest
return challenge.toArray();
}
private void noteDigestResponseData(HashMap parsedDigestResponse) {
byte[] data = parsedDigestResponse.get("nc");
if (data != null) {
nonceCount = Integer.parseInt(new String(data, StandardCharsets.UTF_8));
} else {
nonceCount = -1;
}
data = parsedDigestResponse.get("cipher");
if (data != null) {
cipher = new String(data, StandardCharsets.UTF_8);
} else {
cipher = "";
}
data = parsedDigestResponse.get("authzid");
if (data != null) {
authorizationId = new String(data, StandardCharsets.UTF_8);
} else {
authorizationId = null;
}
}
private byte[] validateDigestResponse(HashMap parsedDigestResponse) throws SaslException {
if (nonceCount != 1) {
throw saslDigest.mechNonceCountMustEqual(1, nonceCount).toSaslException();
}
Charset clientCharset = StandardCharsets.ISO_8859_1;
if (parsedDigestResponse.get("charset") != null) {
String cCharset = new String(parsedDigestResponse.get("charset"), StandardCharsets.UTF_8);
if (cCharset.equals("utf-8")) {
if (StandardCharsets.UTF_8.equals(getCharset())) {
clientCharset = StandardCharsets.UTF_8;
} else {
throw saslDigest.mechUnsupportedCharset("UTF-8").toSaslException();
}
} else {
throw saslDigest.mechUnknownCharset().toSaslException();
}
}
if (parsedDigestResponse.get("username") != null) {
username = new String(parsedDigestResponse.get("username"), clientCharset);
} else {
throw saslDigest.mechMissingDirective("username").toSaslException();
}
if (parsedDigestResponse.get("realm") != null) {
realm = new String(parsedDigestResponse.get("realm"), clientCharset);
} else {
realm = "";
}
if (!arrayContains(realms, realm)) {
throw saslDigest.mechDisallowedClientRealm(realm).toSaslException();
}
if (parsedDigestResponse.get("nonce") == null) {
throw saslDigest.mechMissingDirective("nonce").toSaslException();
}
byte[] nonceFromClient = parsedDigestResponse.get("nonce");
if (! MessageDigest.isEqual(nonce, nonceFromClient)) {
throw saslDigest.mechNoncesDoNotMatch().toSaslException();
}
if (parsedDigestResponse.get("cnonce") == null) {
throw saslDigest.mechMissingDirective("cnonce").toSaslException();
}
cnonce = parsedDigestResponse.get("cnonce");
if (parsedDigestResponse.get("nc") == null) {
throw saslDigest.mechMissingDirective("nc").toSaslException();
}
if (parsedDigestResponse.get("digest-uri") != null) {
receivedClientUri = new String(parsedDigestResponse.get("digest-uri"), clientCharset);
String[] parts = receivedClientUri.split("/", 2);
String expectedServerName = getServerName();
if (! digestUriProtocolAccepted.test(parts[0].toLowerCase(Locale.ROOT)) ||
(expectedServerName != null && ! expectedServerName.toLowerCase(Locale.ROOT).equals(parts[1].toLowerCase(Locale.ROOT)))
) {
throw saslDigest.mechMismatchedWrongDigestUri(receivedClientUri).toSaslException();
}
boundServerName = parts[1];
} else {
throw saslDigest.mechMissingDirective("digest-uri").toSaslException();
}
qop = QOP_AUTH;
if (parsedDigestResponse.get("qop") != null) {
qop = new String(parsedDigestResponse.get("qop"), clientCharset);
if (!arrayContains(QOP_VALUES, qop)) {
throw saslDigest.mechUnexpectedQop(qop).toSaslException();
}
if (qop != null && qop.equals(QOP_AUTH) == false) {
setWrapper(new DigestWrapper(qop.equals(QOP_AUTH_CONF)));
}
}
byte[] digest_urp = handleUserRealmPasswordCallbacks(null, true, defaultRealm);
hA1 = H_A1(messageDigest, digest_urp, nonce, cnonce, authorizationId, clientCharset);
byte[] expectedResponse = digestResponse(messageDigest, hA1, nonce, nonceCount, cnonce, authorizationId, qop, receivedClientUri, true);
// check response
if (parsedDigestResponse.get("response") == null) {
throw saslDigest.mechMissingDirective("response").toSaslException();
}
if (! MessageDigest.isEqual(expectedResponse, parsedDigestResponse.get("response"))) {
throw saslDigest.mechAuthenticationRejectedInvalidProof().toSaslException();
}
createCiphersAndKeys();
// authorization check
String authzid = (authorizationId == null || authorizationId.isEmpty()) ? username : authorizationId;
final AuthorizeCallback authorizeCallback = new AuthorizeCallback(username, authzid);
try {
tryHandleCallbacks(authorizeCallback);
} catch (UnsupportedCallbackException e) {
throw saslDigest.mechAuthorizationUnsupported(e).toSaslException();
}
if (authorizeCallback.isAuthorized()) {
authorizationId = authorizeCallback.getAuthorizedID();
} else {
throw saslDigest.mechAuthorizationFailed(username, authzid).toSaslException();
}
return createResponseAuth();
}
private byte[] createResponseAuth() {
ByteStringBuilder responseAuth = new ByteStringBuilder();
responseAuth.append("rspauth=");
byte[] response_value = digestResponse(messageDigest, hA1, nonce, nonceCount, cnonce, authorizationId, qop, receivedClientUri != null ? receivedClientUri : digestURI, false);
responseAuth.append(response_value);
return responseAuth.toArray();
}
/* (non-Javadoc)
* @see javax.security.sasl.SaslServer#getAuthorizationID()
*/
@Override
public String getAuthorizationID() {
return authorizationId;
}
/* (non-Javadoc)
* @see javax.security.sasl.SaslServer#getNegotiatedProperty(String)
*/
@Override
public Object getNegotiatedProperty(final String propName) {
assertComplete();
switch (propName) {
case Sasl.BOUND_SERVER_NAME:
return boundServerName;
case Sasl.MAX_BUFFER:
return Integer.toString(receivingMaxBuffSize);
case Sasl.QOP:
return qop;
case Sasl.STRENGTH:
if ("3des".equals(cipher)|| "rc4".equals(cipher)) {
return "high";
} else if ("des".equals(cipher)|| "rc4-56".equals(cipher)) {
return "medium";
} else {
return "low";
}
default:
return null;
}
}
/* (non-Javadoc)
* @see org.wildfly.sasl.util.AbstractSaslParticipant#init()
*/
@Override
public void init() {
setNegotiationState(STEP_ONE);
}
@Override
public byte[] evaluateResponse(byte[] response) throws SaslException {
return evaluateMessage(response);
}
@Override
protected byte[] evaluateMessage(int state, final byte[] message) throws SaslException {
switch (state) {
case STEP_ONE:
if (message != null && message.length != 0) {
throw saslDigest.mechInitialChallengeMustBeEmpty().toSaslException();
}
setNegotiationState(STEP_THREE);
return generateChallenge();
case STEP_THREE:
if (message == null || message.length == 0) {
throw saslDigest.mechClientRefusesToInitiateAuthentication().toSaslException();
}
// parse digest response
HashMap parsedDigestResponse;
try {
parsedDigestResponse = parseResponse(message, charset, false, saslDigest);
} catch (AuthenticationMechanismException e) {
throw e.toSaslException();
}
noteDigestResponseData(parsedDigestResponse);
// validate
byte[] response = validateDigestResponse(parsedDigestResponse);
negotiationComplete();
return response;
}
throw Assert.impossibleSwitchCase(state);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy