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