You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by er...@apache.org on 2007/07/19 10:05:40 UTC

svn commit: r557516 - in /directory/apacheds/trunk/protocol-kerberos/src: main/java/org/apache/directory/server/kerberos/kdc/authentication/GenerateTicket.java test/java/org/apache/directory/server/kerberos/protocol/KerberosProtocolHandlerTest.java

Author: erodriguez
Date: Thu Jul 19 01:05:37 2007
New Revision: 557516

URL: http://svn.apache.org/viewvc?view=rev&rev=557516
Log:
Added additional tests for the Kerberos protocol.  These tests and related enhancements to ticket generation now bring the Authentication Service (AS) in line with section 3.1 (The Authentication Service Exchange) of RFC 4120 (The Kerberos Network Authentication Service (V5)).

Modified:
    directory/apacheds/trunk/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/authentication/GenerateTicket.java
    directory/apacheds/trunk/protocol-kerberos/src/test/java/org/apache/directory/server/kerberos/protocol/KerberosProtocolHandlerTest.java

Modified: directory/apacheds/trunk/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/authentication/GenerateTicket.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/authentication/GenerateTicket.java?view=diff&rev=557516&r1=557515&r2=557516
==============================================================================
--- directory/apacheds/trunk/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/authentication/GenerateTicket.java (original)
+++ directory/apacheds/trunk/protocol-kerberos/src/main/java/org/apache/directory/server/kerberos/kdc/authentication/GenerateTicket.java Thu Jul 19 01:05:37 2007
@@ -83,16 +83,31 @@
 
         if ( request.getKdcOptions().get( KdcOptions.FORWARDABLE ) )
         {
+            if ( !config.isForwardableAllowed() )
+            {
+                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
+            }
+
             newTicketBody.setFlag( TicketFlags.FORWARDABLE );
         }
 
         if ( request.getKdcOptions().get( KdcOptions.PROXIABLE ) )
         {
+            if ( !config.isProxiableAllowed() )
+            {
+                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
+            }
+
             newTicketBody.setFlag( TicketFlags.PROXIABLE );
         }
 
         if ( request.getKdcOptions().get( KdcOptions.ALLOW_POSTDATE ) )
         {
+            if ( !config.isPostdateAllowed() )
+            {
+                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
+            }
+
             newTicketBody.setFlag( TicketFlags.MAY_POSTDATE );
         }
 
@@ -112,36 +127,99 @@
         KerberosTime now = new KerberosTime();
         newTicketBody.setAuthTime( now );
 
+        KerberosTime startTime = request.getFrom();
+
+        /*
+         * "If the requested starttime is absent, indicates a time in the past,
+         * or is within the window of acceptable clock skew for the KDC and the
+         * POSTDATE option has not been specified, then the starttime of the
+         * ticket is set to the authentication server's current time."
+         */
+        if ( startTime == null || startTime.lessThan( now ) || startTime.isInClockSkew( config.getAllowableClockSkew() )
+            && !request.getKdcOptions().get( KdcOptions.POSTDATED ) )
+        {
+            startTime = now;
+        }
+
+        /*
+         * "If it indicates a time in the future beyond the acceptable clock skew,
+         * but the POSTDATED option has not been specified, then the error
+         * KDC_ERR_CANNOT_POSTDATE is returned."
+         */
+        if ( startTime != null && startTime.greaterThan( now )
+            && !startTime.isInClockSkew( config.getAllowableClockSkew() )
+            && !request.getKdcOptions().get( KdcOptions.POSTDATED ) )
+        {
+            throw new KerberosException( ErrorType.KDC_ERR_CANNOT_POSTDATE );
+        }
+
+        /*
+         * "Otherwise the requested starttime is checked against the policy of the
+         * local realm and if the ticket's starttime is acceptable, it is set as
+         * requested, and the INVALID flag is set in the new ticket."
+         */
         if ( request.getKdcOptions().get( KdcOptions.POSTDATED ) )
         {
-            // TODO - possibly allow req.from range
             if ( !config.isPostdateAllowed() )
             {
                 throw new KerberosException( ErrorType.KDC_ERR_POLICY );
             }
 
+            newTicketBody.setFlag( TicketFlags.POSTDATED );
             newTicketBody.setFlag( TicketFlags.INVALID );
-            newTicketBody.setStartTime( request.getFrom() );
+            newTicketBody.setStartTime( startTime );
         }
 
         long till = 0;
         if ( request.getTill().getTime() == 0 )
