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.KerberosException;
069import org.apache.directory.shared.kerberos.flags.TicketFlag;
070import org.apache.directory.shared.kerberos.messages.ApReq;
071import org.apache.directory.shared.kerberos.messages.Authenticator;
072import org.apache.directory.shared.kerberos.messages.EncTgsRepPart;
073import org.apache.directory.shared.kerberos.messages.TgsRep;
074import org.apache.directory.shared.kerberos.messages.Ticket;
075import org.slf4j.Logger;
076import org.slf4j.LoggerFactory;
077
078
079/**
080 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
081 */
082public final class TicketGrantingService
083{
084
085    /** the log for this class */
086    private static final Logger LOG_KRB = LoggerFactory.getLogger( Loggers.KERBEROS_LOG.getName() );
087
088    private static final CipherTextHandler CIPHER_TEXT_HANDLER = new CipherTextHandler();
089
090    private static final String SERVICE_NAME = "Ticket-Granting Service (TGS)";
091
092    private static final ChecksumHandler CHRECKSUM_HANDLER = new ChecksumHandler();
093
094
095    private TicketGrantingService()
096    {
097    }
098
099
100    public static void execute( TicketGrantingContext tgsContext ) throws Exception
101    {
102        if ( LOG_KRB.isDebugEnabled() )
103        {
104            monitorRequest( tgsContext );
105        }
106
107        configureTicketGranting( tgsContext );
108        selectEncryptionType( tgsContext );
109        getAuthHeader( tgsContext );
110        // commenting to allow cross-realm auth
111        //verifyTgt( tgsContext );
112        getTicketPrincipalEntry( tgsContext );
113        verifyTgtAuthHeader( tgsContext );
114        verifyBodyChecksum( tgsContext );
115        getRequestPrincipalEntry( tgsContext );
116        generateTicket( tgsContext );
117        buildReply( tgsContext );
118    }
119
120
121    private static void configureTicketGranting( TicketGrantingContext tgsContext ) throws KerberosException
122    {
123        tgsContext.setCipherTextHandler( CIPHER_TEXT_HANDLER );
124
125        if ( tgsContext.getRequest().getProtocolVersionNumber() != KerberosConstants.KERBEROS_V5 )
126        {
127            throw new KerberosException( ErrorType.KDC_ERR_BAD_PVNO );
128        }
129    }
130
131
132    private static void monitorRequest( KdcContext kdcContext )
133    {
134        KdcReq request = kdcContext.getRequest();
135
136        try
137        {
138            String clientAddress = kdcContext.getClientAddress().getHostAddress();
139
140            if ( LOG_KRB.isDebugEnabled() )
141            {
142                StringBuilder sb = new StringBuilder();
143    
144                sb.append( "Received " ).append( SERVICE_NAME ).append( " request:" );
145                sb.append( "\n\tmessageType:           " ).append( request.getMessageType() );
146                sb.append( "\n\tprotocolVersionNumber: " ).append( request.getProtocolVersionNumber() );
147                sb.append( "\n\tclientAddress:         " ).append( clientAddress );
148                sb.append( "\n\tnonce:                 " ).append( request.getKdcReqBody().getNonce() );
149                sb.append( "\n\tkdcOptions:            " ).append( request.getKdcReqBody().getKdcOptions() );
150                sb.append( "\n\tclientPrincipal:       " ).append( request.getKdcReqBody().getCName() );
151                sb.append( "\n\tserverPrincipal:       " ).append( request.getKdcReqBody().getSName() );
152                sb.append( "\n\tencryptionType:        " ).append( 
153                        KerberosUtils.getEncryptionTypesString( request.getKdcReqBody().getEType() ) );
154                sb.append( "\n\trealm:                 " ).append( request.getKdcReqBody().getRealm() );
155                sb.append( "\n\tfrom time:             " ).append( request.getKdcReqBody().getFrom() );
156                sb.append( "\n\ttill time:             " ).append( request.getKdcReqBody().getTill() );
157                sb.append( "\n\trenew-till time:       " ).append( request.getKdcReqBody().getRTime() );
158                sb.append( "\n\thostAddresses:         " ).append( request.getKdcReqBody().getAddresses() );
159    
160                LOG_KRB.debug( sb.toString() );
161            }
162        }
163        catch ( Exception e )
164        {
165            // This is a monitor.  No exceptions should bubble up.
166            LOG_KRB.error( I18n.err( I18n.ERR_153 ), e );
167        }
168    }
169
170
171    private static void selectEncryptionType( TicketGrantingContext tgsContext ) throws Exception
172    {
173        KdcContext kdcContext = tgsContext;
174        KerberosConfig config = kdcContext.getConfig();
175
176        Set<EncryptionType> requestedTypes = kdcContext.getRequest().getKdcReqBody().getEType();
177
178        EncryptionType bestType = KerberosUtils.getBestEncryptionType( requestedTypes, config.getEncryptionTypes() );
179
180        LOG_KRB.debug( "Session will use encryption type {}.", bestType );
181
182        if ( bestType == null )
183        {
184            throw new KerberosException( ErrorType.KDC_ERR_ETYPE_NOSUPP );
185        }
186
187        kdcContext.setEncryptionType( bestType );
188    }
189
190
191    private static void getAuthHeader( TicketGrantingContext tgsContext ) throws Exception
192    {
193        KdcReq request = tgsContext.getRequest();
194
195        if ( ( request.getPaData() == null ) || request.getPaData().isEmpty() )
196        {
197            throw new KerberosException( ErrorType.KDC_ERR_PADATA_TYPE_NOSUPP );
198        }
199
200        byte[] undecodedAuthHeader = null;
201
202        for ( PaData paData : request.getPaData() )
203        {
204            if ( paData.getPaDataType() == PaDataType.PA_TGS_REQ )
205            {
206                undecodedAuthHeader = paData.getPaDataValue();
207            }
208        }
209
210        if ( undecodedAuthHeader == null )
211        {
212            throw new KerberosException( ErrorType.KDC_ERR_PADATA_TYPE_NOSUPP );
213        }
214
215        ApReq authHeader = KerberosDecoder.decodeApReq( 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        KerberosConfig 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 = KerberosUtils.getKerberosPrincipal( tgt.getSName(), tgt.getRealm() ).getName();
236        String requestServerName = KerberosUtils.getKerberosPrincipal(
237            tgsContext.getRequest().getKdcReqBody().getSName(), tgsContext.getRequest().getKdcReqBody().getRealm() )
238            .getName();
239
240        /*
241         * if (tgt.sname is not a TGT for local realm and is not req.sname)
242         *     then error_out(KRB_AP_ERR_NOT_US);
243         */
244        if ( !tgtServerName.equals( config.getServicePrincipal().getName() )
245            && !tgtServerName.equals( requestServerName ) )
246        {
247            throw new KerberosException( ErrorType.KRB_AP_ERR_NOT_US );
248        }
249    }
250
251
252    private static void getTicketPrincipalEntry( TicketGrantingContext tgsContext ) throws KerberosException
253    {
254        PrincipalName principal = tgsContext.getTgt().getSName();
255        PrincipalStore store = tgsContext.getStore();
256
257        KerberosPrincipal principalWithRealm = KerberosUtils.getKerberosPrincipal( principal, tgsContext.getTgt()
258            .getRealm() );
259        PrincipalStoreEntry entry = getEntry( principalWithRealm, store, ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN );
260        tgsContext.setTicketPrincipalEntry( entry );
261    }
262
263
264    private static void verifyTgtAuthHeader( TicketGrantingContext tgsContext ) throws KerberosException
265    {
266        ApReq authHeader = tgsContext.getAuthHeader();
267        Ticket tgt = tgsContext.getTgt();
268
269        KdcOptions kdcOptions = tgsContext.getRequest().getKdcReqBody().getKdcOptions();
270        boolean isValidate = kdcOptions.get( KdcOptions.VALIDATE );
271
272        EncryptionType encryptionType = tgt.getEncPart().getEType();
273        EncryptionKey serverKey = tgsContext.getTicketPrincipalEntry().getKeyMap().get( encryptionType );
274
275        long clockSkew = tgsContext.getConfig().getAllowableClockSkew();
276        ReplayCache replayCache = tgsContext.getReplayCache();
277        boolean emptyAddressesAllowed = tgsContext.getConfig().isEmptyAddressesAllowed();
278        InetAddress clientAddress = tgsContext.getClientAddress();
279        CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler();
280
281        Authenticator authenticator = KerberosUtils.verifyAuthHeader( authHeader, tgt, serverKey, clockSkew,
282            replayCache,
283            emptyAddressesAllowed, clientAddress, cipherTextHandler,
284            KeyUsage.TGS_REQ_PA_TGS_REQ_PADATA_AP_REQ_TGS_SESS_KEY, isValidate );
285
286        tgsContext.setAuthenticator( authenticator );
287    }
288
289
290    /**
291     * RFC4120
292     * <li>Section 3.3.2. Receipt of KRB_TGS_REQ Message -> 2nd paragraph
293     * <li>Section 5.5.1. KRB_AP_REQ Definition -> Authenticator -> cksum
294     */
295    private static void verifyBodyChecksum( TicketGrantingContext tgsContext ) throws KerberosException
296    {
297        KerberosConfig config = tgsContext.getConfig();
298
299        if ( config.isBodyChecksumVerified() )
300        {
301            KdcReqBody body = tgsContext.getRequest().getKdcReqBody();
302            // FIXME how this byte[] is computed??
303            // is it full ASN.1 encoded bytes OR just the bytes of all the values alone?
304            // for now am using the ASN.1 encoded value
305            ByteBuffer buf = ByteBuffer.allocate( body.computeLength() );
306            try
307            {
308                body.encode( buf );
309            }
310            catch ( EncoderException e )
311            {
312                throw new KerberosException( ErrorType.KRB_AP_ERR_INAPP_CKSUM );
313            }
314
315            byte[] bodyBytes = buf.array();
316            Checksum authenticatorChecksum = tgsContext.getAuthenticator().getCksum();
317
318            if ( authenticatorChecksum != null )
319            {
320                // we need the session key
321                Ticket tgt = tgsContext.getTgt();
322                EncTicketPart encTicketPart = tgt.getEncTicketPart();
323                EncryptionKey sessionKey = encTicketPart.getKey();
324
325                if ( authenticatorChecksum == null || authenticatorChecksum.getChecksumType() == null
326                    || authenticatorChecksum.getChecksumValue() == null || bodyBytes == null )
327                {
328                    throw new KerberosException( ErrorType.KRB_AP_ERR_INAPP_CKSUM );
329                }
330
331                LOG_KRB.debug( "Verifying body checksum type '{}'.", authenticatorChecksum.getChecksumType() );
332
333                CHRECKSUM_HANDLER.verifyChecksum( authenticatorChecksum, bodyBytes, sessionKey.getKeyValue(),
334                    KeyUsage.TGS_REQ_PA_TGS_REQ_PADATA_AP_REQ_AUTHNT_CKSUM_TGS_SESS_KEY );
335            }
336        }
337    }
338
339
340    public static void getRequestPrincipalEntry( TicketGrantingContext tgsContext ) throws KerberosException
341    {
342        KerberosPrincipal principal = KerberosUtils.getKerberosPrincipal(
343            tgsContext.getRequest().getKdcReqBody().getSName(), tgsContext.getRequest().getKdcReqBody().getRealm() );
344        PrincipalStore store = tgsContext.getStore();
345
346        PrincipalStoreEntry entry = getEntry( principal, store, ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN );
347        tgsContext.setRequestPrincipalEntry( entry );
348    }
349
350
351    private static void generateTicket( TicketGrantingContext tgsContext ) throws KerberosException
352    {
353        KdcReq request = tgsContext.getRequest();
354        Ticket tgt = tgsContext.getTgt();
355        Authenticator authenticator = tgsContext.getAuthenticator();
356        CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler();
357        KerberosPrincipal ticketPrincipal = KerberosUtils.getKerberosPrincipal(
358            request.getKdcReqBody().getSName(), request.getKdcReqBody().getRealm() );
359
360        EncryptionType encryptionType = tgsContext.getEncryptionType();
361        EncryptionKey serverKey = tgsContext.getRequestPrincipalEntry().getKeyMap().get( encryptionType );
362
363        KerberosConfig config = tgsContext.getConfig();
364
365        tgsContext.getRequest().getKdcReqBody().getAdditionalTickets();
366
367        EncTicketPart newTicketPart = new EncTicketPart();
368
369        newTicketPart.setClientAddresses( tgt.getEncTicketPart().getClientAddresses() );
370
371        processFlags( config, request, tgt, newTicketPart );
372
373        EncryptionKey sessionKey = RandomKeyFactory.getRandomKey( tgsContext.getEncryptionType() );
374        newTicketPart.setKey( sessionKey );
375
376        newTicketPart.setCName( tgt.getEncTicketPart().getCName() );
377        newTicketPart.setCRealm( tgt.getEncTicketPart().getCRealm() );
378
379        if ( request.getKdcReqBody().getEncAuthorizationData() != null )
380        {
381            byte[] authorizationData = cipherTextHandler.decrypt( authenticator.getSubKey(), request.getKdcReqBody()
382                .getEncAuthorizationData(), KeyUsage.TGS_REQ_KDC_REQ_BODY_AUTHZ_DATA_ENC_WITH_TGS_SESS_KEY );
383            AuthorizationData authData = KerberosDecoder.decodeAuthorizationData( authorizationData );
384            authData.addEntry( tgt.getEncTicketPart().getAuthorizationData().getCurrentAD() );
385            newTicketPart.setAuthorizationData( authData );
386        }
387
388        processTransited( newTicketPart, tgt );
389
390        processTimes( config, request, newTicketPart, tgt );
391
392        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.ENC_TKT_IN_SKEY ) )
393        {
394            Ticket[] additionalTkts = tgsContext.getRequest().getKdcReqBody().getAdditionalTickets();
395
396            if ( additionalTkts == null || additionalTkts.length == 0 )
397            {
398                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
399            }
400
401            Ticket additionalTgt = additionalTkts[0];
402            // reject if it is not a TGT
403            if ( !additionalTgt.getEncTicketPart().getFlags().isInitial() )
404            {
405                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
406            }
407
408            serverKey = additionalTgt.getEncTicketPart().getKey();
409            /*
410             * if (server not specified) then
411             *         server = req.second_ticket.client;
412             * endif
413             * 
414             * if ((req.second_ticket is not a TGT) or
415             *     (req.second_ticket.client != server)) then
416             *         error_out(KDC_ERR_POLICY);
417             * endif
418             * 
419             * new_tkt.enc-part := encrypt OCTET STRING using etype_for_key(second-ticket.key), second-ticket.key;
420             */
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            if ( LOG_KRB.isDebugEnabled() )
526            {
527                StringBuilder sb = new StringBuilder();
528    
529                sb.append( "Monitoring " ).append( SERVICE_NAME ).append( " context:" );
530    
531                sb.append( "\n\tclockSkew              " ).append( clockSkew );
532                sb.append( "\n\tchecksumType           " ).append( checksumType );
533                sb.append( "\n\tclientAddress          " ).append( clientAddress );
534                sb.append( "\n\tclientAddresses        " ).append( clientAddresses );
535                sb.append( "\n\tcaddr contains sender  " ).append( caddrContainsSender );
536    
537                PrincipalName requestServerPrincipal = tgsContext.getRequest().getKdcReqBody().getSName();
538                PrincipalStoreEntry requestPrincipal = tgsContext.getRequestPrincipalEntry();
539    
540                sb.append( "\n\tprincipal              " ).append( requestServerPrincipal );
541                sb.append( "\n\tcn                     " ).append( requestPrincipal.getCommonName() );
542                sb.append( "\n\trealm                  " ).append( requestPrincipal.getRealmName() );
543                sb.append( "\n\tprincipal              " ).append( requestPrincipal.getPrincipal() );
544                sb.append( "\n\tSAM type               " ).append( requestPrincipal.getSamType() );
545    
546                PrincipalName ticketServerPrincipal = tgsContext.getTgt().getSName();
547                PrincipalStoreEntry ticketPrincipal = tgsContext.getTicketPrincipalEntry();
548    
549                sb.append( "\n\tprincipal              " ).append( ticketServerPrincipal );
550                sb.append( "\n\tcn                     " ).append( ticketPrincipal.getCommonName() );
551                sb.append( "\n\trealm                  " ).append( ticketPrincipal.getRealmName() );
552                sb.append( "\n\tprincipal              " ).append( ticketPrincipal.getPrincipal() );
553                sb.append( "\n\tSAM type               " ).append( ticketPrincipal.getSamType() );
554    
555                EncryptionType encryptionType = tgsContext.getTgt().getEncPart().getEType();
556                int keyVersion = ticketPrincipal.getKeyMap().get( encryptionType ).getKeyVersion();
557                sb.append( "\n\tTicket key type        " ).append( encryptionType );
558                sb.append( "\n\tService key version    " ).append( keyVersion );
559
560                LOG_KRB.debug( sb.toString() );
561            }
562        }
563        catch ( Exception e )
564        {
565            // This is a monitor.  No exceptions should bubble up.
566            LOG_KRB.error( I18n.err( I18n.ERR_154 ), e );
567        }
568    }
569
570
571    private static void monitorReply( TgsRep success, EncKdcRepPart part )
572    {
573        if ( LOG_KRB.isDebugEnabled() )
574        {
575            try
576            {
577                StringBuilder sb = new StringBuilder();
578    
579                sb.append( "Responding with " ).append( SERVICE_NAME ).append( " reply:" );
580                sb.append( "\n\tmessageType:           " ).append( success.getMessageType() );
581                sb.append( "\n\tprotocolVersionNumber: " ).append( success.getProtocolVersionNumber() );
582                sb.append( "\n\tnonce:                 " ).append( part.getNonce() );
583                sb.append( "\n\tclientPrincipal:       " ).append( success.getCName() );
584                sb.append( "\n\tclient realm:          " ).append( success.getCRealm() );
585                sb.append( "\n\tserverPrincipal:       " ).append( part.getSName() );
586                sb.append( "\n\tserver realm:          " ).append( part.getSRealm() );
587                sb.append( "\n\tauth time:             " ).append( part.getAuthTime() );
588                sb.append( "\n\tstart time:            " ).append( part.getStartTime() );
589                sb.append( "\n\tend time:              " ).append( part.getEndTime() );
590                sb.append( "\n\trenew-till time:       " ).append( part.getRenewTill() );
591                sb.append( "\n\thostAddresses:         " ).append( part.getClientAddresses() );
592
593                LOG_KRB.debug( sb.toString() );
594            }
595            catch ( Exception e )
596            {
597                // This is a monitor.  No exceptions should bubble up.
598                LOG_KRB.error( I18n.err( I18n.ERR_155 ), e );
599            }
600        }
601    }
602
603
604    private static void processFlags( KerberosConfig config, KdcReq request, Ticket tgt,
605        EncTicketPart newTicketPart ) throws KerberosException
606    {
607        if ( tgt.getEncTicketPart().getFlags().isPreAuth() )
608        {
609            newTicketPart.setFlag( TicketFlag.PRE_AUTHENT );
610        }
611
612        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.FORWARDABLE ) )
613        {
614            if ( !config.isForwardableAllowed() )
615            {
616                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
617            }
618
619            if ( !tgt.getEncTicketPart().getFlags().isForwardable() )
620            {
621                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
622            }
623
624            newTicketPart.setFlag( TicketFlag.FORWARDABLE );
625        }
626
627        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.FORWARDED ) )
628        {
629            if ( !config.isForwardableAllowed() )
630            {
631                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
632            }
633
634            if ( !tgt.getEncTicketPart().getFlags().isForwardable() )
635            {
636                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
637            }
638
639            if ( request.getKdcReqBody().getAddresses() != null
640                && request.getKdcReqBody().getAddresses().getAddresses() != null
641                && request.getKdcReqBody().getAddresses().getAddresses().length > 0 )
642            {
643                newTicketPart.setClientAddresses( request.getKdcReqBody().getAddresses() );
644            }
645            else
646            {
647                if ( !config.isEmptyAddressesAllowed() )
648                {
649                    throw new KerberosException( ErrorType.KDC_ERR_POLICY );
650                }
651            }
652
653            newTicketPart.setFlag( TicketFlag.FORWARDED );
654        }
655
656        if ( tgt.getEncTicketPart().getFlags().isForwarded() )
657        {
658            newTicketPart.setFlag( TicketFlag.FORWARDED );
659        }
660
661        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.PROXIABLE ) )
662        {
663            if ( !config.isProxiableAllowed() )
664            {
665                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
666            }
667
668            if ( !tgt.getEncTicketPart().getFlags().isProxiable() )
669            {
670                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
671            }
672
673            newTicketPart.setFlag( TicketFlag.PROXIABLE );
674        }
675
676        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.PROXY ) )
677        {
678            if ( !config.isProxiableAllowed() )
679            {
680                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
681            }
682
683            if ( !tgt.getEncTicketPart().getFlags().isProxiable() )
684            {
685                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
686            }
687
688            if ( request.getKdcReqBody().getAddresses() != null
689                && request.getKdcReqBody().getAddresses().getAddresses() != null
690                && request.getKdcReqBody().getAddresses().getAddresses().length > 0 )
691            {
692                newTicketPart.setClientAddresses( request.getKdcReqBody().getAddresses() );
693            }
694            else
695            {
696                if ( !config.isEmptyAddressesAllowed() )
697                {
698                    throw new KerberosException( ErrorType.KDC_ERR_POLICY );
699                }
700            }
701
702            newTicketPart.setFlag( TicketFlag.PROXY );
703        }
704
705        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.ALLOW_POSTDATE ) )
706        {
707            if ( !config.isPostdatedAllowed() )
708            {
709                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
710            }
711
712            if ( !tgt.getEncTicketPart().getFlags().isMayPosdate() )
713            {
714                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
715            }
716
717            newTicketPart.setFlag( TicketFlag.MAY_POSTDATE );
718        }
719
720        /*
721         * "Otherwise, if the TGT has the MAY-POSTDATE flag set, then the resulting
722         * ticket will be postdated, and the requested starttime is checked against
723         * the policy of the local realm.  If acceptable, the ticket's starttime is
724         * set as requested, and the INVALID flag is set.  The postdated ticket MUST
725         * be validated before use by presenting it to the KDC after the starttime
726         * has been reached.  However, in no case may the starttime, endtime, or
727         * renew-till time of a newly-issued postdated ticket extend beyond the
728         * renew-till time of the TGT."
729         */
730        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) )
731        {
732            if ( !config.isPostdatedAllowed() )
733            {
734                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
735            }
736
737            if ( !tgt.getEncTicketPart().getFlags().isMayPosdate() )
738            {
739                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
740            }
741
742            newTicketPart.setFlag( TicketFlag.POSTDATED );
743            newTicketPart.setFlag( TicketFlag.INVALID );
744
745            newTicketPart.setStartTime( request.getKdcReqBody().getFrom() );
746        }
747
748        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.VALIDATE ) )
749        {
750            if ( !config.isPostdatedAllowed() )
751            {
752                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
753            }
754
755            if ( !tgt.getEncTicketPart().getFlags().isInvalid() )
756            {
757                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
758            }
759
760            KerberosTime startTime = ( tgt.getEncTicketPart().getStartTime() != null )
761                ? tgt.getEncTicketPart().getStartTime()
762                : tgt.getEncTicketPart().getAuthTime();
763
764            if ( startTime.greaterThan( new KerberosTime() ) )
765            {
766                throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_NYV );
767            }
768
769            echoTicket( newTicketPart, tgt );
770            newTicketPart.getFlags().clearFlag( TicketFlag.INVALID );
771        }
772
773        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_0 )
774            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_7 )
775            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_9 )
776            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_10 )
777            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_11 )
778            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_12 )
779            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_13 )
780            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_14 )
781            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_15 )
782            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_16 )
783            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_17 )
784            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_18 )
785            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_19 )
786            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_20 )
787            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_21 )
788            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_22 )
789            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_23 )
790            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_24 )
791            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_25 )
792            || request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_29 ) )
793        {
794            throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
795        }
796    }
797
798
799    private static void processTimes( KerberosConfig config, KdcReq request, EncTicketPart newTicketPart,
800        Ticket tgt ) throws KerberosException
801    {
802        KerberosTime now = new KerberosTime();
803
804        newTicketPart.setAuthTime( tgt.getEncTicketPart().getAuthTime() );
805
806        KerberosTime startTime = request.getKdcReqBody().getFrom();
807
808        /*
809         * "If the requested starttime is absent, indicates a time in the past,
810         * or is within the window of acceptable clock skew for the KDC and the
811         * POSTDATE option has not been specified, then the starttime of the
812         * ticket is set to the authentication server's current time."
813         */
814        if ( startTime == null || startTime.lessThan( now ) || startTime.isInClockSkew( config.getAllowableClockSkew() )
815            && !request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) )
816        {
817            startTime = now;
818        }
819
820        /*
821         * "If it indicates a time in the future beyond the acceptable clock skew,
822         * but the POSTDATED option has not been specified or the MAY-POSTDATE flag
823         * is not set in the TGT, then the error KDC_ERR_CANNOT_POSTDATE is
824         * returned."
825         */
826        if ( startTime != null
827            && startTime.greaterThan( now )
828            && !startTime.isInClockSkew( config.getAllowableClockSkew() )
829            && ( !request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) || !tgt.getEncTicketPart()
830                .getFlags().isMayPosdate() ) )
831        {
832            throw new KerberosException( ErrorType.KDC_ERR_CANNOT_POSTDATE );
833        }
834
835        KerberosTime renewalTime = null;
836        KerberosTime kerberosEndTime = null;
837
838        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEW ) )
839        {
840            if ( !config.isRenewableAllowed() )
841            {
842                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
843            }
844
845            if ( !tgt.getEncTicketPart().getFlags().isRenewable() )
846            {
847                throw new KerberosException( ErrorType.KDC_ERR_BADOPTION );
848            }
849
850            if ( tgt.getEncTicketPart().getRenewTill().lessThan( now ) )
851            {
852                throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_EXPIRED );
853            }
854
855            echoTicket( newTicketPart, tgt );
856
857            newTicketPart.setStartTime( now );
858
859            KerberosTime tgtStartTime = ( tgt.getEncTicketPart().getStartTime() != null )
860                    ? tgt.getEncTicketPart().getStartTime()
861                    : tgt.getEncTicketPart().getAuthTime();
862
863            long oldLife = tgt.getEncTicketPart().getEndTime().getTime() - tgtStartTime.getTime();
864
865            kerberosEndTime = new KerberosTime( Math.min( tgt.getEncTicketPart().getRenewTill().getTime(),
866                now.getTime() + oldLife ) );
867            newTicketPart.setEndTime( kerberosEndTime );
868        }
869        else
870        {
871            if ( newTicketPart.getStartTime() == null )
872            {
873                newTicketPart.setStartTime( now );
874            }
875
876            KerberosTime till;
877            if ( request.getKdcReqBody().getTill().isZero() )
878            {
879                till = KerberosTime.INFINITY;
880            }
881            else
882            {
883                till = request.getKdcReqBody().getTill();
884            }
885
886            /*
887             * The end time is the minimum of (a) the requested till time or (b)
888             * the start time plus maximum lifetime as configured in policy or (c)
889             * the end time of the TGT.
890             */
891            List<KerberosTime> minimizer = new ArrayList<>();
892            minimizer.add( till );
893            minimizer.add( new KerberosTime( startTime.getTime() + config.getMaximumTicketLifetime() ) );
894            minimizer.add( tgt.getEncTicketPart().getEndTime() );
895            kerberosEndTime = Collections.min( minimizer );
896
897            newTicketPart.setEndTime( kerberosEndTime );
898
899            if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEWABLE_OK )
900                && kerberosEndTime.lessThan( request.getKdcReqBody().getTill() )
901                && tgt.getEncTicketPart().getFlags().isRenewable() )
902            {
903                if ( !config.isRenewableAllowed() )
904                {
905                    throw new KerberosException( ErrorType.KDC_ERR_POLICY );
906                }
907
908                // We set the RENEWABLE option for later processing.
909                request.getKdcReqBody().getKdcOptions().set( KdcOptions.RENEWABLE );
910                long rtime = Math.min( request.getKdcReqBody().getTill().getTime(), tgt.getEncTicketPart()
911                    .getRenewTill().getTime() );
912                renewalTime = new KerberosTime( rtime );
913            }
914        }
915
916        if ( renewalTime == null )
917        {
918            renewalTime = request.getKdcReqBody().getRTime();
919        }
920
921        KerberosTime rtime;
922        if ( renewalTime != null && renewalTime.isZero() )
923        {
924            rtime = KerberosTime.INFINITY;
925        }
926        else
927        {
928            rtime = renewalTime;
929        }
930
931        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEWABLE )
932            && tgt.getEncTicketPart().getFlags().isRenewable() )
933        {
934            if ( !config.isRenewableAllowed() )
935            {
936                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
937            }
938
939            newTicketPart.setFlag( TicketFlag.RENEWABLE );
940
941            /*
942             * The renew-till time is the minimum of (a) the requested renew-till
943             * time or (b) the start time plus maximum renewable lifetime as
944             * configured in policy or (c) the renew-till time of the TGT.
945             */
946            List<KerberosTime> minimizer = new ArrayList<>();
947
948            /*
949             * 'rtime' KerberosTime is OPTIONAL
950             */
951            if ( rtime != null )
952            {
953                minimizer.add( rtime );
954            }
955
956            minimizer.add( new KerberosTime( startTime.getTime() + config.getMaximumRenewableLifetime() ) );
957            minimizer.add( tgt.getEncTicketPart().getRenewTill() );
958            newTicketPart.setRenewTill( Collections.min( minimizer ) );
959        }
960
961        /*
962         * "If the requested expiration time minus the starttime (as determined
963         * above) is less than a site-determined minimum lifetime, an error
964         * message with code KDC_ERR_NEVER_VALID is returned."
965         */
966        if ( kerberosEndTime.lessThan( startTime ) )
967        {
968            throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID );
969        }
970
971        long ticketLifeTime = Math.abs( startTime.getTime() - kerberosEndTime.getTime() );
972        if ( ticketLifeTime < config.getAllowableClockSkew() )
973        {
974            throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID );
975        }
976    }
977
978
979    /*
980     * if (realm_tgt_is_for(tgt) := tgt.realm) then
981     *         // tgt issued by local realm
982     *         new_tkt.transited := tgt.transited;
983     * else
984     *         // was issued for this realm by some other realm
985     *         if (tgt.transited.tr-type not supported) then
986     *                 error_out(KDC_ERR_TRTYPE_NOSUPP);
987     *         endif
988     * 
989     *         new_tkt.transited := compress_transited(tgt.transited + tgt.realm)
990     * endif
991     */
992    private static void processTransited( EncTicketPart newTicketPart, Ticket tgt )
993    {
994        // TODO - currently no transited support other than local
995        newTicketPart.setTransited( tgt.getEncTicketPart().getTransited() );
996    }
997
998
999    private static void echoTicket( EncTicketPart newTicketPart, Ticket tgt )
1000    {
1001        EncTicketPart encTicketpart = tgt.getEncTicketPart();
1002        newTicketPart.setAuthorizationData( encTicketpart.getAuthorizationData() );
1003        newTicketPart.setAuthTime( encTicketpart.getAuthTime() );
1004        newTicketPart.setClientAddresses( encTicketpart.getClientAddresses() );
1005        newTicketPart.setCName( encTicketpart.getCName() );
1006        newTicketPart.setEndTime( encTicketpart.getEndTime() );
1007        newTicketPart.setFlags( encTicketpart.getFlags() );
1008        newTicketPart.setRenewTill( encTicketpart.getRenewTill() );
1009        newTicketPart.setKey( encTicketpart.getKey() );
1010        newTicketPart.setTransited( encTicketpart.getTransited() );
1011    }
1012
1013
1014    /**
1015     * Get a PrincipalStoreEntry given a principal.  The ErrorType is used to indicate
1016     * whether any resulting error pertains to a server or client.
1017     *
1018     * @param principal The KerberosPrincipal instance
1019     * @param store The Principal store
1020     * @param errorType The type of error
1021     * @return The PrincipalStoreEntry
1022     * @throws KerberosException If teh entry can't be retrieve
1023     */
1024    public static PrincipalStoreEntry getEntry( KerberosPrincipal principal, PrincipalStore store, ErrorType errorType )
1025        throws KerberosException
1026    {
1027        PrincipalStoreEntry entry = null;
1028
1029        try
1030        {
1031            entry = store.getPrincipal( principal );
1032        }
1033        catch ( Exception e )
1034        {
1035            throw new KerberosException( errorType, e );
1036        }
1037
1038        if ( entry == null )
1039        {
1040            throw new KerberosException( errorType );
1041        }
1042
1043        if ( entry.getKeyMap() == null || entry.getKeyMap().isEmpty() )
1044        {
1045            throw new KerberosException( ErrorType.KDC_ERR_NULL_KEY );
1046        }
1047
1048        return entry;
1049    }
1050
1051}