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.shared.kerberos;
021
022
023import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES3_CBC_MD5;
024import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES3_CBC_SHA1;
025import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES3_CBC_SHA1_KD;
026import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES_CBC_CRC;
027import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES_CBC_MD4;
028import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES_CBC_MD5;
029import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES_EDE3_CBC_ENV_OID;
030import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DSAWITHSHA1_CMSOID;
031import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.MD5WITHRSAENCRYPTION_CMSOID;
032import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.RC2CBC_ENVOID;
033import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.RC4_HMAC;
034import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.RSAENCRYPTION_ENVOID;
035import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.RSAES_OAEP_ENV_OID;
036import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.SHA1WITHRSAENCRYPTION_CMSOID;
037
038import java.net.InetAddress;
039import java.text.ParseException;
040import java.text.SimpleDateFormat;
041import java.util.ArrayList;
042import java.util.HashSet;
043import java.util.LinkedHashMap;
044import java.util.LinkedHashSet;
045import java.util.List;
046import java.util.Locale;
047import java.util.Map;
048import java.util.Set;
049import java.util.TimeZone;
050
051import javax.security.auth.kerberos.KerberosPrincipal;
052
053import org.apache.directory.api.util.Strings;
054import org.apache.directory.server.i18n.I18n;
055import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
056import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
057import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
058import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
059import org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntry;
060import org.apache.directory.shared.kerberos.codec.KerberosDecoder;
061import org.apache.directory.shared.kerberos.codec.options.ApOptions;
062import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
063import org.apache.directory.shared.kerberos.components.EncTicketPart;
064import org.apache.directory.shared.kerberos.components.EncryptionKey;
065import org.apache.directory.shared.kerberos.components.HostAddress;
066import org.apache.directory.shared.kerberos.components.PrincipalName;
067import org.apache.directory.shared.kerberos.exceptions.ErrorType;
068import org.apache.directory.shared.kerberos.exceptions.KerberosException;
069import org.apache.directory.shared.kerberos.messages.ApReq;
070import org.apache.directory.shared.kerberos.messages.Authenticator;
071import org.apache.directory.shared.kerberos.messages.Ticket;
072
073
074/**
075 * An utility class for Kerberos.
076 *
077 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
078 */
079public class KerberosUtils
080{
081    /** A constant for integer optional values */
082    public static final int NULL = -1;
083
084    /** An empty list of principal names */
085    public static final List<String> EMPTY_PRINCIPAL_NAME = new ArrayList<>();
086
087    /** 
088     * an order preserved map containing cipher names to the corresponding algorithm 
089     * names in the descending order of strength
090     */
091    private static final Map<String, String> cipherAlgoMap = new LinkedHashMap<>();
092
093    public static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );
094
095    /** Defines a default date format with a "yyyyMMddHHmmss'Z'" pattern */
096    public static final SimpleDateFormat UTC_DATE_FORMAT = new SimpleDateFormat( "yyyyMMddHHmmss'Z'", Locale.ROOT );
097
098    private static final Set<EncryptionType> oldEncTypes = new HashSet<>();
099
100    static
101    {
102        UTC_DATE_FORMAT.setTimeZone( UTC_TIME_ZONE );
103
104        cipherAlgoMap.put( "rc4", "ArcFourHmac" );
105        cipherAlgoMap.put( "aes256", "AES256" );
106        cipherAlgoMap.put( "aes128", "AES128" );
107        cipherAlgoMap.put( "des3", "DESede" );
108        cipherAlgoMap.put( "des", "DES" );
109
110        oldEncTypes.add( DES_CBC_CRC );
111        oldEncTypes.add( DES_CBC_MD4 );
112        oldEncTypes.add( DES_CBC_MD5 );
113        oldEncTypes.add( DES_EDE3_CBC_ENV_OID );
114        oldEncTypes.add( DES3_CBC_MD5 );
115        oldEncTypes.add( DES3_CBC_SHA1 );
116        oldEncTypes.add( DES3_CBC_SHA1_KD );
117        oldEncTypes.add( DSAWITHSHA1_CMSOID );
118        oldEncTypes.add( MD5WITHRSAENCRYPTION_CMSOID );
119        oldEncTypes.add( SHA1WITHRSAENCRYPTION_CMSOID );
120        oldEncTypes.add( RC2CBC_ENVOID );
121        oldEncTypes.add( RSAENCRYPTION_ENVOID );
122        oldEncTypes.add( RSAES_OAEP_ENV_OID );
123        oldEncTypes.add( RC4_HMAC );
124    }
125
126
127    /**
128     * Parse a KerberosPrincipal instance and return the names. The Principal name
129     * is described in RFC 1964 : <br>
130     * <br>
131     * This name type corresponds to the single-string representation of a<br>
132     * Kerberos name.  (Within the MIT Kerberos V5 implementation, such<br>
133     * names are parseable with the krb5_parse_name() function.)  The<br>
134     * elements included within this name representation are as follows,<br>
135     * proceeding from the beginning of the string:<br>
136     * <br>
137     *  (1) One or more principal name components; if more than one<br>
138     *  principal name component is included, the components are<br>
139     *  separated by `/`.  Arbitrary octets may be included within<br>
140     *  principal name components, with the following constraints and<br>
141     *  special considerations:<br>
142     * <br>
143     *     (1a) Any occurrence of the characters `@` or `/` within a<br>
144     *     name component must be immediately preceded by the `\`<br>
145     *     quoting character, to prevent interpretation as a component<br>
146     *     or realm separator.<br>
147     * <br>
148     *     (1b) The ASCII newline, tab, backspace, and null characters<br>
149     *     may occur directly within the component or may be<br>
150     *     represented, respectively, by `\n`, `\t`, `\b`, or `\0`.<br>
151     * <br>
152     *     (1c) If the `\` quoting character occurs outside the contexts<br>
153     *     described in (1a) and (1b) above, the following character is<br>
154     *     interpreted literally.  As a special case, this allows the<br>
155     *     doubled representation `\\` to represent a single occurrence<br>
156     *     of the quoting character.<br>
157     * <br>
158     *     (1d) An occurrence of the `\` quoting character as the last<br>
159     *     character of a component is illegal.<br>
160     * <br>
161     *  (2) Optionally, a `@` character, signifying that a realm name<br>
162     *  immediately follows. If no realm name element is included, the<br>
163     *  local realm name is assumed.  The `/` , `:`, and null characters<br>
164     *  may not occur within a realm name; the `@`, newline, tab, and<br>
165     *  backspace characters may be included using the quoting<br>
166     *  conventions described in (1a), (1b), and (1c) above.<br>
167     * 
168     * @param principal The principal to be parsed
169     * @return The names as a List of nameComponent
170     * 
171     * @throws ParseException if the name is not valid
172     */
173    public static List<String> getNames( KerberosPrincipal principal ) throws ParseException
174    {
175        if ( principal == null )
176        {
177            return EMPTY_PRINCIPAL_NAME;
178        }
179
180        String names = principal.getName();
181
182        if ( Strings.isEmpty( names ) )
183        {
184            // Empty name...
185            return EMPTY_PRINCIPAL_NAME;
186        }
187
188        return getNames( names );
189    }
190
191
192    /**
193     * Parse a PrincipalName and return the names.
194     */
195    public static List<String> getNames( String principalNames ) throws ParseException
196    {
197        if ( principalNames == null )
198        {
199            return EMPTY_PRINCIPAL_NAME;
200        }
201
202        List<String> nameComponents = new ArrayList<>();
203
204        // Start the parsing. Another State Machine :)
205        char[] chars = principalNames.toCharArray();
206
207        boolean escaped = false;
208        boolean done = false;
209        int start = 0;
210        int pos = 0;
211
212        for ( int i = 0; i < chars.length; i++ )
213        {
214            pos = i;
215
216            switch ( chars[i] )
217            {
218                case '\\':
219                    escaped = !escaped;
220                    break;
221
222                case '/':
223                    if ( escaped )
224                    {
225                        escaped = false;
226                    }
227                    else
228                    {
229                        // We have a new name component
230                        if ( i - start > 0 )
231                        {
232                            String nameComponent = new String( chars, start, i - start );
233                            nameComponents.add( nameComponent );
234                            start = i + 1;
235                        }
236                        else
237                        {
238                            throw new ParseException( I18n.err( I18n.ERR_628 ), i );
239                        }
240                    }
241
242                    break;
243
244                case '@':
245                    if ( escaped )
246                    {
247                        escaped = false;
248                    }
249                    else
250                    {
251                        // We have reached the realm : let's get out
252                        done = true;
253                    }
254
255                    break;
256
257                default:
258            }
259
260            if ( done )
261            {
262                // We have a new name component
263                if ( i - start > 0 )
264                {
265                    String nameComponent = new String( chars, start, i - start );
266                    nameComponents.add( nameComponent );
267                    start = i + 1;
268                }
269                else
270                {
271                    throw new ParseException( I18n.err( I18n.ERR_628 ), i );
272                }
273
274                break;
275            }
276            else if ( i + 1 == chars.length )
277            {
278                // We have a new name component
279                String nameComponent = new String( chars, start, i - start + 1 );
280                nameComponents.add( nameComponent );
281
282                break;
283            }
284        }
285
286        if ( escaped )
287        {
288            throw new ParseException( I18n.err( I18n.ERR_629 ), pos );
289        }
290
291        return nameComponents;
292    }
293
294
295    /**
296     * Constructs a KerberosPrincipal from a PrincipalName and an
297     * optional realm
298     *
299     * @param principal The principal name and type
300     * @param realm The optional realm
301     * 
302     * @return A KerberosPrincipal
303     */
304    public static KerberosPrincipal getKerberosPrincipal( PrincipalName principal, String realm )
305    {
306        String name = principal.getNameString();
307
308        if ( !Strings.isEmpty( realm ) )
309        {
310            name += '@' + realm;
311        }
312
313        return new KerberosPrincipal( name, principal.getNameType().getValue() );
314    }
315
316
317    /**
318     * Get the matching encryption type from the configured types, searching
319     * into the requested types. We returns the first we find.
320     *
321     * @param requestedTypes The client encryption types
322     * @param configuredTypes The configured encryption types
323     * @return The first matching encryption type.
324     */
325    public static EncryptionType getBestEncryptionType( Set<EncryptionType> requestedTypes,
326        Set<EncryptionType> configuredTypes )
327    {
328        for ( EncryptionType encryptionType : configuredTypes )
329        {
330            if ( requestedTypes.contains( encryptionType ) )
331            {
332                return encryptionType;
333            }
334        }
335
336        return null;
337    }
338
339
340    /**
341     * Build a list of encryptionTypes
342     *
343     * @param encryptionTypes The encryptionTypes
344     * @return A list comma separated of the encryptionTypes
345     */
346    public static String getEncryptionTypesString( Set<EncryptionType> encryptionTypes )
347    {
348        StringBuilder sb = new StringBuilder();
349        boolean isFirst = true;
350
351        for ( EncryptionType etype : encryptionTypes )
352        {
353            if ( isFirst )
354            {
355                isFirst = false;
356            }
357            else
358            {
359                sb.append( ", " );
360            }
361
362            sb.append( etype );
363        }
364
365        return sb.toString();
366    }
367
368
369    public static boolean isKerberosString( byte[] value )
370    {
371        if ( value == null )
372        {
373            return false;
374        }
375
376        for ( byte b : value )
377        {
378            if ( ( b < 0x20 ) || ( b > 0x7E ) )
379            {
380                return false;
381            }
382        }
383
384        return true;
385    }
386
387
388    public static String getAlgoNameFromEncType( EncryptionType encType )
389    {
390        String cipherName = Strings.toLowerCaseAscii( encType.getName() );
391
392        for ( String c : cipherAlgoMap.keySet() )
393        {
394            if ( cipherName.startsWith( c ) )
395            {
396                return cipherAlgoMap.get( c );
397            }
398        }
399
400        throw new IllegalArgumentException( "Unknown algorithm name for the encryption type " + encType );
401    }
402
403
404    /**
405     * Order a list of EncryptionType in a decreasing strength order
406     * 
407     * @param etypes The ETypes to order
408     * @return A list of ordered ETypes. he strongest is on the left.
409     */
410    public static Set<EncryptionType> orderEtypesByStrength( Set<EncryptionType> etypes )
411    {
412        Set<EncryptionType> ordered = new LinkedHashSet<>( etypes.size() );
413
414        for ( String algo : cipherAlgoMap.values() )
415        {
416            for ( EncryptionType encType : etypes )
417            {
418                String foundAlgo = getAlgoNameFromEncType( encType );
419
420                if ( algo.equals( foundAlgo ) )
421                {
422                    ordered.add( encType );
423                }
424            }
425        }
426
427        return ordered;
428    }
429
430
431    /**
432     * Get a PrincipalStoreEntry given a principal.  The ErrorType is used to indicate
433     * whether any resulting error pertains to a server or client.
434     */
435    public static PrincipalStoreEntry getEntry( KerberosPrincipal principal, PrincipalStore store, ErrorType errorType )
436        throws KerberosException
437    {
438        PrincipalStoreEntry entry = null;
439
440        try
441        {
442            entry = store.getPrincipal( principal );
443        }
444        catch ( Exception e )
445        {
446            throw new KerberosException( errorType, e );
447        }
448
449        if ( entry == null )
450        {
451            throw new KerberosException( errorType );
452        }
453
454        if ( entry.getKeyMap() == null || entry.getKeyMap().isEmpty() )
455        {
456            throw new KerberosException( ErrorType.KDC_ERR_NULL_KEY );
457        }
458
459        return entry;
460    }
461
462
463    /**
464         * Verifies an AuthHeader using guidelines from RFC 1510 section A.10., "KRB_AP_REQ verification."
465         *
466         * @param authHeader
467         * @param ticket
468         * @param serverKey
469         * @param clockSkew
470         * @param replayCache
471         * @param emptyAddressesAllowed
472         * @param clientAddress
473         * @param lockBox
474         * @param authenticatorKeyUsage
475         * @param isValidate
476         * @return The authenticator.
477         * @throws KerberosException
478         */
479    public static Authenticator verifyAuthHeader( ApReq authHeader, Ticket ticket, EncryptionKey serverKey,
480        long clockSkew, ReplayCache replayCache, boolean emptyAddressesAllowed, InetAddress clientAddress,
481        CipherTextHandler lockBox, KeyUsage authenticatorKeyUsage, boolean isValidate ) throws KerberosException
482    {
483        if ( authHeader.getProtocolVersionNumber() != KerberosConstants.KERBEROS_V5 )
484        {
485            throw new KerberosException( ErrorType.KRB_AP_ERR_BADVERSION );
486        }
487
488        if ( authHeader.getMessageType() != KerberosMessageType.AP_REQ )
489        {
490            throw new KerberosException( ErrorType.KRB_AP_ERR_MSG_TYPE );
491        }
492
493        if ( authHeader.getTicket().getTktVno() != KerberosConstants.KERBEROS_V5 )
494        {
495            throw new KerberosException( ErrorType.KRB_AP_ERR_BADVERSION );
496        }
497
498        EncryptionKey ticketKey = null;
499
500        if ( authHeader.getOption( ApOptions.USE_SESSION_KEY ) )
501        {
502            ticketKey = authHeader.getTicket().getEncTicketPart().getKey();
503        }
504        else
505        {
506            ticketKey = serverKey;
507        }
508
509        if ( ticketKey == null )
510        {
511            // TODO - check server key version number, skvno; requires store
512            //            if ( false )
513            //            {
514            //                throw new KerberosException( ErrorType.KRB_AP_ERR_BADKEYVER );
515            //            }
516
517            throw new KerberosException( ErrorType.KRB_AP_ERR_NOKEY );
518        }
519
520        byte[] encTicketPartData = lockBox.decrypt( ticketKey, ticket.getEncPart(),
521            KeyUsage.AS_OR_TGS_REP_TICKET_WITH_SRVKEY );
522        EncTicketPart encPart = KerberosDecoder.decodeEncTicketPart( encTicketPartData );
523        ticket.setEncTicketPart( encPart );
524
525        byte[] authenticatorData = lockBox.decrypt( ticket.getEncTicketPart().getKey(), authHeader.getAuthenticator(),
526            authenticatorKeyUsage );
527
528        Authenticator authenticator = KerberosDecoder.decodeAuthenticator( authenticatorData );
529
530        if ( !authenticator.getCName().getNameString().equals( ticket.getEncTicketPart().getCName().getNameString() ) )
531        {
532            throw new KerberosException( ErrorType.KRB_AP_ERR_BADMATCH );
533        }
534
535        if ( ticket.getEncTicketPart().getClientAddresses() != null )
536        {
537            if ( !ticket.getEncTicketPart().getClientAddresses().contains( new HostAddress( clientAddress ) ) )
538            {
539                throw new KerberosException( ErrorType.KRB_AP_ERR_BADADDR );
540            }
541        }
542        else
543        {
544            if ( !emptyAddressesAllowed )
545            {
546                throw new KerberosException( ErrorType.KRB_AP_ERR_BADADDR );
547            }
548        }
549
550        KerberosPrincipal serverPrincipal = getKerberosPrincipal( ticket.getSName(), ticket.getRealm() );
551        KerberosPrincipal clientPrincipal = getKerberosPrincipal( authenticator.getCName(), authenticator.getCRealm() );
552        KerberosTime clientTime = authenticator.getCtime();
553        int clientMicroSeconds = authenticator.getCusec();
554
555        if ( replayCache != null )
556        {
557            if ( replayCache.isReplay( serverPrincipal, clientPrincipal, clientTime, clientMicroSeconds ) )
558            {
559                throw new KerberosException( ErrorType.KRB_AP_ERR_REPEAT );
560            }
561
562            replayCache.save( serverPrincipal, clientPrincipal, clientTime, clientMicroSeconds );
563        }
564
565        if ( !authenticator.getCtime().isInClockSkew( clockSkew ) )
566        {
567            throw new KerberosException( ErrorType.KRB_AP_ERR_SKEW );
568        }
569
570        /*
571         * "The server computes the age of the ticket: local (server) time minus
572         * the starttime inside the Ticket.  If the starttime is later than the
573         * current time by more than the allowable clock skew, or if the INVALID
574         * flag is set in the ticket, the KRB_AP_ERR_TKT_NYV error is returned."
575         */
576        KerberosTime startTime = ( ticket.getEncTicketPart().getStartTime() != null ) ? ticket.getEncTicketPart()
577            .getStartTime() : ticket.getEncTicketPart().getAuthTime();
578
579        KerberosTime now = new KerberosTime();
580        boolean isValidStartTime = startTime.lessThan( now );
581
582        if ( !isValidStartTime || ( ticket.getEncTicketPart().getFlags().isInvalid() && !isValidate ) )
583        {
584            // it hasn't yet become valid
585            throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_NYV );
586        }
587
588        // TODO - doesn't take into account skew
589        if ( !ticket.getEncTicketPart().getEndTime().greaterThan( now ) )
590        {
591            throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_EXPIRED );
592        }
593
594        authHeader.getApOptions().set( ApOptions.MUTUAL_REQUIRED );
595
596        return authenticator;
597    }
598
599
600    /**
601     * checks if the given encryption type is *new* (ref sec#3.1.3 of rfc4120)
602     *
603     * @param eType the encryption type
604     * @return true if the encryption type is new, false otherwise
605     */
606    public static boolean isNewEncryptionType( EncryptionType eType )
607    {
608        return !oldEncTypes.contains( eType );
609    }
610
611    /**
612     * Verifies an AuthHeader using guidelines from RFC 1510 section A.10., "KRB_AP_REQ verification."
613     *
614     * @param authHeader
615     * @param ticket
616     * @param serverKey
617     * @param clockSkew
618     * @param replayCache
619     * @param emptyAddressesAllowed
620     * @param clientAddress
621     * @param lockBox
622     * @param authenticatorKeyUsage
623     * @param isValidate
624     * @return The authenticator.
625     * @throws KerberosException
626     *
627    public static Authenticator verifyAuthHeader( ApplicationRequest authHeader, Ticket ticket, EncryptionKey serverKey,
628        long clockSkew, ReplayCache replayCache, boolean emptyAddressesAllowed, InetAddress clientAddress,
629        CipherTextHandler lockBox, KeyUsage authenticatorKeyUsage, boolean isValidate ) throws KerberosException
630    {
631        if ( authHeader.getProtocolVersionNumber() != KerberosConstants.KERBEROS_V5 )
632        {
633            throw new KerberosException( ErrorType.KRB_AP_ERR_BADVERSION );
634        }
635
636        if ( authHeader.getMessageType() != KerberosMessageType.AP_REQ )
637        {
638            throw new KerberosException( ErrorType.KRB_AP_ERR_MSG_TYPE );
639        }
640
641        if ( authHeader.getTicket().getTktVno() != KerberosConstants.KERBEROS_V5 )
642        {
643            throw new KerberosException( ErrorType.KRB_AP_ERR_BADVERSION );
644        }
645
646        EncryptionKey ticketKey = null;
647
648        if ( authHeader.getOption( ApOptions.USE_SESSION_KEY ) )
649        {
650            ticketKey = authHeader.getTicket().getEncTicketPart().getSessionKey();
651        }
652        else
653        {
654            ticketKey = serverKey;
655        }
656
657        if ( ticketKey == null )
658        {
659            // TODO - check server key version number, skvno; requires store
660            if ( false )
661            {
662                throw new KerberosException( ErrorType.KRB_AP_ERR_BADKEYVER );
663            }
664
665            throw new KerberosException( ErrorType.KRB_AP_ERR_NOKEY );
666        }
667
668        EncTicketPart encPart = ( EncTicketPart ) lockBox.unseal( EncTicketPart.class, ticketKey, ticket.getEncPart(),
669            KeyUsage.NUMBER2 );
670        ticket.setEncTicketPart( encPart );
671
672        Authenticator authenticator = ( Authenticator ) lockBox.unseal( Authenticator.class, ticket.getEncTicketPart().getSessionKey(),
673            authHeader.getEncPart(), authenticatorKeyUsage );
674
675        if ( !authenticator.getClientPrincipal().getName().equals( ticket.getEncTicketPart().getClientPrincipal().getName() ) )
676        {
677            throw new KerberosException( ErrorType.KRB_AP_ERR_BADMATCH );
678        }
679
680        if ( ticket.getEncTicketPart().getClientAddresses() != null )
681        {
682            if ( !ticket.getEncTicketPart().getClientAddresses().contains( new HostAddress( clientAddress ) ) )
683            {
684                throw new KerberosException( ErrorType.KRB_AP_ERR_BADADDR );
685            }
686        }
687        else
688        {
689            if ( !emptyAddressesAllowed )
690            {
691                throw new KerberosException( ErrorType.KRB_AP_ERR_BADADDR );
692            }
693        }
694
695        KerberosPrincipal serverPrincipal = ticket.getServerPrincipal();
696        KerberosPrincipal clientPrincipal = authenticator.getClientPrincipal();
697        KerberosTime clientTime = authenticator.getClientTime();
698        int clientMicroSeconds = authenticator.getClientMicroSecond();
699
700        if ( replayCache.isReplay( serverPrincipal, clientPrincipal, clientTime, clientMicroSeconds ) )
701        {
702            throw new KerberosException( ErrorType.KRB_AP_ERR_REPEAT );
703        }
704
705        replayCache.save( serverPrincipal, clientPrincipal, clientTime, clientMicroSeconds );
706
707        if ( !authenticator.getClientTime().isInClockSkew( clockSkew ) )
708        {
709            throw new KerberosException( ErrorType.KRB_AP_ERR_SKEW );
710        }
711
712        /*
713         * "The server computes the age of the ticket: local (server) time minus
714         * the starttime inside the Ticket.  If the starttime is later than the
715         * current time by more than the allowable clock skew, or if the INVALID
716         * flag is set in the ticket, the KRB_AP_ERR_TKT_NYV error is returned."
717         *
718        KerberosTime startTime = ( ticket.getEncTicketPart().getStartTime() != null ) ? ticket.getEncTicketPart().getStartTime() : ticket.getEncTicketPart().getAuthTime();
719
720        KerberosTime now = new KerberosTime();
721        boolean isValidStartTime = startTime.lessThan( now );
722
723        if ( !isValidStartTime || ( ticket.getEncTicketPart().getFlags().isInvalid() && !isValidate ) )
724        {
725            // it hasn't yet become valid
726            throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_NYV );
727        }
728
729        // TODO - doesn't take into account skew
730        if ( !ticket.getEncTicketPart().getEndTime().greaterThan( now ) )
731        {
732            throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_EXPIRED );
733        }
734
735        authHeader.setOption( ApOptions.MUTUAL_REQUIRED );
736
737        return authenticator;
738    }*/
739}