View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    * 
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   * 
19   */
20  package org.apache.directory.shared.kerberos;
21  
22  
23  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES3_CBC_MD5;
24  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES3_CBC_SHA1;
25  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES3_CBC_SHA1_KD;
26  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES_CBC_CRC;
27  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES_CBC_MD4;
28  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES_CBC_MD5;
29  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DES_EDE3_CBC_ENV_OID;
30  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.DSAWITHSHA1_CMSOID;
31  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.MD5WITHRSAENCRYPTION_CMSOID;
32  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.RC2CBC_ENVOID;
33  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.RC4_HMAC;
34  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.RSAENCRYPTION_ENVOID;
35  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.RSAES_OAEP_ENV_OID;
36  import static org.apache.directory.shared.kerberos.codec.types.EncryptionType.SHA1WITHRSAENCRYPTION_CMSOID;
37  
38  import java.net.InetAddress;
39  import java.text.ParseException;
40  import java.text.SimpleDateFormat;
41  import java.util.ArrayList;
42  import java.util.HashSet;
43  import java.util.LinkedHashMap;
44  import java.util.LinkedHashSet;
45  import java.util.List;
46  import java.util.Locale;
47  import java.util.Map;
48  import java.util.Set;
49  import java.util.TimeZone;
50  
51  import javax.security.auth.kerberos.KerberosPrincipal;
52  
53  import org.apache.directory.api.util.Strings;
54  import org.apache.directory.server.i18n.I18n;
55  import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
56  import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
57  import org.apache.directory.server.kerberos.shared.replay.ReplayCache;
58  import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
59  import org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntry;
60  import org.apache.directory.shared.kerberos.codec.KerberosDecoder;
61  import org.apache.directory.shared.kerberos.codec.options.ApOptions;
62  import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
63  import org.apache.directory.shared.kerberos.components.EncTicketPart;
64  import org.apache.directory.shared.kerberos.components.EncryptionKey;
65  import org.apache.directory.shared.kerberos.components.HostAddress;
66  import org.apache.directory.shared.kerberos.components.PrincipalName;
67  import org.apache.directory.shared.kerberos.exceptions.ErrorType;
68  import org.apache.directory.shared.kerberos.exceptions.KerberosException;
69  import org.apache.directory.shared.kerberos.messages.ApReq;
70  import org.apache.directory.shared.kerberos.messages.Authenticator;
71  import org.apache.directory.shared.kerberos.messages.Ticket;
72  
73  
74  /**
75   * An utility class for Kerberos.
76   *
77   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
78   */
79  public class KerberosUtils
80  {
81      /** A constant for integer optional values */
82      public static final int NULL = -1;
83  
84      /** An empty list of principal names */
85      public static final List<String> EMPTY_PRINCIPAL_NAME = new ArrayList<>();
86  
87      /** 
88       * an order preserved map containing cipher names to the corresponding algorithm 
89       * names in the descending order of strength
90       */
91      private static final Map<String, String> cipherAlgoMap = new LinkedHashMap<>();
92  
93      public static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );
94  
95      /** Defines a default date format with a "yyyyMMddHHmmss'Z'" pattern */
96      public static final SimpleDateFormat UTC_DATE_FORMAT = new SimpleDateFormat( "yyyyMMddHHmmss'Z'", Locale.ROOT );
97  
98      private static final Set<EncryptionType> oldEncTypes = new HashSet<>();
99  
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         KerberosTimekerberos/KerberosTime.html#KerberosTime">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 }