org.wildfly.security.sasl.gs2.Gs2SaslClient 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 2015 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.gs2;
import static org.wildfly.security.asn1.ASN1.APPLICATION_SPECIFIC_MASK;
import static org.wildfly.security.mechanism._private.ElytronMessages.saslGs2;
import static org.wildfly.security.sasl.gs2.Gs2Util.TOKEN_HEADER_TAG;
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.SaslException;
import org.ietf.jgss.ChannelBinding;
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.Oid;
import org.wildfly.common.Assert;
import org.wildfly.common.bytes.ByteStringBuilder;
import org.wildfly.security.asn1.DERDecoder;
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.AbstractSaslClient;
import org.wildfly.security.sasl.util.StringPrep;
/**
* SaslClient for the GS2 mechanism family as defined by
* RFC 5801.
*
* @author Farah Juma
*/
final class Gs2SaslClient extends AbstractSaslClient {
private static final int ST_INITIAL_CHALLENGE = 1;
private static final int ST_CHALLENGE_RESPONSE = 2;
private final boolean plus;
private final byte[] bindingData;
private final String bindingType;
private final Oid mechanism;
private GSSContext gssContext;
private ByteStringBuilder gs2HeaderExcludingNonStdFlag;
Gs2SaslClient(final String mechanismName, final String protocol, final String serverName, final CallbackHandler callbackHandler, final String authorizationId,
final Map props, final GSSManager gssManager, final boolean plus, final String bindingType, final byte[] bindingData) throws SaslException {
super(mechanismName, protocol, serverName, callbackHandler, authorizationId, true, saslGs2);
this.bindingType = bindingType;
this.plus = plus;
this.bindingData = bindingData;
try {
mechanism = Gs2.getMechanismForSaslName(gssManager, mechanismName);
} catch (GSSException e) {
throw saslGs2.mechMechanismToOidMappingFailed(e).toSaslException();
}
// Create a GSSContext
String acceptorNameStr = protocol + "@" + serverName;
final GSSName acceptorName;
try {
acceptorName = gssManager.createName(acceptorNameStr, GSSName.NT_HOSTBASED_SERVICE, mechanism);
} catch (GSSException e) {
throw saslGs2.mechUnableToCreateNameForAcceptor(e).toSaslException();
}
// Attempt to obtain a credential
GSSCredential credential = null;
CredentialCallback credentialCallback = new CredentialCallback(GSSKerberosCredential.class);
try {
tryHandleCallbacks(credentialCallback);
credential = credentialCallback.applyToCredential(GSSKerberosCredential.class, GSSKerberosCredential::getGssCredential);
} catch (UnsupportedCallbackException e) {
saslGs2.trace("Unable to obtain GSSCredential, ignored (act as the default initiator principal instead)", e);
}
try {
gssContext = gssManager.createContext(acceptorName, mechanism, credential, GSSContext.INDEFINITE_LIFETIME);
} catch (GSSException e) {
throw saslGs2.mechUnableToCreateGssContext(e).toSaslException();
}
try {
// Set flags
boolean delegateCredential = (credential != null);
if (props.containsKey(WildFlySasl.GS2_DELEGATE_CREDENTIAL)) {
delegateCredential = Boolean.parseBoolean((String) props.get(WildFlySasl.GS2_DELEGATE_CREDENTIAL));
}
gssContext.requestCredDeleg(delegateCredential);
gssContext.requestMutualAuth(true); // Required
} catch (GSSException e) {
throw saslGs2.mechUnableToSetGssContextRequestFlags(e).toSaslException();
}
gs2HeaderExcludingNonStdFlag = createGs2HeaderExcludingNonStdFlag();
try {
boolean gs2CbFlagPUsed = ((bindingData != null) && plus);
ChannelBinding channelBinding = Gs2Util.createChannelBinding(gs2HeaderExcludingNonStdFlag.toArray(), gs2CbFlagPUsed, bindingData);
gssContext.setChannelBinding(channelBinding);
} catch (GSSException e) {
throw saslGs2.mechUnableToSetChannelBinding(e).toSaslException();
}
}
public void dispose() throws SaslException {
try {
gssContext.dispose();
} catch (GSSException e) {
throw saslGs2.mechUnableToDisposeGssContext(e).toSaslException();
} finally {
gssContext = null;
}
}
public void init() {
setNegotiationState(ST_INITIAL_CHALLENGE);
}
protected byte[] evaluateMessage(final int state, final byte[] challenge) throws SaslException {
switch (state) {
case ST_INITIAL_CHALLENGE: {
assert gssContext.isEstablished() == false;
if ((challenge != null) && (challenge.length != 0)) {
throw saslGs2.mechInitialChallengeMustBeEmpty().toSaslException();
}
try {
byte[] response = initSecContext(gssContext, NO_BYTES, 0, 0);
// Expect at least one subsequent exchange
assert gssContext.isEstablished() == false;
setNegotiationState(ST_CHALLENGE_RESPONSE);
return modifyInitialContextToken(response);
} catch (GSSException e) {
throw saslGs2.mechUnableToCreateResponseTokenWithCause(e).toSaslException();
}
}
case ST_CHALLENGE_RESPONSE: {
assert gssContext.isEstablished() == false;
try {
byte[] response = initSecContext(gssContext, challenge, 0, challenge.length);
if (gssContext.isEstablished()) {
if (!gssContext.getMutualAuthState()) {
throw saslGs2.mechMutualAuthenticationNotEnabled().toSaslException();
}
negotiationComplete();
}
return response;
} catch (GSSException e) {
throw saslGs2.mechUnableToCreateResponseTokenWithCause(e).toSaslException();
}
}
}
throw Assert.impossibleSwitchCase(state);
}
/**
* Create a GS2 header, excluding the initial gs2-nonstd-flag, where:
*
* gs2-header = [gs2-nonstd-flag "," ] gs2-cb-flag "," [gs2-authzid] ","
*
* UTF8-1-safe = %x01-2B / %x2D-3C / %x3E-7F
* UTF8-2 = <as defined in RFC 3629 (STD 63)>
* UTF8-3 = <as defined in RFC 3629 (STD 63)>
* UTF8-4 = <as defined in RFC 3629 (STD 63)>
* UTF8-char-safe = UTF8-1-safe / UTF8-2 / UTF8-3 / UTF8-4
* saslname = 1*(UTF8-char-safe / "=2C" / "=3D")
* gs2-authzid = "a=" saslname
* gs2-nonstd-flag = "F"
* cb-name = 1*(ALPHA / DIGIT / "." / "-")
* gs2-cb-flag = ("p=" cb-name) / "n" / "y"
*
* @return the GS2 header, excluding the initial gs2-nonstd-flag
*/
private ByteStringBuilder createGs2HeaderExcludingNonStdFlag() {
ByteStringBuilder header = new ByteStringBuilder();
// gs2-cb-flag
if (bindingData != null) {
if (plus) {
header.append("p=");
header.append(bindingType);
header.append(',');
} else {
header.append("y,");
}
} else {
header.append("n,");
}
// gs2-authzid
final String authorizationId = getAuthorizationId();
if (authorizationId != null) {
header.append("a=");
StringPrep.encode(authorizationId, header, StringPrep.PROFILE_SASL_STORED | StringPrep.MAP_GS2_LOGIN_CHARS);
}
header.append(",");
return header;
}
/**
* Modify the given token by removing the initial context token header, if present, and
* prefix the resulting token with a GS2 header.
*
* @param token the initial context token
* @return the modified token
* @throws GSSException if the given initial context token is invalid
*/
private byte[] modifyInitialContextToken(byte[] token) throws GSSException {
boolean nonStandard = false;
if (token[0] == TOKEN_HEADER_TAG) {
// Remove the initial context token header, where:
// InitialContextToken ::=
// [APPLICATION 0] IMPLICIT SEQUENCE {
// thisMech MechType,
// innerContextToken ANY DEFINED BY thisMech
// -- contents mechanism-specific
// -- ASN.1 structure not required
// }
// MechType ::= OBJECT IDENTIFIER
final DERDecoder decoder = new DERDecoder(token);
decoder.decodeImplicit(APPLICATION_SPECIFIC_MASK, 0);
decoder.startSequence();
String decodedOid = decoder.decodeObjectIdentifier();
if (! mechanism.equals(new Oid(decodedOid))) {
throw new GSSException(GSSException.DEFECTIVE_TOKEN);
}
token = decoder.drain();
} else {
// Set gs2-nonstd-flag
nonStandard = true;
}
ByteStringBuilder modifiedToken = new ByteStringBuilder();
if (nonStandard) {
modifiedToken.append("F,");
}
modifiedToken.append(gs2HeaderExcludingNonStdFlag);
modifiedToken.append(token);
return modifiedToken.toArray();
}
private static byte[] initSecContext(final GSSContext gssContext, final byte[] inputBuf, final int offset, final int len) throws GSSException {
final ClassLoader old = doPrivileged(new SetContextClassLoaderFromClassAction(Gs2SaslClient.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();
}
}