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 javax.security.auth.kerberos.KerberosPrincipal;
24  
25  import org.apache.directory.server.kerberos.kdc.KdcServer;
26  import org.apache.directory.server.kerberos.shared.KerberosMessageType;
27  import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
28  import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
29  import org.apache.directory.server.kerberos.shared.io.encoder.EncryptedDataEncoder;
30  import org.apache.directory.server.kerberos.shared.messages.ErrorMessage;
31  import org.apache.directory.server.kerberos.shared.messages.KdcRequest;
32  import org.apache.directory.server.kerberos.shared.messages.value.EncryptedData;
33  import org.apache.directory.server.kerberos.shared.messages.value.EncryptedTimeStamp;
34  import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
35  import org.apache.directory.server.kerberos.shared.messages.value.KdcOptions;
36  import org.apache.directory.server.kerberos.shared.messages.value.KerberosTime;
37  import org.apache.directory.server.kerberos.shared.messages.value.PaData;
38  import org.apache.directory.server.kerberos.shared.messages.value.RequestBodyModifier;
39  import org.apache.directory.server.kerberos.shared.messages.value.types.PaDataType;
40  import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
41  
42  
43  /**
44   * Tests pre-authentication processing in the Authentication Service (AS) via the
45   * {@link KerberosProtocolHandler}.
46   *
47   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
48   * @version $Rev$, $Date$
49   */
50  public class PreAuthenticationTest extends AbstractAuthenticationServiceTest
51  {
52      private KdcServer config;
53      private PrincipalStore store;
54      private KerberosProtocolHandler handler;
55      private DummySession session;
56  
57  
58      /**
59       * Creates a new instance of {@link PreAuthenticationTest}.
60       */
61      public PreAuthenticationTest()
62      {
63          config = new KdcServer();
64          store = new MapPrincipalStoreImpl();
65          handler = new KerberosProtocolHandler( config, store );
66          session = new DummySession();
67          lockBox = new CipherTextHandler();
68      }
69  
70  
71      /**
72       * Tests when the KDC configuration requires pre-authentication by encrypted
73       * timestamp that an AS_REQ without pre-authentication is rejected with the
74       * correct error message.
75       * 
76       * "If pre-authentication is required, but was not present in the request, an
77       * error message with the code KDC_ERR_PREAUTH_REQUIRED is returned, and a
78       * METHOD-DATA object will be stored in the e-data field of the KRB-ERROR
79       * message to specify which pre-authentication mechanisms are acceptable."
80       */
81      public void testPreAuthenticationRequired()
82      {
83          RequestBodyModifier modifier = new RequestBodyModifier();
84          modifier.setClientName( getPrincipalName( "hnelson" ) );
85          modifier.setServerName( getPrincipalName( "hnelson" ) );
86          modifier.setRealm( "EXAMPLE.COM" );
87          modifier.setEType( config.getEncryptionTypes() );
88  
89          KdcRequest message = new KdcRequest( 5, KerberosMessageType.AS_REQ, null, modifier.getRequestBody() );
90  
91          handler.messageReceived( session, message );
92  
93          ErrorMessage error = ( ErrorMessage ) session.getMessage();
94          assertEquals( "Additional pre-authentication required", 25, error.getErrorCode() );
95      }
96  
97  
98      /**
99       * Tests when the KDC configuration requires pre-authentication by encrypted
100      * timestamp that an AS_REQ with pre-authentication using an incorrect key is
101      * rejected with the correct error message.
102      * 
103      * "If required to do so, the server pre-authenticates the request, and
104      * if the pre-authentication check fails, an error message with the code
105      * KDC_ERR_PREAUTH_FAILED is returned."
106      * 
107      * @throws Exception 
108      */
109     public void testPreAuthenticationIntegrityFailed() throws Exception
110     {
111         RequestBodyModifier modifier = new RequestBodyModifier();
112         modifier.setClientName( getPrincipalName( "hnelson" ) );
113         modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
114         modifier.setRealm( "EXAMPLE.COM" );
115         modifier.setEType( config.getEncryptionTypes() );
116 
117         modifier.setKdcOptions( new KdcOptions() );
118 
119         long now = System.currentTimeMillis();
120 
121         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
122         modifier.setTill( requestedEndTime );
123 
124         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
125 
126         String passPhrase = "badpassword";
127         PaData[] paData = getPreAuthEncryptedTimeStamp( clientPrincipal, passPhrase );
128 
129         KdcRequest message = new KdcRequest( 5, KerberosMessageType.AS_REQ, paData, modifier.getRequestBody() );
130 
131         handler.messageReceived( session, message );
132 
133         ErrorMessage error = ( ErrorMessage ) session.getMessage();
134         assertEquals( "Integrity check on decrypted field failed", 31, error.getErrorCode() );
135     }
136 
137 
138     /**
139      * "If required to do so, the server pre-authenticates the request, and
140      * if the pre-authentication check fails, an error message with the code
141      * KDC_ERR_PREAUTH_FAILED is returned."
142      * 
143      * @throws Exception 
144      */
145     public void testPreAuthenticationFailed() throws Exception
146     {
147         RequestBodyModifier modifier = new RequestBodyModifier();
148         modifier.setClientName( getPrincipalName( "hnelson" ) );
149         modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
150         modifier.setRealm( "EXAMPLE.COM" );
151         modifier.setEType( config.getEncryptionTypes() );
152 
153         modifier.setKdcOptions( new KdcOptions() );
154 
155         long now = System.currentTimeMillis();
156 
157         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
158         modifier.setTill( requestedEndTime );
159 
160         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
161 
162         KerberosTime timeStamp = new KerberosTime( 0 );
163         String passPhrase = "secret";
164         PaData[] paData = getPreAuthEncryptedTimeStamp( clientPrincipal, passPhrase, timeStamp );
165 
166         KdcRequest message = new KdcRequest( 5, KerberosMessageType.AS_REQ, paData, modifier.getRequestBody() );
167 
168         handler.messageReceived( session, message );
169 
170         ErrorMessage error = ( ErrorMessage ) session.getMessage();
171 
172         assertEquals( "Pre-authentication information was invalid", 24, error.getErrorCode() );
173     }
174 
175 
176     /**
177      * Tests when pre-authentication is included that is not supported by the KDC, that
178      * the correct error message is returned.
179      * 
180      * @throws Exception 
181      */
182     public void testPreAuthenticationNoSupport() throws Exception
183     {
184         RequestBodyModifier modifier = new RequestBodyModifier();
185         modifier.setClientName( getPrincipalName( "hnelson" ) );
186         modifier.setServerName( getPrincipalName( "krbtgt/EXAMPLE.COM@EXAMPLE.COM" ) );
187         modifier.setRealm( "EXAMPLE.COM" );
188         modifier.setEType( config.getEncryptionTypes() );
189 
190         modifier.setKdcOptions( new KdcOptions() );
191 
192         long now = System.currentTimeMillis();
193 
194         KerberosTime requestedEndTime = new KerberosTime( now + KerberosTime.DAY );
195         modifier.setTill( requestedEndTime );
196 
197         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
198         String passPhrase = "secret";
199         PaData[] paData = getPreAuthPublicKey( clientPrincipal, passPhrase );
200 
201         KdcRequest message = new KdcRequest( 5, KerberosMessageType.AS_REQ, paData, modifier.getRequestBody() );
202 
203         handler.messageReceived( session, message );
204 
205         ErrorMessage error = ( ErrorMessage ) session.getMessage();
206 
207         assertEquals( "KDC has no support for padata type", 16, error.getErrorCode() );
208     }
209 
210 
211     /**
212      * Returns pre-authentication payload of type PA_PK_AS_REQ.  Note that the actual
213      * payload is an encrypted timestamp, but with only the type set to PA_PK_AS_REQ.
214      * This is being used to test the error condition when an unsupported pre-authentication
215      * type is received by the KDC.  The time for the timestamp is set to the current time.
216      *
217      * @param clientPrincipal
218      * @param passPhrase
219      * @return The array of pre-authentication data.
220      * @throws Exception
221      */
222     private PaData[] getPreAuthPublicKey( KerberosPrincipal clientPrincipal, String passPhrase )
223         throws Exception
224     {
225         KerberosTime timeStamp = new KerberosTime();
226 
227         return getPreAuthPublicKey( clientPrincipal, passPhrase, timeStamp );
228     }
229 
230 
231     /**
232      * Returns pre-authentication payload of type PA_PK_AS_REQ.  Note that the actual
233      * payload is an encrypted timestamp, but with the type set to PA_PK_AS_REQ.  This
234      * is being used to test the error condition caused when an unsupported
235      * pre-authentication type is received by the KDC.
236      *
237      * @param clientPrincipal
238      * @param passPhrase
239      * @param timeStamp
240      * @return The array of pre-authentication data.
241      * @throws Exception
242      */
243     private PaData[] getPreAuthPublicKey( KerberosPrincipal clientPrincipal, String passPhrase,
244         KerberosTime timeStamp ) throws Exception
245     {
246         PaData[] paData = new PaData[1];
247 
248         EncryptedTimeStamp encryptedTimeStamp = new EncryptedTimeStamp( timeStamp, 0 );
249 
250         EncryptionKey clientKey = getEncryptionKey( clientPrincipal, passPhrase );
251 
252         EncryptedData encryptedData = lockBox.seal( clientKey, encryptedTimeStamp, KeyUsage.NUMBER1 );
253 
254         byte[] encodedEncryptedData = EncryptedDataEncoder.encode( encryptedData );
255 
256         PaData preAuth = new PaData();
257         preAuth.setPaDataType( PaDataType.PA_PK_AS_REQ );
258         preAuth.setPaDataValue( encodedEncryptedData );
259 
260         paData[0] = preAuth;
261 
262         return paData;
263     }
264 }