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.io.IOException;
24  import java.net.InetSocketAddress;
25  import java.net.SocketAddress;
26  import java.security.SecureRandom;
27  
28  import javax.security.auth.kerberos.KerberosKey;
29  import javax.security.auth.kerberos.KerberosPrincipal;
30  
31  import junit.framework.TestCase;
32  
33  import org.apache.directory.server.kerberos.shared.KerberosConstants;
34  import org.apache.directory.server.kerberos.shared.KerberosMessageType;
35  import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumHandler;
36  import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumType;
37  import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler;
38  import org.apache.directory.server.kerberos.shared.crypto.encryption.EncryptionType;
39  import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage;
40  import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory;
41  import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
42  import org.apache.directory.server.kerberos.shared.io.encoder.ApplicationRequestEncoder;
43  import org.apache.directory.server.kerberos.shared.io.encoder.KdcRequestEncoder;
44  import org.apache.directory.server.kerberos.shared.messages.ApplicationRequest;
45  import org.apache.directory.server.kerberos.shared.messages.KdcRequest;
46  import org.apache.directory.server.kerberos.shared.messages.components.Authenticator;
47  import org.apache.directory.server.kerberos.shared.messages.components.AuthenticatorModifier;
48  import org.apache.directory.server.kerberos.shared.messages.components.EncTicketPart;
49  import org.apache.directory.server.kerberos.shared.messages.components.EncTicketPartModifier;
50  import org.apache.directory.server.kerberos.shared.messages.components.Ticket;
51  import org.apache.directory.server.kerberos.shared.messages.value.ApOptions;
52  import org.apache.directory.server.kerberos.shared.messages.value.Checksum;
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.KerberosTime;
56  import org.apache.directory.server.kerberos.shared.messages.value.PaData;
57  import org.apache.directory.server.kerberos.shared.messages.value.PrincipalName;
58  import org.apache.directory.server.kerberos.shared.messages.value.RequestBody;
59  import org.apache.directory.server.kerberos.shared.messages.value.TransitedEncoding;
60  import org.apache.directory.server.kerberos.shared.messages.value.flags.TicketFlag;
61  import org.apache.directory.server.kerberos.shared.messages.value.flags.TicketFlags;
62  import org.apache.directory.server.kerberos.shared.messages.value.types.PaDataType;
63  import org.apache.directory.server.kerberos.shared.messages.value.types.PrincipalNameType;
64  import org.apache.mina.common.IoFilterChain;
65  import org.apache.mina.common.IoHandler;
66  import org.apache.mina.common.IoService;
67  import org.apache.mina.common.IoServiceConfig;
68  import org.apache.mina.common.IoSessionConfig;
69  import org.apache.mina.common.TransportType;
70  import org.apache.mina.common.WriteFuture;
71  import org.apache.mina.common.support.BaseIoSession;
72  
73  
74  /**
75   * Abstract base class for Ticket-Granting Service (TGS) tests, with utility methods
76   * for generating message components.
77   * 
78   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
79   * @version $Rev$, $Date$        Ticket ticket = ticketModifier.getTicket();
80  
81  
82   */
83  public abstract class AbstractTicketGrantingServiceTest extends TestCase
84  {
85      protected CipherTextHandler lockBox;
86      protected static final SecureRandom random = new SecureRandom();
87  
88      /** Session attributes that must be verified. */
89      protected EncryptionKey sessionKey;
90      protected EncryptionKey subSessionKey;
91      protected int sequenceNumber;
92      protected KerberosTime now;
93      protected int clientMicroSeconds = 0;
94  
95  
96      protected Ticket getTgt( KerberosPrincipal clientPrincipal, KerberosPrincipal serverPrincipal, String serverPassword )
97          throws Exception
98      {
99          EncryptionKey serverKey = getEncryptionKey( serverPrincipal, serverPassword );
100         return getTicket( clientPrincipal, serverPrincipal, serverKey );
101     }
102 
103 
104     /**
105      * Returns an encryption key derived from a principal name and passphrase.
106      *
107      * @param principal
108      * @param passPhrase
109      * @return The server's {@link EncryptionKey}.
110      */
111     protected EncryptionKey getEncryptionKey( KerberosPrincipal principal, String passPhrase )
112     {
113         KerberosKey kerberosKey = new KerberosKey( principal, passPhrase.toCharArray(), "DES" );
114         byte[] keyBytes = kerberosKey.getEncoded();
115         return new EncryptionKey( EncryptionType.DES_CBC_MD5, keyBytes );
116     }
117 
118 
119     /**
120      * Build the service ticket.  The service ticket contains the session key generated
121      * by the KDC for the client and service to use.  The service will unlock the
122      * authenticator with the session key from the ticket.  The principal in the ticket
123      * must equal the authenticator client principal.
124      * 
125      * If set in the AP Options, the Ticket can also be sealed with the session key.
126      * 
127      * @param clientPrincipal
128      * @param serverPrincipal
129      * @param serverKey 
130      * @return The {@link Ticket}.
131      * @throws KerberosException
132      */
133     protected Ticket getTicket( KerberosPrincipal clientPrincipal, KerberosPrincipal serverPrincipal,
134         EncryptionKey serverKey ) throws KerberosException
135     {
136         EncTicketPartModifier encTicketModifier = new EncTicketPartModifier();
137 
138         TicketFlags ticketFlags = new TicketFlags();
139         ticketFlags.setFlag( TicketFlag.RENEWABLE );
140         encTicketModifier.setFlags( ticketFlags );
141 
142         EncryptionKey sessionKey = RandomKeyFactory.getRandomKey( EncryptionType.DES_CBC_MD5 );
143 
144         encTicketModifier.setSessionKey( sessionKey );
145         encTicketModifier.setClientPrincipal( clientPrincipal );
146         encTicketModifier.setTransitedEncoding( new TransitedEncoding() );
147         encTicketModifier.setAuthTime( new KerberosTime() );
148 
149         long now = System.currentTimeMillis();
150         KerberosTime endTime = new KerberosTime( now + KerberosTime.DAY );
151         encTicketModifier.setEndTime( endTime );
152 
153         KerberosTime renewTill = new KerberosTime( now + KerberosTime.WEEK );
154         encTicketModifier.setRenewTill( renewTill );
155 
156         EncTicketPart encTicketPart = encTicketModifier.getEncTicketPart();
157 
158         EncryptedData encryptedTicketPart = lockBox.seal( serverKey, encTicketPart, KeyUsage.NUMBER2 );
159 
160         Ticket ticket = new Ticket( KerberosConstants.KERBEROS_V5, serverPrincipal, encryptedTicketPart );
161 
162         ticket.setEncTicketPart( encTicketPart );
163 
164         return ticket;
165     }
166 
167 
168     protected EncTicketPartModifier getTicketArchetype( KerberosPrincipal clientPrincipal ) throws KerberosException
169     {
170         EncTicketPartModifier encTicketModifier = new EncTicketPartModifier();
171 
172         TicketFlags ticketFlags = new TicketFlags();
173         ticketFlags.setFlag( TicketFlag.RENEWABLE );
174         encTicketModifier.setFlags( ticketFlags );
175 
176         EncryptionKey sessionKey = RandomKeyFactory.getRandomKey( EncryptionType.DES_CBC_MD5 );
177 
178         encTicketModifier.setSessionKey( sessionKey );
179         encTicketModifier.setClientPrincipal( clientPrincipal );
180         encTicketModifier.setTransitedEncoding( new TransitedEncoding() );
181         encTicketModifier.setAuthTime( new KerberosTime() );
182 
183         long now = System.currentTimeMillis();
184         KerberosTime endTime = new KerberosTime( now + KerberosTime.DAY );
185         encTicketModifier.setEndTime( endTime );
186 
187         KerberosTime renewTill = new KerberosTime( now + KerberosTime.WEEK );
188         encTicketModifier.setRenewTill( renewTill );
189 
190         return encTicketModifier;
191     }
192 
193 
194     protected Ticket getTicket( EncTicketPartModifier encTicketModifier, KerberosPrincipal serverPrincipal,
195         EncryptionKey serverKey ) throws KerberosException
196     {
197         EncTicketPart encTicketPart = encTicketModifier.getEncTicketPart();
198 
199         EncryptedData encryptedTicketPart = lockBox.seal( serverKey, encTicketPart, KeyUsage.NUMBER2 );
200 
201         Ticket ticket = new Ticket();
202         ticket.setTktVno( 5 );
203         ticket.setServerPrincipal( serverPrincipal );
204         ticket.setEncPart( encryptedTicketPart );
205 
206         ticket.setEncTicketPart( encTicketPart );
207 
208         return ticket;
209     }
210 
211 
212     protected KdcRequest getKdcRequest( Ticket tgt, RequestBody requestBody ) throws Exception
213     {
214         return getKdcRequest( tgt, requestBody, ChecksumType.RSA_MD5 );
215     }
216 
217 
218     /**
219      * Create a KdcRequest, suitable for requesting a service Ticket.
220      */
221     protected KdcRequest getKdcRequest( Ticket tgt, RequestBody requestBody, ChecksumType checksumType )
222         throws Exception
223     {
224         // Get the session key from the service ticket.
225         sessionKey = tgt.getEncTicketPart().getSessionKey();
226 
227         // Generate a new sequence number.
228         sequenceNumber = random.nextInt();
229         now = new KerberosTime();
230 
231         EncryptedData authenticator = getAuthenticator( tgt.getEncTicketPart().getClientPrincipal(), requestBody, checksumType );
232 
233         PaData[] paData = getPreAuthenticationData( tgt, authenticator );
234 
235         return new KdcRequest( 5, KerberosMessageType.TGS_REQ, paData, requestBody );
236     }
237 
238 
239     /**
240      * Build the authenticator.  The authenticator communicates the sub-session key the
241      * service will use to unlock the private message.  The service will unlock the
242      * authenticator with the session key from the ticket.  The authenticator client
243      * principal must equal the principal in the ticket.  
244      *
245      * @param clientPrincipal
246      * @return The {@link EncryptedData} containing the {@link Authenticator}.
247      * @throws KerberosException
248      */
249     protected EncryptedData getAuthenticator( KerberosPrincipal clientPrincipal, RequestBody requestBody,
250         ChecksumType checksumType ) throws IOException, KerberosException
251     {
252         AuthenticatorModifier authenticatorModifier = new AuthenticatorModifier();
253 
254         clientMicroSeconds = random.nextInt();
255 
256         authenticatorModifier.setVersionNumber( 5 );
257         authenticatorModifier.setClientPrincipal( clientPrincipal );
258         authenticatorModifier.setClientTime( now );
259         authenticatorModifier.setClientMicroSecond( clientMicroSeconds );
260         authenticatorModifier.setSubSessionKey( subSessionKey );
261         authenticatorModifier.setSequenceNumber( sequenceNumber );
262 
263         Checksum checksum = getBodyChecksum( requestBody, checksumType );
264         authenticatorModifier.setChecksum( checksum );
265 
266         Authenticator authenticator = authenticatorModifier.getAuthenticator();
267 
268         EncryptedData encryptedAuthenticator = lockBox.seal( sessionKey, authenticator, KeyUsage.NUMBER7 );
269 
270         return encryptedAuthenticator;
271     }
272 
273 
274     protected Checksum getBodyChecksum( RequestBody requestBody, ChecksumType checksumType ) throws IOException,
275         KerberosException
276     {
277         KdcRequestEncoder bodyEncoder = new KdcRequestEncoder();
278         byte[] bodyBytes = bodyEncoder.encodeRequestBody( requestBody );
279 
280         ChecksumHandler checksumHandler = new ChecksumHandler();
281         return checksumHandler.calculateChecksum( checksumType, bodyBytes, null, KeyUsage.NUMBER8 );
282     }
283 
284 
285     /**
286      * Make new AP_REQ, aka the "auth header," and package it into pre-authentication data.
287      *
288      * @param ticket
289      * @param authenticator
290      * @return
291      * @throws IOException
292      */
293     protected PaData[] getPreAuthenticationData( Ticket ticket, EncryptedData authenticator )
294         throws IOException
295     {
296         ApplicationRequest applicationRequest = new ApplicationRequest();
297         applicationRequest.setMessageType( KerberosMessageType.AP_REQ );
298         applicationRequest.setProtocolVersionNumber( 5 );
299         applicationRequest.setApOptions( new ApOptions() );
300         applicationRequest.setTicket( ticket );
301         applicationRequest.setEncPart( authenticator );
302 
303         ApplicationRequestEncoder encoder = new ApplicationRequestEncoder();
304         byte[] encodedApReq = encoder.encode( applicationRequest );
305 
306         PaData[] paData = new PaData[1];
307 
308         PaData preAuth = new PaData();
309         preAuth.setPaDataType( PaDataType.PA_TGS_REQ );
310         preAuth.setPaDataValue( encodedApReq );
311 
312         paData[0] = preAuth;
313 
314         return paData;
315     }
316 
317 
318     protected PrincipalName getPrincipalName( String name )
319     {
320         PrincipalName principalName = new PrincipalName();
321         principalName.addName( name );
322         principalName.setNameType( PrincipalNameType.KRB_NT_PRINCIPAL );
323 
324         return principalName;
325     }
326 
327     protected static class DummySession extends BaseIoSession
328     {
329         Object message;
330 
331 
332         @Override
333         public WriteFuture write( Object message )
334         {
335             this.message = message;
336 
337             return super.write( message );
338         }
339 
340 
341         protected Object getMessage()
342         {
343             return message;
344         }
345 
346 
347         protected void updateTrafficMask()
348         {
349             // Do nothing.
350         }
351 
352 
353         public IoService getService()
354         {
355             return null;
356         }
357 
358 
359         public IoHandler getHandler()
360         {
361             return null;
362         }
363 
364 
365         public IoFilterChain getFilterChain()
366         {
367             return null;
368         }
369 
370 
371         public TransportType getTransportType()
372         {
373             return null;
374         }
375 
376 
377         public SocketAddress getRemoteAddress()
378         {
379             return new InetSocketAddress( 10088 );
380         }
381 
382 
383         public SocketAddress getLocalAddress()
384         {
385             return null;
386         }
387 
388 
389         public IoSessionConfig getConfig()
390         {
391             return null;
392         }
393 
394 
395         public int getScheduledWriteRequests()
396         {
397             return 0;
398         }
399 
400 
401         public SocketAddress getServiceAddress()
402         {
403             return null;
404         }
405 
406 
407         public IoServiceConfig getServiceConfig()
408         {
409             return null;
410         }
411 
412 
413         public int getScheduledWriteBytes()
414         {
415             return 0;
416         }
417     }
418 }