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.server.kerberos.protocol;
21  
22  
23  import java.net.InetAddress;
24  import java.util.HashSet;
25  import java.util.Set;
26  
27  import javax.security.auth.kerberos.KerberosPrincipal;
28  
29  import org.apache.directory.server.kerberos.kdc.KdcServer;
30  import org.apache.directory.server.kerberos.shared.KerberosMessageType;
31  import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumType;
32  import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
33  import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
34  import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
35  import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
36  import org.apache.directory.server.kerberos.shared.messages.ErrorMessage;
37  import org.apache.directory.server.kerberos.shared.messages.KdcRequest;
38  import org.apache.directory.server.kerberos.shared.messages.TicketGrantReply;
39  import org.apache.directory.server.kerberos.shared.messages.components.EncTicketPartModifier;
40  import org.apache.directory.server.kerberos.shared.messages.components.Ticket;
41  import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
42  import org.apache.directory.server.kerberos.shared.messages.value.HostAddress;
43  import org.apache.directory.server.kerberos.shared.messages.value.HostAddresses;
44  import org.apache.directory.server.kerberos.shared.messages.value.KdcOptions;
45  import org.apache.directory.server.kerberos.shared.messages.value.KerberosTime;
46  import org.apache.directory.server.kerberos.shared.messages.value.RequestBody;
47  import org.apache.directory.server.kerberos.shared.messages.value.RequestBodyModifier;
48  import org.apache.directory.server.kerberos.shared.messages.value.flags.TicketFlag;
49  import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
50  
51  
52  /**
53   * Tests the Ticket-Granting Service (TGS) via the {@link KerberosProtocolHandler}.
54   * 
55   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
56   * @version $Rev$, $Date$
57   */
58  public class TicketGrantingServiceTest extends AbstractTicketGrantingServiceTest
59  {
60      private KdcServer config;
61      private PrincipalStore store;
62      private KerberosProtocolHandler handler;
63      private DummySession session;
64  
65  
66      /**
67       * Creates a new instance of {@link TicketGrantingServiceTest}.
68       */
69      public TicketGrantingServiceTest()
70      {
71          config = new KdcServer();
72  
73          /*
74           * Body checksum verification must be disabled because we are bypassing
75           * the codecs, where the body bytes are set on the KdcRequest message.
76           */
77          config.setBodyChecksumVerified( false );
78  
79          store = new MapPrincipalStoreImpl();
80          handler = new KerberosProtocolHandler( config, store );
81          session = new DummySession();
82          lockBox = new CipherTextHandler();
83      }
84  
85  
86      /**
87       * Tests the default minimum request, which consists of as little as the
88       * client name, service name, realm, till time, nonce, and encryption types.
89       * 
90       * This is the request archetype.
91       * 
92       * "The TGS exchange between a client and the Kerberos TGS is initiated by a
93       * client ... when it seeks to obtain authentication credentials for a given
94       * server (which might be registered in a remote realm)."
95       * 
96       * "In the first case, the client must already have acquired a ticket for the
97       * Ticket-Granting Service using the AS exchange (the TGT is usually obtained
98       * when a client initially authenticates to the system, such as when a user
99       * logs in)."
100      * 
101      * @throws Exception 
102      */
103     public void testRequestArchetype() throws Exception
104     {
105         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
106         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
107         String serverPassword = "randomKey";
108 
109         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
110 
111         RequestBodyModifier modifier = new RequestBodyModifier();
112         modifier.setServerName( getPrincipalName( "hnelson" ) );
113         modifier.setRealm( "EXAMPLE.COM" );
114         modifier.setEType( config.getEncryptionTypes() );
115         modifier.setNonce( random.nextInt() );
116 
117         KdcOptions kdcOptions = new KdcOptions();
118         modifier.setKdcOptions( kdcOptions );
119 
120         long currentTime = System.currentTimeMillis();
121 
122         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
123         modifier.setTill( requestedEndTime );
124 
125         RequestBody requestBody = modifier.getRequestBody();
126 
127         KdcRequest message = getKdcRequest( tgt, requestBody );
128 
129         handler.messageReceived( session, message );
130     }
131 
132 
133     /**
134      * Tests the protocol version number, which must be '5'.
135      */
136     public void testProtocolVersionNumber()
137     {
138         RequestBodyModifier modifier = new RequestBodyModifier();
139         modifier.setServerName( getPrincipalName( "hnelson" ) );
140         modifier.setRealm( "EXAMPLE.COM" );
141         modifier.setEType( config.getEncryptionTypes() );
142 
143         KdcRequest message = new KdcRequest( 4, KerberosMessageType.TGS_REQ, null, modifier.getRequestBody() );
144 
145         handler.messageReceived( session, message );
146 
147         ErrorMessage error = ( ErrorMessage ) session.getMessage();
148         assertEquals( "Requested protocol version number not supported", 3, error.getErrorCode() );
149     }
150 
151 
152     /**
153      * Tests that a non-existent server principal returns the correct error message.
154      * 
155      * @throws Exception 
156      */
157     public void testServerNotFound() throws Exception
158     {
159         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
160         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
161         String serverPassword = "randomKey";
162 
163         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
164 
165         RequestBodyModifier modifier = new RequestBodyModifier();
166         modifier.setServerName( getPrincipalName( "badservice" ) );
167         modifier.setRealm( "EXAMPLE.COM" );
168         modifier.setEType( config.getEncryptionTypes() );
169         modifier.setNonce( random.nextInt() );
170 
171         KdcOptions kdcOptions = new KdcOptions();
172         modifier.setKdcOptions( kdcOptions );
173 
174         long currentTime = System.currentTimeMillis();
175 
176         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
177         modifier.setTill( requestedEndTime );
178 
179         RequestBody requestBody = modifier.getRequestBody();
180 
181         KdcRequest message = getKdcRequest( tgt, requestBody );
182 
183         handler.messageReceived( session, message );
184 
185         ErrorMessage error = ( ErrorMessage ) session.getMessage();
186         assertEquals( "Server not found in Kerberos database", 7, error.getErrorCode() );
187     }
188 
189 
190     /**
191      * Tests when no ticket is found in the auth header that the request is rejected
192      * with the correct error message.
193      * 
194      * "If no ticket can be found in the padata field, the KDC_ERR_PADATA_TYPE_NOSUPP
195      * error is returned."
196      * 
197      * @throws Exception 
198      */
199     public void testNoTicketFound() throws Exception
200     {
201         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
202         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
203         String serverPassword = "randomKey";
204 
205         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
206 
207         RequestBodyModifier modifier = new RequestBodyModifier();
208         modifier.setServerName( getPrincipalName( "hnelson" ) );
209         modifier.setRealm( "EXAMPLE.COM" );
210         modifier.setEType( config.getEncryptionTypes() );
211         modifier.setNonce( random.nextInt() );
212 
213         KdcOptions kdcOptions = new KdcOptions();
214         modifier.setKdcOptions( kdcOptions );
215 
216         long currentTime = System.currentTimeMillis();
217 
218         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
219         modifier.setTill( requestedEndTime );
220 
221         RequestBody requestBody = modifier.getRequestBody();
222 
223         // Get the session key from the service ticket.
224         sessionKey = tgt.getEncTicketPart().getSessionKey();
225 
226         // Generate a new sequence number.
227         sequenceNumber = random.nextInt();
228         now = new KerberosTime();
229 
230         KdcRequest message = new KdcRequest( 5, KerberosMessageType.TGS_REQ, null, requestBody );
231 
232         handler.messageReceived( session, message );
233 
234         ErrorMessage error = ( ErrorMessage ) session.getMessage();
235         assertEquals( "KDC has no support for padata type", 16, error.getErrorCode() );
236     }
237 
238 
239     /**
240      * Tests that an inappropriate checksum returns the correct error message.
241      * 
242      * @throws Exception 
243      */
244     public void testInappropriateChecksum() throws Exception
245     {
246         config.setBodyChecksumVerified( true );
247 
248         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "tquist@EXAMPLE.COM" );
249         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
250         String serverPassword = "randomKey";
251 
252         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
253 
254         RequestBodyModifier modifier = new RequestBodyModifier();
255         modifier.setServerName( getPrincipalName( "hnelson" ) );
256         modifier.setRealm( "EXAMPLE.COM" );
257         modifier.setEType( config.getEncryptionTypes() );
258         modifier.setNonce( random.nextInt() );
259 
260         KdcOptions kdcOptions = new KdcOptions();
261         modifier.setKdcOptions( kdcOptions );
262 
263         long currentTime = System.currentTimeMillis();
264 
265         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
266         modifier.setTill( requestedEndTime );
267 
268         RequestBody requestBody = modifier.getRequestBody();
269 
270         KdcRequest message = getKdcRequest( tgt, requestBody );
271 
272         handler.messageReceived( session, message );
273 
274         ErrorMessage error = ( ErrorMessage ) session.getMessage();
275         assertEquals( "Inappropriate type of checksum in message", 50, error.getErrorCode() );
276     }
277 
278 
279     /**
280      * Tests that an inappropriate checksum returns the correct error message.
281      * 
282      * @throws Exception 
283      */
284     public void testChecksumTypeNoSupport() throws Exception
285     {
286         config.setBodyChecksumVerified( true );
287 
288         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "tquist@EXAMPLE.COM" );
289         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
290         String serverPassword = "randomKey";
291 
292         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
293 
294         RequestBodyModifier modifier = new RequestBodyModifier();
295         modifier.setServerName( getPrincipalName( "hnelson" ) );
296         modifier.setRealm( "EXAMPLE.COM" );
297         modifier.setEType( config.getEncryptionTypes() );
298         modifier.setNonce( random.nextInt() );
299 
300         KdcOptions kdcOptions = new KdcOptions();
301         modifier.setKdcOptions( kdcOptions );
302 
303         long currentTime = System.currentTimeMillis();
304 
305         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
306         modifier.setTill( requestedEndTime );
307 
308         RequestBody requestBody = modifier.getRequestBody();
309 
310         try
311         {
312             getKdcRequest( tgt, requestBody, ChecksumType.DES_MAC_K );
313         }
314         catch ( KerberosException ke )
315         {
316             assertEquals( "KDC has no support for checksum type", 15, ke.getErrorCode() );
317         }
318     }
319 
320 
321     /**
322      * "If any of the decryptions indicate failed integrity checks, the
323      * KRB_AP_ERR_BAD_INTEGRITY error is returned."
324      * 
325      * @throws Exception
326      */
327     public void testIntegrityCheckedFailed() throws Exception
328     {
329         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
330         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
331         String serverPassword = "badpassword";
332 
333         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
334 
335         RequestBodyModifier modifier = new RequestBodyModifier();
336         modifier.setServerName( getPrincipalName( "hnelson" ) );
337         modifier.setRealm( "EXAMPLE.COM" );
338         modifier.setEType( config.getEncryptionTypes() );
339         modifier.setNonce( random.nextInt() );
340 
341         KdcOptions kdcOptions = new KdcOptions();
342         modifier.setKdcOptions( kdcOptions );
343 
344         long currentTime = System.currentTimeMillis();
345 
346         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
347         modifier.setTill( requestedEndTime );
348 
349         RequestBody requestBody = modifier.getRequestBody();
350 
351         KdcRequest message = getKdcRequest( tgt, requestBody );
352 
353         handler.messageReceived( session, message );
354 
355         ErrorMessage error = ( ErrorMessage ) session.getMessage();
356         assertEquals( "Integrity check on decrypted field failed", 31, error.getErrorCode() );
357     }
358 
359 
360     /**
361      * Tests when the ticket isn't for us that the correct error message is returned.
362      * 
363      * @throws Exception
364      */
365     public void testNotUs() throws Exception
366     {
367         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
368         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@APACHE.ORG" );
369         String serverPassword = "randomKey";
370 
371         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
372 
373         RequestBodyModifier modifier = new RequestBodyModifier();
374         modifier.setServerName( getPrincipalName( "hnelson" ) );
375         modifier.setRealm( "EXAMPLE.COM" );
376         modifier.setEType( config.getEncryptionTypes() );
377         modifier.setNonce( random.nextInt() );
378 
379         KdcOptions kdcOptions = new KdcOptions();
380         modifier.setKdcOptions( kdcOptions );
381 
382         long currentTime = System.currentTimeMillis();
383 
384         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
385         modifier.setTill( requestedEndTime );
386 
387         RequestBody requestBody = modifier.getRequestBody();
388 
389         KdcRequest message = getKdcRequest( tgt, requestBody );
390 
391         handler.messageReceived( session, message );
392 
393         ErrorMessage error = ( ErrorMessage ) session.getMessage();
394         assertEquals( "The ticket isn't for us", 35, error.getErrorCode() );
395     }
396 
397 
398     /**
399      * "The TGS exchange between a client and the Kerberos TGS is initiated by a
400      * client when ... it seeks to renew an existing ticket."
401      * 
402      * @throws Exception 
403      */
404     public void testRenewTicket() throws Exception
405     {
406         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
407         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
408         String serverPassword = "randomKey";
409 
410         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
411 
412         RequestBodyModifier modifier = new RequestBodyModifier();
413         modifier.setServerName( getPrincipalName( "hnelson" ) );
414         modifier.setRealm( "EXAMPLE.COM" );
415         modifier.setEType( config.getEncryptionTypes() );
416         modifier.setNonce( random.nextInt() );
417 
418         KdcOptions kdcOptions = new KdcOptions();
419         kdcOptions.set( KdcOptions.RENEW );
420         modifier.setKdcOptions( kdcOptions );
421 
422         long currentTime = System.currentTimeMillis();
423 
424         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
425         modifier.setTill( requestedEndTime );
426 
427         RequestBody requestBody = modifier.getRequestBody();
428 
429         KdcRequest message = getKdcRequest( tgt, requestBody );
430 
431         handler.messageReceived( session, message );
432 
433         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
434 
435         KerberosTime expectedRenewTillTime = tgt.getEncTicketPart().getRenewTill();
436         boolean isClose = Math.abs( reply.getRenewTill().getTime() - expectedRenewTillTime.getTime() ) < 5000;
437         assertTrue( "Expected renew till time", isClose );
438     }
439 
440 
441     /**
442      * "The TGS exchange between a client and the Kerberos TGS is initiated by a
443      * client when ... it seeks to validate an existing ticket."
444      * 
445      * @throws Exception 
446      */
447     public void testValidateTicket() throws Exception
448     {
449         // Get the mutable ticket part.
450         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
451         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
452 
453         // Make changes to test.
454         encTicketPartModifier.setFlag( TicketFlag.INVALID );
455 
456         // Seal the ticket for the server.
457         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
458         String passPhrase = "randomKey";
459         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
460         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
461 
462         RequestBodyModifier modifier = new RequestBodyModifier();
463         modifier.setServerName( getPrincipalName( "hnelson" ) );
464         modifier.setRealm( "EXAMPLE.COM" );
465         modifier.setEType( config.getEncryptionTypes() );
466         modifier.setNonce( random.nextInt() );
467 
468         KdcOptions kdcOptions = new KdcOptions();
469         kdcOptions.set( KdcOptions.VALIDATE );
470         modifier.setKdcOptions( kdcOptions );
471 
472         long currentTime = System.currentTimeMillis();
473 
474         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
475         modifier.setTill( requestedEndTime );
476 
477         RequestBody requestBody = modifier.getRequestBody();
478 
479         KdcRequest message = getKdcRequest( tgt, requestBody );
480 
481         handler.messageReceived( session, message );
482 
483         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
484 
485         KerberosTime expectedRenewTillTime = tgt.getEncTicketPart().getRenewTill();
486         boolean isClose = Math.abs( reply.getRenewTill().getTime() - expectedRenewTillTime.getTime() ) < 5000;
487         assertTrue( "Expected renew till time", isClose );
488     }
489 
490 
491     /**
492      * "The TGS exchange between a client and the Kerberos TGS is initiated by a
493      * client when ... it seeks to obtain a proxy ticket."
494      * 
495      * @throws Exception 
496      */
497     public void testProxyTicket() throws Exception
498     {
499         // Get the mutable ticket part.
500         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
501         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
502 
503         // Make changes to test.
504         encTicketPartModifier.setFlag( TicketFlag.PROXIABLE );
505 
506         // Seal the ticket for the server.
507         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
508         String passPhrase = "randomKey";
509         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
510         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
511 
512         RequestBodyModifier modifier = new RequestBodyModifier();
513         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
514         modifier.setRealm( "EXAMPLE.COM" );
515         modifier.setEType( config.getEncryptionTypes() );
516         modifier.setNonce( random.nextInt() );
517 
518         KdcOptions kdcOptions = new KdcOptions();
519         kdcOptions.set( KdcOptions.PROXY );
520         modifier.setKdcOptions( kdcOptions );
521 
522         long now = System.currentTimeMillis();
523 
524         KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
525         modifier.setTill( requestedEndTime );
526 
527         HostAddress[] address =
528             { new HostAddress( InetAddress.getByName( null ) ) };
529         HostAddresses addresses = new HostAddresses( address );
530         modifier.setAddresses( addresses );
531 
532         RequestBody requestBody = modifier.getRequestBody();
533         KdcRequest message = getKdcRequest( tgt, requestBody );
534 
535         handler.messageReceived( session, message );
536 
537         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
538 
539         assertTrue( "PROXY flag", reply.getFlags().isProxy() );
540         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
541 
542         assertTrue( "PROXY flag", reply.getTicket().getEncTicketPart().getFlags().isProxy() );
543         assertFalse( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
544 
545         assertNotNull( reply.getTicket().getEncTicketPart().getClientAddresses() );
546     }
547 
548 
549     /**
550      * "The TGS exchange between a client and the Kerberos TGS is initiated by a
551      * client when ... it seeks to obtain a forwarded ticket."
552      * 
553      * @throws Exception 
554      */
555     public void testForwardedTicket() throws Exception
556     {
557         // Get the mutable ticket part.
558         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
559         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
560 
561         // Make changes to test.
562         encTicketPartModifier.setFlag( TicketFlag.FORWARDABLE );
563 
564         // Seal the ticket for the server.
565         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
566         String passPhrase = "randomKey";
567         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
568         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
569 
570         RequestBodyModifier modifier = new RequestBodyModifier();
571         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
572         modifier.setRealm( "EXAMPLE.COM" );
573         modifier.setEType( config.getEncryptionTypes() );
574         modifier.setNonce( random.nextInt() );
575 
576         KdcOptions kdcOptions = new KdcOptions();
577         kdcOptions.set( KdcOptions.FORWARDED );
578         modifier.setKdcOptions( kdcOptions );
579 
580         long now = System.currentTimeMillis();
581 
582         KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
583         modifier.setTill( requestedEndTime );
584 
585         HostAddress[] address =
586             { new HostAddress( InetAddress.getByName( null ) ) };
587         HostAddresses addresses = new HostAddresses( address );
588         modifier.setAddresses( addresses );
589 
590         RequestBody requestBody = modifier.getRequestBody();
591         KdcRequest message = getKdcRequest( tgt, requestBody );
592 
593         handler.messageReceived( session, message );
594 
595         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
596 
597         assertTrue( "FORWARDED flag", reply.getFlags().isForwarded() );
598         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
599 
600         assertTrue( "FORWARDED flag", reply.getTicket().getEncTicketPart().getFlags().isForwarded() );
601         assertFalse( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
602 
603         assertNotNull( reply.getTicket().getEncTicketPart().getClientAddresses() );
604     }
605 
606 
607     /**
608      * As is the case for all application servers, expired tickets are not
609      * accepted by the TGS, so once a renewable or TGT expires, the client
610      * must use a separate exchange to obtain valid tickets.
611      * 
612      * @throws Exception 
613      */
614     public void testExpiredTgt() throws Exception
615     {
616         // Get the mutable ticket part.
617         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
618         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
619 
620         // Make changes to test.
621         encTicketPartModifier.setEndTime( new KerberosTime( 0 ) );
622 
623         // Seal the ticket for the server.
624         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
625         String passPhrase = "randomKey";
626         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
627         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
628 
629         RequestBodyModifier modifier = new RequestBodyModifier();
630         modifier.setServerName( getPrincipalName( "hnelson" ) );
631         modifier.setRealm( "EXAMPLE.COM" );
632         modifier.setEType( config.getEncryptionTypes() );
633         modifier.setNonce( random.nextInt() );
634 
635         KdcOptions kdcOptions = new KdcOptions();
636         modifier.setKdcOptions( kdcOptions );
637 
638         long now = System.currentTimeMillis();
639 
640         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
641         modifier.setTill( requestedEndTime );
642 
643         RequestBody requestBody = modifier.getRequestBody();
644         KdcRequest message = getKdcRequest( tgt, requestBody );
645 
646         handler.messageReceived( session, message );
647 
648         ErrorMessage error = ( ErrorMessage ) session.getMessage();
649         assertEquals( "Ticket expired", 32, error.getErrorCode() );
650     }
651 
652 
653     /**
654      * As is the case for all application servers, expired tickets are not accepted
655      * by the TGS, so once a renewable or TGT expires, the client must use a separate
656      * exchange to obtain valid tickets.
657      * 
658      * @throws Exception 
659      */
660     public void testExpiredRenewableTicket() throws Exception
661     {
662         // Get the mutable ticket part.
663         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
664         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
665 
666         // Make changes to test.
667         encTicketPartModifier.setFlag( TicketFlag.RENEWABLE );
668         encTicketPartModifier.setRenewTill( new KerberosTime( 0 ) );
669 
670         // Seal the ticket for the server.
671         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "ldap/ldap.example.com@EXAMPLE.COM" );
672         String passPhrase = "randomKey";
673         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
674         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
675 
676         RequestBodyModifier modifier = new RequestBodyModifier();
677         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
678         modifier.setRealm( "EXAMPLE.COM" );
679         modifier.setEType( config.getEncryptionTypes() );
680         modifier.setNonce( random.nextInt() );
681 
682         KdcOptions kdcOptions = new KdcOptions();
683         kdcOptions.set( KdcOptions.RENEW );
684         modifier.setKdcOptions( kdcOptions );
685 
686         long now = System.currentTimeMillis();
687 
688         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
689         modifier.setTill( requestedEndTime );
690 
691         RequestBody requestBody = modifier.getRequestBody();
692         KdcRequest message = getKdcRequest( tgt, requestBody );
693 
694         handler.messageReceived( session, message );
695 
696         ErrorMessage error = ( ErrorMessage ) session.getMessage();
697         assertEquals( "Ticket expired", 32, error.getErrorCode() );
698     }
699 
700 
701     /**
702      * Tests when a renewable ticket is presented for renewal, that if the RENEW
703      * flag is NOT set, the ticket is renewed for the endtime of the presented
704      * ticket, as though it were a TGT.
705      *
706      * @throws Exception
707      */
708     public void testRenewableTicketNoRenew() throws Exception
709     {
710         long now = System.currentTimeMillis();
711 
712         // Get the mutable ticket part.
713         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
714         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
715 
716         // Make changes to test.
717         encTicketPartModifier.setFlag( TicketFlag.RENEWABLE );
718         encTicketPartModifier.setStartTime( new KerberosTime( now - KerberosTime.DAY / 2 ) );
719         encTicketPartModifier.setEndTime( new KerberosTime( now + KerberosTime.DAY / 2 ) );
720 
721         // Seal the ticket for the server.
722         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "ldap/ldap.example.com@EXAMPLE.COM" );
723         String passPhrase = "randomKey";
724         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
725         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
726 
727         RequestBodyModifier modifier = new RequestBodyModifier();
728         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
729         modifier.setRealm( "EXAMPLE.COM" );
730         modifier.setEType( config.getEncryptionTypes() );
731         modifier.setNonce( random.nextInt() );
732 
733         KdcOptions kdcOptions = new KdcOptions();
734         modifier.setKdcOptions( kdcOptions );
735 
736         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY / 2 );
737         modifier.setTill( requestedEndTime );
738 
739         RequestBody requestBody = modifier.getRequestBody();
740         KdcRequest message = getKdcRequest( tgt, requestBody );
741 
742         handler.messageReceived( session, message );
743 
744         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
745 
746         KerberosTime expectedEndTime = tgt.getEncTicketPart().getEndTime();
747         boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
748         assertTrue( "Expected renew till time", isClose );
749     }
750 
751 
752     /**
753      * Tests when a renewable ticket is presented for renewal, that if the RENEW
754      * flag is set, the ticket is renewed for the lifetime of the presented ticket.
755      *
756      * @throws Exception
757      */
758     public void testRenewableTicketRenewal() throws Exception
759     {
760         long now = System.currentTimeMillis();
761 
762         // Get the mutable ticket part.
763         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
764         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
765 
766         // Make changes to test.
767         encTicketPartModifier.setFlag( TicketFlag.RENEWABLE );
768         encTicketPartModifier.setStartTime( new KerberosTime( now - KerberosTime.DAY / 2 ) );
769         encTicketPartModifier.setEndTime( new KerberosTime( now + KerberosTime.DAY / 2 ) );
770 
771         // Seal the ticket for the server.
772         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "ldap/ldap.example.com@EXAMPLE.COM" );
773         String passPhrase = "randomKey";
774         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
775         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
776 
777         RequestBodyModifier modifier = new RequestBodyModifier();
778         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
779         modifier.setRealm( "EXAMPLE.COM" );
780         modifier.setEType( config.getEncryptionTypes() );
781         modifier.setNonce( random.nextInt() );
782 
783         KdcOptions kdcOptions = new KdcOptions();
784         kdcOptions.set( KdcOptions.RENEW );
785         modifier.setKdcOptions( kdcOptions );
786 
787         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY / 2 );
788         modifier.setTill( requestedEndTime );
789 
790         RequestBody requestBody = modifier.getRequestBody();
791         KdcRequest message = getKdcRequest( tgt, requestBody );
792 
793         handler.messageReceived( session, message );
794 
795         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
796 
797         KerberosTime expectedEndTime = new KerberosTime( now + KerberosTime.DAY );
798         boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
799         assertTrue( "Expected renew till time", isClose );
800     }
801 
802 
803     /**
804      * Test when an unsupported encryption type is requested, that the request is
805      * rejected with the correct error message.
806      * 
807      * "If the server cannot accommodate any encryption type requested by the
808      * client, an error message with code KDC_ERR_ETYPE_NOSUPP is returned."
809      * 
810      * @throws Exception 
811      */
812     public void testEncryptionTypeNoSupport() throws Exception
813     {
814         RequestBodyModifier modifier = new RequestBodyModifier();
815         modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
816         modifier.setRealm( "EXAMPLE.COM" );
817 
818         Set<EncryptionType> encryptionTypes = new HashSet<EncryptionType>();
819         encryptionTypes.add( EncryptionType.DES3_CBC_MD5 );
820 
821         modifier.setEType( encryptionTypes );
822 
823         modifier.setKdcOptions( new KdcOptions() );
824 
825         long now = System.currentTimeMillis();
826 
827         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
828         modifier.setTill( requestedEndTime );
829 
830         KdcRequest message = new KdcRequest( 5, KerberosMessageType.TGS_REQ, null, modifier.getRequestBody() );
831 
832         handler.messageReceived( session, message );
833 
834         ErrorMessage error = ( ErrorMessage ) session.getMessage();
835         assertEquals( "KDC has no support for encryption type", 14, error.getErrorCode() );
836     }
837 
838 
839     /**
840      * Tests that when a server principal is not configured with Kerberos keys that
841      * the correct error message is returned.
842      * 
843      * @throws Exception 
844      */
845     public void testServerNullKey() throws Exception
846     {
847         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
848         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
849         String serverPassword = "randomKey";
850 
851         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
852 
853         RequestBodyModifier modifier = new RequestBodyModifier();
854         modifier.setServerName( getPrincipalName( "tquist" ) );
855         modifier.setRealm( "EXAMPLE.COM" );
856         modifier.setEType( config.getEncryptionTypes() );
857         modifier.setNonce( random.nextInt() );
858 
859         KdcOptions kdcOptions = new KdcOptions();
860         modifier.setKdcOptions( kdcOptions );
861 
862         long currentTime = System.currentTimeMillis();
863 
864         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
865         modifier.setTill( requestedEndTime );
866 
867         RequestBody requestBody = modifier.getRequestBody();
868 
869         KdcRequest message = getKdcRequest( tgt, requestBody );
870 
871         handler.messageReceived( session, message );
872 
873         ErrorMessage error = ( ErrorMessage ) session.getMessage();
874         assertEquals( "The client or server has a null key", 9, error.getErrorCode() );
875     }
876 
877 
878     /**
879      * Tests when the starttime is absent and the POSTDATED option has not been
880      * specified, that the starttime of the ticket is set to the authentication
881      * server's current time.
882      * 
883      * "If the requested starttime is absent, indicates a time in the past,
884      * or is within the window of acceptable clock skew for the KDC and the
885      * POSTDATE option has not been specified, then the starttime of the
886      * ticket is set to the authentication server's current time."
887      * 
888      * @throws Exception 
889      */
890     public void testStartTimeAbsentNoPostdate() throws Exception
891     {
892         // Get the mutable ticket part.
893         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
894         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
895 
896         // Make changes to test.
897 
898         // Seal the ticket for the server.
899         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
900         String passPhrase = "randomKey";
901         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
902         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
903 
904         RequestBodyModifier modifier = new RequestBodyModifier();
905         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
906         modifier.setRealm( "EXAMPLE.COM" );
907         modifier.setEType( config.getEncryptionTypes() );
908         modifier.setNonce( random.nextInt() );
909 
910         modifier.setKdcOptions( new KdcOptions() );
911 
912         long now = System.currentTimeMillis();
913 
914         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
915         modifier.setTill( requestedEndTime );
916 
917         RequestBody requestBody = modifier.getRequestBody();
918         KdcRequest message = getKdcRequest( tgt, requestBody );
919 
920         handler.messageReceived( session, message );
921 
922         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
923 
924         KerberosTime expectedStartTime = new KerberosTime( now );
925         boolean isClose = reply.getStartTime() == null
926             || Math.abs( reply.getStartTime().getTime() - expectedStartTime.getTime() ) < 5000;
927         assertTrue( "Expected start time", isClose );
928     }
929 
930 
931     /**
932      * Tests when the starttime indicates a time in the past and the POSTDATED option
933      * has not been specified, that the starttime of the ticket is set to the
934      * authentication server's current time.
935      * 
936      * "If the requested starttime is absent, indicates a time in the past,
937      * or is within the window of acceptable clock skew for the KDC and the
938      * POSTDATE option has not been specified, then the starttime of the
939      * ticket is set to the authentication server's current time."
940      * 
941      * @throws Exception 
942      */
943     public void testStartTimeInThePastNoPostdate() throws Exception
944     {
945         // Get the mutable ticket part.
946         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
947         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
948 
949         // Make changes to test.
950 
951         // Seal the ticket for the server.
952         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
953         String passPhrase = "randomKey";
954         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
955         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
956 
957         RequestBodyModifier modifier = new RequestBodyModifier();
958         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
959         modifier.setRealm( "EXAMPLE.COM" );
960         modifier.setEType( config.getEncryptionTypes() );
961         modifier.setNonce( random.nextInt() );
962 
963         modifier.setKdcOptions( new KdcOptions() );
964 
965         long now = System.currentTimeMillis();
966 
967         KerberosTime requestedStartTime = new KerberosTime( now + -1 * KerberosTime.DAY );
968         modifier.setFrom( requestedStartTime );
969 
970         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
971         modifier.setTill( requestedEndTime );
972 
973         RequestBody requestBody = modifier.getRequestBody();
974         KdcRequest message = getKdcRequest( tgt, requestBody );
975 
976         handler.messageReceived( session, message );
977 
978         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
979 
980         KerberosTime expectedStartTime = new KerberosTime( now );
981         boolean isClose = reply.getStartTime() == null
982             || Math.abs( reply.getStartTime().getTime() - expectedStartTime.getTime() ) < 5000;
983         assertTrue( "Expected start time", isClose );
984     }
985 
986 
987     /**
988      * Tests when the starttime is within the window of acceptable clock skew for
989      * the KDC and the POSTDATED option has not been specified, that the starttime
990      * of the ticket is set to the authentication server's current time.
991      * 
992      * "If the requested starttime is absent, indicates a time in the past,
993      * or is within the window of acceptable clock skew for the KDC and the
994      * POSTDATE option has not been specified, then the starttime of the
995      * ticket is set to the authentication server's current time."
996      * 
997      * @throws Exception 
998      */
999     public void testStartTimeAcceptableClockSkewNoPostdate() throws Exception
1000     {
1001         // Get the mutable ticket part.
1002         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1003         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1004 
1005         // Make changes to test.
1006 
1007         // Seal the ticket for the server.
1008         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1009         String passPhrase = "randomKey";
1010         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1011         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1012 
1013         RequestBodyModifier modifier = new RequestBodyModifier();
1014         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1015         modifier.setRealm( "EXAMPLE.COM" );
1016         modifier.setEType( config.getEncryptionTypes() );
1017         modifier.setNonce( random.nextInt() );
1018 
1019         modifier.setKdcOptions( new KdcOptions() );
1020 
1021         long now = System.currentTimeMillis();
1022 
1023         KerberosTime requestedStartTime = new KerberosTime( now );
1024         modifier.setFrom( requestedStartTime );
1025 
1026         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
1027         modifier.setTill( requestedEndTime );
1028 
1029         RequestBody requestBody = modifier.getRequestBody();
1030         KdcRequest message = getKdcRequest( tgt, requestBody );
1031 
1032         handler.messageReceived( session, message );
1033 
1034         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1035 
1036         KerberosTime expectedStartTime = new KerberosTime( now );
1037         boolean isClose = reply.getStartTime() == null
1038             || Math.abs( reply.getStartTime().getTime() - expectedStartTime.getTime() ) < 5000;
1039         assertTrue( "Expected start time", isClose );
1040     }
1041 
1042 
1043     /**
1044      * Tests when a start time is after an end time that the request is rejected with the
1045      * correct error message.
1046      * 
1047      * "If the requested expiration time minus the starttime (as determined above)
1048      * is less than a site-determined minimum lifetime, an error message with code
1049      * KDC_ERR_NEVER_VALID is returned."
1050      *
1051      * @throws Exception
1052      */
1053     public void testStartTimeOrderNeverValid() throws Exception
1054     {
1055         // Get the mutable ticket part.
1056         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1057         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1058 
1059         // Make changes to test.
1060         encTicketPartModifier.setFlag( TicketFlag.MAY_POSTDATE );
1061 
1062         // Seal the ticket for the server.
1063         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1064         String passPhrase = "randomKey";
1065         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1066         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1067 
1068         RequestBodyModifier modifier = new RequestBodyModifier();
1069         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1070         modifier.setRealm( "EXAMPLE.COM" );
1071         modifier.setEType( config.getEncryptionTypes() );
1072         modifier.setNonce( random.nextInt() );
1073 
1074         KdcOptions kdcOptions = new KdcOptions();
1075         kdcOptions.set( KdcOptions.POSTDATED );
1076         modifier.setKdcOptions( kdcOptions );
1077 
1078         long now = System.currentTimeMillis();
1079 
1080         KerberosTime requestedStartTime = new KerberosTime( now + KerberosTime.DAY );
1081         modifier.setFrom( requestedStartTime );
1082 
1083         KerberosTime requestedEndTime = new KerberosTime( now );
1084         modifier.setTill( requestedEndTime );
1085 
1086         RequestBody requestBody = modifier.getRequestBody();
1087         KdcRequest message = getKdcRequest( tgt, requestBody );
1088 
1089         handler.messageReceived( session, message );
1090 
1091         ErrorMessage error = ( ErrorMessage ) session.getMessage();
1092         assertEquals( "Requested start time is later than end time", 11, error.getErrorCode() );
1093     }
1094 
1095 
1096     /**
1097      * Tests when the absolute value of the difference between the start time is
1098      * and the end time is less than a configured minimum, that the request is
1099      * rejected with the correct error message.
1100      * 
1101      * "If the requested expiration time minus the starttime (as determined above)
1102      * is less than a site-determined minimum lifetime, an error message with code
1103      * KDC_ERR_NEVER_VALID is returned."
1104      *
1105      * @throws Exception
1106      */
1107     public void testStartTimeMinimumNeverValid() throws Exception
1108     {
1109         // Get the mutable ticket part.
1110         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1111         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1112 
1113         // Make changes to test.
1114         encTicketPartModifier.setFlag( TicketFlag.MAY_POSTDATE );
1115 
1116         // Seal the ticket for the server.
1117         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1118         String passPhrase = "randomKey";
1119         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1120         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1121 
1122         RequestBodyModifier modifier = new RequestBodyModifier();
1123         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1124         modifier.setRealm( "EXAMPLE.COM" );
1125         modifier.setEType( config.getEncryptionTypes() );
1126         modifier.setNonce( random.nextInt() );
1127 
1128         KdcOptions kdcOptions = new KdcOptions();
1129         modifier.setKdcOptions( kdcOptions );
1130 
1131         long now = System.currentTimeMillis();
1132 
1133         KerberosTime requestedStartTime = new KerberosTime( now );
1134         modifier.setFrom( requestedStartTime );
1135 
1136         KerberosTime requestedEndTime = new KerberosTime( now + 4 * KerberosTime.MINUTE );
1137         modifier.setTill( requestedEndTime );
1138 
1139         RequestBody requestBody = modifier.getRequestBody();
1140         KdcRequest message = getKdcRequest( tgt, requestBody );
1141 
1142         handler.messageReceived( session, message );
1143 
1144         ErrorMessage error = ( ErrorMessage ) session.getMessage();
1145         assertEquals( "Requested start time is later than end time", 11, error.getErrorCode() );
1146     }
1147 
1148 
1149     /**
1150      * Tests when a valid starttime is specified but the POSTDATE flag is not set,
1151      * that the request is rejected with the correct error message.
1152      * 
1153      * "If it indicates a time in the future beyond the acceptable clock skew, but
1154      * the POSTDATED option has not been specified, then the error
1155      * KDC_ERR_CANNOT_POSTDATE is returned."
1156      * 
1157      * @throws Exception 
1158      */
1159     public void testStartTimeNoPostdated() throws Exception
1160     {
1161         // Get the mutable ticket part.
1162         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1163         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1164 
1165         // Make changes to test.
1166         encTicketPartModifier.setFlag( TicketFlag.MAY_POSTDATE );
1167 
1168         // Seal the ticket for the server.
1169         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1170         String passPhrase = "randomKey";
1171         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1172         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1173 
1174         RequestBodyModifier modifier = new RequestBodyModifier();
1175         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1176         modifier.setRealm( "EXAMPLE.COM" );
1177         modifier.setEType( config.getEncryptionTypes() );
1178         modifier.setNonce( random.nextInt() );
1179 
1180         modifier.setKdcOptions( new KdcOptions() );
1181 
1182         long now = System.currentTimeMillis();
1183 
1184         KerberosTime requestedStartTime = new KerberosTime( now + 10 * KerberosTime.MINUTE );
1185         modifier.setFrom( requestedStartTime );
1186 
1187         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
1188         modifier.setTill( requestedEndTime );
1189 
1190         RequestBody requestBody = modifier.getRequestBody();
1191         KdcRequest message = getKdcRequest( tgt, requestBody );
1192 
1193         handler.messageReceived( session, message );
1194 
1195         ErrorMessage error = ( ErrorMessage ) session.getMessage();
1196         assertEquals( "Ticket not eligible for postdating", 10, error.getErrorCode() );
1197     }
1198 
1199 
1200     /**
1201      * Tests that a user-specified start time is honored when that start time does not
1202      * violate policy.
1203      * 
1204      * "Otherwise the requested starttime is checked against the policy of the local
1205      * realm (the administrator might decide to prohibit certain types or ranges of
1206      * postdated tickets), and if the ticket's starttime is acceptable, it is set as
1207      * requested, and the INVALID flag is set in the new ticket.  The postdated
1208      * ticket MUST be validated before use by presenting it to the KDC after the
1209      * starttime has been reached."
1210      * 
1211      * "If the new ticket is postdated (the starttime is in the future), its
1212      * INVALID flag will also be set."
1213      * 
1214      * "The flags field of the new ticket will have the following options set
1215      * if they have been requested and if the policy of the local realm
1216      * allows:  FORWARDABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, RENEWABLE."
1217      * 
1218      * @throws Exception
1219      */
1220     public void testSpecificStartTime() throws Exception
1221     {
1222         long now = System.currentTimeMillis();
1223 
1224         // Get the mutable ticket part.
1225         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1226         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1227 
1228         // Make changes to test.
1229         encTicketPartModifier.setFlag( TicketFlag.MAY_POSTDATE );
1230         // Service ticket end time will be limited by TGT end time.
1231         encTicketPartModifier.setEndTime( new KerberosTime( now + 3 * KerberosTime.DAY ) );
1232 
1233         // Seal the ticket for the server.
1234         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1235         String passPhrase = "randomKey";
1236         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1237         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1238 
1239         RequestBodyModifier modifier = new RequestBodyModifier();
1240         modifier.setServerName( getPrincipalName( "hnelson" ) );
1241         modifier.setRealm( "EXAMPLE.COM" );
1242         modifier.setEType( config.getEncryptionTypes() );
1243         modifier.setNonce( random.nextInt() );
1244 
1245         KdcOptions kdcOptions = new KdcOptions();
1246         kdcOptions.set( KdcOptions.POSTDATED );
1247         modifier.setKdcOptions( kdcOptions );
1248 
1249         KerberosTime requestedStartTime = new KerberosTime( now + KerberosTime.DAY );
1250         modifier.setFrom( requestedStartTime );
1251 
1252         KerberosTime requestedEndTime = new KerberosTime( now + 2 * KerberosTime.DAY );
1253         modifier.setTill( requestedEndTime );
1254 
1255         RequestBody requestBody = modifier.getRequestBody();
1256         KdcRequest message = getKdcRequest( tgt, requestBody );
1257 
1258         handler.messageReceived( session, message );
1259 
1260         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1261 
1262         assertTrue( "Requested start time", requestedStartTime.equals( reply.getStartTime() ) );
1263         assertTrue( "Requested end time", requestedEndTime.equals( reply.getEndTime() ) );
1264         assertTrue( "POSTDATED flag", reply.getFlags().isPostdated() );
1265         assertTrue( "INVALID flag", reply.getFlags().isInvalid() );
1266 
1267         assertTrue( "Requested start time", requestedStartTime.equals( reply.getTicket().getEncTicketPart().getStartTime() ) );
1268         assertTrue( "Requested end time", requestedEndTime.equals( reply.getEndTime() ) );
1269         assertTrue( "POSTDATED flag", reply.getTicket().getEncTicketPart().getFlags().isPostdated() );
1270         assertTrue( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
1271     }
1272 
1273 
1274     /**
1275      * Tests when pre-authentication used during initial authentication, that the flag
1276      * is carried forward to derivative tickets.
1277      *
1278      * @throws Exception
1279      */
1280     public void testPreAuthenticationFlag() throws Exception
1281     {
1282         // Get the mutable ticket part.
1283         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1284         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1285 
1286         // Make changes to test.
1287         encTicketPartModifier.setFlag( TicketFlag.PRE_AUTHENT );
1288 
1289         // Seal the ticket for the server.
1290         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1291         String passPhrase = "randomKey";
1292         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1293         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1294 
1295         RequestBodyModifier modifier = new RequestBodyModifier();
1296         modifier.setServerName( getPrincipalName( "hnelson" ) );
1297         modifier.setRealm( "EXAMPLE.COM" );
1298         modifier.setEType( config.getEncryptionTypes() );
1299         modifier.setNonce( random.nextInt() );
1300 
1301         KdcOptions kdcOptions = new KdcOptions();
1302         modifier.setKdcOptions( kdcOptions );
1303 
1304         long now = System.currentTimeMillis();
1305 
1306         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
1307         modifier.setTill( requestedEndTime );
1308 
1309         RequestBody requestBody = modifier.getRequestBody();
1310         KdcRequest message = getKdcRequest( tgt, requestBody );
1311 
1312         handler.messageReceived( session, message );
1313 
1314         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1315 
1316         assertTrue( "PRE_AUTHENT flag", reply.getTicket().getEncTicketPart().getFlags().isPreAuth() );
1317     }
1318 
1319 
1320     /**
1321      * Tests that a user-specified end time is honored when that end time does not
1322      * violate policy.
1323      * 
1324      * "The expiration time of the ticket will be set to the earlier of the
1325      * requested endtime and a time determined by local policy, possibly by
1326      * using realm- or principal-specific factors."
1327      *
1328      * @throws Exception
1329      */
1330     public void testSpecificEndTime() throws Exception
1331     {
1332         // Get the mutable ticket part.
1333         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1334         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1335 
1336         // Make changes to test.
1337 
1338         // Seal the ticket for the server.
1339         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1340         String passPhrase = "randomKey";
1341         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1342         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1343 
1344         RequestBodyModifier modifier = new RequestBodyModifier();
1345         modifier.setServerName( getPrincipalName( "hnelson" ) );
1346         modifier.setRealm( "EXAMPLE.COM" );
1347         modifier.setEType( config.getEncryptionTypes() );
1348         modifier.setNonce( random.nextInt() );
1349 
1350         KdcOptions kdcOptions = new KdcOptions();
1351         modifier.setKdcOptions( kdcOptions );
1352 
1353         long now = System.currentTimeMillis();
1354 
1355         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY / 2 );
1356         modifier.setTill( requestedEndTime );
1357 
1358         RequestBody requestBody = modifier.getRequestBody();
1359         KdcRequest message = getKdcRequest( tgt, requestBody );
1360 
1361         handler.messageReceived( session, message );
1362 
1363         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1364 
1365         assertTrue( "Requested end time", requestedEndTime.equals( reply.getEndTime() ) );
1366     }
1367 
1368 
1369     /**
1370      * Tests when an end time is requested that exceeds the maximum end time as 
1371      * configured in policy that the maximum allowable end time is returned instead
1372      * of the requested end time.
1373      * 
1374      * "The expiration time of the ticket will be set to the earlier of the
1375      * requested endtime and a time determined by local policy, possibly by
1376      * using realm- or principal-specific factors."
1377      *
1378      * @throws Exception
1379      */
1380     public void testEndTimeExceedsMaximumAllowable() throws Exception
1381     {
1382         // Get the mutable ticket part.
1383         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1384         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1385 
1386         // Make changes to test.
1387 
1388         // Seal the ticket for the server.
1389         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1390         String passPhrase = "randomKey";
1391         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1392         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1393 
1394         RequestBodyModifier modifier = new RequestBodyModifier();
1395         modifier.setServerName( getPrincipalName( "hnelson" ) );
1396         modifier.setRealm( "EXAMPLE.COM" );
1397         modifier.setEType( config.getEncryptionTypes() );
1398         modifier.setNonce( random.nextInt() );
1399 
1400         KdcOptions kdcOptions = new KdcOptions();
1401         modifier.setKdcOptions( kdcOptions );
1402 
1403         long now = System.currentTimeMillis();
1404 
1405         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.WEEK );
1406         modifier.setTill( requestedEndTime );
1407 
1408         RequestBody requestBody = modifier.getRequestBody();
1409         KdcRequest message = getKdcRequest( tgt, requestBody );
1410 
1411         handler.messageReceived( session, message );
1412 
1413         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1414 
1415         KerberosTime expectedEndTime = new KerberosTime( now + KerberosTime.DAY );
1416         boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
1417         assertTrue( "Expected end time", isClose );
1418     }
1419 
1420 
1421     /**
1422      * Tests that a requested zulu end time of the epoch ("19700101000000Z") results
1423      * in the maximum endtime permitted according to KDC policy.  The zulu epoch is
1424      * the same as '0' (zero) milliseconds in Java.
1425      * 
1426      * @throws Exception
1427      */
1428     public void testEpochEndTime() throws Exception
1429     {
1430         // Get the mutable ticket part.
1431         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1432         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1433 
1434         // Make changes to test.
1435 
1436         // Seal the ticket for the server.
1437         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1438         String passPhrase = "randomKey";
1439         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1440         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1441 
1442         RequestBodyModifier modifier = new RequestBodyModifier();
1443         modifier.setServerName( getPrincipalName( "hnelson" ) );
1444         modifier.setRealm( "EXAMPLE.COM" );
1445         modifier.setEType( config.getEncryptionTypes() );
1446         modifier.setNonce( random.nextInt() );
1447 
1448         modifier.setKdcOptions( new KdcOptions() );
1449 
1450         String epoch = "19700101000000Z";
1451         KerberosTime requestedEndTime = KerberosTime.getTime( epoch );
1452         modifier.setTill( requestedEndTime );
1453 
1454         RequestBody requestBody = modifier.getRequestBody();
1455 
1456         KdcRequest message = getKdcRequest( tgt, requestBody );
1457 
1458         handler.messageReceived( session, message );
1459 
1460         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1461 
1462         long now = System.currentTimeMillis();
1463         KerberosTime expectedEndTime = new KerberosTime( now + KerberosTime.DAY );
1464         boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
1465         assertTrue( "Expected end time", isClose );
1466     }
1467 
1468 
1469     /**
1470      * Tests whether a renewable ticket will be accepted in lieu of a non-renewable
1471      * ticket if the requested ticket expiration date cannot be satisfied by a
1472      * non-renewable ticket (due to configuration constraints).
1473      * 
1474      * "If the requested expiration time for the ticket exceeds what was determined
1475      * as above, and if the 'RENEWABLE-OK' option was requested, then the 'RENEWABLE'
1476      * flag is set in the new ticket, and the renew-till value is set as if the
1477      * 'RENEWABLE' option were requested (the field and option names are described
1478      * fully in Section 5.4.1).
1479      * 
1480      * @throws Exception 
1481      */
1482     public void testRenewableOk() throws Exception
1483     {
1484         // Get the mutable ticket part.
1485         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1486         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1487 
1488         // Make changes to test.
1489         encTicketPartModifier.setFlag( TicketFlag.RENEWABLE );
1490 
1491         // Seal the ticket for the server.
1492         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1493         String passPhrase = "randomKey";
1494         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1495         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1496 
1497         RequestBodyModifier modifier = new RequestBodyModifier();
1498         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1499         modifier.setRealm( "EXAMPLE.COM" );
1500         modifier.setEType( config.getEncryptionTypes() );
1501         modifier.setNonce( random.nextInt() );
1502 
1503         KdcOptions kdcOptions = new KdcOptions();
1504         kdcOptions.set( KdcOptions.RENEWABLE_OK );
1505         modifier.setKdcOptions( kdcOptions );
1506 
1507         long now = System.currentTimeMillis();
1508 
1509         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.WEEK );
1510         modifier.setTill( requestedEndTime );
1511 
1512         RequestBody requestBody = modifier.getRequestBody();
1513         KdcRequest message = getKdcRequest( tgt, requestBody );
1514 
1515         handler.messageReceived( session, message );
1516 
1517         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1518 
1519         KerberosTime expectedEndTime = new KerberosTime( now + KerberosTime.DAY );
1520         boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
1521         assertTrue( "Expected end time", isClose );
1522 
1523         assertTrue( "RENEWABLE flag", reply.getFlags().isRenewable() );
1524         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
1525 
1526         KerberosTime expectedRenewTillTime = new KerberosTime( now + KerberosTime.WEEK );
1527         isClose = Math.abs( reply.getRenewTill().getTime() - expectedRenewTillTime.getTime() ) < 5000;
1528         assertTrue( "Expected renew-till time", isClose );
1529     }
1530 
1531 
1532     /**
1533      * Tests forwardable tickets.
1534      * 
1535      * "The flags field of the new ticket will have the following options set
1536      * if they have been requested and if the policy of the local realm
1537      * allows:  FORWARDABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, RENEWABLE."
1538      * 
1539      * @throws Exception 
1540      */
1541     public void testForwardableTicket() throws Exception
1542     {
1543         // Get the mutable ticket part.
1544         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1545         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1546 
1547         // Make changes to test.
1548         encTicketPartModifier.setFlag( TicketFlag.FORWARDABLE );
1549 
1550         // Seal the ticket for the server.
1551         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1552         String passPhrase = "randomKey";
1553         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1554         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1555 
1556         RequestBodyModifier modifier = new RequestBodyModifier();
1557         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1558         modifier.setRealm( "EXAMPLE.COM" );
1559         modifier.setEType( config.getEncryptionTypes() );
1560         modifier.setNonce( random.nextInt() );
1561 
1562         KdcOptions kdcOptions = new KdcOptions();
1563         kdcOptions.set( KdcOptions.FORWARDABLE );
1564         modifier.setKdcOptions( kdcOptions );
1565 
1566         long now = System.currentTimeMillis();
1567 
1568         KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
1569         modifier.setTill( requestedEndTime );
1570 
1571         RequestBody requestBody = modifier.getRequestBody();
1572         KdcRequest message = getKdcRequest( tgt, requestBody );
1573 
1574         handler.messageReceived( session, message );
1575 
1576         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1577 
1578         assertTrue( "FORWARDABLE flag", reply.getFlags().isForwardable() );
1579         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
1580 
1581         assertTrue( "FORWARDABLE flag", reply.getTicket().getEncTicketPart().getFlags().isForwardable() );
1582         assertFalse( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
1583     }
1584 
1585 
1586     /**
1587      * Tests allow postdating of derivative tickets.
1588      * 
1589      * "The flags field of the new ticket will have the following options set
1590      * if they have been requested and if the policy of the local realm
1591      * allows:  FORWARDABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, RENEWABLE."
1592      * 
1593      * @throws Exception 
1594      */
1595     public void testAllowPostdate() throws Exception
1596     {
1597         // Get the mutable ticket part.
1598         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1599         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1600 
1601         // Make changes to test.
1602         encTicketPartModifier.setFlag( TicketFlag.MAY_POSTDATE );
1603 
1604         // Seal the ticket for the server.
1605         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1606         String passPhrase = "randomKey";
1607         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1608         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1609 
1610         RequestBodyModifier modifier = new RequestBodyModifier();
1611         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1612         modifier.setRealm( "EXAMPLE.COM" );
1613         modifier.setEType( config.getEncryptionTypes() );
1614         modifier.setNonce( random.nextInt() );
1615 
1616         KdcOptions kdcOptions = new KdcOptions();
1617         kdcOptions.set( KdcOptions.ALLOW_POSTDATE );
1618         modifier.setKdcOptions( kdcOptions );
1619 
1620         long now = System.currentTimeMillis();
1621 
1622         KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
1623         modifier.setTill( requestedEndTime );
1624 
1625         RequestBody requestBody = modifier.getRequestBody();
1626         KdcRequest message = getKdcRequest( tgt, requestBody );
1627 
1628         handler.messageReceived( session, message );
1629 
1630         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1631 
1632         assertTrue( "MAY_POSTDATE flag", reply.getFlags().isMayPosdate() );
1633         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
1634 
1635         assertTrue( "MAY_POSTDATE flag", reply.getTicket().getEncTicketPart().getFlags().isMayPosdate() );
1636         assertFalse( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
1637     }
1638 
1639 
1640     /**
1641      * Tests proxiable tickets.
1642      * 
1643      * "The flags field of the new ticket will have the following options set
1644      * if they have been requested and if the policy of the local realm
1645      * allows:  FORWARDABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, RENEWABLE."
1646      * 
1647      * @throws Exception 
1648      */
1649     public void testProxiableTicket() throws Exception
1650     {
1651         // Get the mutable ticket part.
1652         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1653         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1654 
1655         // Make changes to test.
1656         encTicketPartModifier.setFlag( TicketFlag.PROXIABLE );
1657 
1658         // Seal the ticket for the server.
1659         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1660         String passPhrase = "randomKey";
1661         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1662         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1663 
1664         RequestBodyModifier modifier = new RequestBodyModifier();
1665         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1666         modifier.setRealm( "EXAMPLE.COM" );
1667         modifier.setEType( config.getEncryptionTypes() );
1668         modifier.setNonce( random.nextInt() );
1669 
1670         KdcOptions kdcOptions = new KdcOptions();
1671         kdcOptions.set( KdcOptions.PROXIABLE );
1672         modifier.setKdcOptions( kdcOptions );
1673 
1674         long now = System.currentTimeMillis();
1675 
1676         KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
1677         modifier.setTill( requestedEndTime );
1678 
1679         RequestBody requestBody = modifier.getRequestBody();
1680         KdcRequest message = getKdcRequest( tgt, requestBody );
1681 
1682         handler.messageReceived( session, message );
1683 
1684         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1685 
1686         assertTrue( "PROXIABLE flag", reply.getFlags().isProxiable() );
1687         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
1688 
1689         assertTrue( "PROXIABLE flag", reply.getTicket().getEncTicketPart().getFlags().isProxiable() );
1690         assertFalse( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
1691     }
1692 
1693 
1694     /**
1695      * Tests that a user-specified renew-till time is honored when that renew-till
1696      * time does not violate policy.
1697      * 
1698      * "If the RENEWABLE option has been requested or if the RENEWABLE-OK
1699      * option has been set and a renewable ticket is to be issued, then the
1700      * renew-till field MAY be set to the earliest of ... its requested value [or]
1701      * the starttime of the ticket plus the maximum renewable lifetime
1702      * set by the policy of the local realm."
1703      * 
1704      * @throws Exception 
1705      */
1706     public void testRenewableTicket() throws Exception
1707     {
1708         // Get the mutable ticket part.
1709         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1710         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1711 
1712         // Make changes to test.
1713         encTicketPartModifier.setFlag( TicketFlag.RENEWABLE );
1714 
1715         // Seal the ticket for the server.
1716         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1717         String passPhrase = "randomKey";
1718         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1719         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1720 
1721         RequestBodyModifier modifier = new RequestBodyModifier();
1722         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1723         modifier.setRealm( "EXAMPLE.COM" );
1724         modifier.setEType( config.getEncryptionTypes() );
1725         modifier.setNonce( random.nextInt() );
1726 
1727         KdcOptions kdcOptions = new KdcOptions();
1728         kdcOptions.set( KdcOptions.RENEWABLE );
1729         modifier.setKdcOptions( kdcOptions );
1730 
1731         long now = System.currentTimeMillis();
1732 
1733         KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
1734         modifier.setTill( requestedEndTime );
1735 
1736         KerberosTime requestedRenewTillTime = new KerberosTime( now + KerberosTime.WEEK / 2 );
1737         modifier.setRtime( requestedRenewTillTime );
1738 
1739         RequestBody requestBody = modifier.getRequestBody();
1740         KdcRequest message = getKdcRequest( tgt, requestBody );
1741 
1742         handler.messageReceived( session, message );
1743 
1744         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1745 
1746         assertTrue( "RENEWABLE flag", reply.getFlags().isRenewable() );
1747         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
1748 
1749         assertTrue( "RENEWABLE flag", reply.getTicket().getEncTicketPart().getFlags().isRenewable() );
1750         assertFalse( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
1751 
1752         assertTrue( "Requested renew-till time", requestedRenewTillTime.equals( reply.getRenewTill() ) );
1753     }
1754 
1755 
1756     /**
1757      * Tests when a renew-till time is requested that exceeds the maximum renew-till
1758      * time as configured in policy that the maximum allowable renew-till time is
1759      * returned instead of the requested renew-till time.
1760      * 
1761      * "If the RENEWABLE option has been requested or if the RENEWABLE-OK
1762      * option has been set and a renewable ticket is to be issued, then the
1763      * renew-till field MAY be set to the earliest of ... its requested value [or]
1764      * the starttime of the ticket plus the maximum renewable lifetime
1765      * set by the policy of the local realm."
1766      * 
1767      * @throws Exception 
1768      */
1769     public void testRenewableTicketExceedsMaximumAllowable() throws Exception
1770     {
1771         // Get the mutable ticket part.
1772         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1773         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1774 
1775         // Make changes to test.
1776         encTicketPartModifier.setFlag( TicketFlag.RENEWABLE );
1777 
1778         // Seal the ticket for the server.
1779         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1780         String passPhrase = "randomKey";
1781         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1782         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1783 
1784         RequestBodyModifier modifier = new RequestBodyModifier();
1785         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1786         modifier.setRealm( "EXAMPLE.COM" );
1787         modifier.setEType( config.getEncryptionTypes() );
1788         modifier.setNonce( random.nextInt() );
1789 
1790         KdcOptions kdcOptions = new KdcOptions();
1791         kdcOptions.set( KdcOptions.RENEWABLE );
1792         modifier.setKdcOptions( kdcOptions );
1793 
1794         long now = System.currentTimeMillis();
1795 
1796         KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
1797         modifier.setTill( requestedEndTime );
1798 
1799         KerberosTime requestedRenewTillTime = new KerberosTime( now + 2 * KerberosTime.WEEK );
1800         modifier.setRtime( requestedRenewTillTime );
1801 
1802         RequestBody requestBody = modifier.getRequestBody();
1803         KdcRequest message = getKdcRequest( tgt, requestBody );
1804 
1805         handler.messageReceived( session, message );
1806 
1807         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1808 
1809         assertTrue( "RENEWABLE flag", reply.getFlags().isRenewable() );
1810         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
1811 
1812         assertTrue( "RENEWABLE flag", reply.getTicket().getEncTicketPart().getFlags().isRenewable() );
1813         assertFalse( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
1814 
1815         KerberosTime expectedRenewTillTime = new KerberosTime( now + KerberosTime.WEEK );
1816         boolean isClose = Math.abs( reply.getRenewTill().getTime() - expectedRenewTillTime.getTime() ) < 5000;
1817         assertTrue( "Expected renew-till time", isClose );
1818     }
1819 
1820 
1821     /**
1822      * "The ciphertext part of the response in the KRB_TGS_REP message is encrypted
1823      * in the sub-session key from the Authenticator, if present, or in the session
1824      * key from the TGT."
1825      *     
1826      * @throws Exception 
1827      */
1828     public void testAuthenticatorSubKey() throws Exception
1829     {
1830         // Get the mutable ticket part.
1831         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1832         EncTicketPartModifier encTicketPartModifier = getTicketArchetype( clientPrincipal );
1833 
1834         // Make changes to test.
1835 
1836         // Seal the ticket for the server.
1837         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1838         String passPhrase = "randomKey";
1839         EncryptionKey serverKey = getEncryptionKey( serverPrincipal, passPhrase );
1840         Ticket tgt = getTicket( encTicketPartModifier, serverPrincipal, serverKey );
1841 
1842         RequestBodyModifier modifier = new RequestBodyModifier();
1843         modifier.setServerName( getPrincipalName( "ldap/ldap.example.com@EXAMPLE.COM" ) );
1844         modifier.setRealm( "EXAMPLE.COM" );
1845         modifier.setEType( config.getEncryptionTypes() );
1846         modifier.setNonce( random.nextInt() );
1847 
1848         KdcOptions kdcOptions = new KdcOptions();
1849         modifier.setKdcOptions( kdcOptions );
1850 
1851         long now = System.currentTimeMillis();
1852 
1853         KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
1854         modifier.setTill( requestedEndTime );
1855 
1856         subSessionKey = RandomKeyFactory.getRandomKey( EncryptionType.DES_CBC_MD5 );
1857 
1858         RequestBody requestBody = modifier.getRequestBody();
1859         KdcRequest message = getKdcRequest( tgt, requestBody );
1860 
1861         handler.messageReceived( session, message );
1862 
1863         TicketGrantReply reply = ( TicketGrantReply ) session.getMessage();
1864 
1865         assertFalse( "INVALID flag", reply.getFlags().isInvalid() );
1866         assertFalse( "INVALID flag", reply.getTicket().getEncTicketPart().getFlags().isInvalid() );
1867     }
1868 
1869 
1870     /**
1871      * Tests that the option RESERVED, which is bad for a TGS_REQ, is rejected
1872      * with the correct error message.
1873      *
1874      * @throws Exception
1875      */
1876     public void testBadOptionReserved() throws Exception
1877     {
1878         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
1879         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" );
1880         String serverPassword = "randomKey";
1881 
1882         Ticket tgt = getTgt( clientPrincipal, serverPrincipal, serverPassword );
1883 
1884         RequestBodyModifier modifier = new RequestBodyModifier();
1885         modifier.setServerName( getPrincipalName( "hnelson" ) );
1886         modifier.setRealm( "EXAMPLE.COM" );
1887         modifier.setEType( config.getEncryptionTypes() );
1888         modifier.setNonce( random.nextInt() );
1889 
1890         KdcOptions kdcOptions = new KdcOptions();
1891         kdcOptions.set( KdcOptions.RESERVED );
1892         modifier.setKdcOptions( kdcOptions );
1893 
1894         long currentTime = System.currentTimeMillis();
1895 
1896         KerberosTime requestedEndTime = new KerberosTime( currentTime + KerberosTime.DAY );
1897         modifier.setTill( requestedEndTime );
1898 
1899         RequestBody requestBody = modifier.getRequestBody();
1900 
1901         KdcRequest message = getKdcRequest( tgt, requestBody );
1902 
1903         handler.messageReceived( session, message );
1904 
1905         ErrorMessage error = ( ErrorMessage ) session.getMessage();
1906         assertEquals( "KDC cannot accommodate requested option", 13, error.getErrorCode() );
1907     }
1908 }