+        {
             till = Long.MAX_VALUE;
+        }
         else
+        {
             till = request.getTill().getTime();
+        }
+
         /*
          new_tkt.endtime := min(till,
          new_tkt.starttime+client.max_life,
          new_tkt.starttime+server.max_life,
          new_tkt.starttime+max_life_for_realm);
          */
-        long endTime = Math.min( now.getTime() + config.getMaximumTicketLifetime(), till );
+        long endTime = Math.min( till, startTime.getTime() + config.getMaximumTicketLifetime() );
         KerberosTime kerberosEndTime = new KerberosTime( endTime );
         newTicketBody.setEndTime( kerberosEndTime );
 
+        /*
+         * "If the requested expiration time minus the starttime (as determined
+         * above) is less than a site-determined minimum lifetime, an error
+         * message with code KDC_ERR_NEVER_VALID is returned."
+         */
+        if ( kerberosEndTime.lessThan( startTime ) )
+        {
+            throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID );
+        }
+
+        long ticketLifeTime = Math.abs( startTime.getTime() - kerberosEndTime.getTime() );
+        if ( ticketLifeTime < config.getAllowableClockSkew() )
+        {
+            throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID );
+        }
+
+        /*
+         * "If the requested expiration time for the ticket exceeds what was determined
+         * as above, and if the 'RENEWABLE-OK' option was requested, then the 'RENEWABLE'
+         * flag is set in the new ticket, and the renew-till value is set as if the
+         * 'RENEWABLE' option were requested."
+         */
         long tempRtime = 0;
         if ( request.getKdcOptions().get( KdcOptions.RENEWABLE_OK ) && request.getTill().greaterThan( kerberosEndTime ) )
         {
+            if ( !config.isRenewableAllowed() )
+            {
+                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
+            }
+
             request.getKdcOptions().set( KdcOptions.RENEWABLE );
             tempRtime = request.getTill().getTime();
         }
@@ -157,7 +235,6 @@
          omit new_tkt.renew-till;
          endif
          */
