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.message.token;
21  
22  import java.io.IOException;
23  import java.security.Key;
24  import java.security.Principal;
25  import java.security.PrivilegedActionException;
26  import java.util.Set;
27  
28  import javax.crypto.SecretKey;
29  import javax.crypto.spec.SecretKeySpec;
30  import javax.security.auth.Subject;
31  import javax.security.auth.callback.Callback;
32  import javax.security.auth.callback.CallbackHandler;
33  import javax.security.auth.callback.UnsupportedCallbackException;
34  import javax.security.auth.kerberos.KerberosTicket;
35  import javax.security.auth.login.LoginContext;
36  import javax.security.auth.login.LoginException;
37  
38  import org.apache.wss4j.common.bsp.BSPEnforcer;
39  import org.apache.wss4j.common.bsp.BSPRule;
40  import org.apache.wss4j.common.ext.WSSecurityException;
41  import org.apache.wss4j.common.ext.WSSecurityException.ErrorCode;
42  import org.apache.wss4j.common.kerberos.KerberosClientExceptionAction;
43  import org.apache.wss4j.common.kerberos.KerberosContext;
44  import org.apache.wss4j.common.kerberos.KerberosContextAndServiceNameCallback;
45  import org.apache.wss4j.common.token.BinarySecurity;
46  import org.apache.wss4j.dom.WSConstants;
47  import org.ietf.jgss.GSSCredential;
48  import org.w3c.dom.Document;
49  import org.w3c.dom.Element;
50  
51  /**
52   * Kerberos Security Token.
53   */
54  public class KerberosSecurity extends BinarySecurity {
55  
56      private static final org.slf4j.Logger LOG =
57          org.slf4j.LoggerFactory.getLogger(KerberosSecurity.class);
58      private SecretKey secretKey;
59  
60      /**
61       * This constructor creates a new Kerberos token object and initializes
62       * it from the data contained in the element.
63       *
64       * @param elem the element containing the Kerberos token data
65       * @param bspEnforcer a BSPEnforcer instance to enforce BSP rules
66       * @throws WSSecurityException
67       */
68      public KerberosSecurity(Element elem, BSPEnforcer bspEnforcer) throws WSSecurityException {
69          super(elem, bspEnforcer);
70          String valueType = getValueType();
71          if (!WSConstants.WSS_GSS_KRB_V5_AP_REQ.equals(valueType)) {
72              bspEnforcer.handleBSPRule(BSPRule.R6902);
73          }
74      }
75  
76      /**
77       * This constructor creates a new Kerberos element.
78       *
79       * @param doc
80       */
81      public KerberosSecurity(Document doc) {
82          super(doc);
83      }
84  
85      /**
86       * Return true if this token is a Kerberos V5 AP REQ token
87       */
88      public boolean isV5ApReq() {
89          String type = getValueType();
90          return WSConstants.WSS_KRB_V5_AP_REQ.equals(type)
91              || WSConstants.WSS_KRB_V5_AP_REQ1510.equals(type)
92              || WSConstants.WSS_KRB_V5_AP_REQ4120.equals(type);
93      }
94  
95      /**
96       * Return true if this token is a Kerberos GSS V5 AP REQ token
97       */
98      public boolean isGssV5ApReq() {
99          String type = getValueType();
100         return WSConstants.WSS_GSS_KRB_V5_AP_REQ.equals(type)
101             || WSConstants.WSS_GSS_KRB_V5_AP_REQ1510.equals(type)
102             || WSConstants.WSS_GSS_KRB_V5_AP_REQ4120.equals(type);
103     }
104 
105     /**
106      * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this
107      * BinarySecurityToken.
108      * @param callbackHandler a CallbackHandler instance to retrieve a password (optional),
109      * JAAS Login Module name (required) + service name (required)
110      * @throws WSSecurityException
111      */
112     public void retrieveServiceTicket(
113         CallbackHandler callbackHandler
114     ) throws WSSecurityException {
115         KerberosContextAndServiceNameCallback contextAndServiceNameCallback =
116             new KerberosContextAndServiceNameCallback();
117         try {
118             callbackHandler.handle(new Callback[]{contextAndServiceNameCallback});
119         } catch (IOException | UnsupportedCallbackException e) {
120             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, e);
121         }
122 
123         String jaasLoginModuleName = contextAndServiceNameCallback.getContextName();
124         if (jaasLoginModuleName == null) {
125             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
126                                           "kerberosCallbackContextNameNotSupplied");
127         }
128         String serviceName = contextAndServiceNameCallback.getServiceName();
129         if (serviceName == null) {
130             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE,
131                                           "kerberosCallbackServiceNameNotSupplied");
132         }
133 
134         retrieveServiceTicket(jaasLoginModuleName, callbackHandler, serviceName);
135     }
136 
137     /**
138      * Retrieve a service ticket from a KDC using the Kerberos JAAS module, and set it in this
139      * BinarySecurityToken.
140      * @param jaasLoginModuleName the JAAS Login Module name to use
141      * @param callbackHandler a CallbackHandler instance to retrieve a password (optional)
142      * @param serviceName the desired Kerberized service
143      * @throws WSSecurityException
144      */
145     public void retrieveServiceTicket(
146         String jaasLoginModuleName,
147         CallbackHandler callbackHandler,
148         String serviceName
149     ) throws WSSecurityException {
150         retrieveServiceTicket(jaasLoginModuleName, callbackHandler, serviceName, false);
151     }
152 
153     public void retrieveServiceTicket(
154         String jaasLoginModuleName,
155         CallbackHandler callbackHandler,
156         String serviceName,
157         boolean isUsernameServiceNameForm
158     ) throws WSSecurityException {
159         retrieveServiceTicket(jaasLoginModuleName, callbackHandler, serviceName,
160                               isUsernameServiceNameForm, false);
161     }
162 
163     public void retrieveServiceTicket(
164          String jaasLoginModuleName,
165          CallbackHandler callbackHandler,
166          String serviceName,
167          boolean isUsernameServiceNameForm,
168          boolean requestCredDeleg
169     ) throws WSSecurityException {
170         retrieveServiceTicket(jaasLoginModuleName, callbackHandler, serviceName,
171                               isUsernameServiceNameForm, requestCredDeleg, null);
172     }
173 
174     public void retrieveServiceTicket(
175         String jaasLoginModuleName,
176         CallbackHandler callbackHandler,
177         String serviceName,
178         boolean isUsernameServiceNameForm,
179         boolean requestCredDeleg,
180         GSSCredential delegatedCredential
181     ) throws WSSecurityException {
182         // Get a TGT from the KDC using JAAS
183         LoginContext loginContext = null;
184         try {
185             if (callbackHandler == null) {
186                 loginContext = new LoginContext(jaasLoginModuleName);
187             } else {
188                 loginContext = new LoginContext(jaasLoginModuleName, callbackHandler);
189             }
190             loginContext.login();
191         } catch (LoginException ex) {
192             LOG.debug(ex.getMessage(), ex);
193             throw new WSSecurityException(
194                 WSSecurityException.ErrorCode.FAILURE, ex,
195                 "kerberosLoginError",
196                 new Object[] {ex.getMessage()}
197             );
198         }
199         LOG.debug("Successfully authenticated to the TGT");
200 
201         Subject clientSubject = loginContext.getSubject();
202         Set<Principal> clientPrincipals = clientSubject.getPrincipals();
203         if (clientPrincipals.isEmpty()) {
204             throw new WSSecurityException(
205                 WSSecurityException.ErrorCode.FAILURE,
206                 "kerberosLoginError",
207                 new Object[] {"No Client principals found after login"});
208         }
209         // Store the TGT
210         KerberosTicket tgt = getKerberosTicket(clientSubject, null);
211 
212         decorateSubject(clientSubject);
213 
214         // Get the service ticket
215         KerberosClientExceptionAction action =
216             new KerberosClientExceptionAction(clientPrincipals.iterator().next(), serviceName,
217                                               isUsernameServiceNameForm, requestCredDeleg,
218                                               delegatedCredential, false, false);
219         KerberosContext krbCtx = null;
220         try {
221             krbCtx = Subject.doAs(clientSubject, action);
222 
223             // Get the secret key from KerberosContext if available, otherwise use Kerberos ticket's session key
224             Key sessionKey = krbCtx.getSecretKey();
225             if (sessionKey != null) {
226                 secretKey = new SecretKeySpec(sessionKey.getEncoded(), sessionKey.getAlgorithm());
227             } else {
228                 KerberosTicket serviceTicket = getKerberosTicket(clientSubject, tgt);
229                 if (serviceTicket != null) {
230                     secretKey = serviceTicket.getSessionKey();
231                 }
232             }
233 
234             if (secretKey == null) {
235                 LOG.debug("No secret key for kerberos was found");
236             } else {
237                 LOG.debug("Successfully retrieved a secret key for kerberos");
238             }
239 
240             setToken(krbCtx.getKerberosToken());
241         } catch (PrivilegedActionException e) {
242             Throwable cause = e.getCause();
243             if (cause instanceof WSSecurityException) {
244                 throw (WSSecurityException) cause;
245             } else {
246                 throw new WSSecurityException(
247                      ErrorCode.FAILURE, new Exception(cause), "kerberosServiceTicketError"
248                 );
249             }
250         } finally {
251             if (krbCtx != null) {
252                 krbCtx.dispose();
253             }
254         }
255         LOG.debug("Successfully retrieved a service ticket");
256 
257         if (getValueType().length() == 0) {
258             setValueType(WSConstants.WSS_GSS_KRB_V5_AP_REQ);
259         }
260     }
261 
262     // Allow subclasses to decorate the Subject if required.
263     protected void decorateSubject(Subject subject) {
264 
265     }
266 
267     /**
268      * Get a KerberosTicket from the clientSubject parameter, that is not equal to the supplied KerberosTicket
269      * parameter (can be null)
270      */
271     private KerberosTicket getKerberosTicket(Subject clientSubject, KerberosTicket previousTicket) {
272         Set<KerberosTicket> privateCredentials = clientSubject.getPrivateCredentials(KerberosTicket.class);
273         if (privateCredentials == null || privateCredentials.isEmpty()) {
274             LOG.debug("Kerberos client subject private credentials are null");
275             return null;
276         }
277 
278         for (KerberosTicket privateCredential : privateCredentials) {
279             if (!privateCredential.equals(previousTicket)) {
280                 return privateCredential;
281             }
282         }
283         return null;
284     }
285 
286     /**
287      * Get the SecretKey associated with the service principal
288      * @return the SecretKey associated with the service principal
289      */
290     public SecretKey getSecretKey() {
291         return secretKey;
292     }
293 
294     /**
295      * Return true if the valueType represents a Kerberos Token
296      * @param valueType the valueType of the token
297      * @return true if the valueType represents a Kerberos Token
298      */
299     public static boolean isKerberosToken(String valueType) {
300         return WSConstants.WSS_KRB_V5_AP_REQ.equals(valueType)
301             || WSConstants.WSS_GSS_KRB_V5_AP_REQ.equals(valueType)
302             || WSConstants.WSS_KRB_V5_AP_REQ1510.equals(valueType)
303             || WSConstants.WSS_GSS_KRB_V5_AP_REQ1510.equals(valueType)
304             || WSConstants.WSS_KRB_V5_AP_REQ4120.equals(valueType)
305             || WSConstants.WSS_GSS_KRB_V5_AP_REQ4120.equals(valueType);
306     }
307 
308     @Override
309     public boolean equals(Object object) {
310         if (!(object instanceof KerberosSecurity)) {
311             return false;
312         }
313 
314         KerberosSecurity that = (KerberosSecurity)object;
315         if (secretKey != null && !secretKey.equals(that.secretKey)) {
316             return false;
317         } else if (secretKey == null && that.secretKey != null) {
318             return false;
319         }
320 
321         return super.equals(object);
322     }
323 
324     @Override
325     public int hashCode() {
326         int hashCode = 17;
327         if (secretKey != null) {
328             hashCode *= 31 + secretKey.hashCode();
329         }
330         hashCode *= 31 + super.hashCode();
331 
332         return hashCode;
333     }
334 }