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.common.spnego;
21  
22  import java.security.Principal;
23  import java.security.PrivilegedActionException;
24  import java.util.Set;
25  
26  import javax.security.auth.Subject;
27  import javax.security.auth.callback.CallbackHandler;
28  import javax.security.auth.login.LoginContext;
29  import javax.security.auth.login.LoginException;
30  
31  import org.apache.wss4j.common.ext.WSSecurityException;
32  import org.apache.wss4j.common.ext.WSSecurityException.ErrorCode;
33  import org.apache.wss4j.common.kerberos.KerberosClientExceptionAction;
34  import org.apache.wss4j.common.kerberos.KerberosContext;
35  import org.apache.wss4j.common.kerberos.KerberosServiceContext;
36  import org.apache.wss4j.common.kerberos.KerberosServiceExceptionAction;
37  import org.ietf.jgss.GSSContext;
38  import org.ietf.jgss.GSSCredential;
39  import org.ietf.jgss.GSSException;
40  import org.ietf.jgss.MessageProp;
41  
42  /**
43   * This class wraps a GSSContext and provides some functionality to obtain and validate spnego tokens.
44   */
45  public class SpnegoTokenContext {
46  
47      private static final org.slf4j.Logger LOG =
48          org.slf4j.LoggerFactory.getLogger(SpnegoTokenContext.class);
49  
50      private GSSContext secContext;
51      private byte[] token;
52      private boolean mutualAuth;
53      private SpnegoClientAction clientAction;
54      private SpnegoServiceAction serviceAction;
55      private GSSCredential delegationCredential;
56      private Principal spnegoPrincipal;
57  
58      /**
59       * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this
60       * BinarySecurityToken.
61       * @param jaasLoginModuleName the JAAS Login Module name to use
62       * @param callbackHandler a CallbackHandler instance to retrieve a password (optional)
63       * @param serviceName the desired Kerberized service
64       * @throws WSSecurityException
65       */
66      public void retrieveServiceTicket(
67          String jaasLoginModuleName,
68          CallbackHandler callbackHandler,
69          String serviceName
70      ) throws WSSecurityException {
71          retrieveServiceTicket(jaasLoginModuleName, callbackHandler, serviceName, false);
72      }
73  
74  
75      /**
76       * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this
77       * BinarySecurityToken.
78       * @param jaasLoginModuleName the JAAS Login Module name to use
79       * @param callbackHandler a CallbackHandler instance to retrieve a password (optional)
80       * @param serviceName the desired Kerberized service
81       * @param isUsernameServiceNameForm
82       * @throws WSSecurityException
83       */
84      public void retrieveServiceTicket(
85          String jaasLoginModuleName,
86          CallbackHandler callbackHandler,
87          String serviceName,
88          boolean isUsernameServiceNameForm
89      ) throws WSSecurityException {
90          retrieveServiceTicket(jaasLoginModuleName, callbackHandler, serviceName,
91                                isUsernameServiceNameForm, false, null);
92      }
93  
94      /**
95       * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this
96       * BinarySecurityToken.
97       * @param jaasLoginModuleName the JAAS Login Module name to use
98       * @param callbackHandler a CallbackHandler instance to retrieve a password (optional)
99       * @param serviceName the desired Kerberized service
100      * @param isUsernameServiceNameForm
101      * @param requestCredDeleg Whether to request credential delegation or not
102      * @param delegationCredential The delegation credential to use
103      * @throws WSSecurityException
104      */
105     public void retrieveServiceTicket(
106         String jaasLoginModuleName,
107         CallbackHandler callbackHandler,
108         String serviceName,
109         boolean isUsernameServiceNameForm,
110         boolean requestCredDeleg,
111         GSSCredential delegationCredential
112     ) throws WSSecurityException {
113 
114         // Get a TGT from the KDC using JAAS
115         LoginContext loginContext = null;
116         try {
117             if (callbackHandler == null) {
118                 loginContext = new LoginContext(jaasLoginModuleName);
119             } else {
120                 loginContext = new LoginContext(jaasLoginModuleName, callbackHandler);
121             }
122             loginContext.login();
123         } catch (LoginException ex) {
124             LOG.debug(ex.getMessage(), ex);
125             throw new WSSecurityException(
126                 WSSecurityException.ErrorCode.FAILURE, ex, "kerberosLoginError",
127                 new Object[] {ex.getMessage()});
128         }
129         LOG.debug("Successfully authenticated to the TGT");
130 
131         Subject clientSubject = loginContext.getSubject();
132         Set<Principal> clientPrincipals = clientSubject.getPrincipals();
133         if (clientPrincipals.isEmpty()) {
134             throw new WSSecurityException(
135                 WSSecurityException.ErrorCode.FAILURE,
136                 "kerberosLoginError",
137                 new Object[] {"No Client principals found after login"});
138         }
139 
140         // Get the service ticket
141         if (clientAction != null) {
142             clientAction.setServiceName(serviceName);
143             clientAction.setMutualAuth(mutualAuth);
144             clientAction.setUserNameServiceForm(isUsernameServiceNameForm);
145             token = Subject.doAs(clientSubject, clientAction);
146             if (token == null) {
147                 throw new WSSecurityException(
148                     WSSecurityException.ErrorCode.FAILURE, "kerberosServiceTicketError"
149                 );
150             }
151 
152             secContext = clientAction.getContext();
153         } else {
154             KerberosClientExceptionAction action =
155                 new KerberosClientExceptionAction(null, serviceName,
156                                                   isUsernameServiceNameForm,
157                                                   requestCredDeleg,
158                                                   delegationCredential,
159                                                   true,
160                                                   mutualAuth);
161             KerberosContext krbCtx = null;
162             try {
163                 krbCtx = (KerberosContext) Subject.doAs(clientSubject, action);
164 
165                 token = krbCtx.getKerberosToken();
166                 if (token == null) {
167                     throw new WSSecurityException(
168                         WSSecurityException.ErrorCode.FAILURE, "kerberosServiceTicketError"
169                     );
170                 }
171 
172                 secContext = krbCtx.getGssContext();
173             } catch (PrivilegedActionException e) {
174                 Throwable cause = e.getCause();
175                 if (cause instanceof WSSecurityException) {
176                     throw (WSSecurityException) cause;
177                 } else {
178                     throw new WSSecurityException(
179                          ErrorCode.FAILURE, new Exception(cause), "kerberosServiceTicketError"
180                     );
181                 }
182             }
183         }
184 
185         LOG.debug("Successfully retrieved a service ticket");
186     }
187 
188     /**
189      * Validate a service ticket.
190      * @param jaasLoginModuleName
191      * @param callbackHandler
192      * @param serviceName
193      * @param ticket
194      * @throws WSSecurityException
195      */
196     public void validateServiceTicket(
197         String jaasLoginModuleName,
198         CallbackHandler callbackHandler,
199         String serviceName,
200         byte[] ticket
201     ) throws WSSecurityException {
202         validateServiceTicket(jaasLoginModuleName, callbackHandler, serviceName, false, ticket);
203      }
204 
205     /**
206      * Validate a service ticket.
207      * @param jaasLoginModuleName
208      * @param callbackHandler
209      * @param serviceName
210      * @param ticket
211      * @throws WSSecurityException
212      */
213     public void validateServiceTicket(
214         String jaasLoginModuleName,
215         CallbackHandler callbackHandler,
216         String serviceName,
217         boolean isUsernameServiceNameForm,
218         byte[] ticket
219     ) throws WSSecurityException {
220         // Get a TGT from the KDC using JAAS
221         LoginContext loginContext = null;
222         try {
223             if (callbackHandler == null) {
224                 loginContext = new LoginContext(jaasLoginModuleName);
225             } else {
226                 loginContext = new LoginContext(jaasLoginModuleName, callbackHandler);
227             }
228             loginContext.login();
229         } catch (LoginException ex) {
230             LOG.debug(ex.getMessage(), ex);
231             throw new WSSecurityException(
232                 WSSecurityException.ErrorCode.FAILURE, ex, "kerberosLoginError",
233                 new Object[] {ex.getMessage()});
234         }
235         LOG.debug("Successfully authenticated to the TGT");
236 
237         // Get the service name to use - fall back on the principal
238         Subject subject = loginContext.getSubject();
239         String service = serviceName;
240         if (service == null) {
241             Set<Principal> principals = subject.getPrincipals();
242             if (principals.isEmpty()) {
243                 throw new WSSecurityException(
244                     WSSecurityException.ErrorCode.FAILURE,
245                     "kerberosLoginError",
246                     new Object[] {"No Client principals found after login"});
247             }
248             service = principals.iterator().next().getName();
249         }
250 
251         // Validate the ticket
252         if (serviceAction != null) {
253             serviceAction.setTicket(ticket);
254             serviceAction.setServiceName(service);
255             serviceAction.setUsernameServiceNameForm(isUsernameServiceNameForm);
256             token = Subject.doAs(subject, serviceAction);
257             secContext = serviceAction.getContext();
258         } else {
259             KerberosServiceExceptionAction action =
260                 new KerberosServiceExceptionAction(ticket, service,
261                                                    isUsernameServiceNameForm, true);
262             KerberosServiceContext krbCtx = null;
263             try {
264                 krbCtx = (KerberosServiceContext) Subject.doAs(subject, action);
265 
266                 token = krbCtx.getKerberosToken();
267                 if (token == null) {
268                     throw new WSSecurityException(
269                         WSSecurityException.ErrorCode.FAILURE, "kerberosServiceTicketError"
270                     );
271                 }
272 
273                 secContext = krbCtx.getGssContext();
274                 delegationCredential = krbCtx.getDelegationCredential();
275                 spnegoPrincipal = krbCtx.getPrincipal();
276             } catch (PrivilegedActionException e) {
277                 Throwable cause = e.getCause();
278                 if (cause instanceof WSSecurityException) {
279                     throw (WSSecurityException) cause;
280                 } else {
281                     throw new WSSecurityException(
282                          ErrorCode.FAILURE, new Exception(cause), "kerberosServiceTicketError"
283                     );
284                 }
285             }
286         }
287 
288         LOG.debug("Successfully validated a service ticket");
289     }
290 
291     /**
292      * Whether to enable mutual authentication or not. This only applies to retrieve service ticket.
293      */
294     public void setMutualAuth(boolean mutualAuthentication) {
295         mutualAuth = mutualAuthentication;
296     }
297 
298     /**
299      * Get the SPNEGO token that was created.
300      */
301     public byte[] getToken() {
302         return token;
303     }
304 
305     /**
306      * Whether a connection has been established (at the service side)
307      */
308     public boolean isEstablished() {
309         if (secContext == null) {
310             return false;
311         }
312         return secContext.isEstablished();
313     }
314 
315     /**
316      * Unwrap a key
317      */
318     public byte[] unwrapKey(byte[] secret) throws WSSecurityException {
319         MessageProp mProp = new MessageProp(0, true);
320         try {
321             return secContext.unwrap(secret, 0, secret.length, mProp);
322         } catch (GSSException e) {
323             LOG.debug("Error in cleaning up a GSS context", e);
324             throw new WSSecurityException(
325                 WSSecurityException.ErrorCode.FAILURE, e, "spnegoKeyError"
326             );
327         }
328     }
329 
330     /**
331      * Wrap a key
332      */
333     public byte[] wrapKey(byte[] secret) throws WSSecurityException {
334         MessageProp mProp = new MessageProp(0, true);
335         try {
336             return secContext.wrap(secret, 0, secret.length, mProp);
337         } catch (GSSException e) {
338             LOG.debug("Error in cleaning up a GSS context", e);
339             throw new WSSecurityException(
340                 WSSecurityException.ErrorCode.FAILURE, e, "spnegoKeyError"
341             );
342         }
343     }
344 
345     /**
346      * Set a custom SpnegoClientAction implementation to use
347      */
348     public void setSpnegoClientAction(SpnegoClientAction spnegoClientAction) {
349         this.clientAction = spnegoClientAction;
350     }
351 
352     /**
353      * Set a custom SpnegoServiceAction implementation to use
354      */
355     public void setSpnegoServiceAction(SpnegoServiceAction spnegoServiceAction) {
356         this.serviceAction = spnegoServiceAction;
357     }
358 
359     public void clear() {
360         token = null;
361         mutualAuth = false;
362         delegationCredential = null;
363         spnegoPrincipal = null;
364         try {
365             secContext.dispose();
366         } catch (GSSException e) {
367             LOG.debug("Error in cleaning up a GSS context", e);
368         }
369     }
370 
371     public GSSCredential getDelegationCredential() {
372         return delegationCredential;
373     }
374 
375     public Principal getSpnegoPrincipal() {
376         return spnegoPrincipal;
377     }
378 
379 }