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.ws.security.spnego;
21  
22  import java.security.Principal;
23  import java.util.Set;
24  
25  import javax.security.auth.Subject;
26  import javax.security.auth.callback.CallbackHandler;
27  import javax.security.auth.login.LoginContext;
28  import javax.security.auth.login.LoginException;
29  
30  import org.apache.ws.security.WSSecurityException;
31  import org.ietf.jgss.GSSContext;
32  import org.ietf.jgss.GSSException;
33  import org.ietf.jgss.MessageProp;
34  
35  /**
36   * This class wraps a GSSContext and provides some functionality to obtain and validate spnego tokens.
37   */
38  public class SpnegoTokenContext {
39      
40      private static final org.apache.commons.logging.Log LOG = 
41          org.apache.commons.logging.LogFactory.getLog(SpnegoTokenContext.class);
42      
43      private GSSContext secContext;
44      private byte[] token;
45      private boolean mutualAuth;
46      private SpnegoClientAction clientAction = new DefaultSpnegoClientAction();
47      private SpnegoServiceAction serviceAction = new DefaultSpnegoServiceAction();
48  
49      /**
50       * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this
51       * BinarySecurityToken.
52       * @param jaasLoginModuleName the JAAS Login Module name to use
53       * @param callbackHandler a CallbackHandler instance to retrieve a password (optional)
54       * @param serviceName the desired Kerberized service
55       * @throws WSSecurityException
56       */
57      public void retrieveServiceTicket(
58          String jaasLoginModuleName, 
59          CallbackHandler callbackHandler,
60          String serviceName
61      ) throws WSSecurityException {
62          // Get a TGT from the KDC using JAAS
63          LoginContext loginContext = null;
64          try {
65              if (callbackHandler == null) {
66                  loginContext = new LoginContext(jaasLoginModuleName);
67              } else {
68                  loginContext = new LoginContext(jaasLoginModuleName, callbackHandler);
69              }
70              loginContext.login();
71          } catch (LoginException ex) {
72              if (LOG.isDebugEnabled()) {
73                  LOG.debug(ex.getMessage(), ex);
74              }
75              throw new WSSecurityException(
76                  WSSecurityException.FAILURE,
77                  "kerberosLoginError", 
78                  new Object[] {ex.getMessage()},
79                  ex
80              );
81          }
82          if (LOG.isDebugEnabled()) {
83              LOG.debug("Successfully authenticated to the TGT");
84          }
85          
86          Subject clientSubject = loginContext.getSubject();
87          Set<Principal> clientPrincipals = clientSubject.getPrincipals();
88          if (clientPrincipals.isEmpty()) {
89              throw new WSSecurityException(
90                  WSSecurityException.FAILURE, 
91                  "kerberosLoginError", 
92                  new Object[] {"No Client principals found after login"}
93              );
94          }
95          
96          // Get the service ticket
97          clientAction.setServiceName(serviceName);
98          clientAction.setMutualAuth(mutualAuth);
99          token = (byte[])Subject.doAs(clientSubject, clientAction);
100         if (token == null) {
101             throw new WSSecurityException(
102                 WSSecurityException.FAILURE, "kerberosServiceTicketError"
103             );
104         }
105         
106         secContext = clientAction.getContext();
107         if (LOG.isDebugEnabled()) {
108             LOG.debug("Successfully retrieved a service ticket");
109         }
110         
111     }
112     
113     /**
114      * Validate a service ticket.
115      * @param jaasLoginModuleName
116      * @param callbackHandler
117      * @param serviceName
118      * @param ticket
119      * @throws WSSecurityException
120      */
121     public void validateServiceTicket(
122         String jaasLoginModuleName, 
123         CallbackHandler callbackHandler,
124         String serviceName,
125         byte[] ticket
126     ) throws WSSecurityException {
127         // Get a TGT from the KDC using JAAS
128         LoginContext loginContext = null;
129         try {
130             if (callbackHandler == null) {
131                 loginContext = new LoginContext(jaasLoginModuleName);
132             } else {
133                 loginContext = new LoginContext(jaasLoginModuleName, callbackHandler);
134             }
135             loginContext.login();
136         } catch (LoginException ex) {
137             if (LOG.isDebugEnabled()) {
138                 LOG.debug(ex.getMessage(), ex);
139             }
140             throw new WSSecurityException(
141                 WSSecurityException.FAILURE,
142                 "kerberosLoginError", 
143                 new Object[] {ex.getMessage()},
144                 ex
145             );
146         }
147         if (LOG.isDebugEnabled()) {
148             LOG.debug("Successfully authenticated to the TGT");
149         }
150 
151         // Get the service name to use - fall back on the principal
152         Subject subject = loginContext.getSubject();
153         String service = serviceName;
154         if (service == null) {
155             Set<Principal> principals = subject.getPrincipals();
156             if (principals.isEmpty()) {
157                 throw new WSSecurityException(
158                     WSSecurityException.FAILURE, 
159                     "kerberosLoginError", 
160                     new Object[] {"No Client principals found after login"}
161                 );
162             }
163             service = principals.iterator().next().getName();
164         }
165 
166         // Validate the ticket
167         serviceAction.setTicket(ticket);
168         serviceAction.setServiceName(service);
169         token = (byte[])Subject.doAs(subject, serviceAction);
170         
171         secContext = serviceAction.getContext();
172         if (LOG.isDebugEnabled()) {
173             LOG.debug("Successfully validated a service ticket");
174         }
175 
176     }
177     
178     /**
179      * Whether to enable mutual authentication or not. This only applies to retrieve service ticket.
180      */
181     public void setMutualAuth(boolean mutualAuthentication) {
182         mutualAuth = mutualAuthentication;
183     }
184 
185     /**
186      * Get the SPNEGO token that was created.
187      */
188     public byte[] getToken() {
189         return token;
190     }
191     
192     /**
193      * Whether a connection has been established (at the service side)
194      */
195     public boolean isEstablished() {
196         if (secContext == null) {
197             return false;
198         }
199         return secContext.isEstablished();
200     }
201     
202     /**
203      * Unwrap a key
204      */
205     public byte[] unwrapKey(byte[] secret) throws WSSecurityException {
206         MessageProp mProp = new MessageProp(0, true);
207         try {
208             return secContext.unwrap(secret, 0, secret.length, mProp);
209         } catch (GSSException e) {
210             if (LOG.isDebugEnabled()) {
211                 LOG.debug("Error in cleaning up a GSS context", e);
212             }
213             throw new WSSecurityException(
214                 WSSecurityException.FAILURE, "spnegoKeyError", null, e
215             );
216         }
217     }
218     
219     /**
220      * Wrap a key
221      */
222     public byte[] wrapKey(byte[] secret) throws WSSecurityException {
223         MessageProp mProp = new MessageProp(0, true);
224         try {
225             return secContext.wrap(secret, 0, secret.length, mProp);
226         } catch (GSSException e) {
227             if (LOG.isDebugEnabled()) {
228                 LOG.debug("Error in cleaning up a GSS context", e);
229             }
230             throw new WSSecurityException(
231                 WSSecurityException.FAILURE, "spnegoKeyError", null, e
232             );
233         }
234     }
235     
236     /**
237      * Set a custom SpnegoClientAction implementation to use
238      */
239     public void setSpnegoClientAction(SpnegoClientAction spnegoClientAction) {
240         this.clientAction = spnegoClientAction;
241     }
242     
243     /**
244      * Set a custom SpnegoServiceAction implementation to use
245      */
246     public void setSpnegoServiceAction(SpnegoServiceAction spnegoServiceAction) {
247         this.serviceAction = spnegoServiceAction;
248     }
249     
250     public void clear() {
251         token = null;
252         mutualAuth = false;
253         try {
254             secContext.dispose();
255         } catch (GSSException e) {
256             if (LOG.isDebugEnabled()) {
257                 LOG.debug("Error in cleaning up a GSS context", e);
258             }
259         }
260     }
261     
262 }