-
         if ( tempRtime == 0 || request.getRtime() == null )
         {
             tempRtime = request.getTill().getTime();
@@ -169,25 +246,27 @@
 
         if ( request.getKdcOptions().get( KdcOptions.RENEWABLE ) )
         {
-            newTicketBody.setFlag( TicketFlags.RENEWABLE );
-
-            /*
-             * 'from' KerberosTime is OPTIONAL
-             */
-            KerberosTime fromTime = request.getFrom();
-
-            if ( fromTime == null )
+            if ( !config.isRenewableAllowed() )
             {
-                fromTime = new KerberosTime();
+                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
             }
 
-            long renewTill = Math.min( fromTime.getTime() + config.getMaximumRenewableLifetime(), tempRtime );
+            newTicketBody.setFlag( TicketFlags.RENEWABLE );
+
+            long renewTill = Math.min( tempRtime, startTime.getTime() + config.getMaximumRenewableLifetime() );
             newTicketBody.setRenewTill( new KerberosTime( renewTill ) );
         }
 
         if ( request.getAddresses() != null )
         {
             newTicketBody.setClientAddresses( request.getAddresses() );
+        }
+        else
+        {
+            if ( !config.isEmptyAddressesAllowed() )
+            {
+                throw new KerberosException( ErrorType.KDC_ERR_POLICY );
+            }
         }
 
         EncTicketPart ticketPart = newTicketBody.getEncTicketPart();

Modified: directory/apacheds/trunk/protocol-kerberos/src/test/java/org/apache/directory/server/kerberos/protocol/KerberosProtocolHandlerTest.java
URL: http://svn.apache.org/viewvc/directory/apacheds/trunk/protocol-kerberos/src/test/java/org/apache/directory/server/kerberos/protocol/KerberosProtocolHandlerTest.java?view=diff&rev=557516&r1=557515&r2=557516
==============================================================================
--- directory/apacheds/trunk/protocol-kerberos/src/test/java/org/apache/directory/server/kerberos/protocol/KerberosProtocolHandlerTest.java (original)
+++ directory/apacheds/trunk/protocol-kerberos/src/test/java/org/apache/directory/server/kerberos/protocol/KerberosProtocolHandlerTest.java Thu Jul 19 01:05:37 2007
@@ -29,6 +29,7 @@
 
 import org.apache.directory.server.kerberos.kdc.KdcConfiguration;
 import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
 import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
 import org.apache.directory.server.kerberos.shared.io.encoder.EncryptedDataEncoder;
 import org.apache.directory.server.kerberos.shared.messages.AuthenticationReply;
@@ -47,6 +48,7 @@
 import org.apache.directory.server.kerberos.shared.messages.value.PrincipalNameModifier;
 import org.apache.directory.server.kerberos.shared.messages.value.PrincipalNameType;
 import org.apache.directory.server.kerberos.shared.messages.value.RequestBodyModifier;
+import org.apache.directory.server.kerberos.shared.messages.value.TicketFlags;
 import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
 import org.apache.directory.server.kerberos.shared.store.TicketFactory;
 import org.apache.mina.common.IoFilterChain;
@@ -71,6 +73,7 @@
     private PrincipalStore store;
     private KerberosProtocolHandler handler;
     private DummySession session;
+    private CipherTextHandler lockBox;
 
 
     /**
@@ -82,6 +85,7 @@
         store = new MapPrincipalStoreImpl();
         handler = new KerberosProtocolHandler( config, store );
         session = new DummySession();
+        lockBox = new CipherTextHandler();
     }
 
 
@@ -156,6 +160,10 @@
 
     /**
      * Tests that a non-existent client principal returns the correct error message.
+     * 
+     * "If the requested client principal named in the request is
+     * unknown because it doesn't exist in the KDC's principal database,
+     * then an error message with a KDC_ERR_C_PRINCIPAL_UNKNOWN is returned."
      */
     public void testClientNotFound()
     {
@@ -178,6 +186,11 @@
      * Tests when the KDC configuration requires pre-authentication by encrypted
      * timestamp that an AS_REQ without pre-authentication is rejected with the
      * correct error message.
+     * 
+     * "If pre-authentication is required, but was not present in the request, an
+     * error message with the code KDC_ERR_PREAUTH_REQUIRED is returned, and a
+     * METHOD-DATA object will be stored in the e-data field of the KRB-ERROR
+     * message to specify which pre-authentication mechanisms are acceptable."
      */
     public void testPreAuthenticationRequired()
     {
@@ -197,6 +210,88 @@
 
 
     /**
+     * Tests when the KDC configuration requires pre-authentication by encrypted
+     * timestamp that an AS_REQ with pre-authentication using an incorrect key is
+     * rejected with the correct error message.
+     * 
+     * "If required to do so, the server pre-authenticates the request, and
+     * if the pre-authentication check fails, an error message with the code
+     * KDC_ERR_PREAUTH_FAILED is returned."
+     * 
+     * @throws Exception 
+     */
+    public void testPreAuthenticationFailed() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        modifier.setKdcOptions( new KdcOptions() );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+
+        String passPhrase = "badpassword";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "Integrity check on decrypted field failed", 31, error.getErrorCode() );
+    }
+
+
+    /**
+     * Test when an unsupported encryption type is requested, that the request is
+     * rejected with the correct error message.
+     * 
+     * "If the server cannot accommodate any encryption type requested by the
+     * client, an error message with code KDC_ERR_ETYPE_NOSUPP is returned."
+     * 
+     * @throws Exception 
+     */
+    public void testEncryptionTypeNoSupport() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+
+        EncryptionType[] encryptionTypes = new EncryptionType[]
+            { EncryptionType.DES3_CBC_MD5 };
+
+        modifier.setEType( encryptionTypes );
+
+        modifier.setKdcOptions( new KdcOptions() );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "KDC has no support for encryption type", 14, error.getErrorCode() );
+    }
+
+
+    /**
      * Tests that a non-existent server principal returns the correct error message.
      * 
      * @throws Exception 
@@ -273,12 +368,18 @@
 
 
     /**
-     * Tests that a user-specified end time is honored when that end time does not
-     * violate policy.
-     *
-     * @throws Exception
+     * Tests when the starttime is absent and the POSTDATED option has not been
+     * specified, that the starttime of the ticket is set to the authentication
+     * server's current time.
+     * 
+     * "If the requested starttime is absent, indicates a time in the past,
+     * or is within the window of acceptable clock skew for the KDC and the
+     * POSTDATE option has not been specified, then the starttime of the
+     * ticket is set to the authentication server's current time."
+     * 
+     * @throws Exception 
      */
