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  package org.apache.wss4j.stax.impl.securityToken;
20  
21  import java.io.IOException;
22  import java.security.Key;
23  import java.security.Principal;
24  import java.security.PrivilegedActionException;
25  import java.util.Set;
26  
27  import javax.crypto.spec.SecretKeySpec;
28  import javax.security.auth.Subject;
29  import javax.security.auth.callback.Callback;
30  import javax.security.auth.callback.CallbackHandler;
31  import javax.security.auth.callback.UnsupportedCallbackException;
32  import javax.security.auth.kerberos.KerberosTicket;
33  import javax.security.auth.login.LoginContext;
34  import javax.security.auth.login.LoginException;
35  
36  import org.apache.wss4j.common.ext.WSSecurityException;
37  import org.apache.wss4j.common.ext.WSSecurityException.ErrorCode;
38  import org.apache.wss4j.common.kerberos.KerberosClientExceptionAction;
39  import org.apache.wss4j.common.kerberos.KerberosContext;
40  import org.apache.wss4j.common.kerberos.KerberosContextAndServiceNameCallback;
41  import org.apache.wss4j.common.util.KeyUtils;
42  import org.apache.wss4j.stax.securityToken.WSSecurityTokenConstants;
43  import org.apache.xml.security.exceptions.XMLSecurityException;
44  import org.apache.xml.security.stax.impl.securityToken.GenericOutboundSecurityToken;
45  
46  public class KerberosClientSecurityToken extends GenericOutboundSecurityToken {
47  
48      private CallbackHandler callbackHandler;
49      private Key secretKey;
50      private byte[] ticket;
51  
52      public KerberosClientSecurityToken(byte[] ticket, Key secretKey, String id) {
53          super(id, WSSecurityTokenConstants.KERBEROS_TOKEN);
54          this.ticket = ticket;
55          this.secretKey = secretKey;
56      }
57  
58      public KerberosClientSecurityToken(CallbackHandler callbackHandler, String id) {
59          super(id, WSSecurityTokenConstants.KERBEROS_TOKEN);
60          this.callbackHandler = callbackHandler;
61      }
62  
63      private void getTGT() throws WSSecurityException {
64          try {
65              KerberosContextAndServiceNameCallback contextAndServiceNameCallback = new KerberosContextAndServiceNameCallback();
66              callbackHandler.handle(new Callback[]{contextAndServiceNameCallback});
67  
68              if (contextAndServiceNameCallback.getContextName() == null) {
69                  throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "kerberosCallbackContextNameNotSupplied");
70              }
71              if (contextAndServiceNameCallback.getServiceName() == null) {
72                  throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "kerberosCallbackServiceNameNotSupplied");
73              }
74  
75              LoginContext loginContext = new LoginContext(contextAndServiceNameCallback.getContextName(), callbackHandler);
76              loginContext.login();
77  
78              Subject clientSubject = loginContext.getSubject();
79              Set<Principal> clientPrincipals = clientSubject.getPrincipals();
80              if (clientPrincipals.isEmpty()) {
81                  throw new WSSecurityException(
82                      WSSecurityException.ErrorCode.FAILURE,
83                      "kerberosLoginError",
84                      new Object[] {"No Client principals found after login"}
85                  );
86              }
87              // Store the TGT
88              KerberosTicket tgt = getKerberosTicket(clientSubject, null);
89  
90              // Get the service ticket
91              KerberosClientExceptionAction action =
92                  new KerberosClientExceptionAction(clientPrincipals.iterator().next(),
93                                                    contextAndServiceNameCallback.getServiceName(),
94                                                    contextAndServiceNameCallback.isUsernameServiceNameForm(),
95                                                    contextAndServiceNameCallback.isRequestCredDeleg());
96              KerberosContext krbCtx = null;
97              try {
98                  krbCtx = Subject.doAs(clientSubject, action);
99  
100                 // Get the secret key from KerberosContext if available, otherwise use Kerberos ticket's session key
101                 Key sessionKey = krbCtx.getSecretKey();
102                 if (sessionKey != null) {
103                     secretKey = new SecretKeySpec(sessionKey.getEncoded(), sessionKey.getAlgorithm());
104                 } else {
105                     KerberosTicket serviceTicket = getKerberosTicket(clientSubject, tgt);
106                     secretKey = serviceTicket.getSessionKey();
107                 }
108 
109                 ticket = krbCtx.getKerberosToken();
110             } catch (PrivilegedActionException e) {
111                 Throwable cause = e.getCause();
112                 if (cause instanceof WSSecurityException) {
113                     throw (WSSecurityException) cause;
114                 } else {
115                     throw new WSSecurityException(
116                          ErrorCode.FAILURE, new Exception(cause), "kerberosServiceTicketError"
117                     );
118                 }
119             }
120         } catch (LoginException | UnsupportedCallbackException | IOException e) {
121             throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, e);
122         }
123     }
124 
125     /**
126      * Get a KerberosTicket from the clientSubject parameter, that is not equal to the supplied KerberosTicket
127      * parameter (can be null)
128      */
129     private KerberosTicket getKerberosTicket(Subject clientSubject, KerberosTicket previousTicket) {
130         Set<KerberosTicket> privateCredentials = clientSubject.getPrivateCredentials(KerberosTicket.class);
131         if (privateCredentials == null || privateCredentials.isEmpty()) {
132             return null;
133         }
134 
135         for (KerberosTicket privateCredential : privateCredentials) {
136             if (!privateCredential.equals(previousTicket)) {
137                 return privateCredential;
138             }
139         }
140         return null;
141     }
142 
143     @Override
144     public Key getSecretKey(String algorithmURI) throws XMLSecurityException {
145         Key key = super.getSecretKey(algorithmURI);
146         if (key != null) {
147             return key;
148         }
149         if (this.secretKey == null) {
150             getTGT();
151         }
152 
153         byte[] sk = this.secretKey.getEncoded();
154 
155         key = KeyUtils.prepareSecretKey(algorithmURI, sk);
156         setSecretKey(algorithmURI, key);
157         return key;
158     }
159 
160     public byte[] getTicket() throws XMLSecurityException {
161         if (this.ticket == null) {
162             getTGT();
163         }
164         return ticket;
165     }
166 }