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