-    public void testSpecificEndTime() throws Exception
+    public void testStartTimeAbsentNoPostdate() throws Exception
     {
         RequestBodyModifier modifier = new RequestBodyModifier();
         modifier.setClientName( getPrincipalName( "hnelson" ) );
@@ -304,18 +405,26 @@
 
         AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
 
-        assertTrue( "Requested end time", requestedEndTime.equals( reply.getEndTime() ) );
+        KerberosTime expectedStartTime = new KerberosTime( now );
+        boolean isClose = reply.getStartTime() == null
+            || Math.abs( reply.getStartTime().getTime() - expectedStartTime.getTime() ) < 5000;
+        assertTrue( "Expected start time", isClose );
     }
 
 
     /**
-     * Tests when an end time is requested that exceeds the maximum end time as 
-     * configured in policy that the maximum allowable end time is returned instead
-     * of the requested end time.
-     *
-     * @throws Exception
+     * Tests when the starttime indicates a time in the past and the POSTDATED option
+     * has not been specified, that the starttime of the ticket is set to the
+     * authentication server's current time.
+     * 
+     * "If the requested starttime is absent, indicates a time in the past,
+     * or is within the window of acceptable clock skew for the KDC and the
+     * POSTDATE option has not been specified, then the starttime of the
+     * ticket is set to the authentication server's current time."
+     * 
+     * @throws Exception 
      */
-    public void testEndTimeExceedsMaximumAllowable() throws Exception
+    public void testStartTimeInThePastNoPostdate() throws Exception
     {
         RequestBodyModifier modifier = new RequestBodyModifier();
         modifier.setClientName( getPrincipalName( "hnelson" ) );
@@ -327,7 +436,10 @@
 
         long now = System.currentTimeMillis();
 
-        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.WEEK );
+        KerberosTime requestedStartTime = new KerberosTime( now + -1 * KerberosTime.DAY );
+        modifier.setFrom( requestedStartTime );
+
+        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
         modifier.setTill( requestedEndTime );
 
         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
@@ -341,33 +453,852 @@
 
         AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
 
-        KerberosTime expectedEndTime = new KerberosTime( now + KerberosTime.DAY );
-        boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
-        assertTrue( "Expected end time", isClose );
+        KerberosTime expectedStartTime = new KerberosTime( now );
+        boolean isClose = reply.getStartTime() == null
+            || Math.abs( reply.getStartTime().getTime() - expectedStartTime.getTime() ) < 5000;
+        assertTrue( "Expected start time", isClose );
     }
 
 
     /**
-     * Tests that RENEWABLE and RENEWABLE_OK are mutually exclusive.  RENEWABLE_OK
-     * should be set by default, but if a request is made for a RENEWABLE ticket then
-     * the RENEWABLE_OK flag should be cleared.
-     */
-    public void testRenewableOk()
-    {
-        // RENEWABLE_OK defaulted on.
-        // if ( renew_till non-zero || renewable set )
-        // {
-        //     clear renewable_ok
-        // }
+     * Tests when the starttime is within the window of acceptable clock skew for
+     * the KDC and the POSTDATED option has not been specified, that the starttime
+     * of the ticket is set to the authentication server's current time.
+     * 
+     * "If the requested starttime is absent, indicates a time in the past,
+     * or is within the window of acceptable clock skew for the KDC and the
+     * POSTDATE option has not been specified, then the starttime of the
+     * ticket is set to the authentication server's current time."
+     * 
+     * @throws Exception 
+     */
+    public void testStartTimeAcceptableClockSkewNoPostdate() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        modifier.setKdcOptions( new KdcOptions() );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedStartTime = new KerberosTime( now );
+        modifier.setFrom( requestedStartTime );
+
+        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        KerberosTime expectedStartTime = new KerberosTime( now );
+        boolean isClose = reply.getStartTime() == null
+            || Math.abs( reply.getStartTime().getTime() - expectedStartTime.getTime() ) < 5000;
+        assertTrue( "Expected start time", isClose );
     }
 
 
