org.wildfly.security.sasl.gssapi.GssapiClient Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS 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).
/*
* 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.gssapi;
import static org.wildfly.security.mechanism._private.ElytronMessages.saslGssapi;
import static org.wildfly.security.mechanism.gssapi.GSSCredentialSecurityFactory.KERBEROS_V5;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp;
import org.wildfly.common.Assert;
import org.wildfly.security.auth.callback.CredentialCallback;
import org.wildfly.security.credential.GSSKerberosCredential;
import org.wildfly.security.manager.action.SetContextClassLoaderAction;
import org.wildfly.security.manager.action.SetContextClassLoaderFromClassAction;
import org.wildfly.security.sasl.WildFlySasl;
import org.wildfly.security.sasl.util.SaslMechanismInformation;
/**
* SaslClient for the GSSAPI mechanism as defined by RFC 4752
*
* @author Darran Lofthouse
*/
final class GssapiClient extends AbstractGssapiMechanism implements SaslClient {
private static final int INITIAL_CHALLENGE_STATE = 1;
private static final int CHALLENGE_RESPONSE_STATE = 2;
private static final int SECURITY_LAYER_NEGOTIATION_STATE = 3;
private final String authorizationId;
GssapiClient(final String protocol, final String serverName, final Map props,
final CallbackHandler callbackHandler, final String authorizationId) throws SaslException {
super(SaslMechanismInformation.Names.GSSAPI, protocol, serverName, props, callbackHandler);
this.authorizationId = authorizationId;
// Initialise our GSSContext
GSSManager manager = GSSManager.getInstance();
String acceptorNameString = protocol + "@" + serverName;
saslGssapi.tracef("Acceptor Name '%s'", acceptorNameString);
final GSSName acceptorName;
try {
// The client can use other name types but this should be added to the config.
acceptorName = manager.createName(acceptorNameString, GSSName.NT_HOSTBASED_SERVICE, KERBEROS_V5);
} catch (GSSException e) {
throw saslGssapi.mechUnableToCreateNameForAcceptor(e).toSaslException();
}
// Pull the credential if we have it.
GSSCredential credential = null;
Object credObj = props.get(Sasl.CREDENTIALS);
if (credObj instanceof GSSCredential) {
saslGssapi.trace("Using GSSCredential supplied in properties.");
credential = (GSSCredential) credObj;
} else if (credObj instanceof GSSKerberosCredential) {
saslGssapi.trace("Using GSSCredential supplied in properties.");
credential = ((GSSKerberosCredential) credObj).getGssCredential();
}
if (credential == null) {
CredentialCallback callback = new CredentialCallback(GSSKerberosCredential.class);
try {
tryHandleCallbacks(callback);
GSSKerberosCredential kerberosCredential = callback.getCredential(GSSKerberosCredential.class);
if (kerberosCredential != null) {
saslGssapi.tracef("GSSCredential obtained from CredentialCallback.");
credential = kerberosCredential.getGssCredential();
} else {
saslGssapi.trace("No GSSCredential obtained from CredentialCallback.");
}
} catch (UnsupportedCallbackException e) {
saslGssapi.trace("CallbackHandler does not support CredentialCallback", e);
}
}
final GSSContext gssContext;
try {
gssContext = manager.createContext(acceptorName, KERBEROS_V5, credential, GSSContext.INDEFINITE_LIFETIME);
} catch (GSSException e) {
throw saslGssapi.mechUnableToCreateGssContext(e).toSaslException();
}
try {
// JDK only sets this if a credential was supplied, we should support a config override.
// i.e. we may have a credential because it was delegated to us - doesn't mean we want
// to delegate it further - at same point we may have a Subject on ACC and still want to delegate.
boolean delegate = credential != null;
if (props.containsKey(WildFlySasl.GSSAPI_DELEGATE_CREDENTIAL)) {
delegate = Boolean.parseBoolean((String) props.get(WildFlySasl.GSSAPI_DELEGATE_CREDENTIAL));
}
saslGssapi.tracef("Delegating credential = %b", delegate);
if (delegate) {
gssContext.requestCredDeleg(true);
}
// The client must pass the integ_req_flag of true.
gssContext.requestInteg(true);
// This was requested so that integrity protection can be used to negotiate the security layer,
// further integrity protection will be based on the negotiated security layer.
// requestMutualAuth if: -
// 1 - The client requests it.
// 2 - The client will be requesting a security layer. Will interpret as may be requesting as
// client and server could agree auth only.
boolean serverAuth = false;
if (props.containsKey(Sasl.SERVER_AUTH)) {
serverAuth = Boolean.parseBoolean((String) props.get(Sasl.SERVER_AUTH));
}
boolean mayRequireSecurityLayer = mayRequireSecurityLater(orderedQops);
boolean requestMutualAuth = serverAuth || mayRequireSecurityLayer;
saslGssapi.tracef("Setting requering mutual authentication to %b", requestMutualAuth);
gssContext.requestMutualAuth(requestMutualAuth);
// Request sequence detection if a security layer could be requested.
if (mayRequireSecurityLayer) {
saslGssapi.trace("Requesting sequence detection.");
gssContext.requestSequenceDet(true);
}
// Need to set this is we may want confidentiality, integrity is always requested.
for (QOP current : orderedQops) {
if (current == QOP.AUTH_CONF) {
saslGssapi.trace("Requesting confidentiality");
gssContext.requestConf(true);
break;
}
}
} catch (GSSException e) {
throw saslGssapi.mechUnableToSetGssContextRequestFlags(e).toSaslException();
}
// Channel Binding Is Not Supported
this.gssContext = gssContext;
}
private boolean mayRequireSecurityLater(final QOP[] preferredQop) {
for (QOP current : preferredQop) {
if (current == QOP.AUTH_INT || current == QOP.AUTH_CONF) {
return true;
}
}
return false;
}
private QOP findAgreeableQop(final byte securityLayer) throws SaslException {
for (QOP current : orderedQops) {
if (current.includedBy(securityLayer) && isCompatibleWithGssContext(current)) {
return current;
}
}
throw saslGssapi.mechInsufficientQopsAvailable().toSaslException();
}
private boolean isCompatibleWithGssContext(final QOP qop) {
switch (qop) {
case AUTH_INT:
return gssContext.getIntegState();
case AUTH_CONF:
return gssContext.getIntegState() && gssContext.getConfState();
default:
return true;
}
}
@Override
public void init() {
setNegotiationState(INITIAL_CHALLENGE_STATE);
}
@Override
public boolean hasInitialResponse() {
return true;
}
@Override
public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
return evaluateMessage(challenge);
}
@Override
protected byte[] evaluateMessage(int state, final byte[] message) throws SaslException {
switch (state) {
case INITIAL_CHALLENGE_STATE:
// GSSAPI is a client first mechanism, this state both verifies that requirement is met and
// provides the first token from the client
assert gssContext.isEstablished() == false;
if (message.length > 0) {
throw saslGssapi.mechInitialChallengeMustBeEmpty().toSaslException();
}
try {
byte[] response = initSecContext(gssContext, NO_BYTES, 0, 0);
if (gssContext.isEstablished()) {
saslGssapi.trace("GSSContext established, transitioning to negotiate security layer.");
setNegotiationState(SECURITY_LAYER_NEGOTIATION_STATE);
} else {
saslGssapi.trace("GSSContext not established, expecting subsequent exchanges.");
setNegotiationState(CHALLENGE_RESPONSE_STATE);
}
return response;
} catch (GSSException e) {
throw saslGssapi.mechUnableToCreateResponseToken(e).toSaslException();
}
case CHALLENGE_RESPONSE_STATE:
// This state is to handle the subsequent exchange of tokens up until the point the
// GSSContext is established
assert gssContext.isEstablished() == false;
try {
byte[] response = initSecContext(gssContext, message, 0, message.length);
if (gssContext.isEstablished()) {
saslGssapi.trace("GSSContext established, transitioning to negotiate security layer.");
setNegotiationState(SECURITY_LAYER_NEGOTIATION_STATE);
if (response == null) response = NO_BYTES;
} else {
saslGssapi.trace("GSSContext not established, expecting subsequent exchanges.");
}
return response;
} catch (GSSException e) {
throw saslGssapi.mechUnableToHandleResponseFromServer(e).toSaslException();
}
case SECURITY_LAYER_NEGOTIATION_STATE:
assert gssContext.isEstablished();
MessageProp msgProp = new MessageProp(0, false);
try {
byte[] unwrapped = gssContext.unwrap(message, 0, message.length, msgProp);
if (unwrapped.length != 4) {
throw saslGssapi.mechBadLengthOfMessageForNegotiatingSecurityLayer().toSaslException();
}
byte qopByte = unwrapped[0];
selectedQop = findAgreeableQop(qopByte);
maxBuffer = networkOrderBytesToInt(unwrapped, 1, 3);
saslGssapi.tracef("Selected QOP=%s, maxBuffer=%d", selectedQop, maxBuffer);
if (relaxComplianceChecks == false && maxBuffer > 0 && (qopByte & QOP.AUTH_INT.getValue()) == 0
&& (qopByte & QOP.AUTH_CONF.getValue()) == 0) {
throw saslGssapi.mechReceivedMaxMessageSizeWhenNoSecurityLayer(maxBuffer).toSaslException();
}
maxBuffer = gssContext.getWrapSizeLimit(0, selectedQop == QOP.AUTH_CONF, maxBuffer);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(selectedQop.getValue());
if (selectedQop == QOP.AUTH) {
// No security layer selected to must set response to 000.
baos.write(new byte[] { 0x00, 0x00, 0x00 });
} else {
actualMaxReceiveBuffer = configuredMaxReceiveBuffer!=0 ? configuredMaxReceiveBuffer : maxBuffer;
saslGssapi.tracef("Out max buffer %d", actualMaxReceiveBuffer);
baos.write(intToNetworkOrderBytes(actualMaxReceiveBuffer));
}
if (authorizationId != null) {
baos.write(authorizationId.getBytes(StandardCharsets.UTF_8));
}
byte[] response = baos.toByteArray();
msgProp = new MessageProp(0, false);
response = gssContext.wrap(response, 0, response.length, msgProp);
if (selectedQop != QOP.AUTH) {
saslGssapi.trace("Setting message wrapper.");
setWrapper(new GssapiWrapper(selectedQop == QOP.AUTH_CONF));
}
saslGssapi.trace("Negotiation Complete");
negotiationComplete();
return response;
} catch (IOException e) {
throw saslGssapi.mechUnableToCreateResponseToken(e).toSaslException();
} catch (GSSException e) {
throw saslGssapi.mechUnableToUnwrapSecurityLayerNegotiationMessage(e).toSaslException();
}
}
throw Assert.impossibleSwitchCase(state);
}
private static byte[] initSecContext(final GSSContext gssContext, final byte[] inputBuf, final int offset, final int len) throws GSSException {
final ClassLoader old = doPrivileged(new SetContextClassLoaderFromClassAction(GssapiClient.class));
try {
return gssContext.initSecContext(inputBuf, offset, len);
} finally {
doPrivileged(new SetContextClassLoaderAction(old));
}
}
private static T doPrivileged(final PrivilegedAction action) {
return System.getSecurityManager() != null ? AccessController.doPrivileged(action) : action.run();
}
}