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 */
020package org.apache.directory.server.kerberos.kdc.ticketgrant;
021
022
023import java.net.InetAddress;
024import java.nio.ByteBuffer;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.List;
028import java.util.Set;
029
030import javax.security.auth.kerberos.KerberosPrincipal;
031
032import org.apache.directory.api.asn1.EncoderException;
033import org.apache.directory.api.ldap.model.constants.Loggers;
034import org.apache.directory.server.i18n.I18n;
035import org.apache.directory.server.kerberos.KerberosConfig;
036import org.apache.directory.server.kerberos.kdc.KdcContext;
037import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumHandler;
038import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
039import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
040import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
041import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
042import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
043import org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntry;
044import org.apache.directory.shared.kerberos.KerberosConstants;
045import org.apache.directory.shared.kerberos.KerberosTime;
046import org.apache.directory.shared.kerberos.KerberosUtils;
047import org.apache.directory.shared.kerberos.codec.KerberosDecoder;
048import org.apache.directory.shared.kerberos.codec.options.KdcOptions;
049import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
050import org.apache.directory.shared.kerberos.codec.types.LastReqType;
051import org.apache.directory.shared.kerberos.codec.types.PaDataType;
052import org.apache.directory.shared.kerberos.components.AuthorizationData;
053import org.apache.directory.shared.kerberos.components.Checksum;
054import org.apache.directory.shared.kerberos.components.EncKdcRepPart;
055import org.apache.directory.shared.kerberos.components.EncTicketPart;
056import org.apache.directory.shared.kerberos.components.EncryptedData;
057import org.apache.directory.shared.kerberos.components.EncryptionKey;
058import org.apache.directory.shared.kerberos.components.HostAddress;
059import org.apache.directory.shared.kerberos.components.HostAddresses;
060import org.apache.directory.shared.kerberos.components.KdcReq;
061import org.apache.directory.shared.kerberos.components.KdcReqBody;
062import org.apache.directory.shared.kerberos.components.LastReq;
063import org.apache.directory.shared.kerberos.components.LastReqEntry;
064import org.apache.directory.shared.kerberos.components.PaData;
065import org.apache.directory.shared.kerberos.components.PrincipalName;
066import org.apache.directory.shared.kerberos.crypto.checksum.ChecksumType;
067import org.apache.directory.shared.kerberos.exceptions.ErrorType;
068import org.apache.directory.shared.kerberos.exceptions.InvalidTicketException;
069import org.apache.directory.shared.kerberos.exceptions.KerberosException;
070import org.apache.directory.shared.kerberos.flags.TicketFlag;
071import org.apache.directory.shared.kerberos.messages.ApReq;
072import org.apache.directory.shared.kerberos.messages.Authenticator;
073import org.apache.directory.shared.kerberos.messages.EncTgsRepPart;
074import org.apache.directory.shared.kerberos.messages.TgsRep;
075import org.apache.directory.shared.kerberos.messages.Ticket;
076import org.slf4j.Logger;
077import org.slf4j.LoggerFactory;
078
079
080/**
081 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
082 */
083public final class TicketGrantingService
084{
085
086    /** the log for this class */
087    private static final Logger LOG_KRB = LoggerFactory.getLogger( Loggers.KERBEROS_LOG.getName() );
088
089    private static final CipherTextHandler CIPHER_TEXT_HANDLER = new CipherTextHandler();
090
091    private static final String SERVICE_NAME = "Ticket-Granting Service (TGS)";
092
093    private static final ChecksumHandler CHRECKSUM_HANDLER = new ChecksumHandler();
094
095
096    private TicketGrantingService()
097    {
098    }
099
100
101    public static void execute( TicketGrantingContext tgsContext ) throws Exception
102    {
103        if ( LOG_KRB.isDebugEnabled() )
104        {
105            monitorRequest( tgsContext );
106        }
107
108        configureTicketGranting( tgsContext );
109        selectEncryptionType( tgsContext );
110        getAuthHeader( tgsContext );
111        // commenting to allow cross-realm auth
112        //verifyTgt( tgsContext );
113        getTicketPrincipalEntry( tgsContext );
114        verifyTgtAuthHeader( tgsContext );
115        verifyBodyChecksum( tgsContext );
116        getRequestPrincipalEntry( tgsContext );
117        generateTicket( tgsContext );
118        buildReply( tgsContext );
119    }
120
121
122    private static void configureTicketGranting( TicketGrantingContext tgsContext ) throws KerberosException
123    {
124        tgsContext.setCipherTextHandler( CIPHER_TEXT_HANDLER );
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        KdcReq 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.getKdcReqBody().getNonce() );
148            sb.append( "\n\t" + "kdcOptions:            " + request.getKdcReqBody().getKdcOptions() );
149            sb.append( "\n\t" + "clientPrincipal:       " + request.getKdcReqBody().getCName() );
150            sb.append( "\n\t" + "serverPrincipal:       " + request.getKdcReqBody().getSName() );
151            sb.append( "\n\t" + "encryptionType:        "
152                + KerberosUtils.getEncryptionTypesString( request.getKdcReqBody().getEType() ) );
153            sb.append( "\n\t" + "realm:                 " + request.getKdcReqBody().getRealm() );
154            sb.append( "\n\t" + "from time:             " + request.getKdcReqBody().getFrom() );
155            sb.append( "\n\t" + "till time:             " + request.getKdcReqBody().getTill() );
156            sb.append( "\n\t" + "renew-till time:       " + request.getKdcReqBody().getRTime() );
157            sb.append( "\n\t" + "hostAddresses:         " + request.getKdcReqBody().getAddresses() );
158
159            LOG_KRB.debug( sb.toString() );
160        }
161        catch ( Exception e )
162        {
163            // This is a monitor.  No exceptions should bubble up.
164            LOG_KRB.error( I18n.err( I18n.ERR_153 ), e );
165        }
166    }
167
168
169    private static void selectEncryptionType( TicketGrantingContext tgsContext ) throws Exception
170    {
171        KdcContext kdcContext = tgsContext;
172        KerberosConfig config = kdcContext.getConfig();
173
174        Set<EncryptionType> requestedTypes = kdcContext.getRequest().getKdcReqBody().getEType();
175
176        EncryptionType bestType = KerberosUtils.getBestEncryptionType( requestedTypes, config.getEncryptionTypes() );
177
178        LOG_KRB.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        KdcReq request = tgsContext.getRequest();
192
193        if ( ( request.getPaData() == null ) || ( request.getPaData().size() < 1 ) )
194        {
195            throw new KerberosException( ErrorType.KDC_ERR_PADATA_TYPE_NOSUPP );
196        }
197
198        byte[] undecodedAuthHeader = null;
199
200        for ( PaData paData : request.getPaData() )
201        {
202            if ( paData.getPaDataType() == PaDataType.PA_TGS_REQ )
203            {
204                undecodedAuthHeader = paData.getPaDataValue();
205            }
206        }
207
208        if ( undecodedAuthHeader == null )
209        {
210            throw new KerberosException( ErrorType.KDC_ERR_PADATA_TYPE_NOSUPP );
211        }
212
213        ApReq authHeader = KerberosDecoder.decodeApReq( undecodedAuthHeader );
214
215        Ticket tgt = authHeader.getTicket();
216
217        tgsContext.setAuthHeader( authHeader );
218        tgsContext.setTgt( tgt );
219    }
220
221
222    public static void verifyTgt( TicketGrantingContext tgsContext ) throws KerberosException
223    {
224        KerberosConfig config = tgsContext.getConfig();
225        Ticket tgt = tgsContext.getTgt();
226
227        // Check primary realm.
228        if ( !tgt.getRealm().equals( config.getPrimaryRealm() ) )
229        {
230            throw new KerberosException( ErrorType.KRB_AP_ERR_NOT_US );
231        }
232
233        String tgtServerName = KerberosUtils.getKerberosPrincipal( tgt.getSName(), tgt.getRealm() ).getName();
234        String requestServerName = KerberosUtils.getKerberosPrincipal(
235            tgsContext.getRequest().getKdcReqBody().getSName(), tgsContext.getRequest().getKdcReqBody().getRealm() )
236            .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        PrincipalName principal = tgsContext.getTgt().getSName();
253        PrincipalStore store = tgsContext.getStore();
254
255        KerberosPrincipal principalWithRealm = KerberosUtils.getKerberosPrincipal( principal, tgsContext.getTgt()
256            .getRealm() );
257        PrincipalStoreEntry entry = getEntry( principalWithRealm, store, ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN );
258        tgsContext.setTicketPrincipalEntry( entry );
259    }
260
261
262    private static void verifyTgtAuthHeader( TicketGrantingContext tgsContext ) throws KerberosException
263    {
264        ApReq authHeader = tgsContext.getAuthHeader();
265        Ticket tgt = tgsContext.getTgt();
266
267        KdcOptions kdcOptions = tgsContext.getRequest().getKdcReqBody().getKdcOptions();
268        boolean isValidate = kdcOptions.get( KdcOptions.VALIDATE );
269
270        EncryptionType encryptionType = tgt.getEncPart().getEType();
271        EncryptionKey serverKey = tgsContext.getTicketPrincipalEntry().getKeyMap().get( encryptionType );
272
273        long clockSkew = tgsContext.getConfig().getAllowableClockSkew();
274        ReplayCache replayCache = tgsContext.getReplayCache();
275        boolean emptyAddressesAllowed = tgsContext.getConfig().isEmptyAddressesAllowed();
276        InetAddress clientAddress = tgsContext.getClientAddress();
277        CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler();
278
279        Authenticator authenticator = KerberosUtils.verifyAuthHeader( authHeader, tgt, serverKey, clockSkew,
280            replayCache,
281            emptyAddressesAllowed, clientAddress, cipherTextHandler,
282            KeyUsage.TGS_REQ_PA_TGS_REQ_PADATA_AP_REQ_TGS_SESS_KEY, isValidate );
283
284        tgsContext.setAuthenticator( authenticator );
285    }
286
287
288    /**
289     * RFC4120
290     * <li>Section 3.3.2. Receipt of KRB_TGS_REQ Message -> 2nd paragraph
291     * <li>Section 5.5.1. KRB_AP_REQ Definition -> Authenticator -> cksum
292     */
293    private static void verifyBodyChecksum( TicketGrantingContext tgsContext ) throws KerberosException
294    {
295        KerberosConfig config = tgsContext.getConfig();
296
297        if ( config.isBodyChecksumVerified() )
298        {
299            KdcReqBody body = tgsContext.getRequest().getKdcReqBody();
300            // FIXME how this byte[] is computed??
301            // is it full ASN.1 encoded bytes OR just the bytes of all the values alone?
302            // for now am using the ASN.1 encoded value
303            ByteBuffer buf = ByteBuffer.allocate( body.computeLength() );
304            try
305            {
306                body.encode( buf );
307            }
308            catch ( EncoderException e )
309            {
310                throw new KerberosException( ErrorType.KRB_AP_ERR_INAPP_CKSUM );
311            }
312
313            byte[] bodyBytes = buf.array();
314            Checksum authenticatorChecksum = tgsContext.getAuthenticator().getCksum();
315
316            if ( authenticatorChecksum != null )
317            {
318                // we need the session key
319                Ticket tgt = tgsContext.getTgt();
320                EncTicketPart encTicketPart = tgt.getEncTicketPart();
321                EncryptionKey sessionKey = encTicketPart.getKey();
322
323                if ( authenticatorChecksum == null || authenticatorChecksum.getChecksumType() == null
324                    || authenticatorChecksum.getChecksumValue() == null || bodyBytes == null )
325                {
326                    throw new KerberosException( ErrorType.KRB_AP_ERR_INAPP_CKSUM );
327                }
328
329                LOG_KRB.debug( "Verifying body checksum type '{}'.", authenticatorChecksum.getChecksumType() );
330
331                CHRECKSUM_HANDLER.verifyChecksum( authenticatorChecksum, bodyBytes, sessionKey.getKeyValue(),
332                    KeyUsage.TGS_REQ_PA_TGS_REQ_PADATA_AP_REQ_AUTHNT_CKSUM_TGS_SESS_KEY );
333            }
334        }
335    }
336
337
338    public static void getRequestPrincipalEntry( TicketGrantingContext tgsContext ) throws KerberosException
339    {
340        KerberosPrincipal principal = KerberosUtils.getKerberosPrincipal(
341            tgsContext.getRequest().getKdcReqBody().getSName(), tgsContext.getRequest().getKdcReqBody().getRealm() );
342        PrincipalStore store = tgsContext.getStore();
343
344        PrincipalStoreEntry entry = getEntry( principal, store, ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN );
345        tgsContext.setRequestPrincipalEntry( entry );
346    }
347
348
349    private static void generateTicket( TicketGrantingContext tgsContext ) throws KerberosException,
350        InvalidTicketException
351    {
352        KdcReq request = tgsContext.getRequest();
353        Ticket tgt = tgsContext.getTgt();
354        Authenticator authenticator = tgsContext.getAuthenticator();
355        CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler();
356        KerberosPrincipal ticketPrincipal = KerberosUtils.getKerberosPrincipal(
357            request.getKdcReqBody().getSName(), request.getKdcReqBody().getRealm() );
358
359        EncryptionType encryptionType = tgsContext.getEncryptionType();
360        EncryptionKey serverKey = tgsContext.getRequestPrincipalEntry().getKeyMap().get( encryptionType );
361
362        KerberosConfig config = tgsContext.getConfig();
363
364        tgsContext.getRequest().getKdcReqBody().getAdditionalTickets();
365
366        EncTicketPart newTicketPart = new EncTicketPart();
367
368        newTicketPart.setClientAddresses( tgt.getEncTicketPart().getClientAddresses() );
369
370        processFlags( config, request, tgt, newTicketPart );
371
372        EncryptionKey sessionKey = RandomKeyFactory.getRandomKey( tgsContext.getEncryptionType() );
373        newTicketPart.setKey( sessionKey );
374
375        newTicketPart.setCName( tgt.getEncTicketPart().getCName() );
376        newTicketPart.setCRealm( tgt.getEncTicketPart().getCRealm() );
377
378        if ( request.getKdcReqBody().getEncAuthorizationData() != null )
379        {
380            byte[] authorizationData = cipherTextHandler.decrypt( authenticator.getSubKey(), request.getKdcReqBody()
381                .getEncAuthorizationData(), KeyUsage.TGS_REQ_KDC_REQ_BODY_AUTHZ_DATA_ENC_WITH_TGS_SESS_KEY );
382            AuthorizationData authData = KerberosDecoder.decodeAuthorizationData( authorizationData );
383            authData.addEntry( tgt.getEncTicketPart().getAuthorizationData().getCurrentAD() );
384            newTicketPart.setAuthorizationData( authData );
385        }
386
387        processTransited( newTicketPart, tgt );
388
389        processTimes( config, request, newTicketPart, tgt );
390
391        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.ENC_TKT_IN_SKEY ) )
392        {
393            Ticket[] additionalTkts = tgsContext.getRequest().getKdcReqBody().getAdditionalTickets();
394
395            if ( additionalTkts == null || additionalTkts.length == 0 )
396            {
397                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
398            }
399
400            Ticket additionalTgt = additionalTkts[0];
401            // reject if it is not a TGT
402            if ( !additionalTgt.getEncTicketPart().getFlags().isInitial() )
403            {
404                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
405            }
406
407            serverKey = additionalTgt.getEncTicketPart().getKey();
408            /*
409             * if (server not specified) then
410             *         server = req.second_ticket.client;
411             * endif
412             * 
413             * if ((req.second_ticket is not a TGT) or
414             *     (req.second_ticket.client != server)) then
415             *         error_out(KDC_ERR_POLICY);
416             * endif
417             * 
418             * new_tkt.enc-part := encrypt OCTET STRING using etype_for_key(second-ticket.key), second-ticket.key;
419             */
420            //throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
421        }
422
423        EncryptedData encryptedData = cipherTextHandler.seal( serverKey, newTicketPart,
424            KeyUsage.AS_OR_TGS_REP_TICKET_WITH_SRVKEY );
425
426        Ticket newTicket = new Ticket( request.getKdcReqBody().getSName(), encryptedData );
427        newTicket.setEncTicketPart( newTicketPart );
428        newTicket.setRealm( request.getKdcReqBody().getRealm() );
429
430        tgsContext.setNewTicket( newTicket );
431    }
432
433
434    private static void buildReply( TicketGrantingContext tgsContext ) throws KerberosException
435    {
436        KdcReq request = tgsContext.getRequest();
437        Ticket tgt = tgsContext.getTgt();
438        Ticket newTicket = tgsContext.getNewTicket();
439
440        TgsRep reply = new TgsRep();
441
442        reply.setCName( tgt.getEncTicketPart().getCName() );
443        reply.setCRealm( tgt.getEncTicketPart().getCRealm() );
444        reply.setTicket( newTicket );
445
446        EncKdcRepPart encKdcRepPart = new EncKdcRepPart();
447
448        encKdcRepPart.setKey( newTicket.getEncTicketPart().getKey() );
449        encKdcRepPart.setNonce( request.getKdcReqBody().getNonce() );
450        // TODO - resp.last-req := fetch_last_request_info(client); requires store
451        // FIXME temporary fix, IMO we should create some new ATs to store this info in DIT
452        LastReq lastReq = new LastReq();
453        lastReq.addEntry( new LastReqEntry( LastReqType.TIME_OF_INITIAL_REQ, new KerberosTime() ) );
454        encKdcRepPart.setLastReq( lastReq );
455
456        encKdcRepPart.setFlags( newTicket.getEncTicketPart().getFlags() );
457        encKdcRepPart.setClientAddresses( newTicket.getEncTicketPart().getClientAddresses() );
458        encKdcRepPart.setAuthTime( newTicket.getEncTicketPart().getAuthTime() );
459        encKdcRepPart.setStartTime( newTicket.getEncTicketPart().getStartTime() );
460        encKdcRepPart.setEndTime( newTicket.getEncTicketPart().getEndTime() );
461        encKdcRepPart.setSName( newTicket.getSName() );
462        encKdcRepPart.setSRealm( newTicket.getRealm() );
463
464        if ( newTicket.getEncTicketPart().getFlags().isRenewable() )
465        {
466            encKdcRepPart.setRenewTill( newTicket.getEncTicketPart().getRenewTill() );
467        }
468
469        if ( LOG_KRB.isDebugEnabled() )
470        {
471            monitorContext( tgsContext );
472            monitorReply( reply, encKdcRepPart );
473        }
474
475        EncTgsRepPart encTgsRepPart = new EncTgsRepPart();
476        encTgsRepPart.setEncKdcRepPart( encKdcRepPart );
477
478        Authenticator authenticator = tgsContext.getAuthenticator();
479
480        EncryptedData encryptedData;
481
482        if ( authenticator.getSubKey() != null )
483        {
484            encryptedData = CIPHER_TEXT_HANDLER.seal( authenticator.getSubKey(), encTgsRepPart,
485                KeyUsage.TGS_REP_ENC_PART_TGS_AUTHNT_SUB_KEY );
486        }
487        else
488        {
489            encryptedData = CIPHER_TEXT_HANDLER.seal( tgt.getEncTicketPart().getKey(), encTgsRepPart,
490                KeyUsage.TGS_REP_ENC_PART_TGS_SESS_KEY );
491        }
492
493        reply.setEncPart( encryptedData );
494        reply.setEncKdcRepPart( encKdcRepPart );
495
496        tgsContext.setReply( reply );
497    }
498
499
500    private static void monitorContext( TicketGrantingContext tgsContext )
501    {
502        try
503        {
504            Ticket tgt = tgsContext.getTgt();
505            long clockSkew = tgsContext.getConfig().getAllowableClockSkew();
506
507            Checksum cksum = tgsContext.getAuthenticator().getCksum();
508
509            ChecksumType checksumType = null;
510            if ( cksum != null )
511            {
512                checksumType = cksum.getChecksumType();
513            }
514
515            InetAddress clientAddress = tgsContext.getClientAddress();
516            HostAddresses clientAddresses = tgt.getEncTicketPart().getClientAddresses();
517
518            boolean caddrContainsSender = false;
519            if ( tgt.getEncTicketPart().getClientAddresses() != null )
520            {
521                caddrContainsSender = tgt.getEncTicketPart().getClientAddresses()
522                    .contains( new HostAddress( clientAddress ) );
523            }
524
525            StringBuffer sb = new StringBuffer();
526
527            sb.append( "Monitoring " + SERVICE_NAME + " context:" );
528
529            sb.append( "\n\t" + "clockSkew              " + clockSkew );
530            sb.append( "\n\t" + "checksumType           " + checksumType );
531            sb.append( "\n\t" + "clientAddress          " + clientAddress );
532            sb.append( "\n\t" + "clientAddresses        " + clientAddresses );
533            sb.append( "\n\t" + "caddr contains sender  " + caddrContainsSender );
534
535            PrincipalName requestServerPrincipal = tgsContext.getRequest().getKdcReqBody().getSName();
536            PrincipalStoreEntry requestPrincipal = tgsContext.getRequestPrincipalEntry();
537
538            sb.append( "\n\t" + "principal              " + requestServerPrincipal );
539            sb.append( "\n\t" + "cn                     " + requestPrincipal.getCommonName() );
540            sb.append( "\n\t" + "realm                  " + requestPrincipal.getRealmName() );
541            sb.append( "\n\t" + "principal              " + requestPrincipal.getPrincipal() );
542            sb.append( "\n\t" + "SAM type               " + requestPrincipal.getSamType() );
543
544            PrincipalName ticketServerPrincipal = tgsContext.getTgt().getSName();
545            PrincipalStoreEntry ticketPrincipal = tgsContext.getTicketPrincipalEntry();
546
547            sb.append( "\n\t" + "principal              " + ticketServerPrincipal );
548            sb.append( "\n\t" + "cn                     " + ticketPrincipal.getCommonName() );
549            sb.append( "\n\t" + "realm                  " + ticketPrincipal.getRealmName() );
550            sb.append( "\n\t" + "principal              " + ticketPrincipal.getPrincipal() );
551            sb.append( "\n\t" + "SAM type               " + ticketPrincipal.getSamType() );
552
553            EncryptionType encryptionType = tgsContext.getTgt().getEncPart().getEType();
554            int keyVersion = ticketPrincipal.getKeyMap().get( encryptionType ).getKeyVersion();
555            sb.append( "\n\t" + "Ticket key type        " + encryptionType );
556            sb.append( "\n\t" + "Service key version    " + keyVersion );
557
558            LOG_KRB.debug( sb.toString() );
559        }
560        catch ( Exception e )
561        {
562            // This is a monitor.  No exceptions should bubble up.
563            LOG_KRB.error( I18n.err( I18n.ERR_154 ), e );
564        }
565    }
566
567
568    private static void monitorReply( TgsRep success, EncKdcRepPart part )
569    {
570        try
571        {
572            StringBuffer sb = new StringBuffer();
573
574            sb.append( "Responding with " + SERVICE_NAME + " reply:" );
575            sb.append( "\n\t" + "messageType:           " + success.getMessageType() );
576            sb.append( "\n\t" + "protocolVersionNumber: " + success.getProtocolVersionNumber() );
577            sb.append( "\n\t" + "nonce:                 " + part.getNonce() );
578            sb.append( "\n\t" + "clientPrincipal:       " + success.getCName() );
579            sb.append( "\n\t" + "client realm:          " + success.getCRealm() );
580            sb.append( "\n\t" + "serverPrincipal:       " + part.getSName() );
581            sb.append( "\n\t" + "server realm:          " + part.getSRealm() );
582            sb.append( "\n\t" + "auth time:             " + part.getAuthTime() );
583            sb.append( "\n\t" + "start time:            " + part.getStartTime() );
584            sb.append( "\n\t" + "end time:              " + part.getEndTime() );
585            sb.append( "\n\t" + "renew-till time:       " + part.getRenewTill() );
586            sb.append( "\n\t" + "hostAddresses:         " + part.getClientAddresses() );
587
588            LOG_KRB.debug( sb.toString() );
589        }
590        catch ( Exception e )
591        {
592            // This is a monitor.  No exceptions should bubble up.
593            LOG_KRB.error( I18n.err( I18n.ERR_155 ), e );
594        }
595    }
596
597
598    private static void processFlags( KerberosConfig config, KdcReq request, Ticket tgt,
599        EncTicketPart newTicketPart ) throws KerberosException
600    {
601        if ( tgt.getEncTicketPart().getFlags().isPreAuth() )
602        {
603            newTicketPart.setFlag( TicketFlag.PRE_AUTHENT );
604        }
605
606        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.FORWARDABLE ) )
607        {
608            if ( !config.isForwardableAllowed() )
609            {
610                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
611            }
612
613            if ( !tgt.getEncTicketPart().getFlags().isForwardable() )
614            {
615                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
616            }
617
618            newTicketPart.setFlag( TicketFlag.FORWARDABLE );
619        }
620
621        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.FORWARDED ) )
622        {
623            if ( !config.isForwardableAllowed() )
624            {
625                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
626            }
627
628            if ( !tgt.getEncTicketPart().getFlags().isForwardable() )
629            {
630                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
631            }
632
633            if ( request.getKdcReqBody().getAddresses() != null
634                && request.getKdcReqBody().getAddresses().getAddresses() != null
635                && request.getKdcReqBody().getAddresses().getAddresses().length > 0 )
636            {
637                newTicketPart.setClientAddresses( request.getKdcReqBody().getAddresses() );
638            }
639            else
640            {
641                if ( !config.isEmptyAddressesAllowed() )
642                {
643                    throw new KerberosException( ErrorType.KDC_ERR_POLICY );
644                }
645            }
646
647            newTicketPart.setFlag( TicketFlag.FORWARDED );
648        }
649
650        if ( tgt.getEncTicketPart().getFlags().isForwarded() )
651        {
652            newTicketPart.setFlag( TicketFlag.FORWARDED );
653        }
654
655        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.PROXIABLE ) )
656        {
657            if ( !config.isProxiableAllowed() )
658            {
659                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
660            }
661
662            if ( !tgt.getEncTicketPart().getFlags().isProxiable() )
663            {
664                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
665            }
666
667            newTicketPart.setFlag( TicketFlag.PROXIABLE );
668        }
669
670        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.PROXY ) )
671        {
672            if ( !config.isProxiableAllowed() )
673            {
674                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
675            }
676
677            if ( !tgt.getEncTicketPart().getFlags().isProxiable() )
678            {
679                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
680            }
681
682            if ( request.getKdcReqBody().getAddresses() != null
683                && request.getKdcReqBody().getAddresses().getAddresses() != null
684                && request.getKdcReqBody().getAddresses().getAddresses().length > 0 )
685            {
686                newTicketPart.setClientAddresses( request.getKdcReqBody().getAddresses() );
687            }
688            else
689            {
690                if ( !config.isEmptyAddressesAllowed() )
691                {
692                    throw new KerberosException( ErrorType.KDC_ERR_POLICY );
693                }
694            }
695
696            newTicketPart.setFlag( TicketFlag.PROXY );
697        }
698
699        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.ALLOW_POSTDATE ) )
700        {
701            if ( !config.isPostdatedAllowed() )
702            {
703                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
704            }
705
706            if ( !tgt.getEncTicketPart().getFlags().isMayPosdate() )
707            {
708                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
709            }
710
711            newTicketPart.setFlag( TicketFlag.MAY_POSTDATE );
712        }
713
714        /*
715         * "Otherwise, if the TGT has the MAY-POSTDATE flag set, then the resulting
716         * ticket will be postdated, and the requested starttime is checked against
717         * the policy of the local realm.  If acceptable, the ticket's starttime is
718         * set as requested, and the INVALID flag is set.  The postdated ticket MUST
719         * be validated before use by presenting it to the KDC after the starttime
720         * has been reached.  However, in no case may the starttime, endtime, or
721         * renew-till time of a newly-issued postdated ticket extend beyond the
722         * renew-till time of the TGT."
723         */
724        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) )
725        {
726            if ( !config.isPostdatedAllowed() )
727            {
728                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
729            }
730
731            if ( !tgt.getEncTicketPart().getFlags().isMayPosdate() )
732            {
733                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
734            }
735
736            newTicketPart.setFlag( TicketFlag.POSTDATED );
737            newTicketPart.setFlag( TicketFlag.INVALID );
738
739            newTicketPart.setStartTime( request.getKdcReqBody().getFrom() );
740        }
741
742        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.VALIDATE ) )
743        {
744            if ( !config.isPostdatedAllowed() )
745            {
746                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
747            }
748
749            if ( !tgt.getEncTicketPart().getFlags().isInvalid() )
750            {
751                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
752            }
753
754            KerberosTime startTime = ( tgt.getEncTicketPart().getStartTime() != null )
755                ? tgt.getEncTicketPart().getStartTime()
756                : tgt.getEncTicketPart().getAuthTime();
757
758            if ( startTime.greaterThan( new KerberosTime() ) )
759            {
760                throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_NYV );
761            }
762
763            echoTicket( newTicketPart, tgt );
764            newTicketPart.getFlags().clearFlag( TicketFlag.INVALID );
765        }
766
767        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_0 )
768            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_7 )
769            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_9 )
770            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_10 )
771            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_11 )
772            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_12 )
773            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_13 )
774            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_14 )
775            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_15 )
776            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_16 )
777            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_17 )
778            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_18 )
779            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_19 )
780            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_20 )
781            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_21 )
782            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_22 )
783            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_23 )
784            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_24 )
785            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_25 )
786            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_29 ) )
787        {
788            throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
789        }
790    }
791
792
793    private static void processTimes( KerberosConfig config, KdcReq request, EncTicketPart newTicketPart,
794        Ticket tgt ) throws KerberosException
795    {
796        KerberosTime now = new KerberosTime();
797
798        newTicketPart.setAuthTime( tgt.getEncTicketPart().getAuthTime() );
799
800        KerberosTime startTime = request.getKdcReqBody().getFrom();
801
802        /*
803         * "If the requested starttime is absent, indicates a time in the past,
804         * or is within the window of acceptable clock skew for the KDC and the
805         * POSTDATE option has not been specified, then the starttime of the
806         * ticket is set to the authentication server's current time."
807         */
808        if ( startTime == null || startTime.lessThan( now ) || startTime.isInClockSkew( config.getAllowableClockSkew() )
809            && !request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) )
810        {
811            startTime = now;
812        }
813
814        /*
815         * "If it indicates a time in the future beyond the acceptable clock skew,
816         * but the POSTDATED option has not been specified or the MAY-POSTDATE flag
817         * is not set in the TGT, then the error KDC_ERR_CANNOT_POSTDATE is
818         * returned."
819         */
820        if ( startTime != null
821            && startTime.greaterThan( now )
822            && !startTime.isInClockSkew( config.getAllowableClockSkew() )
823            && ( !request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) || !tgt.getEncTicketPart()
824                .getFlags().isMayPosdate() ) )
825        {
826            throw new KerberosException( ErrorType.KDC_ERR_CANNOT_POSTDATE );
827        }
828
829        KerberosTime renewalTime = null;
830        KerberosTime kerberosEndTime = null;
831
832        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEW ) )
833        {
834            if ( !config.isRenewableAllowed() )
835            {
836                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
837            }
838
839            if ( !tgt.getEncTicketPart().getFlags().isRenewable() )
840            {
841                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
842            }
843
844            if ( tgt.getEncTicketPart().getRenewTill().lessThan( now ) )
845            {
846                throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_EXPIRED );
847            }
848
849            echoTicket( newTicketPart, tgt );
850
851            newTicketPart.setStartTime( now );
852
853            KerberosTime tgtStartTime = ( tgt.getEncTicketPart().getStartTime() != null )
854                    ? tgt.getEncTicketPart().getStartTime()
855                    : tgt.getEncTicketPart().getAuthTime();
856
857            long oldLife = tgt.getEncTicketPart().getEndTime().getTime() - tgtStartTime.getTime();
858
859            kerberosEndTime = new KerberosTime( Math.min( tgt.getEncTicketPart().getRenewTill().getTime(),
860                now.getTime() + oldLife ) );
861            newTicketPart.setEndTime( kerberosEndTime );
862        }
863        else
864        {
865            if ( newTicketPart.getStartTime() == null )
866            {
867                newTicketPart.setStartTime( now );
868            }
869
870            KerberosTime till;
871            if ( request.getKdcReqBody().getTill().isZero() )
872            {
873                till = KerberosTime.INFINITY;
874            }
875            else
876            {
877                till = request.getKdcReqBody().getTill();
878            }
879
880            /*
881             * The end time is the minimum of (a) the requested till time or (b)
882             * the start time plus maximum lifetime as configured in policy or (c)
883             * the end time of the TGT.
884             */
885            List<KerberosTime> minimizer = new ArrayList<KerberosTime>();
886            minimizer.add( till );
887            minimizer.add( new KerberosTime( startTime.getTime() + config.getMaximumTicketLifetime() ) );
888            minimizer.add( tgt.getEncTicketPart().getEndTime() );
889            kerberosEndTime = Collections.min( minimizer );
890
891            newTicketPart.setEndTime( kerberosEndTime );
892
893            if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEWABLE_OK )
894                && kerberosEndTime.lessThan( request.getKdcReqBody().getTill() )
895                && tgt.getEncTicketPart().getFlags().isRenewable() )
896            {
897                if ( !config.isRenewableAllowed() )
898                {
899                    throw new KerberosException( ErrorType.KDC_ERR_POLICY );
900                }
901
902                // We set the RENEWABLE option for later processing.
903                request.getKdcReqBody().getKdcOptions().set( KdcOptions.RENEWABLE );
904                long rtime = Math.min( request.getKdcReqBody().getTill().getTime(), tgt.getEncTicketPart()
905                    .getRenewTill().getTime() );
906                renewalTime = new KerberosTime( rtime );
907            }
908        }
909
910        if ( renewalTime == null )
911        {
912            renewalTime = request.getKdcReqBody().getRTime();
913        }
914
915        KerberosTime rtime;
916        if ( renewalTime != null && renewalTime.isZero() )
917        {
918            rtime = KerberosTime.INFINITY;
919        }
920        else
921        {
922            rtime = renewalTime;
923        }
924
925        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEWABLE )
926            && tgt.getEncTicketPart().getFlags().isRenewable() )
927        {
928            if ( !config.isRenewableAllowed() )
929            {
930                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
931            }
932
933            newTicketPart.setFlag( TicketFlag.RENEWABLE );
934
935            /*
936             * The renew-till time is the minimum of (a) the requested renew-till
937             * time or (b) the start time plus maximum renewable lifetime as
938             * configured in policy or (c) the renew-till time of the TGT.
939             */
940            List<KerberosTime> minimizer = new ArrayList<KerberosTime>();
941
942            /*
943             * 'rtime' KerberosTime is OPTIONAL
944             */
945            if ( rtime != null )
946            {
947                minimizer.add( rtime );
948            }
949
950            minimizer.add( new KerberosTime( startTime.getTime() + config.getMaximumRenewableLifetime() ) );
951            minimizer.add( tgt.getEncTicketPart().getRenewTill() );
952            newTicketPart.setRenewTill( Collections.min( minimizer ) );
953        }
954
955        /*
956         * "If the requested expiration time minus the starttime (as determined
957         * above) is less than a site-determined minimum lifetime, an error
958         * message with code KDC_ERR_NEVER_VALID is returned."
959         */
960        if ( kerberosEndTime.lessThan( startTime ) )
961        {
962            throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID );
963        }
964
965        long ticketLifeTime = Math.abs( startTime.getTime() - kerberosEndTime.getTime() );
966        if ( ticketLifeTime < config.getAllowableClockSkew() )
967        {
968            throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID );
969        }
970    }
971
972
973    /*
974     * if (realm_tgt_is_for(tgt) := tgt.realm) then
975     *         // tgt issued by local realm
976     *         new_tkt.transited := tgt.transited;
977     * else
978     *         // was issued for this realm by some other realm
979     *         if (tgt.transited.tr-type not supported) then
980     *                 error_out(KDC_ERR_TRTYPE_NOSUPP);
981     *         endif
982     * 
983     *         new_tkt.transited := compress_transited(tgt.transited + tgt.realm)
984     * endif
985     */
986    private static void processTransited( EncTicketPart newTicketPart, Ticket tgt )
987    {
988        // TODO - currently no transited support other than local
989        newTicketPart.setTransited( tgt.getEncTicketPart().getTransited() );
990    }
991
992
993    private static void echoTicket( EncTicketPart newTicketPart, Ticket tgt )
994    {
995        EncTicketPart encTicketpart = tgt.getEncTicketPart();
996        newTicketPart.setAuthorizationData( encTicketpart.getAuthorizationData() );
997        newTicketPart.setAuthTime( encTicketpart.getAuthTime() );
998        newTicketPart.setClientAddresses( encTicketpart.getClientAddresses() );
999        newTicketPart.setCName( encTicketpart.getCName() );
1000        newTicketPart.setEndTime( encTicketpart.getEndTime() );
1001        newTicketPart.setFlags( encTicketpart.getFlags() );
1002        newTicketPart.setRenewTill( encTicketpart.getRenewTill() );
1003        newTicketPart.setKey( encTicketpart.getKey() );
1004        newTicketPart.setTransited( encTicketpart.getTransited() );
1005    }
1006
1007
1008    /**
1009     * Get a PrincipalStoreEntry given a principal.  The ErrorType is used to indicate
1010     * whether any resulting error pertains to a server or client.
1011     *
1012     * @param principal The KerberosPrincipal instance
1013     * @param store The Principal store
1014     * @param errorType The type of error
1015     * @return The PrincipalStoreEntry
1016     * @throws KerberosException If teh entry can't be retrieve
1017     */
1018    public static PrincipalStoreEntry getEntry( KerberosPrincipal principal, PrincipalStore store, ErrorType errorType )
1019        throws KerberosException
1020    {
1021        PrincipalStoreEntry entry = null;
1022
1023        try
1024        {
1025            entry = store.getPrincipal( principal );
1026        }
1027        catch ( Exception e )
1028        {
1029            throw new KerberosException( errorType, e );
1030        }
1031
1032        if ( entry == null )
1033        {
1034            throw new KerberosException( errorType );
1035        }
1036
1037        if ( entry.getKeyMap() == null || entry.getKeyMap().isEmpty() )
1038        {
1039            throw new KerberosException( ErrorType.KDC_ERR_NULL_KEY );
1040        }
1041
1042        return entry;
1043    }
1044
1045}