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.changepw.protocol;
21  
22  
23  import java.io.IOException;
24  import java.io.UnsupportedEncodingException;
25  import java.net.InetAddress;
26  import java.net.InetSocketAddress;
27  import java.net.SocketAddress;
28  import java.net.UnknownHostException;
29  
30  import javax.security.auth.kerberos.KerberosPrincipal;
31  
32  import junit.framework.TestCase;
33  import org.apache.directory.server.changepw.ChangePasswordServer;
34  import org.apache.directory.server.changepw.io.ChangePasswordDataEncoder;
35  import org.apache.directory.server.changepw.messages.ChangePasswordError;
36  import org.apache.directory.server.changepw.messages.ChangePasswordRequest;
37  import org.apache.directory.server.changepw.value.ChangePasswordData;
38  import org.apache.directory.server.changepw.value.ChangePasswordDataModifier;
39  import org.apache.directory.server.kerberos.shared.KerberosMessageType;
40  import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
41  import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
42  import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
43  import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
44  import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
45  import org.apache.directory.server.kerberos.shared.messages.ApplicationRequest;
46  import org.apache.directory.server.kerberos.shared.messages.ErrorMessage;
47  import org.apache.directory.server.kerberos.shared.messages.application.PrivateMessage;
48  import org.apache.directory.server.kerberos.shared.messages.components.AuthenticatorModifier;
49  import org.apache.directory.server.kerberos.shared.messages.components.EncKrbPrivPart;
50  import org.apache.directory.server.kerberos.shared.messages.components.EncKrbPrivPartModifier;
51  import org.apache.directory.server.kerberos.shared.messages.components.Ticket;
52  import org.apache.directory.server.kerberos.shared.messages.value.ApOptions;
53  import org.apache.directory.server.kerberos.shared.messages.value.EncryptedData;
54  import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
55  import org.apache.directory.server.kerberos.shared.messages.value.HostAddress;
56  import org.apache.directory.server.kerberos.shared.messages.value.KerberosTime;
57  import org.apache.directory.server.kerberos.shared.messages.value.PrincipalName;
58  import org.apache.directory.server.kerberos.shared.messages.value.types.PrincipalNameType;
59  import org.apache.directory.server.kerberos.shared.store.PrincipalStore;
60  import org.apache.directory.server.kerberos.shared.store.TicketFactory;
61  import org.apache.mina.common.IoFilterChain;
62  import org.apache.mina.common.IoHandler;
63  import org.apache.mina.common.IoService;
64  import org.apache.mina.common.IoServiceConfig;
65  import org.apache.mina.common.IoSessionConfig;
66  import org.apache.mina.common.TransportType;
67  import org.apache.mina.common.WriteFuture;
68  import org.apache.mina.common.support.BaseIoSession;
69  
70  
71  /**
72   * Tests the ChangePasswordProtocolHandler.
73   *
74   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
75   * @version $Rev$, $Date$
76   */
77  public class ChangepwProtocolHandlerTest extends TestCase
78  {
79      /**
80       * The Change Password SUCCESS result code.
81       */
82      // Never used...
83      //private static final byte[] SUCCESS = new byte[]
84      //    { ( byte ) 0x00, ( byte ) 0x00 };
85  
86      private ChangePasswordServer config;
87      private PrincipalStore store;
88      private ChangePasswordProtocolHandler handler;
89      private DummySession session;
90  
91      private CipherTextHandler cipherTextHandler = new CipherTextHandler();
92  
93  
94      /**
95       * Creates a new instance of ChangepwProtocolHandlerTest.
96       */
97      public ChangepwProtocolHandlerTest()
98      {
99          config = new ChangePasswordServer();
100         store = new MapPrincipalStoreImpl();
101         handler = new ChangePasswordProtocolHandler( config, store );
102         session = new DummySession();
103     }
104 
105 
106     /**
107      * Tests the protocol version number, which must be '1'.
108      */
109     public void testProtocolVersionNumber()
110     {
111         ChangePasswordRequest message = new ChangePasswordRequest( ( short ) 2, null, null );
112 
113         handler.messageReceived( session, message );
114 
115         ChangePasswordError reply = ( ChangePasswordError ) session.getMessage();
116         ErrorMessage error = reply.getErrorMessage();
117         assertEquals( "Protocol version unsupported", 6, error.getErrorCode() );
118     }
119 
120 
121     /**
122      * Tests when a service ticket is missing that the request is rejected with
123      * the correct error message.
124      */
125     public void testMissingTicket()
126     {
127         ChangePasswordRequest message = new ChangePasswordRequest( ( short ) 1, null, null );
128 
129         handler.messageReceived( session, message );
130 
131         ChangePasswordError reply = ( ChangePasswordError ) session.getMessage();
132         ErrorMessage error = reply.getErrorMessage();
133         assertEquals( "Request failed due to an error in authentication processing", 3, error.getErrorCode() );
134     }
135 
136 
137     /**
138      * Tests when the INITIAL flag is missing that the request is rejected with
139      * the correct error message.
140      *
141      * @throws Exception
142      */
143     public void testInitialFlagRequired() throws Exception
144     {
145         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
146 
147         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "kadmin/changepw@EXAMPLE.COM" );
148         String serverPassword = "secret";
149 
150         TicketFactory ticketFactory = new TicketFactory();
151         EncryptionKey serverKey = ticketFactory.getServerKey( serverPrincipal, serverPassword );
152         Ticket serviceTicket = ticketFactory.getTicket( clientPrincipal, serverPrincipal, serverKey );
153 
154         EncryptionKey subSessionKey = RandomKeyFactory.getRandomKey( EncryptionType.DES_CBC_MD5 );
155 
156         ApOptions apOptions = new ApOptions();
157 
158         AuthenticatorModifier modifier = new AuthenticatorModifier();
159         modifier.setVersionNumber( 5 );
160         modifier.setClientRealm( "EXAMPLE.COM" );
161         modifier.setClientName( getPrincipalName( "hnelson" ) );
162         modifier.setClientTime( new KerberosTime() );
163         modifier.setClientMicroSecond( 0 );
164 
165         modifier.setSubSessionKey( subSessionKey );
166 
167         EncryptedData encryptedAuthenticator = cipherTextHandler.seal( serviceTicket.getEncTicketPart().getSessionKey(), modifier
168                 .getAuthenticator(), KeyUsage.NUMBER11 );
169 
170         ApplicationRequest apReq = new ApplicationRequest( apOptions, serviceTicket, encryptedAuthenticator );
171 
172         String newPassword = "secretsecret";
173 
174         PrivateMessage priv = getChangePasswordPrivateMessage( newPassword, subSessionKey );
175 
176         ChangePasswordRequest message = new ChangePasswordRequest( ( short ) 1, apReq, priv );
177 
178         handler.messageReceived( session, message );
179 
180         ChangePasswordError reply = ( ChangePasswordError ) session.getMessage();
181         ErrorMessage error = reply.getErrorMessage();
182         assertEquals( "Initial flag required", 7, error.getErrorCode() );
183 
184         //ChangePasswordReply reply = ( ChangePasswordReply ) session.getMessage();
185 
186         //processChangePasswordReply( reply, serviceTicket.getSessionKey(), subSessionKey );
187     }
188 
189     /**
190      * TODO : Check if this method is important or not. It was called in
191      * the testInitialFlagRequired() method above, but this call has been commented
192      * private void processChangePasswordReply( ChangePasswordReply reply, EncryptionKey sessionKey,
193      * EncryptionKey subSessionKey ) throws Exception
194      * {
195      * PrivateMessage privateMessage = reply.getPrivateMessage();
196      * <p/>
197      * EncryptedData encPrivPart = privateMessage.getEncryptedPart();
198      * <p/>
199      * EncKrbPrivPart privPart;
200      * <p/>
201      * try
202      * {
203      * privPart = ( EncKrbPrivPart ) cipherTextHandler.unseal( EncKrbPrivPart.class, subSessionKey, encPrivPart,
204      * KeyUsage.NUMBER13 );
205      * }
206      * catch ( KerberosException ke )
207      * {
208      * return;
209      * }
210      * <p/>
211      * // Verify result code.
212      * byte[] resultCode = privPart.getUserData();
213      * <p/>
214      * assertTrue( "Password change returned SUCCESS (0x00 0x00).", Arrays.equals( SUCCESS, resultCode ) );
215      * }
216      */
217 
218 
219     public void testSetPassword() throws Exception
220     {
221         KerberosPrincipal clientPrincipal = new KerberosPrincipal( "hnelson@EXAMPLE.COM" );
222 
223         KerberosPrincipal serverPrincipal = new KerberosPrincipal( "kadmin/changepw@EXAMPLE.COM" );
224         String serverPassword = "secret";
225 
226         TicketFactory ticketFactory = new TicketFactory();
227         EncryptionKey serverKey = ticketFactory.getServerKey( serverPrincipal, serverPassword );
228         Ticket serviceTicket = ticketFactory.getTicket( clientPrincipal, serverPrincipal, serverKey );
229 
230         EncryptionKey subSessionKey = RandomKeyFactory.getRandomKey( EncryptionType.DES_CBC_MD5 );
231 
232         ApOptions apOptions = new ApOptions();
233 
234         AuthenticatorModifier modifier = new AuthenticatorModifier();
235         modifier.setVersionNumber( 5 );
236         modifier.setClientRealm( "EXAMPLE.COM" );
237         modifier.setClientName( getPrincipalName( "hnelson" ) );
238         modifier.setClientTime( new KerberosTime() );
239         modifier.setClientMicroSecond( 0 );
240 
241         EncryptedData encryptedAuthenticator = cipherTextHandler.seal( serverKey, modifier.getAuthenticator(),
242                 KeyUsage.NUMBER11 );
243 
244         ApplicationRequest apReq = new ApplicationRequest( apOptions, serviceTicket, encryptedAuthenticator );
245 
246         String newPassword = "secretsecret";
247 
248         PrivateMessage priv = getSetPasswordPrivateMessage( newPassword, subSessionKey, getPrincipalName( "hnelson" ) );
249 
250         ChangePasswordRequest message = new ChangePasswordRequest( ( short ) 0xFF80, apReq, priv );
251 
252         handler.messageReceived( session, message );
253 
254         ChangePasswordError reply = ( ChangePasswordError ) session.getMessage();
255         ErrorMessage error = reply.getErrorMessage();
256         assertEquals( "Protocol version unsupported", 6, error.getErrorCode() );
257     }
258 
259 
260     /*
261      * Legacy kpasswd (Change Password) version.  User data is the password bytes.
262      */
263     private PrivateMessage getChangePasswordPrivateMessage( String newPassword, EncryptionKey subSessionKey )
264             throws UnsupportedEncodingException, KerberosException, UnknownHostException
265     {
266         // Make private message part.
267         EncKrbPrivPartModifier privPartModifier = new EncKrbPrivPartModifier();
268         privPartModifier.setUserData( newPassword.getBytes( "UTF-8" ) );
269         privPartModifier.setSenderAddress( new HostAddress( InetAddress.getLocalHost() ) );
270         EncKrbPrivPart encReqPrivPart = privPartModifier.getEncKrbPrivPart();
271 
272         // Seal private message part.
273         EncryptedData encryptedPrivPart = cipherTextHandler.seal( subSessionKey, encReqPrivPart, KeyUsage.NUMBER13 );
274 
275         // Make private message with private message part.
276         PrivateMessage privateMessage = new PrivateMessage();
277         privateMessage.setProtocolVersionNumber( 5 );
278         privateMessage.setMessageType( KerberosMessageType.ENC_PRIV_PART );
279         privateMessage.setEncryptedPart( encryptedPrivPart );
280 
281         return privateMessage;
282     }
283 
284 
285     /*
286      * Set/Change Password version.  User data is an encoding of the new password and the target principal.
287      */
288     private PrivateMessage getSetPasswordPrivateMessage( String newPassword, EncryptionKey subSessionKey,
289             PrincipalName targetPrincipalName ) throws UnsupportedEncodingException, KerberosException,
290             UnknownHostException, IOException
291     {
292         // Make private message part.
293         EncKrbPrivPartModifier privPartModifier = new EncKrbPrivPartModifier();
294 
295         ChangePasswordDataModifier dataModifier = new ChangePasswordDataModifier();
296         dataModifier.setNewPassword( newPassword.getBytes() );
297         dataModifier.setTargetName( targetPrincipalName );
298         dataModifier.setTargetRealm( "EXAMPLE.COM" );
299         ChangePasswordData data = dataModifier.getChangePasswdData();
300 
301         ChangePasswordDataEncoder encoder = new ChangePasswordDataEncoder();
302         byte[] dataBytes = encoder.encode( data );
303 
304         privPartModifier.setUserData( dataBytes );
305 
306         privPartModifier.setSenderAddress( new HostAddress( InetAddress.getLocalHost() ) );
307         EncKrbPrivPart encReqPrivPart = privPartModifier.getEncKrbPrivPart();
308 
309         // Seal private message part.
310         EncryptedData encryptedPrivPart = cipherTextHandler.seal( subSessionKey, encReqPrivPart, KeyUsage.NUMBER13 );
311 
312         // Make private message with private message part.
313         PrivateMessage privateMessage = new PrivateMessage();
314         privateMessage.setProtocolVersionNumber( 5 );
315         privateMessage.setMessageType( KerberosMessageType.ENC_PRIV_PART );
316         privateMessage.setEncryptedPart( encryptedPrivPart );
317 
318         return privateMessage;
319     }
320 
321 
322     private PrincipalName getPrincipalName( String name )
323     {
324         PrincipalName principalName = new PrincipalName();
325         principalName.addName( name );
326         principalName.setNameType( PrincipalNameType.KRB_NT_PRINCIPAL );
327 
328         return principalName;
329     }
330 
331     private static class DummySession extends BaseIoSession
332     {
333         Object message;
334 
335 
336         @Override
337         public WriteFuture write( Object message )
338         {
339             this.message = message;
340 
341             return super.write( message );
342         }
343 
344 
345         private Object getMessage()
346         {
347             return message;
348         }
349 
350 
351         protected void updateTrafficMask()
352         {
353             // Do nothing.
354         }
355 
356 
357         public IoService getService()
358         {
359             return null;
360         }
361 
362 
363         public IoHandler getHandler()
364         {
365             return null;
366         }
367 
368 
369         public IoFilterChain getFilterChain()
370         {
371             return null;
372         }
373 
374 
375         public TransportType getTransportType()
376         {
377             return null;
378         }
379 
380 
381         public SocketAddress getRemoteAddress()
382         {
383             return new InetSocketAddress( 10464 );
384         }
385 
386 
387         public SocketAddress getLocalAddress()
388         {
389             return null;
390         }
391 
392 
393         public IoSessionConfig getConfig()
394         {
395             return null;
396         }
397 
398 
399         public int getScheduledWriteRequests()
400         {
401             return 0;
402         }
403 
404 
405         public SocketAddress getServiceAddress()
406         {
407             return null;
408         }
409 
410 
411         public IoServiceConfig getServiceConfig()
412         {
413             return null;
414         }
415 
416 
417         public int getScheduledWriteBytes()
418         {
419             return 0;
420         }
421     }
422 }