View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.wss4j.dom.validate;
21  
22  import java.security.Key;
23  import java.security.Principal;
24  import java.security.PrivilegedActionException;
25  import java.util.Set;
26  
27  import javax.security.auth.Subject;
28  import javax.security.auth.callback.CallbackHandler;
29  import javax.security.auth.login.LoginContext;
30  import javax.security.auth.login.LoginException;
31  
32  import org.apache.wss4j.common.ext.WSSecurityException;
33  import org.apache.wss4j.common.ext.WSSecurityException.ErrorCode;
34  import org.apache.wss4j.common.kerberos.KerberosServiceContext;
35  import org.apache.wss4j.common.kerberos.KerberosServiceExceptionAction;
36  import org.apache.wss4j.common.kerberos.KerberosTokenDecoder;
37  import org.apache.wss4j.common.kerberos.KerberosTokenDecoderException;
38  import org.apache.wss4j.common.token.BinarySecurity;
39  import org.apache.wss4j.dom.handler.RequestData;
40  import org.apache.wss4j.dom.message.token.KerberosSecurity;
41  
42  /**
43   */
44  public class KerberosTokenValidator implements Validator {
45  
46      private static final org.slf4j.Logger LOG =
47          org.slf4j.LoggerFactory.getLogger(KerberosTokenValidator.class);
48  
49      private String serviceName;
50      private CallbackHandler callbackHandler;
51      private String contextName;
52      private KerberosTokenDecoder kerberosTokenDecoder;
53      private boolean isUsernameServiceNameForm;
54      private boolean spnego;
55  
56      /**
57       * Get the JAAS Login context name to use.
58       * @return the JAAS Login context name to use
59       */
60      public String getContextName() {
61          return contextName;
62      }
63  
64      /**
65       * Set the JAAS Login context name to use.
66       * @param contextName the JAAS Login context name to use
67       */
68      public void setContextName(String contextName) {
69          this.contextName = contextName;
70      }
71  
72      /**
73       * Get the CallbackHandler to use with the LoginContext
74       * @return the CallbackHandler to use with the LoginContext
75       */
76      public CallbackHandler getCallbackHandler() {
77          return callbackHandler;
78      }
79  
80      /**
81       * Set the CallbackHandler to use with the LoginContext. It can be null.
82       * @param callbackHandler the CallbackHandler to use with the LoginContext
83       */
84      public void setCallbackHandler(CallbackHandler callbackHandler) {
85          this.callbackHandler = callbackHandler;
86      }
87  
88      /**
89       * The name of the service to use when contacting the KDC. This value can be null, in which
90       * case it defaults to the current principal name.
91       * @param serviceName the name of the service to use when contacting the KDC
92       */
93      public void setServiceName(String serviceName) {
94          this.serviceName = serviceName;
95      }
96  
97      /**
98       * Get the name of the service to use when contacting the KDC. This value can be null, in which
99       * case it defaults to the current principal name.
100      * @return the name of the service to use when contacting the KDC
101      */
102     public String getServiceName() {
103         return serviceName;
104     }
105 
106     /**
107      * Get the KerberosTokenDecoder instance used to extract a session key from the received Kerberos
108      * token.
109      * @return the KerberosTokenDecoder instance used to extract a session key
110      */
111     public KerberosTokenDecoder getKerberosTokenDecoder() {
112         return kerberosTokenDecoder;
113     }
114 
115     /**
116      * Set the KerberosTokenDecoder instance used to extract a session key from the received Kerberos
117      * token.
118      * @param kerberosTokenDecoder the KerberosTokenDecoder instance used to extract a session key
119      */
120     public void setKerberosTokenDecoder(KerberosTokenDecoder kerberosTokenDecoder) {
121         this.kerberosTokenDecoder = kerberosTokenDecoder;
122     }
123 
124     /**
125      * Validate the credential argument. It must contain a non-null BinarySecurityToken.
126      *
127      * @param credential the Credential to be validated
128      * @param data the RequestData associated with the request
129      * @throws WSSecurityException on a failed validation
130      */
131     public Credential validate(Credential credential, RequestData data) throws WSSecurityException {
132         if (credential == null || credential.getBinarySecurityToken() == null) {
133             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "noCredential");
134         }
135 
136         BinarySecurity binarySecurity = credential.getBinarySecurityToken();
137         if (!(binarySecurity instanceof KerberosSecurity)) {
138             return credential;
139         }
140 
141         if (LOG.isDebugEnabled()) {
142             try {
143                 String jaasAuth = System.getProperty("java.security.auth.login.config");
144                 String krbConf = System.getProperty("java.security.krb5.conf");
145                 LOG.debug("KerberosTokenValidator - Using JAAS auth login file: " + jaasAuth);
146                 LOG.debug("KerberosTokenValidator - Using KRB conf file: " + krbConf);
147             } catch (SecurityException ex) {
148                 LOG.debug(ex.getMessage(), ex);
149             }
150         }
151 
152         // Get a TGT from the KDC using JAAS
153         LoginContext loginContext = null;
154         try {
155             if (callbackHandler != null) {
156                 loginContext = new LoginContext(getContextName(), callbackHandler);
157             } else if (data.getCallbackHandler() != null) {
158                 loginContext = new LoginContext(getContextName(), data.getCallbackHandler());
159             } else {
160                 loginContext = new LoginContext(getContextName());
161             }
162             loginContext.login();
163         } catch (LoginException ex) {
164             LOG.debug(ex.getMessage(), ex);
165             throw new WSSecurityException(
166                 WSSecurityException.ErrorCode.FAILURE, ex,
167                 "kerberosLoginError",
168                 new Object[] {ex.getMessage()}
169             );
170         }
171         LOG.debug("Successfully authenticated to the TGT");
172 
173         byte[] token = binarySecurity.getToken();
174 
175         // Get the service name to use - fall back on the principal
176         Subject subject = loginContext.getSubject();
177         String service = serviceName;
178         if (service == null) {
179             Set<Principal> principals = subject.getPrincipals();
180             if (principals.isEmpty()) {
181                 throw new WSSecurityException(
182                     WSSecurityException.ErrorCode.FAILURE,
183                     "kerberosLoginError",
184                     new Object[] {"No Client principals found after login"});
185             }
186             service = principals.iterator().next().getName();
187         }
188 
189         // Validate the ticket
190         KerberosServiceExceptionAction action =
191             new KerberosServiceExceptionAction(token, service,
192                                                isUsernameServiceNameForm(), spnego);
193         KerberosServiceContext krbServiceCtx = null;
194         try {
195             krbServiceCtx = Subject.doAs(subject, action);
196         } catch (PrivilegedActionException e) {
197             Throwable cause = e.getCause();
198             if (cause instanceof WSSecurityException) {
199                 throw (WSSecurityException) cause;
200             } else {
201                 throw new WSSecurityException(
202                     ErrorCode.FAILURE, new Exception(cause), "kerberosTicketValidationError"
203                 );
204             }
205         }
206 
207         credential.setPrincipal(krbServiceCtx.getPrincipal());
208         credential.setDelegationCredential(krbServiceCtx.getDelegationCredential());
209 
210         // Check to see if the session key is available in KerberosServiceContext
211         LOG.debug("Trying to obtain the Session Key from the KerberosServiceContext.");
212         Key sessionKey = krbServiceCtx.getSessionKey();
213         if (null != sessionKey) {
214             LOG.debug("Found session key in the KerberosServiceContext.");
215             credential.setSecretKey(sessionKey.getEncoded());
216         } else {
217             LOG.debug("Session key is not found in the KerberosServiceContext.");
218         }
219 
220         // Otherwise, try to extract the session key from the token if a KerberosTokenDecoder implementation is
221         // available
222         if (null == credential.getSecretKey() && kerberosTokenDecoder != null) {
223             LOG.debug("KerberosTokenDecoder is set.Trying to obtain the session key from it.");
224             kerberosTokenDecoder.clear();
225             kerberosTokenDecoder.setToken(token);
226             kerberosTokenDecoder.setSubject(subject);
227             try {
228                 byte[] key = kerberosTokenDecoder.getSessionKey();
229                 if (null != key) {
230                     LOG.debug("Session key obtained from the KerberosTokenDecoder.");
231                     credential.setSecretKey(key);
232                 } else {
233                     LOG.debug("Session key could not be obtained from the KerberosTokenDecoder.");
234                 }
235             } catch (KerberosTokenDecoderException e) {
236                 // TODO
237                 throw new WSSecurityException(ErrorCode.FAILURE, e, "Error retrieving session key.");
238             }
239         } else {
240             LOG.debug("KerberosTokenDecoder is not set.");
241         }
242 
243         LOG.debug("Successfully validated a ticket");
244 
245         return credential;
246     }
247 
248     /**
249      * SPN can be configured to be in either <b>"hostbased"</b> or <b>"username"</b> form.<br/>
250      *     - <b>"hostbased"</b> - specifies that the service principal name should be interpreted as a "host-based" name as specified in GSS API Rfc, section "4.1: Host-Based Service Name Form" - The service name, as it is specified in LDAP/AD, as it is listed in the KDC.<br/>
251      *     - <b>"username"</b> - specifies that the service principal name should be interpreted as a "username" name as specified in GSS API Rfc, section "4.2: User Name Form" � This is usually the client username in LDAP/AD used for authentication to the KDC.
252      *
253      * <br/><br/>Default is <b>"hostbased"</b>.
254      *
255      * @return the isUsernameServiceNameForm
256      */
257     public boolean isUsernameServiceNameForm() {
258         return isUsernameServiceNameForm;
259     }
260 
261     /**
262      * If true - sets the SPN form to "username"
263      * <br/>If false<b>(default)</b> - the SPN form is "hostbased"
264      *
265      * @see KerberosSecurity#retrieveServiceTicket(String, CallbackHandler, String, boolean)
266      *
267      * @param isUsernameServiceNameForm the isUsernameServiceNameForm to set
268      */
269     public void setUsernameServiceNameForm(boolean isUsernameServiceNameForm) {
270         this.isUsernameServiceNameForm = isUsernameServiceNameForm;
271     }
272 
273     public boolean isSpnego() {
274         return spnego;
275     }
276 
277     public void setSpnego(boolean spnego) {
278         this.spnego = spnego;
279     }
280 }