/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.wss4j.common.spnego; import java.security.Principal; import java.security.PrivilegedActionException; import java.util.Set; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import org.apache.wss4j.common.ext.WSSecurityException; import org.apache.wss4j.common.ext.WSSecurityException.ErrorCode; import org.apache.wss4j.common.kerberos.KerberosClientExceptionAction; import org.apache.wss4j.common.kerberos.KerberosContext; import org.apache.wss4j.common.kerberos.KerberosServiceContext; import org.apache.wss4j.common.kerberos.KerberosServiceExceptionAction; import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.MessageProp; /** * This class wraps a GSSContext and provides some functionality to obtain and validate spnego tokens. */ public class SpnegoTokenContext { private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(SpnegoTokenContext.class); private GSSContext secContext; private byte[] token; private boolean mutualAuth; private SpnegoClientAction clientAction; private SpnegoServiceAction serviceAction; private GSSCredential delegationCredential; private Principal spnegoPrincipal; /** * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this * BinarySecurityToken. * @param jaasLoginModuleName the JAAS Login Module name to use * @param callbackHandler a CallbackHandler instance to retrieve a password (optional) * @param serviceName the desired Kerberized service * @throws WSSecurityException */ public void retrieveServiceTicket( String jaasLoginModuleName, CallbackHandler callbackHandler, String serviceName ) throws WSSecurityException { retrieveServiceTicket(jaasLoginModuleName, callbackHandler, serviceName, false); } /** * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this * BinarySecurityToken. * @param jaasLoginModuleName the JAAS Login Module name to use * @param callbackHandler a CallbackHandler instance to retrieve a password (optional) * @param serviceName the desired Kerberized service * @param isUsernameServiceNameForm * @throws WSSecurityException */ public void retrieveServiceTicket( String jaasLoginModuleName, CallbackHandler callbackHandler, String serviceName, boolean isUsernameServiceNameForm ) throws WSSecurityException { retrieveServiceTicket(jaasLoginModuleName, callbackHandler, serviceName, isUsernameServiceNameForm, false, null); } /** * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this * BinarySecurityToken. * @param jaasLoginModuleName the JAAS Login Module name to use * @param callbackHandler a CallbackHandler instance to retrieve a password (optional) * @param serviceName the desired Kerberized service * @param isUsernameServiceNameForm * @param requestCredDeleg Whether to request credential delegation or not * @param delegationCredential The delegation credential to use * @throws WSSecurityException */ public void retrieveServiceTicket( String jaasLoginModuleName, CallbackHandler callbackHandler, String serviceName, boolean isUsernameServiceNameForm, boolean requestCredDeleg, GSSCredential delegationCredential ) throws WSSecurityException { // Get a TGT from the KDC using JAAS LoginContext loginContext = null; try { if (callbackHandler == null) { loginContext = new LoginContext(jaasLoginModuleName); } else { loginContext = new LoginContext(jaasLoginModuleName, callbackHandler); } loginContext.login(); } catch (LoginException ex) { LOG.debug(ex.getMessage(), ex); throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, ex, "kerberosLoginError", new Object[] {ex.getMessage()}); } LOG.debug("Successfully authenticated to the TGT"); Subject clientSubject = loginContext.getSubject(); Set<Principal> clientPrincipals = clientSubject.getPrincipals(); if (clientPrincipals.isEmpty()) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "kerberosLoginError", new Object[] {"No Client principals found after login"}); } // Get the service ticket if (clientAction != null) { clientAction.setServiceName(serviceName); clientAction.setMutualAuth(mutualAuth); clientAction.setUserNameServiceForm(isUsernameServiceNameForm); token = Subject.doAs(clientSubject, clientAction); if (token == null) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "kerberosServiceTicketError" ); } secContext = clientAction.getContext(); } else { KerberosClientExceptionAction action = new KerberosClientExceptionAction(null, serviceName, isUsernameServiceNameForm, requestCredDeleg, delegationCredential, true, mutualAuth); KerberosContext krbCtx = null; try { krbCtx = (KerberosContext) Subject.doAs(clientSubject, action); token = krbCtx.getKerberosToken(); if (token == null) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "kerberosServiceTicketError" ); } secContext = krbCtx.getGssContext(); } catch (PrivilegedActionException e) { Throwable cause = e.getCause(); if (cause instanceof WSSecurityException) { throw (WSSecurityException) cause; } else { throw new WSSecurityException( ErrorCode.FAILURE, new Exception(cause), "kerberosServiceTicketError" ); } } } LOG.debug("Successfully retrieved a service ticket"); } /** * Validate a service ticket. * @param jaasLoginModuleName * @param callbackHandler * @param serviceName * @param ticket * @throws WSSecurityException */ public void validateServiceTicket( String jaasLoginModuleName, CallbackHandler callbackHandler, String serviceName, byte[] ticket ) throws WSSecurityException { validateServiceTicket(jaasLoginModuleName, callbackHandler, serviceName, false, ticket); } /** * Validate a service ticket. * @param jaasLoginModuleName * @param callbackHandler * @param serviceName * @param ticket * @throws WSSecurityException */ public void validateServiceTicket( String jaasLoginModuleName, CallbackHandler callbackHandler, String serviceName, boolean isUsernameServiceNameForm, byte[] ticket ) throws WSSecurityException { // Get a TGT from the KDC using JAAS LoginContext loginContext = null; try { if (callbackHandler == null) { loginContext = new LoginContext(jaasLoginModuleName); } else { loginContext = new LoginContext(jaasLoginModuleName, callbackHandler); } loginContext.login(); } catch (LoginException ex) { LOG.debug(ex.getMessage(), ex); throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, ex, "kerberosLoginError", new Object[] {ex.getMessage()}); } LOG.debug("Successfully authenticated to the TGT"); // Get the service name to use - fall back on the principal Subject subject = loginContext.getSubject(); String service = serviceName; if (service == null) { Set<Principal> principals = subject.getPrincipals(); if (principals.isEmpty()) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "kerberosLoginError", new Object[] {"No Client principals found after login"}); } service = principals.iterator().next().getName(); } // Validate the ticket if (serviceAction != null) { serviceAction.setTicket(ticket); serviceAction.setServiceName(service); serviceAction.setUsernameServiceNameForm(isUsernameServiceNameForm); token = Subject.doAs(subject, serviceAction); secContext = serviceAction.getContext(); } else { KerberosServiceExceptionAction action = new KerberosServiceExceptionAction(ticket, service, isUsernameServiceNameForm, true); KerberosServiceContext krbCtx = null; try { krbCtx = (KerberosServiceContext) Subject.doAs(subject, action); token = krbCtx.getKerberosToken(); if (token == null) { throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, "kerberosServiceTicketError" ); } secContext = krbCtx.getGssContext(); delegationCredential = krbCtx.getDelegationCredential(); spnegoPrincipal = krbCtx.getPrincipal(); } catch (PrivilegedActionException e) { Throwable cause = e.getCause(); if (cause instanceof WSSecurityException) { throw (WSSecurityException) cause; } else { throw new WSSecurityException( ErrorCode.FAILURE, new Exception(cause), "kerberosServiceTicketError" ); } } } LOG.debug("Successfully validated a service ticket"); } /** * Whether to enable mutual authentication or not. This only applies to retrieve service ticket. */ public void setMutualAuth(boolean mutualAuthentication) { mutualAuth = mutualAuthentication; } /** * Get the SPNEGO token that was created. */ public byte[] getToken() { return token; } /** * Whether a connection has been established (at the service side) */ public boolean isEstablished() { if (secContext == null) { return false; } return secContext.isEstablished(); } /** * Unwrap a key */ public byte[] unwrapKey(byte[] secret) throws WSSecurityException { MessageProp mProp = new MessageProp(0, true); try { return secContext.unwrap(secret, 0, secret.length, mProp); } catch (GSSException e) { LOG.debug("Error in cleaning up a GSS context", e); throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, e, "spnegoKeyError" ); } } /** * Wrap a key */ public byte[] wrapKey(byte[] secret) throws WSSecurityException { MessageProp mProp = new MessageProp(0, true); try { return secContext.wrap(secret, 0, secret.length, mProp); } catch (GSSException e) { LOG.debug("Error in cleaning up a GSS context", e); throw new WSSecurityException( WSSecurityException.ErrorCode.FAILURE, e, "spnegoKeyError" ); } } /** * Set a custom SpnegoClientAction implementation to use */ public void setSpnegoClientAction(SpnegoClientAction spnegoClientAction) { this.clientAction = spnegoClientAction; } /** * Set a custom SpnegoServiceAction implementation to use */ public void setSpnegoServiceAction(SpnegoServiceAction spnegoServiceAction) { this.serviceAction = spnegoServiceAction; } public void clear() { token = null; mutualAuth = false; delegationCredential = null; spnegoPrincipal = null; try { secContext.dispose(); } catch (GSSException e) { LOG.debug("Error in cleaning up a GSS context", e); } } public GSSCredential getDelegationCredential() { return delegationCredential; } public Principal getSpnegoPrincipal() { return spnegoPrincipal; } }