-    private PreAuthenticationData[] getPreAuthenticationData( KerberosPrincipal clientPrincipal, String passPhrase )
-        throws Exception
+    /**
+     * Tests when a start time is after an end time that the request is rejected with the
+     * correct error message.
+     * 
+     * "If the requested expiration time minus the starttime (as determined above)
+     * is less than a site-determined minimum lifetime, an error message with code
+     * KDC_ERR_NEVER_VALID is returned."
+     *
+     * @throws Exception
+     */
+    public void testStartTimeOrderNeverValid() throws Exception
     {
-        PreAuthenticationData[] paData = new PreAuthenticationData[1];
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.POSTDATED );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedStartTime = new KerberosTime( now + KerberosTime.DAY );
+        modifier.setFrom( requestedStartTime );
+
+        KerberosTime requestedEndTime = new KerberosTime( now );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "Requested start time is later than end time", 11, error.getErrorCode() );
+    }
+
+
+    /**
+     * Tests when the absolute value of the difference between the start time is
+     * and the end time is less than a configured minimum, that the request is
+     * rejected with the correct error message.
+     * 
+     * "If the requested expiration time minus the starttime (as determined above)
+     * is less than a site-determined minimum lifetime, an error message with code
+     * KDC_ERR_NEVER_VALID is returned."
+     *
+     * @throws Exception
+     */
+    public void testStartTimeMinimumNeverValid() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        modifier.setKdcOptions( new KdcOptions() );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedStartTime = new KerberosTime( now );
+        modifier.setFrom( requestedStartTime );
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 4 * KerberosTime.MINUTE );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "Requested start time is later than end time", 11, error.getErrorCode() );
+    }
+
+
+    /**
+     * Tests when a valid starttime is specified but the POSTDATE flag is not set,
+     * that the request is rejected with the correct error message.
+     * 
+     * "If it indicates a time in the future beyond the acceptable clock skew, but
+     * the POSTDATED option has not been specified, then the error
+     * KDC_ERR_CANNOT_POSTDATE is returned."
+     * 
+     * @throws Exception 
+     */
+    public void testStartTimeNoPostdated() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        modifier.setKdcOptions( new KdcOptions() );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedStartTime = new KerberosTime( now + 10 * KerberosTime.MINUTE );
+        modifier.setFrom( requestedStartTime );
+
+        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "Ticket not eligible for postdating", 10, error.getErrorCode() );
+    }
+
+
+    /**
+     * Tests that a user-specified start time is honored when that start time does not
+     * violate policy.
+     * 
+     * "Otherwise the requested starttime is checked against the policy of the local
+     * realm (the administrator might decide to prohibit certain types or ranges of
+     * postdated tickets), and if the ticket's starttime is acceptable, it is set as
+     * requested, and the INVALID flag is set in the new ticket.  The postdated
+     * ticket MUST be validated before use by presenting it to the KDC after the
+     * starttime has been reached."
+     * 
+     * "If the new ticket is postdated (the starttime is in the future), its
+     * INVALID flag will also be set."
+     * 
+     * "The flags field of the new ticket will have the following options set
+     * if they have been requested and if the policy of the local realm
+     * allows:  FORWARDABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, RENEWABLE."
+     * 
+     * @throws Exception
+     */
+    public void testSpecificStartTime() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.POSTDATED );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedStartTime = new KerberosTime( now + KerberosTime.DAY );
+        modifier.setFrom( requestedStartTime );
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 2 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        assertTrue( "Requested start time", requestedStartTime.equals( reply.getStartTime() ) );
+        assertTrue( "Requested end time", requestedEndTime.equals( reply.getEndTime() ) );
+        assertTrue( "POSTDATED flag", reply.getFlags().get( TicketFlags.POSTDATED ) );
+        assertTrue( "INVALID flag", reply.getFlags().get( TicketFlags.INVALID ) );
+
+        assertTrue( "Requested start time", requestedStartTime.equals( reply.getTicket().getStartTime() ) );
+        assertTrue( "Requested end time", requestedEndTime.equals( reply.getEndTime() ) );
+        assertTrue( "POSTDATED flag", reply.getTicket().getFlags().get( TicketFlags.POSTDATED ) );
+        assertTrue( "INVALID flag", reply.getTicket().getFlags().get( TicketFlags.INVALID ) );
+
+        assertTrue( "PRE_AUTHENT flag", reply.getTicket().getFlags().get( TicketFlags.PRE_AUTHENT ) );
+    }
+
 
