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.directory.server.kerberos.kdc.ticketgrant;
21  
22  
23  import java.net.InetAddress;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.Set;
28  
29  import javax.security.auth.kerberos.KerberosPrincipal;
30  
31  import org.apache.directory.server.kerberos.kdc.KdcContext;
32  import org.apache.directory.server.kerberos.kdc.KdcServer;
33  import org.apache.directory.server.kerberos.shared.KerberosConstants;
34  import org.apache.directory.server.kerberos.shared.KerberosUtils;
35  import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumHandler;
36  import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumType;
37  import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
38  import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
39  import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
40  import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
41  import org.apache.directory.server.kerberos.shared.exceptions.ErrorType;
42  import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
43  import org.apache.directory.server.kerberos.shared.io.decoder.ApplicationRequestDecoder;
44  import org.apache.directory.server.kerberos.shared.messages.ApplicationRequest;
45  import org.apache.directory.server.kerberos.shared.messages.KdcReply;
46  import org.apache.directory.server.kerberos.shared.messages.KdcRequest;
47  import org.apache.directory.server.kerberos.shared.messages.TicketGrantReply;
48  import org.apache.directory.server.kerberos.shared.messages.components.Authenticator;
49  import org.apache.directory.server.kerberos.shared.messages.components.EncTicketPart;
50  import org.apache.directory.server.kerberos.shared.messages.components.EncTicketPartModifier;
51  import org.apache.directory.server.kerberos.shared.messages.components.Ticket;
52  import org.apache.directory.server.kerberos.shared.messages.value.AuthorizationData;
53  import org.apache.directory.server.kerberos.shared.messages.value.Checksum;
54  import org.apache.directory.server.kerberos.shared.messages.value.EncryptedData;
55  import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
56  import org.apache.directory.server.kerberos.shared.messages.value.HostAddress;
57  import org.apache.directory.server.kerberos.shared.messages.value.HostAddresses;
58  import org.apache.directory.server.kerberos.shared.messages.value.KdcOptions;
59  import org.apache.directory.server.kerberos.shared.messages.value.KerberosTime;
60  import org.apache.directory.server.kerberos.shared.messages.value.LastRequest;
61  import org.apache.directory.server.kerberos.shared.messages.value.PaData;
62  import org.apache.directory.server.kerberos.shared.messages.value.flags.TicketFlag;
63  import org.apache.directory.server.kerberos.shared.messages.value.types.PaDataType;
64  import org.apache.directory.server.kerberos.shared.replay.InMemoryReplayCache;
65  import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
66  import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
67  import org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntry;
68  import org.slf4j.Logger;
69  import org.slf4j.LoggerFactory;
70  
71  
72  /**
73   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
74   * @version $Rev: 583938 $, $Date: 2007-10-11 21:57:20 +0200 (Thu, 11 Oct 2007) $
75   */
76  public class TicketGrantingService
77  {
78      
79      /** the log for this class */
80      private static final Logger LOG = LoggerFactory.getLogger( TicketGrantingService.class );
81      
82      private static final InMemoryReplayCache replayCache = new InMemoryReplayCache();
83      private static final CipherTextHandler cipherTextHandler = new CipherTextHandler();
84  
85      private static final String SERVICE_NAME = "Ticket-Granting Service (TGS)";
86  
87      private static final ChecksumHandler checksumHandler = new ChecksumHandler();
88  
89      public static void execute( TicketGrantingContext tgsContext ) throws Exception
90      {
91          if ( LOG.isDebugEnabled() )
92          {
93              monitorRequest( tgsContext );
94          }
95  
96          configureTicketGranting( tgsContext);
97          selectEncryptionType( tgsContext );
98          getAuthHeader( tgsContext );
99          verifyTgt( tgsContext );
100         getTicketPrincipalEntry( tgsContext );
101         verifyTgtAuthHeader( tgsContext );
102         verifyBodyChecksum( tgsContext );
103         getRequestPrincipalEntry( tgsContext );
104         generateTicket( tgsContext );
105         buildReply( tgsContext );
106 
107         if ( LOG.isDebugEnabled() )
108         {
109             monitorContext( tgsContext );
110             monitorReply( tgsContext );
111         }
112 
113         sealReply( tgsContext );
114     }
115     
116     
117     private static void configureTicketGranting( TicketGrantingContext tgsContext ) throws KerberosException
118     {
119         KdcServer config = tgsContext.getConfig();
120         long clockSkew = config.getAllowableClockSkew();
121         replayCache.setClockSkew( clockSkew );
122         tgsContext.setReplayCache( replayCache );
123 
124         tgsContext.setCipherTextHandler( cipherTextHandler );
125 
126         if ( tgsContext.getRequest().getProtocolVersionNumber() != KerberosConstants.KERBEROS_V5 )
127         {
128             throw new KerberosException( ErrorType.KDC_ERR_BAD_PVNO );
129         }
130     }
131     
132 
133     private static void monitorRequest( KdcContext kdcContext ) throws Exception
134     {
135         KdcRequest request = kdcContext.getRequest();
136 
137         try
138         {
139             String clientAddress = kdcContext.getClientAddress().getHostAddress();
140 
141             StringBuffer sb = new StringBuffer();
142 
143             sb.append( "Received " + SERVICE_NAME + " request:" );
144             sb.append( "\n\t" + "messageType:           " + request.getMessageType() );
145             sb.append( "\n\t" + "protocolVersionNumber: " + request.getProtocolVersionNumber() );
146             sb.append( "\n\t" + "clientAddress:         " + clientAddress );
147             sb.append( "\n\t" + "nonce:                 " + request.getNonce() );
148             sb.append( "\n\t" + "kdcOptions:            " + request.getKdcOptions() );
149             sb.append( "\n\t" + "clientPrincipal:       " + request.getClientPrincipal() );
150             sb.append( "\n\t" + "serverPrincipal:       " + request.getServerPrincipal() );
151             sb.append( "\n\t" + "encryptionType:        " + KerberosUtils.getEncryptionTypesString( request.getEType() ) );
152             sb.append( "\n\t" + "realm:                 " + request.getRealm() );
153             sb.append( "\n\t" + "from time:             " + request.getFrom() );
154             sb.append( "\n\t" + "till time:             " + request.getTill() );
155             sb.append( "\n\t" + "renew-till time:       " + request.getRtime() );
156             sb.append( "\n\t" + "hostAddresses:         " + request.getAddresses() );
157 
158             LOG.debug( sb.toString() );
159         }
160         catch ( Exception e )
161         {
162             // This is a monitor.  No exceptions should bubble up.
163             LOG.error( "Error in request monitor", e );
164         }
165     }
166     
167     
168     private static void selectEncryptionType( TicketGrantingContext tgsContext ) throws Exception
169     {
170         KdcContext kdcContext = (KdcContext)tgsContext;
171         KdcServer config = kdcContext.getConfig();
172 
173         Set<EncryptionType> requestedTypes = kdcContext.getRequest().getEType();
174 
175         EncryptionType bestType = KerberosUtils.getBestEncryptionType( requestedTypes, config.getEncryptionTypes() );
176 
177         LOG.debug( "Session will use encryption type {}.", bestType );
178 
179         if ( bestType == null )
180         {
181             throw new KerberosException( ErrorType.KDC_ERR_ETYPE_NOSUPP );
182         }
183 
184         kdcContext.setEncryptionType( bestType );
185     }
186     
187     
188     private static void getAuthHeader( TicketGrantingContext tgsContext ) throws Exception
189     {
190         KdcRequest request = tgsContext.getRequest();
191 
192         PaData[] preAuthData = request.getPreAuthData();
193 
194         if ( preAuthData == null || preAuthData.length < 1 )
195         {
196             throw new KerberosException( ErrorType.KDC_ERR_PADATA_TYPE_NOSUPP );
197         }
198 
199         byte[] undecodedAuthHeader = null;
200 
201         for ( int ii = 0; ii < preAuthData.length; ii++ )
202         {
203             if ( preAuthData[ii].getPaDataType() == PaDataType.PA_TGS_REQ )
204             {
205                 undecodedAuthHeader = preAuthData[ii].getPaDataValue();
206             }
207         }
208 
209         if ( undecodedAuthHeader == null )
210         {
211             throw new KerberosException( ErrorType.KDC_ERR_PADATA_TYPE_NOSUPP );
212         }
213 
214         ApplicationRequestDecoder decoder = new ApplicationRequestDecoder();
215         ApplicationRequest authHeader = decoder.decode( undecodedAuthHeader );
216         
217         Ticket tgt = authHeader.getTicket();
218 
219         tgsContext.setAuthHeader( authHeader );
220         tgsContext.setTgt( tgt );
221     }
222     
223     
224     public static void verifyTgt( TicketGrantingContext tgsContext ) throws KerberosException
225     {
226         KdcServer config = tgsContext.getConfig();
227         Ticket tgt = tgsContext.getTgt();
228 
229         // Check primary realm.
230         if ( !tgt.getRealm().equals( config.getPrimaryRealm() ) )
231         {
232             throw new KerberosException( ErrorType.KRB_AP_ERR_NOT_US );
233         }
234 
235         String tgtServerName = tgt.getServerPrincipal().getName();
236         String requestServerName = tgsContext.getRequest().getServerPrincipal().getName();
237 
238         /*
239          * if (tgt.sname is not a TGT for local realm and is not req.sname)
240          *     then error_out(KRB_AP_ERR_NOT_US);
241          */
242         if ( !tgtServerName.equals( config.getServicePrincipal().getName() )
243             && !tgtServerName.equals( requestServerName ) )
244         {
245             throw new KerberosException( ErrorType.KRB_AP_ERR_NOT_US );
246         }
247     }
248     
249     
250     private static void getTicketPrincipalEntry( TicketGrantingContext tgsContext ) throws KerberosException
251     {
252         KerberosPrincipal principal = tgsContext.getTgt().getServerPrincipal();
253         PrincipalStore store = tgsContext.getStore();
254 
255         PrincipalStoreEntry entry = KerberosUtils.getEntry( principal, store, ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN );
256         tgsContext.setTicketPrincipalEntry( entry );
257     }
258 
259 
260     private static void verifyTgtAuthHeader( TicketGrantingContext tgsContext ) throws KerberosException
261     {
262         ApplicationRequest authHeader = tgsContext.getAuthHeader();
263         Ticket tgt = tgsContext.getTgt();
264         
265         boolean isValidate = tgsContext.getRequest().getKdcOptions().get( KdcOptions.VALIDATE );
266 
267         EncryptionType encryptionType = tgt.getEncPart().getEType();
268         EncryptionKey serverKey = tgsContext.getTicketPrincipalEntry().getKeyMap().get( encryptionType );
269 
270         long clockSkew = tgsContext.getConfig().getAllowableClockSkew();
271         ReplayCache replayCache = tgsContext.getReplayCache();
272         boolean emptyAddressesAllowed = tgsContext.getConfig().isEmptyAddressesAllowed();
273         InetAddress clientAddress = tgsContext.getClientAddress();
274         CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler();
275 
276         Authenticator authenticator = KerberosUtils.verifyAuthHeader( authHeader, tgt, serverKey, clockSkew, replayCache,
277             emptyAddressesAllowed, clientAddress, cipherTextHandler, KeyUsage.NUMBER7, isValidate );
278 
279         tgsContext.setAuthenticator( authenticator );
280     }
281     
282     
283     private static void verifyBodyChecksum( TicketGrantingContext tgsContext ) throws KerberosException
284     {
285         KdcServer config = tgsContext.getConfig();
286 
287         if ( config.isBodyChecksumVerified() )
288         {
289             byte[] bodyBytes = tgsContext.getRequest().getBodyBytes();
290             Checksum authenticatorChecksum = tgsContext.getAuthenticator().getChecksum();
291 
292             if ( authenticatorChecksum == null || authenticatorChecksum.getChecksumType() == null
293                 || authenticatorChecksum.getChecksumValue() == null || bodyBytes == null )
294             {
295                 throw new KerberosException( ErrorType.KRB_AP_ERR_INAPP_CKSUM );
296             }
297 
298             LOG.debug( "Verifying body checksum type '{}'.", authenticatorChecksum.getChecksumType() );
299 
300             checksumHandler.verifyChecksum( authenticatorChecksum, bodyBytes, null, KeyUsage.NUMBER8 );
301         }
302     }
303     
304 
305     public static void getRequestPrincipalEntry( TicketGrantingContext tgsContext ) throws KerberosException
306     {
307         KerberosPrincipal principal = tgsContext.getRequest().getServerPrincipal();
308         PrincipalStore store = tgsContext.getStore();
309 
310         PrincipalStoreEntry entry = KerberosUtils.getEntry( principal, store, ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN );
311         tgsContext.setRequestPrincipalEntry( entry );
312     }
313 
314     
315     private static void generateTicket( TicketGrantingContext tgsContext ) throws KerberosException
316     {
317         KdcRequest request = tgsContext.getRequest();
318         Ticket tgt = tgsContext.getTgt();
319         Authenticator authenticator = tgsContext.getAuthenticator();
320         CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler();
321         KerberosPrincipal ticketPrincipal = request.getServerPrincipal();
322 
323         EncryptionType encryptionType = tgsContext.getEncryptionType();
324         EncryptionKey serverKey = tgsContext.getRequestPrincipalEntry().getKeyMap().get( encryptionType );
325 
326         KdcServer config = tgsContext.getConfig();
327 
328         EncTicketPartModifier newTicketBody = new EncTicketPartModifier();
329 
330         newTicketBody.setClientAddresses( tgt.getEncTicketPart().getClientAddresses() );
331 
332         processFlags( config, request, tgt, newTicketBody );
333 
334         EncryptionKey sessionKey = RandomKeyFactory.getRandomKey( tgsContext.getEncryptionType() );
335         newTicketBody.setSessionKey( sessionKey );
336 
337         newTicketBody.setClientPrincipal( tgt.getEncTicketPart().getClientPrincipal() );
338 
339         if ( request.getEncAuthorizationData() != null )
340         {
341             AuthorizationData authData = ( AuthorizationData ) cipherTextHandler.unseal( AuthorizationData.class,
342                 authenticator.getSubSessionKey(), request.getEncAuthorizationData(), KeyUsage.NUMBER4 );
343             authData.add( tgt.getEncTicketPart().getAuthorizationData() );
344             newTicketBody.setAuthorizationData( authData );
345         }
346 
347         processTransited( newTicketBody, tgt );
348 
349         processTimes( config, request, newTicketBody, tgt );
350 
351         EncTicketPart ticketPart = newTicketBody.getEncTicketPart();
352 
353         if ( request.getOption( KdcOptions.ENC_TKT_IN_SKEY ) )
354         {
355             /*
356              * if (server not specified) then
357              *         server = req.second_ticket.client;
358              * endif
359              * 
360              * if ((req.second_ticket is not a TGT) or
361              *     (req.second_ticket.client != server)) then
362              *         error_out(KDC_ERR_POLICY);
363              * endif
364              * 
365              * new_tkt.enc-part := encrypt OCTET STRING using etype_for_key(second-ticket.key), second-ticket.key;
366              */
367             throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
368         }
369         else
370         {
371             EncryptedData encryptedData = cipherTextHandler.seal( serverKey, ticketPart, KeyUsage.NUMBER2 );
372 
373             Ticket newTicket = new Ticket( ticketPrincipal, encryptedData );
374             newTicket.setEncTicketPart( ticketPart );
375 
376             tgsContext.setNewTicket( newTicket );
377         }
378     }
379     
380 
381     private static void buildReply( TicketGrantingContext tgsContext ) throws KerberosException
382     {
383         KdcRequest request = tgsContext.getRequest();
384         Ticket tgt = tgsContext.getTgt();
385         Ticket newTicket = tgsContext.getNewTicket();
386 
387         TicketGrantReply reply = new TicketGrantReply();
388         reply.setClientPrincipal( tgt.getEncTicketPart().getClientPrincipal() );
389         reply.setTicket( newTicket );
390         reply.setKey( newTicket.getEncTicketPart().getSessionKey() );
391         reply.setNonce( request.getNonce() );
392         // TODO - resp.last-req := fetch_last_request_info(client); requires store
393         reply.setLastRequest( new LastRequest() );
394         reply.setFlags( newTicket.getEncTicketPart().getFlags() );
395         reply.setClientAddresses( newTicket.getEncTicketPart().getClientAddresses() );
396         reply.setAuthTime( newTicket.getEncTicketPart().getAuthTime() );
397         reply.setStartTime( newTicket.getEncTicketPart().getStartTime() );
398         reply.setEndTime( newTicket.getEncTicketPart().getEndTime() );
399         reply.setServerPrincipal( newTicket.getServerPrincipal() );
400 
401         if ( newTicket.getEncTicketPart().getFlags().isRenewable() )
402         {
403             reply.setRenewTill( newTicket.getEncTicketPart().getRenewTill() );
404         }
405 
406         tgsContext.setReply( reply );
407     }
408     
409     
410     private static void sealReply( TicketGrantingContext tgsContext ) throws KerberosException
411     {
412         TicketGrantReply reply = ( TicketGrantReply ) tgsContext.getReply();
413         Ticket tgt = tgsContext.getTgt();
414         CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler();
415         Authenticator authenticator = tgsContext.getAuthenticator();
416 
417         EncryptedData encryptedData;
418 
419         if ( authenticator.getSubSessionKey() != null )
420         {
421             encryptedData = cipherTextHandler.seal( authenticator.getSubSessionKey(), reply, KeyUsage.NUMBER9 );
422         }
423         else
424         {
425             encryptedData = cipherTextHandler.seal( tgt.getEncTicketPart().getSessionKey(), reply, KeyUsage.NUMBER8 );
426         }
427 
428         reply.setEncPart( encryptedData );
429     }
430     
431     
432     
433     private static void monitorContext( TicketGrantingContext tgsContext )
434     {
435         try
436         {
437             Ticket tgt = tgsContext.getTgt();
438             long clockSkew = tgsContext.getConfig().getAllowableClockSkew();
439             ChecksumType checksumType = tgsContext.getAuthenticator().getChecksum().getChecksumType();
440             InetAddress clientAddress = tgsContext.getClientAddress();
441             HostAddresses clientAddresses = tgt.getEncTicketPart().getClientAddresses();
442 
443             boolean caddrContainsSender = false;
444             if ( tgt.getEncTicketPart().getClientAddresses() != null )
445             {
446                 caddrContainsSender = tgt.getEncTicketPart().getClientAddresses().contains( new HostAddress( clientAddress ) );
447             }
448 
449             StringBuffer sb = new StringBuffer();
450 
451             sb.append( "Monitoring " + SERVICE_NAME + " context:" );
452 
453             sb.append( "\n\t" + "clockSkew              " + clockSkew );
454             sb.append( "\n\t" + "checksumType           " + checksumType );
455             sb.append( "\n\t" + "clientAddress          " + clientAddress );
456             sb.append( "\n\t" + "clientAddresses        " + clientAddresses );
457             sb.append( "\n\t" + "caddr contains sender  " + caddrContainsSender );
458 
459             KerberosPrincipal requestServerPrincipal = tgsContext.getRequest().getServerPrincipal();
460             PrincipalStoreEntry requestPrincipal = tgsContext.getRequestPrincipalEntry();
461 
462             sb.append( "\n\t" + "principal              " + requestServerPrincipal );
463             sb.append( "\n\t" + "cn                     " + requestPrincipal.getCommonName() );
464             sb.append( "\n\t" + "realm                  " + requestPrincipal.getRealmName() );
465             sb.append( "\n\t" + "principal              " + requestPrincipal.getPrincipal() );
466             sb.append( "\n\t" + "SAM type               " + requestPrincipal.getSamType() );
467 
468             KerberosPrincipal ticketServerPrincipal = tgsContext.getTgt().getServerPrincipal();
469             PrincipalStoreEntry ticketPrincipal = tgsContext.getTicketPrincipalEntry();
470 
471             sb.append( "\n\t" + "principal              " + ticketServerPrincipal );
472             sb.append( "\n\t" + "cn                     " + ticketPrincipal.getCommonName() );
473             sb.append( "\n\t" + "realm                  " + ticketPrincipal.getRealmName() );
474             sb.append( "\n\t" + "principal              " + ticketPrincipal.getPrincipal() );
475             sb.append( "\n\t" + "SAM type               " + ticketPrincipal.getSamType() );
476 
477             EncryptionType encryptionType = tgsContext.getTgt().getEncPart().getEType();
478             int keyVersion = ticketPrincipal.getKeyMap().get( encryptionType ).getKeyVersion();
479             sb.append( "\n\t" + "Ticket key type        " + encryptionType );
480             sb.append( "\n\t" + "Service key version    " + keyVersion );
481 
482             LOG.debug( sb.toString() );
483         }
484         catch ( Exception e )
485         {
486             // This is a monitor.  No exceptions should bubble up.
487             LOG.error( "Error in context monitor", e );
488         }
489     }
490 
491     
492     private static void monitorReply( KdcContext kdcContext )
493     {
494         Object reply = kdcContext.getReply();
495 
496         if ( reply instanceof KdcReply )
497         {
498             KdcReply success = ( KdcReply ) reply;
499 
500             try
501             {
502                 StringBuffer sb = new StringBuffer();
503 
504                 sb.append( "Responding with " + SERVICE_NAME + " reply:" );
505                 sb.append( "\n\t" + "messageType:           " + success.getMessageType() );
506                 sb.append( "\n\t" + "protocolVersionNumber: " + success.getProtocolVersionNumber() );
507                 sb.append( "\n\t" + "nonce:                 " + success.getNonce() );
508                 sb.append( "\n\t" + "clientPrincipal:       " + success.getClientPrincipal() );
509                 sb.append( "\n\t" + "client realm:          " + success.getClientRealm() );
510                 sb.append( "\n\t" + "serverPrincipal:       " + success.getServerPrincipal() );
511                 sb.append( "\n\t" + "server realm:          " + success.getServerRealm() );
512                 sb.append( "\n\t" + "auth time:             " + success.getAuthTime() );
513                 sb.append( "\n\t" + "start time:            " + success.getStartTime() );
514                 sb.append( "\n\t" + "end time:              " + success.getEndTime() );
515                 sb.append( "\n\t" + "renew-till time:       " + success.getRenewTill() );
516                 sb.append( "\n\t" + "hostAddresses:         " + success.getClientAddresses() );
517 
518                 LOG.debug( sb.toString() );
519             }
520             catch ( Exception e )
521             {
522                 // This is a monitor.  No exceptions should bubble up.
523                 LOG.error( "Error in reply monitor", e );
524             }
525         }
526     }
527     
528 
529     
530     private static void processFlags( KdcServer config, KdcRequest request, Ticket tgt,
531         EncTicketPartModifier newTicketBody ) throws KerberosException
532     {
533         if ( tgt.getEncTicketPart().getFlags().isPreAuth() )
534         {
535             newTicketBody.setFlag( TicketFlag.PRE_AUTHENT );
536         }
537 
538         if ( request.getOption( KdcOptions.FORWARDABLE ) )
539         {
540             if ( !config.isForwardableAllowed() )
541             {
542                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
543             }
544 
545             if ( !tgt.getEncTicketPart().getFlags().isForwardable() )
546             {
547                 throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
548             }
549 
550             newTicketBody.setFlag( TicketFlag.FORWARDABLE );
551         }
552 
553         if ( request.getOption( KdcOptions.FORWARDED ) )
554         {
555             if ( !config.isForwardableAllowed() )
556             {
557                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
558             }
559 
560             if ( !tgt.getEncTicketPart().getFlags().isForwardable() )
561             {
562                 throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
563             }
564 
565             if ( request.getAddresses() != null && request.getAddresses().getAddresses() != null
566                 && request.getAddresses().getAddresses().length > 0 )
567             {
568                 newTicketBody.setClientAddresses( request.getAddresses() );
569             }
570             else
571             {
572                 if ( !config.isEmptyAddressesAllowed() )
573                 {
574                     throw new KerberosException( ErrorType.KDC_ERR_POLICY );
575                 }
576             }
577 
578             newTicketBody.setFlag( TicketFlag.FORWARDED );
579         }
580 
581         if ( tgt.getEncTicketPart().getFlags().isForwarded() )
582         {
583             newTicketBody.setFlag( TicketFlag.FORWARDED );
584         }
585 
586         if ( request.getOption( KdcOptions.PROXIABLE ) )
587         {
588             if ( !config.isProxiableAllowed() )
589             {
590                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
591             }
592 
593             if ( !tgt.getEncTicketPart().getFlags().isProxiable() )
594             {
595                 throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
596             }
597 
598             newTicketBody.setFlag( TicketFlag.PROXIABLE );
599         }
600 
601         if ( request.getOption( KdcOptions.PROXY ) )
602         {
603             if ( !config.isProxiableAllowed() )
604             {
605                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
606             }
607 
608             if ( !tgt.getEncTicketPart().getFlags().isProxiable() )
609             {
610                 throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
611             }
612 
613             if ( request.getAddresses() != null && request.getAddresses().getAddresses() != null
614                 && request.getAddresses().getAddresses().length > 0 )
615             {
616                 newTicketBody.setClientAddresses( request.getAddresses() );
617             }
618             else
619             {
620                 if ( !config.isEmptyAddressesAllowed() )
621                 {
622                     throw new KerberosException( ErrorType.KDC_ERR_POLICY );
623                 }
624             }
625 
626             newTicketBody.setFlag( TicketFlag.PROXY );
627         }
628 
629         if ( request.getOption( KdcOptions.ALLOW_POSTDATE ) )
630         {
631             if ( !config.isPostdatedAllowed() )
632             {
633                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
634             }
635 
636             if ( !tgt.getEncTicketPart().getFlags().isMayPosdate() )
637             {
638                 throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
639             }
640 
641             newTicketBody.setFlag( TicketFlag.MAY_POSTDATE );
642         }
643 
644         /*
645          * "Otherwise, if the TGT has the MAY-POSTDATE flag set, then the resulting
646          * ticket will be postdated, and the requested starttime is checked against
647          * the policy of the local realm.  If acceptable, the ticket's starttime is
648          * set as requested, and the INVALID flag is set.  The postdated ticket MUST
649          * be validated before use by presenting it to the KDC after the starttime
650          * has been reached.  However, in no case may the starttime, endtime, or
651          * renew-till time of a newly-issued postdated ticket extend beyond the
652          * renew-till time of the TGT."
653          */
654         if ( request.getOption( KdcOptions.POSTDATED ) )
655         {
656             if ( !config.isPostdatedAllowed() )
657             {
658                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
659             }
660 
661             if ( !tgt.getEncTicketPart().getFlags().isMayPosdate() )
662             {
663                 throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
664             }
665 
666             newTicketBody.setFlag( TicketFlag.POSTDATED );
667             newTicketBody.setFlag( TicketFlag.INVALID );
668 
669             newTicketBody.setStartTime( request.getFrom() );
670         }
671 
672         if ( request.getOption( KdcOptions.VALIDATE ) )
673         {
674             if ( !config.isPostdatedAllowed() )
675             {
676                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
677             }
678 
679             if ( !tgt.getEncTicketPart().getFlags().isInvalid() )
680             {
681                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
682             }
683 
684             KerberosTime startTime = ( tgt.getEncTicketPart().getStartTime() != null ) ? 
685                     tgt.getEncTicketPart().getStartTime() : 
686                         tgt.getEncTicketPart().getAuthTime();
687 
688             if ( startTime.greaterThan( new KerberosTime() ) )
689             {
690                 throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_NYV );
691             }
692 
693             echoTicket( newTicketBody, tgt );
694             newTicketBody.clearFlag( TicketFlag.INVALID );
695         }
696 
697         if ( request.getOption( KdcOptions.RESERVED ) )
698         {
699             throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
700         }
701     }
702 
703 
704     private static void processTimes( KdcServer config, KdcRequest request, EncTicketPartModifier newTicketBody,
705         Ticket tgt ) throws KerberosException
706     {
707         KerberosTime now = new KerberosTime();
708 
709         newTicketBody.setAuthTime( tgt.getEncTicketPart().getAuthTime() );
710 
711         KerberosTime startTime = request.getFrom();
712 
713         /*
714          * "If the requested starttime is absent, indicates a time in the past,
715          * or is within the window of acceptable clock skew for the KDC and the
716          * POSTDATE option has not been specified, then the starttime of the
717          * ticket is set to the authentication server's current time."
718          */
719         if ( startTime == null || startTime.lessThan( now ) || startTime.isInClockSkew( config.getAllowableClockSkew() )
720             && !request.getOption( KdcOptions.POSTDATED ) )
721         {
722             startTime = now;
723         }
724 
725         /*
726          * "If it indicates a time in the future beyond the acceptable clock skew,
727          * but the POSTDATED option has not been specified or the MAY-POSTDATE flag
728          * is not set in the TGT, then the error KDC_ERR_CANNOT_POSTDATE is
729          * returned."
730          */
731         if ( startTime != null && startTime.greaterThan( now )
732             && !startTime.isInClockSkew( config.getAllowableClockSkew() )
733             && ( !request.getOption( KdcOptions.POSTDATED ) || !tgt.getEncTicketPart().getFlags().isMayPosdate() ) )
734         {
735             throw new KerberosException( ErrorType.KDC_ERR_CANNOT_POSTDATE );
736         }
737 
738         KerberosTime renewalTime = null;
739         KerberosTime kerberosEndTime = null;
740 
741         if ( request.getOption( KdcOptions.RENEW ) )
742         {
743             if ( !config.isRenewableAllowed() )
744             {
745                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
746             }
747 
748             if ( !tgt.getEncTicketPart().getFlags().isRenewable() )
749             {
750                 throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
751             }
752 
753             if ( tgt.getEncTicketPart().getRenewTill().lessThan( now ) )
754             {
755                 throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_EXPIRED );
756             }
757 
758             echoTicket( newTicketBody, tgt );
759 
760             newTicketBody.setStartTime( now );
761 
762             KerberosTime tgtStartTime = ( tgt.getEncTicketPart().getStartTime() != null ) ? 
763                 tgt.getEncTicketPart().getStartTime() : 
764                     tgt.getEncTicketPart().getAuthTime();
765 
766             long oldLife = tgt.getEncTicketPart().getEndTime().getTime() - tgtStartTime.getTime();
767 
768             kerberosEndTime = new KerberosTime( Math.min( tgt.getEncTicketPart().getRenewTill().getTime(), now.getTime() + oldLife ) );
769             newTicketBody.setEndTime( kerberosEndTime );
770         }
771         else
772         {
773             if ( newTicketBody.getEncTicketPart().getStartTime() == null )
774             {
775                 newTicketBody.setStartTime( now );
776             }
777 
778             KerberosTime till;
779             if ( request.getTill().isZero() )
780             {
781                 till = KerberosTime.INFINITY;
782             }
783             else
784             {
785                 till = request.getTill();
786             }
787 
788             /*
789              * The end time is the minimum of (a) the requested till time or (b)
790              * the start time plus maximum lifetime as configured in policy or (c)
791              * the end time of the TGT.
792              */
793             List<KerberosTime> minimizer = new ArrayList<KerberosTime>();
794             minimizer.add( till );
795             minimizer.add( new KerberosTime( startTime.getTime() + config.getMaximumTicketLifetime() ) );
796             minimizer.add( tgt.getEncTicketPart().getEndTime() );
797             kerberosEndTime = Collections.min( minimizer );
798 
799             newTicketBody.setEndTime( kerberosEndTime );
800 
801             if ( request.getOption( KdcOptions.RENEWABLE_OK ) && kerberosEndTime.lessThan( request.getTill() )
802                 && tgt.getEncTicketPart().getFlags().isRenewable() )
803             {
804                 if ( !config.isRenewableAllowed() )
805                 {
806                     throw new KerberosException( ErrorType.KDC_ERR_POLICY );
807                 }
808 
809                 // We set the RENEWABLE option for later processing.                           
810                 request.setOption( KdcOptions.RENEWABLE );
811                 long rtime = Math.min( request.getTill().getTime(), tgt.getEncTicketPart().getRenewTill().getTime() );
812                 renewalTime = new KerberosTime( rtime );
813             }
814         }
815 
816         if ( renewalTime == null )
817         {
818             renewalTime = request.getRtime();
819         }
820 
821         KerberosTime rtime;
822         if ( renewalTime != null && renewalTime.isZero() )
823         {
824             rtime = KerberosTime.INFINITY;
825         }
826         else
827         {
828             rtime = renewalTime;
829         }
830 
831         if ( request.getOption( KdcOptions.RENEWABLE ) && tgt.getEncTicketPart().getFlags().isRenewable() )
832         {
833             if ( !config.isRenewableAllowed() )
834             {
835                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
836             }
837 
838             newTicketBody.setFlag( TicketFlag.RENEWABLE );
839 
840             /*
841              * The renew-till time is the minimum of (a) the requested renew-till
842              * time or (b) the start time plus maximum renewable lifetime as
843              * configured in policy or (c) the renew-till time of the TGT.
844              */
845             List<KerberosTime> minimizer = new ArrayList<KerberosTime>();
846 
847             /*
848              * 'rtime' KerberosTime is OPTIONAL
849              */
850             if ( rtime != null )
851             {
852                 minimizer.add( rtime );
853             }
854 
855             minimizer.add( new KerberosTime( startTime.getTime() + config.getMaximumRenewableLifetime() ) );
856             minimizer.add( tgt.getEncTicketPart().getRenewTill() );
857             newTicketBody.setRenewTill( Collections.min( minimizer ) );
858         }
859 
860         /*
861          * "If the requested expiration time minus the starttime (as determined
862          * above) is less than a site-determined minimum lifetime, an error
863          * message with code KDC_ERR_NEVER_VALID is returned."
864          */
865         if ( kerberosEndTime.lessThan( startTime ) )
866         {
867             throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID );
868         }
869 
870         long ticketLifeTime = Math.abs( startTime.getTime() - kerberosEndTime.getTime() );
871         if ( ticketLifeTime < config.getAllowableClockSkew() )
872         {
873             throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID );
874         }
875     }
876 
877 
878     /*
879      * if (realm_tgt_is_for(tgt) := tgt.realm) then
880      *         // tgt issued by local realm
881      *         new_tkt.transited := tgt.transited;
882      * else
883      *         // was issued for this realm by some other realm
884      *         if (tgt.transited.tr-type not supported) then
885      *                 error_out(KDC_ERR_TRTYPE_NOSUPP);
886      *         endif
887      * 
888      *         new_tkt.transited := compress_transited(tgt.transited + tgt.realm)
889      * endif
890      */    
891     private static void processTransited( EncTicketPartModifier newTicketBody, Ticket tgt )
892     {
893         // TODO - currently no transited support other than local
894         newTicketBody.setTransitedEncoding( tgt.getEncTicketPart().getTransitedEncoding() );
895     }
896 
897     
898     private static void echoTicket( EncTicketPartModifier newTicketBody, Ticket tgt )
899     {
900         EncTicketPart encTicketpart = tgt.getEncTicketPart();
901         newTicketBody.setAuthorizationData( encTicketpart.getAuthorizationData() );
902         newTicketBody.setAuthTime( encTicketpart.getAuthTime() );
903         newTicketBody.setClientAddresses( encTicketpart.getClientAddresses() );
904         newTicketBody.setClientPrincipal( encTicketpart.getClientPrincipal() );
905         newTicketBody.setEndTime( encTicketpart.getEndTime() );
906         newTicketBody.setFlags( encTicketpart.getFlags() );
907         newTicketBody.setRenewTill( encTicketpart.getRenewTill() );
908         newTicketBody.setSessionKey( encTicketpart.getSessionKey() );
909         newTicketBody.setTransitedEncoding( encTicketpart.getTransitedEncoding() );
910     }
911 }