org.wildfly.security.sasl.gssapi.GssapiServer Maven / Gradle / Ivy
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.gssapi;
import static org.wildfly.security.mechanism._private.ElytronMessages.saslGssapi;
import static org.wildfly.security.mechanism.gssapi.GSSCredentialSecurityFactory.KERBEROS_V5;
import static org.wildfly.security.sasl.WildFlySasl.GSSAPI_CREATE_NAME_GSS_INIT;
import static org.wildfly.security.sasl.util.SaslMechanismInformation.Names.GSSAPI;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import javax.security.auth.callback.Callback;
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.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.ietf.jgss.Oid;
import org.wildfly.common.Assert;
import org.wildfly.security.auth.callback.IdentityCredentialCallback;
import org.wildfly.security.auth.callback.ServerCredentialCallback;
import org.wildfly.security.credential.GSSKerberosCredential;
/**
* SaslServer for the GSSAPI mechanism as defined by RFC 4752
*
* @author Darran Lofthouse
*/
final class GssapiServer extends AbstractGssapiMechanism implements SaslServer {
private static final int ACCEPTOR_STATE = 1;
private static final int SECURITY_LAYER_ADVERTISER = 2;
private static final int SECURITY_LAYER_RECEIVER = 3;
private String authorizationId;
private String boundServerName;
private byte offeredSecurityLayer;
GssapiServer(final String protocol, final String serverName, final Map props,
final CallbackHandler callbackHandler) throws SaslException {
super(GSSAPI, protocol, serverName, props, callbackHandler);
// Initialise our GSSContext
GSSManager manager = GSSManager.getInstance();
// JDK-8194073 workaround (for Oracle JDK + native Kerberos)
if (props.containsKey(GSSAPI_CREATE_NAME_GSS_INIT) && Boolean.parseBoolean((String) props.get(GSSAPI_CREATE_NAME_GSS_INIT))) {
try { // createName call ensure correct GSSManager initialization
manager.createName("dummy", GSSName.NT_USER_NAME, KERBEROS_V5);
saslGssapi.trace("createName workaround for native GSS initialization applied");
} catch (GSSException e1) {
saslGssapi.trace("Exception while applying createName workaround for native GSS initialization", e1);
}
}
GSSContext gssContext = null;
GSSCredential ourCredential = null;
ServerCredentialCallback gssCredentialCallback = new ServerCredentialCallback(GSSKerberosCredential.class);
try {
saslGssapi.trace("Obtaining GSSCredential for the service from callback handler...");
callbackHandler.handle(new Callback[] { gssCredentialCallback });
ourCredential = gssCredentialCallback.applyToCredential(GSSKerberosCredential.class, GSSKerberosCredential::getGssCredential);
} catch (IOException e) {
throw saslGssapi.mechCallbackHandlerFailedForUnknownReason(e).toSaslException();
} catch (UnsupportedCallbackException e) {
saslGssapi.trace("Unable to obtain GSSCredential from CallbackHandler", e);
}
try {
if (ourCredential == null) {
GSSName ourName;
if (serverName != null) {
// According to the Javadoc we will have a protocol and server name.
String localName = protocol + "@" + serverName;
saslGssapi.tracef("Our name is '%s'", localName);
ourName = manager.createName(localName, GSSName.NT_HOSTBASED_SERVICE, KERBEROS_V5);
} else {
saslGssapi.tracef("Our name is unbound");
ourName = null;
}
ourCredential = manager.createCredential(ourName, GSSContext.INDEFINITE_LIFETIME, KERBEROS_V5,
GSSCredential.ACCEPT_ONLY);
}
gssContext = manager.createContext(ourCredential);
} catch (GSSException e) {
throw saslGssapi.mechUnableToCreateGssContext(e).toSaslException();
}
// We don't request integrity or confidentiality as that is only
// supported on the client side.
this.gssContext = gssContext;
}
@Override
public void init() {
setNegotiationState(ACCEPTOR_STATE);
}
@Override
public String getAuthorizationID() {
assertComplete();
return authorizationId;
}
@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 ACCEPTOR_STATE:
assert gssContext.isEstablished() == false;
try {
byte[] response = gssContext.acceptSecContext(message, 0, message.length);
if (gssContext.isEstablished()) {
Oid actualMech = gssContext.getMech();
saslGssapi.tracef("Negotiated mechanism %s", actualMech);
if (KERBEROS_V5.equals(actualMech) == false) {
throw saslGssapi.mechNegotiatedMechanismWasNotKerberosV5().toSaslException();
}
setNegotiationState(SECURITY_LAYER_ADVERTISER);
if (response == null || response.length == 0) {
saslGssapi.trace("No response so triggering next state immediately.");
return evaluateMessage(null);
}
} else {
saslGssapi.trace("GSSContext not established, expecting subsequent exchange.");
}
return response;
} catch (GSSException e) {
throw saslGssapi.mechUnableToAcceptClientMessage(e).toSaslException();
}
case SECURITY_LAYER_ADVERTISER:
// This state expects at most to be called with an empty message, it will then advertise
// the currently support security layer and transition to the next state to await a response
if (message != null && message.length > 0) {
throw saslGssapi.mechInitialChallengeMustBeEmpty().toSaslException();
}
byte[] response = new byte[4];
byte supportedSecurityLayers = 0x00;
boolean offeringSecurityLayer = false;
for (QOP current : orderedQops) {
switch (current) {
case AUTH_INT:
if (gssContext.getIntegState()) {
supportedSecurityLayers |= current.getValue();
offeringSecurityLayer = true;
saslGssapi.trace("Offering AUTH_INT");
} else {
saslGssapi.trace("No integrity protection so unable to offer AUTH_INT");
}
break;
case AUTH_CONF:
if (gssContext.getConfState()) {
supportedSecurityLayers |= current.getValue();
offeringSecurityLayer = true;
saslGssapi.trace("Offering AUTH_CONF");
} else {
saslGssapi.trace("No confidentiality available so unable to offer AUTH_CONF");
}
break;
default:
supportedSecurityLayers |= current.getValue();
}
}
if (supportedSecurityLayers == 0x00) {
throw saslGssapi.mechInsufficientQopsAvailable().toSaslException();
}
response[0] = supportedSecurityLayers;
try {
byte[] length;
if (offeringSecurityLayer) {
saslGssapi.tracef("Our max buffer size %d", configuredMaxReceiveBuffer);
length = intToNetworkOrderBytes(configuredMaxReceiveBuffer);
} else {
saslGssapi.trace("Not offering a security layer so zero length.");
length = new byte[] { 0x00, 0x00, 0x00 };
}
System.arraycopy(length, 0, response, 1, 3);
MessageProp msgProp = new MessageProp(0, false);
response = gssContext.wrap(response, 0, 4, msgProp);
} catch (GSSException e) {
throw saslGssapi.mechUnableToGenerateChallenge(e).toSaslException();
}
saslGssapi.trace("Transitioning to receive chosen security layer from client");
offeredSecurityLayer = supportedSecurityLayers;
setNegotiationState(SECURITY_LAYER_RECEIVER);
return response;
case SECURITY_LAYER_RECEIVER:
MessageProp msgProp = new MessageProp(0, false);
byte[] unwrapped;
try {
unwrapped = gssContext.unwrap(message, 0, message.length, msgProp);
} catch (GSSException e) {
throw saslGssapi.mechUnableToUnwrapMessage(e).toSaslException();
}
if (unwrapped.length < 4) {
throw saslGssapi.mechInvalidMessageOnUnwrapping(unwrapped.length).toSaslException();
}
// What we offered and our own list of QOP could be different so we compare against what we offered as we know we
// only offered it if the underlying GssContext also supports it.
if ((offeredSecurityLayer & unwrapped[0]) == 0x00) {
throw saslGssapi.mechSelectedUnofferedQop().toSaslException();
}
QOP selectedQop = QOP.mapFromValue(unwrapped[0]);
assert selectedQop != null;
maxBuffer = networkOrderBytesToInt(unwrapped, 1, 3);
saslGssapi.tracef("Client selected security layer %s, with maxBuffer of %d", selectedQop, maxBuffer);
if (relaxComplianceChecks == false && selectedQop == QOP.AUTH && maxBuffer != 0) {
throw saslGssapi.mechNoSecurityLayerButLengthReceived().toSaslException();
}
try {
maxBuffer = gssContext.getWrapSizeLimit(0, selectedQop == QOP.AUTH_CONF, maxBuffer);
} catch (GSSException e) {
throw saslGssapi.mechUnableToGetMaximumSizeOfMessage(e).toSaslException();
}
this.selectedQop = selectedQop;
try {
String targetName = gssContext.getTargName().toString();
String[] targetNameParts = targetName.split("[/@]");
boundServerName = targetNameParts.length > 1 ? targetNameParts[1] : targetName;
} catch (GSSException e) {
throw saslGssapi.mechUnableToDetermineBoundServerName(e).toSaslException();
}
final String authenticationId;
try {
authenticationId = gssContext.getSrcName().toString();
} catch (GSSException e) {
throw saslGssapi.mechUnableToDeterminePeerName(e).toSaslException();
}
final String authorizationId;
if (unwrapped.length > 4) {
authorizationId = new String(unwrapped, 4, unwrapped.length - 4, StandardCharsets.UTF_8);
} else {
authorizationId = authenticationId;
}
saslGssapi.tracef("Authentication ID=%s, Authorization ID=%s", authenticationId, authorizationId);
AuthorizeCallback cb = new AuthorizeCallback(authenticationId, authorizationId);
handleCallbacks(new Callback[] {cb});
if (cb.isAuthorized() == false) {
throw saslGssapi.mechAuthorizationFailed(authenticationId, authorizationId).toSaslException();
}
this.authorizationId = authorizationId;
if (selectedQop != QOP.AUTH) {
saslGssapi.trace("Setting message wrapper.");
setWrapper(new GssapiWrapper(selectedQop == QOP.AUTH_CONF));
}
try {
GSSCredential gssCredential = gssContext.getDelegCred();
if (gssCredential != null) {
tryHandleCallbacks(new IdentityCredentialCallback(new GSSKerberosCredential(gssCredential), true));
} else {
saslGssapi.trace("No GSSCredential delegated during authentication.");
}
} catch (UnsupportedCallbackException | GSSException e) {
// ignored
} catch (SaslException e) {
throw e;
}
saslGssapi.trace("Negotiation complete.");
negotiationComplete();
// By now this is the end.
return null;
}
throw Assert.impossibleSwitchCase(state);
}
@Override
public Object getNegotiatedProperty(String propName) {
assertComplete();
if (Sasl.BOUND_SERVER_NAME.equals(propName)) {
return boundServerName;
}
return super.getNegotiatedProperty(propName);
}
}