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.authentication;
021
022
023import java.net.InetAddress;
024import java.nio.ByteBuffer;
025import java.util.Date;
026import java.util.List;
027import java.util.Set;
028
029import javax.security.auth.kerberos.KerberosKey;
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.sam.SamException;
038import org.apache.directory.server.kerberos.sam.SamSubsystem;
039import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
040import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
041import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
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.ETypeInfo;
053import org.apache.directory.shared.kerberos.components.ETypeInfo2;
054import org.apache.directory.shared.kerberos.components.ETypeInfo2Entry;
055import org.apache.directory.shared.kerberos.components.ETypeInfoEntry;
056import org.apache.directory.shared.kerberos.components.EncKdcRepPart;
057import org.apache.directory.shared.kerberos.components.EncTicketPart;
058import org.apache.directory.shared.kerberos.components.EncryptedData;
059import org.apache.directory.shared.kerberos.components.EncryptionKey;
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.MethodData;
065import org.apache.directory.shared.kerberos.components.PaData;
066import org.apache.directory.shared.kerberos.components.PaEncTsEnc;
067import org.apache.directory.shared.kerberos.components.PrincipalName;
068import org.apache.directory.shared.kerberos.components.TransitedEncoding;
069import org.apache.directory.shared.kerberos.exceptions.ErrorType;
070import org.apache.directory.shared.kerberos.exceptions.InvalidTicketException;
071import org.apache.directory.shared.kerberos.exceptions.KerberosException;
072import org.apache.directory.shared.kerberos.flags.TicketFlag;
073import org.apache.directory.shared.kerberos.flags.TicketFlags;
074import org.apache.directory.shared.kerberos.messages.AsRep;
075import org.apache.directory.shared.kerberos.messages.EncAsRepPart;
076import org.apache.directory.shared.kerberos.messages.Ticket;
077import org.slf4j.Logger;
078import org.slf4j.LoggerFactory;
079
080
081/**
082 * Subsystem in charge of authenticating the incoming users.
083 * 
084 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
085 */
086public final class AuthenticationService
087{
088    /** The log for this class. */
089    private static final Logger LOG_KRB = LoggerFactory.getLogger( Loggers.KERBEROS_LOG.getName() );
090
091    /** The module responsible for encryption and decryption */
092    private static final CipherTextHandler CIPHER_TEXT_HANDLER = new CipherTextHandler();
093
094    /** The service name */
095    private static final String SERVICE_NAME = "Authentication Service (AS)";
096
097
098    private AuthenticationService()
099    {
100    }
101
102
103    /**
104     * Handle the authentication, given a specific context
105     *
106     * @param authContext The authentication context
107     * @throws Exception If the authentication failed
108     */
109    public static void execute( AuthenticationContext authContext ) throws Exception
110    {
111        if ( LOG_KRB.isDebugEnabled() )
112        {
113            monitorRequest( authContext );
114        }
115
116        authContext.setCipherTextHandler( CIPHER_TEXT_HANDLER );
117
118        int kerberosVersion = authContext.getRequest().getProtocolVersionNumber();
119
120        if ( kerberosVersion != KerberosConstants.KERBEROS_V5 )
121        {
122            LOG_KRB.error( "Kerberos V{} is not supported", kerberosVersion );
123            throw new KerberosException( ErrorType.KDC_ERR_BAD_PVNO );
124        }
125
126        selectEncryptionType( authContext );
127        getClientEntry( authContext );
128        verifyPolicy( authContext );
129        verifySam( authContext );
130        verifyEncryptedTimestamp( authContext );
131
132        getServerEntry( authContext );
133        generateTicket( authContext );
134        buildReply( authContext );
135    }
136
137
138    /**
139     * 
140     * @param authContext
141     * @throws KerberosException
142     * @throws InvalidTicketException
143     */
144    private static void selectEncryptionType( AuthenticationContext authContext ) throws KerberosException,
145        InvalidTicketException
146    {
147
148        LOG_KRB.debug( "--> Selecting the EncryptionType" );
149        KdcContext kdcContext = authContext;
150        KerberosConfig config = kdcContext.getConfig();
151
152        Set<EncryptionType> requestedTypes = kdcContext.getRequest().getKdcReqBody().getEType();
153        LOG_KRB.debug( "Encryption types requested by client {}.", requestedTypes );
154
155        EncryptionType bestType = KerberosUtils.getBestEncryptionType( requestedTypes, config.getEncryptionTypes() );
156
157        LOG_KRB.debug( "Session will use encryption type {}.", bestType );
158
159        if ( bestType == null )
160        {
161            LOG_KRB.error( "No encryptionType selected !" );
162            throw new KerberosException( ErrorType.KDC_ERR_ETYPE_NOSUPP );
163        }
164
165        kdcContext.setEncryptionType( bestType );
166    }
167
168
169    private static void getClientEntry( AuthenticationContext authContext ) throws KerberosException,
170        InvalidTicketException
171    {
172        LOG_KRB.debug( "--> Getting the client Entry" );
173        KdcReqBody kdcReqBody = authContext.getRequest().getKdcReqBody();
174        KerberosPrincipal principal = KerberosUtils.getKerberosPrincipal(
175            kdcReqBody.getCName(),
176            kdcReqBody.getRealm() );
177        PrincipalStore store = authContext.getStore();
178
179        try
180        {
181            PrincipalStoreEntry storeEntry = KerberosUtils.getEntry( principal, store,
182                ErrorType.KDC_ERR_C_PRINCIPAL_UNKNOWN );
183            authContext.setClientEntry( storeEntry );
184
185            LOG_KRB.debug( "Found entry {} for principal {}", storeEntry.getDistinguishedName(), principal );
186        }
187        catch ( KerberosException ke )
188        {
189            LOG_KRB.error( "Error while searching for client {} : {}", principal, ke.getMessage() );
190            throw ke;
191        }
192    }
193
194
195    private static void verifyPolicy( AuthenticationContext authContext ) throws KerberosException,
196        InvalidTicketException
197    {
198        LOG_KRB.debug( "--> Verifying the policy" );
199        PrincipalStoreEntry entry = authContext.getClientEntry();
200
201        if ( entry.isDisabled() )
202        {
203            LOG_KRB.error( "The entry {} is disabled", entry.getDistinguishedName() );
204            throw new KerberosException( ErrorType.KDC_ERR_CLIENT_REVOKED );
205        }
206
207        if ( entry.isLockedOut() )
208        {
209            LOG_KRB.error( "The entry {} is locked out", entry.getDistinguishedName() );
210            throw new KerberosException( ErrorType.KDC_ERR_CLIENT_REVOKED );
211        }
212
213        if ( entry.getExpiration().getTime() < new Date().getTime() )
214        {
215            LOG_KRB.error( "The entry {} has been revoked", entry.getDistinguishedName() );
216            throw new KerberosException( ErrorType.KDC_ERR_CLIENT_REVOKED );
217        }
218    }
219
220
221    private static void verifySam( AuthenticationContext authContext ) throws KerberosException, InvalidTicketException
222    {
223        LOG_KRB.debug( "--> Verifying using SAM subsystem." );
224        KdcReq request = authContext.getRequest();
225        KerberosConfig config = authContext.getConfig();
226
227        PrincipalStoreEntry clientEntry = authContext.getClientEntry();
228        String clientName = clientEntry.getPrincipal().getName();
229
230        EncryptionKey clientKey = null;
231
232        if ( clientEntry.getSamType() != null )
233        {
234            if ( LOG_KRB.isDebugEnabled() )
235            {
236                LOG_KRB
237                    .debug(
238                        "Entry for client principal {} has a valid SAM type.  Invoking SAM subsystem for pre-authentication.",
239                        clientName );
240            }
241
242            List<PaData> preAuthData = request.getPaData();
243
244            if ( ( preAuthData == null ) || ( preAuthData.size() == 0 ) )
245            {
246                LOG_KRB.debug( "No PreAuth Data" );
247                throw new KerberosException( ErrorType.KDC_ERR_PREAUTH_REQUIRED, preparePreAuthenticationError(
248                    authContext.getEncryptionType(), config
249                        .getEncryptionTypes() ) );
250            }
251
252            try
253            {
254                for ( PaData paData : preAuthData )
255                {
256                    if ( paData.getPaDataType().equals( PaDataType.PA_ENC_TIMESTAMP ) )
257                    {
258                        KerberosKey samKey = SamSubsystem.getInstance().verify( clientEntry,
259                            paData.getPaDataValue() );
260                        clientKey = new EncryptionKey( EncryptionType.getTypeByValue( samKey.getKeyType() ), samKey
261                            .getEncoded() );
262                    }
263                }
264            }
265            catch ( SamException se )
266            {
267                LOG_KRB.error( "Error : {}", se.getMessage() );
268                throw new KerberosException( ErrorType.KRB_ERR_GENERIC, se );
269            }
270
271            authContext.setClientKey( clientKey );
272            authContext.setPreAuthenticated( true );
273
274            if ( LOG_KRB.isDebugEnabled() )
275            {
276                LOG_KRB.debug( "Pre-authentication using SAM subsystem successful for {}.", clientName );
277            }
278        }
279    }
280
281
282    private static void verifyEncryptedTimestamp( AuthenticationContext authContext ) throws KerberosException,
283        InvalidTicketException
284    {
285        LOG_KRB.debug( "--> Verifying using encrypted timestamp." );
286
287        KerberosConfig config = authContext.getConfig();
288        KdcReq request = authContext.getRequest();
289        CipherTextHandler cipherTextHandler = authContext.getCipherTextHandler();
290        PrincipalStoreEntry clientEntry = authContext.getClientEntry();
291        String clientName = clientEntry.getPrincipal().getName();
292
293        EncryptionKey clientKey = null;
294
295        if ( clientEntry.getSamType() == null )
296        {
297            LOG_KRB.debug(
298                "Entry for client principal {} has no SAM type.  Proceeding with standard pre-authentication.",
299                clientName );
300
301            EncryptionType encryptionType = authContext.getEncryptionType();
302            clientKey = clientEntry.getKeyMap().get( encryptionType );
303
304            if ( clientKey == null )
305            {
306                LOG_KRB.error( "No key for client {}", clientEntry.getDistinguishedName() );
307                throw new KerberosException( ErrorType.KDC_ERR_NULL_KEY );
308            }
309
310            if ( config.isPaEncTimestampRequired() )
311            {
312                List<PaData> preAuthData = request.getPaData();
313
314                if ( preAuthData == null )
315                {
316                    LOG_KRB.debug( "PRE_AUTH required..." );
317                    throw new KerberosException( ErrorType.KDC_ERR_PREAUTH_REQUIRED,
318                        preparePreAuthenticationError( authContext.getEncryptionType(), config.getEncryptionTypes() ) );
319                }
320
321                PaEncTsEnc timestamp = null;
322
323                for ( PaData paData : preAuthData )
324                {
325                    if ( paData.getPaDataType().equals( PaDataType.PA_ENC_TIMESTAMP ) )
326                    {
327                        EncryptedData dataValue = KerberosDecoder.decodeEncryptedData( paData.getPaDataValue() );
328                        byte[] decryptedData = cipherTextHandler.decrypt( clientKey, dataValue,
329                            KeyUsage.AS_REQ_PA_ENC_TIMESTAMP_WITH_CKEY );
330                        timestamp = KerberosDecoder.decodePaEncTsEnc( decryptedData );
331                    }
332                }
333
334                if ( timestamp == null )
335                {
336                    LOG_KRB.error( "No timestamp found" );
337                    throw new KerberosException( ErrorType.KDC_ERR_PREAUTH_REQUIRED,
338                        preparePreAuthenticationError( authContext.getEncryptionType(), config.getEncryptionTypes() ) );
339                }
340
341                if ( !timestamp.getPaTimestamp().isInClockSkew( config.getAllowableClockSkew() ) )
342                {
343                    LOG_KRB.error( "Timestamp not in delay" );
344
345                    throw new KerberosException( ErrorType.KDC_ERR_PREAUTH_FAILED );
346                }
347
348                /*
349                 * if(decrypted_enc_timestamp and usec is replay)
350                 *         error_out(KDC_ERR_PREAUTH_FAILED);
351                 * endif
352                 * 
353                 * add decrypted_enc_timestamp and usec to replay cache;
354                 */
355            }
356        }
357
358        authContext.setClientKey( clientKey );
359        authContext.setPreAuthenticated( true );
360
361        if ( LOG_KRB.isDebugEnabled() )
362        {
363            LOG_KRB.debug( "Pre-authentication by encrypted timestamp successful for {}.", clientName );
364        }
365    }
366
367
368    private static void getServerEntry( AuthenticationContext authContext ) throws KerberosException,
369        InvalidTicketException
370    {
371        PrincipalName principal = authContext.getRequest().getKdcReqBody().getSName();
372        PrincipalStore store = authContext.getStore();
373
374        LOG_KRB.debug( "--> Getting the server entry for {}" + principal );
375
376        KerberosPrincipal principalWithRealm = new KerberosPrincipal( principal.getNameString() + "@"
377            + authContext.getRequest().getKdcReqBody().getRealm() );
378        authContext.setServerEntry( KerberosUtils.getEntry( principalWithRealm, store,
379            ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN ) );
380    }
381
382
383    private static void generateTicket( AuthenticationContext authContext ) throws KerberosException,
384        InvalidTicketException
385    {
386        KdcReq request = authContext.getRequest();
387        CipherTextHandler cipherTextHandler = authContext.getCipherTextHandler();
388        PrincipalName serverPrincipal = request.getKdcReqBody().getSName();
389
390        LOG_KRB.debug( "--> Generating ticket for {}", serverPrincipal );
391
392        EncryptionType encryptionType = authContext.getEncryptionType();
393        EncryptionKey serverKey = authContext.getServerEntry().getKeyMap().get( encryptionType );
394
395        PrincipalName ticketPrincipal = request.getKdcReqBody().getSName();
396
397        EncTicketPart encTicketPart = new EncTicketPart();
398        KerberosConfig config = authContext.getConfig();
399
400        // The INITIAL flag indicates that a ticket was issued using the AS protocol.
401        TicketFlags ticketFlags = new TicketFlags();
402        encTicketPart.setFlags( ticketFlags );
403        ticketFlags.setFlag( TicketFlag.INITIAL );
404
405        // The PRE-AUTHENT flag indicates that the client used pre-authentication.
406        if ( authContext.isPreAuthenticated() )
407        {
408            ticketFlags.setFlag( TicketFlag.PRE_AUTHENT );
409        }
410
411        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.FORWARDABLE ) )
412        {
413            if ( !config.isForwardableAllowed() )
414            {
415                LOG_KRB.error( "Ticket cannot be generated, because Forwadable is not allowed" );
416                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
417            }
418
419            ticketFlags.setFlag( TicketFlag.FORWARDABLE );
420        }
421
422        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.PROXIABLE ) )
423        {
424            if ( !config.isProxiableAllowed() )
425            {
426                LOG_KRB.error( "Ticket cannot be generated, because proxyiable is not allowed" );
427                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
428            }
429
430            ticketFlags.setFlag( TicketFlag.PROXIABLE );
431        }
432
433        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.ALLOW_POSTDATE ) )
434        {
435            if ( !config.isPostdatedAllowed() )
436            {
437                LOG_KRB.error( "Ticket cannot be generated, because Posdate is not allowed" );
438                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
439            }
440
441            ticketFlags.setFlag( TicketFlag.MAY_POSTDATE );
442        }
443
444        KdcOptions kdcOptions = request.getKdcReqBody().getKdcOptions();
445
446        if ( kdcOptions.get( KdcOptions.RENEW )
447            || kdcOptions.get( KdcOptions.VALIDATE )
448            || kdcOptions.get( KdcOptions.PROXY )
449            || kdcOptions.get( KdcOptions.FORWARDED )
450            || kdcOptions.get( KdcOptions.ENC_TKT_IN_SKEY ) )
451        {
452            String msg = "";
453            
454            if ( kdcOptions.get( KdcOptions.RENEW ) )
455            {
456                msg = "Ticket cannot be generated, as it's a renew";
457            }
458            
459            if ( kdcOptions.get( KdcOptions.VALIDATE ) )
460            {
461                msg = "Ticket cannot be generated, as it's a validate";
462            }
463            
464            if ( kdcOptions.get( KdcOptions.PROXY ) )
465            {
466                msg = "Ticket cannot be generated, as it's a proxy";
467            }
468            
469            if ( kdcOptions.get( KdcOptions.FORWARDED ) )
470            {
471                msg = "Ticket cannot be generated, as it's forwarded";
472            }
473            
474            if ( kdcOptions.get( KdcOptions.ENC_TKT_IN_SKEY ) )
475            {
476                msg = "Ticket cannot be generated, as it's a user-to-user ";
477            }
478            
479            if ( LOG_KRB.isDebugEnabled() )
480            {
481                LOG_KRB.debug( msg );
482            }
483
484            throw new KerberosException( ErrorType.KDC_ERR_BADOPTION, msg );
485        }
486
487        EncryptionKey sessionKey = RandomKeyFactory.getRandomKey( authContext.getEncryptionType() );
488        encTicketPart.setKey( sessionKey );
489
490        encTicketPart.setCName( request.getKdcReqBody().getCName() );
491        encTicketPart.setCRealm( request.getKdcReqBody().getRealm() );
492        encTicketPart.setTransited( new TransitedEncoding() );
493        String serverRealm = request.getKdcReqBody().getRealm();
494
495        KerberosTime now = new KerberosTime();
496
497        encTicketPart.setAuthTime( now );
498
499        KerberosTime startTime = request.getKdcReqBody().getFrom();
500
501        /*
502         * "If the requested starttime is absent, indicates a time in the past,
503         * or is within the window of acceptable clock skew for the KDC and the
504         * POSTDATE option has not been specified, then the starttime of the
505         * ticket is set to the authentication server's current time."
506         */
507        if ( startTime == null || startTime.lessThan( now ) || startTime.isInClockSkew( config.getAllowableClockSkew() )
508            && !request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) )
509        {
510            startTime = now;
511        }
512
513        /*
514         * "If it indicates a time in the future beyond the acceptable clock skew,
515         * but the POSTDATED option has not been specified, then the error
516         * KDC_ERR_CANNOT_POSTDATE is returned."
517         */
518        if ( ( startTime != null ) && startTime.greaterThan( now )
519            && !startTime.isInClockSkew( config.getAllowableClockSkew() )
520            && !request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) )
521        {
522            String msg = "Ticket cannot be generated, as it's in the future and the POSTDATED option is not set in the request";
523            LOG_KRB.error( msg );
524            throw new KerberosException( ErrorType.KDC_ERR_CANNOT_POSTDATE, msg );
525        }
526
527        /*
528         * "Otherwise the requested starttime is checked against the policy of the
529         * local realm and if the ticket's starttime is acceptable, it is set as
530         * requested, and the INVALID flag is set in the new ticket."
531         */
532        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) )
533        {
534            if ( !config.isPostdatedAllowed() )
535            {
536                String msg = "Ticket cannot be generated, cause issuing POSTDATED tickets is not allowed";
537                LOG_KRB.error( msg );
538                throw new KerberosException( ErrorType.KDC_ERR_POLICY, msg );
539            }
540
541            ticketFlags.setFlag( TicketFlag.POSTDATED );
542            ticketFlags.setFlag( TicketFlag.INVALID );
543        }
544
545        encTicketPart.setStartTime( startTime );
546        
547        long till = 0;
548
549        if ( request.getKdcReqBody().getTill().getTime() == 0 )
550        {
551            till = Long.MAX_VALUE;
552        }
553        else
554        {
555            till = request.getKdcReqBody().getTill().getTime();
556        }
557
558        /*
559         * The end time is the minimum of (a) the requested till time or (b)
560         * the start time plus maximum lifetime as configured in policy.
561         */
562        long endTime = Math.min( till, startTime.getTime() + config.getMaximumTicketLifetime() );
563        KerberosTime kerberosEndTime = new KerberosTime( endTime );
564        encTicketPart.setEndTime( kerberosEndTime );
565
566        /*
567         * "If the requested expiration time minus the starttime (as determined
568         * above) is less than a site-determined minimum lifetime, an error
569         * message with code KDC_ERR_NEVER_VALID is returned."
570         */
571        if ( kerberosEndTime.lessThan( startTime ) )
572        {
573            String msg = "Ticket cannot be generated, as the endTime is below the startTime";
574            LOG_KRB.error( msg );
575            throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID, msg );
576        }
577
578        long ticketLifeTime = Math.abs( startTime.getTime() - kerberosEndTime.getTime() );
579
580        if ( ticketLifeTime < config.getMinimumTicketLifetime() )
581        {
582            String msg = "Ticket cannot be generated, as the Lifetime is too small";
583            LOG_KRB.error( msg );
584            throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID, msg );
585        }
586
587        /*
588         * "If the requested expiration time for the ticket exceeds what was determined
589         * as above, and if the 'RENEWABLE-OK' option was requested, then the 'RENEWABLE'
590         * flag is set in the new ticket, and the renew-till value is set as if the
591         * 'RENEWABLE' option were requested."
592         */
593        KerberosTime tempRtime = request.getKdcReqBody().getRTime();
594
595        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEWABLE_OK )
596            && request.getKdcReqBody().getTill().greaterThan( kerberosEndTime ) )
597        {
598            if ( !config.isRenewableAllowed() )
599            {
600                String msg = "Ticket cannot be generated, as the renew date is exceeded";
601                LOG_KRB.error( msg );
602                throw new KerberosException( ErrorType.KDC_ERR_POLICY, msg );
603            }
604
605            request.getKdcReqBody().getKdcOptions().set( KdcOptions.RENEWABLE );
606            tempRtime = request.getKdcReqBody().getTill();
607        }
608
609        if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEWABLE ) )
610        {
611            if ( !config.isRenewableAllowed() )
612            {
613                String msg = "Ticket cannot be generated, as Renewable is not allowed";
614                LOG_KRB.error( msg );
615                throw new KerberosException( ErrorType.KDC_ERR_POLICY, msg );
616            }
617
618            ticketFlags.setFlag( TicketFlag.RENEWABLE );
619
620            if ( tempRtime == null || tempRtime.isZero() )
621            {
622                tempRtime = KerberosTime.INFINITY;
623            }
624
625            /*
626             * The renew-till time is the minimum of (a) the requested renew-till
627             * time or (b) the start time plus maximum renewable lifetime as
628             * configured in policy.
629             */
630            long renewTill = Math.min( tempRtime.getTime(), startTime.getTime() + config.getMaximumRenewableLifetime() );
631            encTicketPart.setRenewTill( new KerberosTime( renewTill ) );
632        }
633
634        if ( request.getKdcReqBody().getAddresses() != null
635            && request.getKdcReqBody().getAddresses().getAddresses() != null
636            && request.getKdcReqBody().getAddresses().getAddresses().length > 0 )
637        {
638            encTicketPart.setClientAddresses( request.getKdcReqBody().getAddresses() );
639        }
640        else
641        {
642            if ( !config.isEmptyAddressesAllowed() )
643            {
644                String msg = "Ticket cannot be generated, as the addresses are null, and it's not allowed";
645                LOG_KRB.error( msg );
646                throw new KerberosException( ErrorType.KDC_ERR_POLICY, msg );
647            }
648        }
649
650        EncryptedData encryptedData = cipherTextHandler.seal( serverKey, encTicketPart,
651            KeyUsage.AS_OR_TGS_REP_TICKET_WITH_SRVKEY );
652
653        Ticket newTicket = new Ticket( ticketPrincipal, encryptedData );
654
655        newTicket.setRealm( serverRealm );
656        newTicket.setEncTicketPart( encTicketPart );
657
658        LOG_KRB.debug( "Ticket will be issued for access to {}.", serverPrincipal.toString() );
659
660        authContext.setTicket( newTicket );
661    }
662
663
664    private static void buildReply( AuthenticationContext authContext ) throws KerberosException,
665        InvalidTicketException
666    {
667        LOG_KRB.debug( "--> Building reply" );
668        KdcReq request = authContext.getRequest();
669        Ticket ticket = authContext.getTicket();
670
671        AsRep reply = new AsRep();
672
673        reply.setCName( request.getKdcReqBody().getCName() );
674        reply.setCRealm( request.getKdcReqBody().getRealm() );
675        reply.setTicket( ticket );
676
677        EncKdcRepPart encKdcRepPart = new EncKdcRepPart();
678        //session key
679        encKdcRepPart.setKey( ticket.getEncTicketPart().getKey() );
680
681        // TODO - fetch lastReq for this client; requires store
682        // FIXME temporary fix, IMO we should create some new ATs to store this info in DIT
683        LastReq lastReq = new LastReq();
684        lastReq.addEntry( new LastReqEntry( LastReqType.TIME_OF_INITIAL_REQ, new KerberosTime() ) );
685        encKdcRepPart.setLastReq( lastReq );
686        // TODO - resp.key-expiration := client.expiration; requires store
687
688        encKdcRepPart.setNonce( request.getKdcReqBody().getNonce() );
689
690        encKdcRepPart.setFlags( ticket.getEncTicketPart().getFlags() );
691        encKdcRepPart.setAuthTime( ticket.getEncTicketPart().getAuthTime() );
692        encKdcRepPart.setStartTime( ticket.getEncTicketPart().getStartTime() );
693        encKdcRepPart.setEndTime( ticket.getEncTicketPart().getEndTime() );
694
695        if ( ticket.getEncTicketPart().getFlags().isRenewable() )
696        {
697            encKdcRepPart.setRenewTill( ticket.getEncTicketPart().getRenewTill() );
698        }
699
700        encKdcRepPart.setSName( ticket.getSName() );
701        encKdcRepPart.setSRealm( ticket.getRealm() );
702        encKdcRepPart.setClientAddresses( ticket.getEncTicketPart().getClientAddresses() );
703
704        EncAsRepPart encAsRepPart = new EncAsRepPart();
705        encAsRepPart.setEncKdcRepPart( encKdcRepPart );
706
707        if ( LOG_KRB.isDebugEnabled() )
708        {
709            monitorContext( authContext );
710            monitorReply( reply, encKdcRepPart );
711        }
712
713        EncryptionKey clientKey = authContext.getClientKey();
714        EncryptedData encryptedData = CIPHER_TEXT_HANDLER.seal( clientKey, encAsRepPart,
715            KeyUsage.AS_REP_ENC_PART_WITH_CKEY );
716        reply.setEncPart( encryptedData );
717        //FIXME the below setter is useless, remove it
718        reply.setEncKdcRepPart( encKdcRepPart );
719
720        authContext.setReply( reply );
721    }
722
723
724    private static void monitorRequest( KdcContext kdcContext )
725    {
726        KdcReq request = kdcContext.getRequest();
727
728        if ( LOG_KRB.isDebugEnabled() )
729        {
730            try
731            {
732                String clientAddress = kdcContext.getClientAddress().getHostAddress();
733
734                StringBuffer sb = new StringBuffer();
735
736                sb.append( "Received " + SERVICE_NAME + " request:" );
737                sb.append( "\n\t" + "messageType:           " + request.getMessageType() );
738                sb.append( "\n\t" + "protocolVersionNumber: " + request.getProtocolVersionNumber() );
739                sb.append( "\n\t" + "clientAddress:         " + clientAddress );
740                sb.append( "\n\t" + "nonce:                 " + request.getKdcReqBody().getNonce() );
741                sb.append( "\n\t" + "kdcOptions:            " + request.getKdcReqBody().getKdcOptions() );
742                sb.append( "\n\t" + "clientPrincipal:       " + request.getKdcReqBody().getCName() );
743                sb.append( "\n\t" + "serverPrincipal:       " + request.getKdcReqBody().getSName() );
744                sb.append( "\n\t" + "encryptionType:        "
745                    + KerberosUtils.getEncryptionTypesString( request.getKdcReqBody().getEType() ) );
746                sb.append( "\n\t" + "realm:                 " + request.getKdcReqBody().getRealm() );
747                sb.append( "\n\t" + "from time:             " + request.getKdcReqBody().getFrom() );
748                sb.append( "\n\t" + "till time:             " + request.getKdcReqBody().getTill() );
749                sb.append( "\n\t" + "renew-till time:       " + request.getKdcReqBody().getRTime() );
750                sb.append( "\n\t" + "hostAddresses:         " + request.getKdcReqBody().getAddresses() );
751
752                String message = sb.toString();
753                LOG_KRB.debug( message );
754            }
755            catch ( Exception e )
756            {
757                // This is a monitor.  No exceptions should bubble up.
758                LOG_KRB.error( I18n.err( I18n.ERR_153 ), e );
759            }
760        }
761    }
762
763
764    private static void monitorContext( AuthenticationContext authContext )
765    {
766        try
767        {
768            long clockSkew = authContext.getConfig().getAllowableClockSkew();
769            InetAddress clientAddress = authContext.getClientAddress();
770
771            StringBuilder sb = new StringBuilder();
772
773            sb.append( "Monitoring " + SERVICE_NAME + " context:" );
774
775            sb.append( "\n\t" + "clockSkew              " + clockSkew );
776            sb.append( "\n\t" + "clientAddress          " + clientAddress );
777
778            KerberosPrincipal clientPrincipal = authContext.getClientEntry().getPrincipal();
779            PrincipalStoreEntry clientEntry = authContext.getClientEntry();
780
781            sb.append( "\n\t" + "principal              " + clientPrincipal );
782            sb.append( "\n\t" + "cn                     " + clientEntry.getCommonName() );
783            sb.append( "\n\t" + "realm                  " + clientEntry.getRealmName() );
784            sb.append( "\n\t" + "principal              " + clientEntry.getPrincipal() );
785            sb.append( "\n\t" + "SAM type               " + clientEntry.getSamType() );
786
787            PrincipalName serverPrincipal = authContext.getRequest().getKdcReqBody().getSName();
788            PrincipalStoreEntry serverEntry = authContext.getServerEntry();
789
790            sb.append( "\n\t" + "principal              " + serverPrincipal );
791            sb.append( "\n\t" + "cn                     " + serverEntry.getCommonName() );
792            sb.append( "\n\t" + "realm                  " + serverEntry.getRealmName() );
793            sb.append( "\n\t" + "principal              " + serverEntry.getPrincipal() );
794            sb.append( "\n\t" + "SAM type               " + serverEntry.getSamType() );
795
796            EncryptionType encryptionType = authContext.getEncryptionType();
797            int clientKeyVersion = clientEntry.getKeyMap().get( encryptionType ).getKeyVersion();
798            int serverKeyVersion = serverEntry.getKeyMap().get( encryptionType ).getKeyVersion();
799            sb.append( "\n\t" + "Request key type       " + encryptionType );
800            sb.append( "\n\t" + "Client key version     " + clientKeyVersion );
801            sb.append( "\n\t" + "Server key version     " + serverKeyVersion );
802
803            String message = sb.toString();
804
805            LOG_KRB.debug( message );
806        }
807        catch ( Exception e )
808        {
809            // This is a monitor.  No exceptions should bubble up.
810            LOG_KRB.error( I18n.err( I18n.ERR_154 ), e );
811        }
812    }
813
814
815    private static void monitorReply( AsRep reply, EncKdcRepPart part )
816    {
817        if ( LOG_KRB.isDebugEnabled() )
818        {
819            try
820            {
821                StringBuffer sb = new StringBuffer();
822
823                sb.append( "Responding with " + SERVICE_NAME + " reply:" );
824                sb.append( "\n\t" + "messageType:           " + reply.getMessageType() );
825                sb.append( "\n\t" + "protocolVersionNumber: " + reply.getProtocolVersionNumber() );
826                sb.append( "\n\t" + "nonce:                 " + part.getNonce() );
827                sb.append( "\n\t" + "clientPrincipal:       " + reply.getCName() );
828                sb.append( "\n\t" + "client realm:          " + reply.getCRealm() );
829                sb.append( "\n\t" + "serverPrincipal:       " + part.getSName() );
830                sb.append( "\n\t" + "server realm:          " + part.getSRealm() );
831                sb.append( "\n\t" + "auth time:             " + part.getAuthTime() );
832                sb.append( "\n\t" + "start time:            " + part.getStartTime() );
833                sb.append( "\n\t" + "end time:              " + part.getEndTime() );
834                sb.append( "\n\t" + "renew-till time:       " + part.getRenewTill() );
835                sb.append( "\n\t" + "hostAddresses:         " + part.getClientAddresses() );
836
837                String message = sb.toString();
838
839                LOG_KRB.debug( message );
840            }
841            catch ( Exception e )
842            {
843                // This is a monitor.  No exceptions should bubble up.
844                LOG_KRB.error( I18n.err( I18n.ERR_155 ), e );
845            }
846        }
847    }
848
849
850    /**
851     * Prepares a pre-authentication error message containing required
852     * encryption types.
853     *
854     * @param encryptionTypes
855     * @return The error message as bytes.
856     */
857    private static byte[] preparePreAuthenticationError( EncryptionType requestedType,
858        Set<EncryptionType> encryptionTypes )
859    {
860        boolean isNewEtype = KerberosUtils.isNewEncryptionType( requestedType );
861
862        ETypeInfo2 eTypeInfo2 = new ETypeInfo2();
863
864        ETypeInfo eTypeInfo = new ETypeInfo();
865
866        for ( EncryptionType encryptionType : encryptionTypes )
867        {
868            if ( !isNewEtype )
869            {
870                ETypeInfoEntry etypeInfoEntry = new ETypeInfoEntry( encryptionType, null );
871                eTypeInfo.addETypeInfoEntry( etypeInfoEntry );
872            }
873
874            ETypeInfo2Entry etypeInfo2Entry = new ETypeInfo2Entry( encryptionType );
875            eTypeInfo2.addETypeInfo2Entry( etypeInfo2Entry );
876        }
877
878        byte[] encTypeInfo = null;
879        byte[] encTypeInfo2 = null;
880        try
881        {
882            if ( !isNewEtype )
883            {
884                ByteBuffer buffer = ByteBuffer.allocate( eTypeInfo.computeLength() );
885                encTypeInfo = eTypeInfo.encode( buffer ).array();
886            }
887
888            ByteBuffer buffer = ByteBuffer.allocate( eTypeInfo2.computeLength() );
889            encTypeInfo2 = eTypeInfo2.encode( buffer ).array();
890        }
891        catch ( EncoderException ioe )
892        {
893            return null;
894        }
895
896        MethodData methodData = new MethodData();
897
898        methodData.addPaData( new PaData( PaDataType.PA_ENC_TIMESTAMP, null ) );
899
900        if ( !isNewEtype )
901        {
902            methodData.addPaData( new PaData( PaDataType.PA_ENCTYPE_INFO, encTypeInfo ) );
903        }
904
905        methodData.addPaData( new PaData( PaDataType.PA_ENCTYPE_INFO2, encTypeInfo2 ) );
906
907        try
908        {
909            ByteBuffer buffer = ByteBuffer.allocate( methodData.computeLength() );
910            return methodData.encode( buffer ).array();
911        }
912        catch ( EncoderException ee )
913        {
914            LOG_KRB.warn( "Failed to encode the etype information", ee );
915            return null;
916        }
917    }
918}