-        CipherTextHandler lockBox = new CipherTextHandler();
+    /**
+     * Tests that a user-specified end time is honored when that end time does not
+     * violate policy.
+     * 
+     * "The expiration time of the ticket will be set to the earlier of the
+     * requested endtime and a time determined by local policy, possibly by
+     * using realm- or principal-specific factors."
+     *
+     * @throws Exception
+     */
+    public void testSpecificEndTime() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        modifier.setKdcOptions( new KdcOptions() );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY / 2 );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        assertTrue( "Requested end time", requestedEndTime.equals( reply.getEndTime() ) );
+
+        assertTrue( "PRE_AUTHENT flag", reply.getTicket().getFlags().get( TicketFlags.PRE_AUTHENT ) );
+    }
+
+
+    /**
+     * Tests when an end time is requested that exceeds the maximum end time as 
+     * configured in policy that the maximum allowable end time is returned instead
+     * of the requested end time.
+     * 
+     * "The expiration time of the ticket will be set to the earlier of the
+     * requested endtime and a time determined by local policy, possibly by
+     * using realm- or principal-specific factors."
+     *
+     * @throws Exception
+     */
+    public void testEndTimeExceedsMaximumAllowable() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        modifier.setKdcOptions( new KdcOptions() );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.WEEK );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        KerberosTime expectedEndTime = new KerberosTime( now + KerberosTime.DAY );
+        boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
+        assertTrue( "Expected end time", isClose );
+    }
+
+
+    /**
+     * Tests that a requested zulu end time of the epoch ("19700101000000Z") results
+     * in the maximum endtime permitted according to KDC policy.  The zulu epoch is
+     * the same as '0' (zero) milliseconds in Java.
+     * 
+     * @throws Exception
+     */
+    public void testEpochEndTime() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        modifier.setKdcOptions( new KdcOptions() );
+
+        String epoch = "19700101000000Z";
+        KerberosTime requestedEndTime = KerberosTime.getTime( epoch );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        long now = System.currentTimeMillis();
+        KerberosTime expectedEndTime = new KerberosTime( now + KerberosTime.DAY );
+        boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
+        assertTrue( "Expected end time", isClose );
+    }
+
+
+    /**
+     * Tests whether a renewable ticket will be accepted in lieu of a non-renewable
+     * ticket if the requested ticket expiration date cannot be satisfied by a
+     * non-renewable ticket (due to configuration constraints).
+     * 
+     * "If the requested expiration time for the ticket exceeds what was determined
+     * as above, and if the 'RENEWABLE-OK' option was requested, then the 'RENEWABLE'
+     * flag is set in the new ticket, and the renew-till value is set as if the
+     * 'RENEWABLE' option were requested (the field and option names are described
+     * fully in Section 5.4.1).
+     * 
+     * @throws Exception 
+     */
+    public void testRenewableOk() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.RENEWABLE_OK );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.WEEK );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        KerberosTime expectedEndTime = new KerberosTime( now + KerberosTime.DAY );
+        boolean isClose = Math.abs( reply.getEndTime().getTime() - expectedEndTime.getTime() ) < 5000;
+        assertTrue( "Expected end time", isClose );
+
+        assertTrue( "RENEWABLE flag", reply.getFlags().get( TicketFlags.RENEWABLE ) );
+        assertFalse( "INVALID flag", reply.getFlags().get( TicketFlags.INVALID ) );
+
+        KerberosTime expectedRenewTillTime = new KerberosTime( now + KerberosTime.WEEK );
+        isClose = Math.abs( reply.getRenewTill().getTime() - expectedRenewTillTime.getTime() ) < 5000;
+        assertTrue( "Expected renew-till time", isClose );
+    }
+
+
+    /**
+     * Tests forwardable tickets.
+     * 
+     * "The flags field of the new ticket will have the following options set
+     * if they have been requested and if the policy of the local realm
+     * allows:  FORWARDABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, RENEWABLE."
+     * 
+     * @throws Exception 
+     */
+    public void testForwardableTicket() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.FORWARDABLE );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        assertTrue( "FORWARDABLE flag", reply.getFlags().get( TicketFlags.FORWARDABLE ) );
+        assertFalse( "INVALID flag", reply.getFlags().get( TicketFlags.INVALID ) );
+
+        assertTrue( "FORWARDABLE flag", reply.getTicket().getFlags().get( TicketFlags.FORWARDABLE ) );
+        assertFalse( "INVALID flag", reply.getTicket().getFlags().get( TicketFlags.INVALID ) );
+    }
+
+
+    /**
+     * Tests allow postdating of derivative tickets.
+     * 
+     * "The flags field of the new ticket will have the following options set
+     * if they have been requested and if the policy of the local realm
+     * allows:  FORWARDABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, RENEWABLE."
+     * 
+     * @throws Exception 
+     */
+    public void testAllowPostdate() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.ALLOW_POSTDATE );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        assertTrue( "MAY_POSTDATE flag", reply.getFlags().get( TicketFlags.MAY_POSTDATE ) );
+        assertFalse( "INVALID flag", reply.getFlags().get( TicketFlags.INVALID ) );
+
+        assertTrue( "MAY_POSTDATE flag", reply.getTicket().getFlags().get( TicketFlags.MAY_POSTDATE ) );
+        assertFalse( "INVALID flag", reply.getTicket().getFlags().get( TicketFlags.INVALID ) );
+    }
+
+
+    /**
+     * Tests proxiable tickets.
+     * 
+     * "The flags field of the new ticket will have the following options set
+     * if they have been requested and if the policy of the local realm
+     * allows:  FORWARDABLE, MAY-POSTDATE, POSTDATED, PROXIABLE, RENEWABLE."
+     * 
+     * @throws Exception 
+     */
+    public void testProxiableTicket() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.PROXIABLE );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        assertTrue( "PROXIABLE flag", reply.getFlags().get( TicketFlags.PROXIABLE ) );
+        assertFalse( "INVALID flag", reply.getFlags().get( TicketFlags.INVALID ) );
+
+        assertTrue( "PROXIABLE flag", reply.getTicket().getFlags().get( TicketFlags.PROXIABLE ) );
+        assertFalse( "INVALID flag", reply.getTicket().getFlags().get( TicketFlags.INVALID ) );
+    }
+
+
+    /**
+     * Tests that a user-specified renew-till time is honored when that renew-till
+     * time does not violate policy.
+     * 
+     * "If the RENEWABLE option has been requested or if the RENEWABLE-OK
+     * option has been set and a renewable ticket is to be issued, then the
+     * renew-till field MAY be set to the earliest of ... its requested value [or]
+     * the starttime of the ticket plus the maximum renewable lifetime
+     * set by the policy of the local realm."
+     * 
+     * @throws Exception 
+     */
+    public void testRenewableTicket() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.RENEWABLE );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosTime requestedRenewTillTime = new KerberosTime( now + KerberosTime.WEEK / 2 );
+        modifier.setTill( requestedRenewTillTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        assertTrue( "RENEWABLE flag", reply.getFlags().get( TicketFlags.RENEWABLE ) );
+        assertFalse( "INVALID flag", reply.getFlags().get( TicketFlags.INVALID ) );
+
+        assertTrue( "RENEWABLE flag", reply.getTicket().getFlags().get( TicketFlags.RENEWABLE ) );
+        assertFalse( "INVALID flag", reply.getTicket().getFlags().get( TicketFlags.INVALID ) );
+
+        assertTrue( "Requested renew-till time", requestedRenewTillTime.equals( reply.getRenewTill() ) );
+    }
+
+
+    /**
+     * Tests when a renew-till time is requested that exceeds the maximum renew-till
+     * time as configured in policy that the maximum allowable renew-till time is
+     * returned instead of the requested renew-till time.
+     * 
+     * "If the RENEWABLE option has been requested or if the RENEWABLE-OK
+     * option has been set and a renewable ticket is to be issued, then the
+     * renew-till field MAY be set to the earliest of ... its requested value [or]
+     * the starttime of the ticket plus the maximum renewable lifetime
+     * set by the policy of the local realm."
+     * 
+     * @throws Exception 
+     */
+    public void testRenewableTicketExceedsMaximumAllowable() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.RENEWABLE );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosTime requestedRenewTillTime = new KerberosTime( now + 2 * KerberosTime.WEEK );
+        modifier.setTill( requestedRenewTillTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        AuthenticationReply reply = ( AuthenticationReply ) session.getMessage();
+
+        assertTrue( "RENEWABLE flag", reply.getFlags().get( TicketFlags.RENEWABLE ) );
+        assertFalse( "INVALID flag", reply.getFlags().get( TicketFlags.INVALID ) );
+
+        assertTrue( "RENEWABLE flag", reply.getTicket().getFlags().get( TicketFlags.RENEWABLE ) );
+        assertFalse( "INVALID flag", reply.getTicket().getFlags().get( TicketFlags.INVALID ) );
+
+        KerberosTime expectedRenewTillTime = new KerberosTime( now + KerberosTime.WEEK );
+        boolean isClose = Math.abs( reply.getRenewTill().getTime() - expectedRenewTillTime.getTime() ) < 5000;
+        assertTrue( "Expected renew-till time", isClose );
+    }
+
+
+    /**
+     * Tests that the option RENEW, which is bad for an AS_REQ, is rejected
+     * with the correct error message.
+     *
+     * @throws Exception
+     */
+    public void testBadOptionRenew() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.RENEW );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "KDC cannot accommodate requested option", 13, error.getErrorCode() );
+    }
+
+
+    /**
+     * Tests that the option VALIDATE, which is bad for an AS_REQ, is rejected
+     * with the correct error message.
+     *
+     * @throws Exception
+     */
+    public void testBadOptionValidate() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.VALIDATE );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "KDC cannot accommodate requested option", 13, error.getErrorCode() );
+    }
+
+
+    /**
+     * Tests that the option PROXY, which is bad for an AS_REQ, is rejected
+     * with the correct error message.
+     *
+     * @throws Exception
+     */
+    public void testBadOptionProxy() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.PROXY );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "KDC cannot accommodate requested option", 13, error.getErrorCode() );
+    }
+
+
+    /**
+     * Tests that the option FORWARDED, which is bad for an AS_REQ, is rejected
+     * with the correct error message.
+     *
+     * @throws Exception
+     */
+    public void testBadOptionForwarded() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.FORWARDED );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "KDC cannot accommodate requested option", 13, error.getErrorCode() );
+    }
+
+
+    /**
+     * Tests that the option ENC_TKT_IN_SKEY, which is bad for an AS_REQ, is rejected
+     * with the correct error message.
+     *
+     * @throws Exception
+     */
+    public void testBadOptionEncTktInSkey() throws Exception
+    {
+        RequestBodyModifier modifier = new RequestBodyModifier();
+        modifier.setClientName( getPrincipalName( "hnelson" ) );
+        modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
+        modifier.setRealm( "EXAMPLE.COM" );
+        modifier.setEType( config.getEncryptionTypes() );
+
+        KdcOptions kdcOptions = new KdcOptions();
+        kdcOptions.set( KdcOptions.ENC_TKT_IN_SKEY );
+        modifier.setKdcOptions( kdcOptions );
+
+        long now = System.currentTimeMillis();
+
+        KerberosTime requestedEndTime = new KerberosTime( now + 1 * KerberosTime.DAY );
+        modifier.setTill( requestedEndTime );
+
+        KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
+        String passPhrase = "secret";
+        PreAuthenticationData[] paData = getPreAuthenticationData( clientPrincipal, passPhrase );
+
+        KdcRequest message = new KdcRequest( 5, MessageType.KRB_AS_REQ, paData, modifier.getRequestBody() );
+
+        handler.messageReceived( session, message );
+
+        ErrorMessage error = ( ErrorMessage ) session.getMessage();
+        assertEquals( "KDC cannot accommodate requested option", 13, error.getErrorCode() );
+    }
+
+
+    private PreAuthenticationData[] getPreAuthenticationData( KerberosPrincipal clientPrincipal, String passPhrase )
+        throws Exception
+    {
+        PreAuthenticationData[] paData = new PreAuthenticationData[1];
 
         KerberosTime timeStamp = new KerberosTime();
         EncryptedTimeStamp encryptedTimeStamp = new EncryptedTimeStamp( timeStamp, 0 );