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.shared.crypto.encryption;
21  
22  
23  import java.security.InvalidKeyException;
24  import java.security.NoSuchAlgorithmException;
25  import java.security.Provider;
26  import java.security.Security;
27  import java.util.Arrays;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Set;
32  
33  import javax.crypto.Cipher;
34  import javax.crypto.KeyGenerator;
35  import javax.crypto.Mac;
36  import javax.crypto.SecretKey;
37  import javax.crypto.SecretKeyFactory;
38  import javax.crypto.spec.DESKeySpec;
39  
40  import junit.framework.TestCase;
41  
42  
43  /**
44   * Test cases for the encryption types used by Kerberos "5.2" per RFC 4120,
45   * "The Kerberos Network Authentication Service (V5)."
46   * 
47   * We MUST support:
48   * Encryption: AES256-CTS-HMAC-SHA1-96 [RFC3962]
49   * Checksums: HMAC-SHA1-96-AES256 [RFC3962]
50   * 
51   * We SHOULD support:
52   * Encryption: AES128-CTS-HMAC-SHA1-96, DES-CBC-MD5, DES3-CBC-SHA1-KD
53   * Checksums: DES-MD5, HMAC-SHA1-DES3-KD, HMAC-SHA1-96-AES128
54   * 
55   * Also important for interoperability is:
56   * ArcFour with HMAC/md5, DES-CBC-CRC
57   * 
58   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
59   * @version $Rev$, $Date$
60   */
61  public class KeyTypeTest extends TestCase
62  {
63      /**
64       * Tests that the cipher types used by Kerberos exist, namely
65       * DES, DESede, RC4, and AES.
66       */
67      public void testKeyTypes()
68      {
69          String[] names = getCryptoImpls( "Cipher" );
70          List ciphers = Arrays.asList( names );
71  
72          assertTrue( ciphers.contains( "DES" ) );
73          assertTrue( ciphers.contains( "DESede" ) );
74          assertTrue( ciphers.contains( "TripleDES" ) );
75          assertTrue( ciphers.contains( "ARCFOUR" ) );
76          assertTrue( ciphers.contains( "RC4" ) );
77          assertTrue( ciphers.contains( "AES" ) );
78      }
79  
80  
81      /**
82       * Tests that the message digest types used by Kerberos exist, namely
83       * SHA1 and MD5.
84       */
85      public void testMessageDigestTypes()
86      {
87          String[] names = getCryptoImpls( "MessageDigest" );
88          List ciphers = Arrays.asList( names );
89  
90          assertTrue( ciphers.contains( "MD5" ) );
91          assertTrue( ciphers.contains( "SHA1" ) );
92      }
93  
94  
95      /**
96       * Tests that the MAC types used by Kerberos exist, namely
97       * HmacMD5 and HmacSHA1.
98       */
99      public void testMacTypes()
100     {
101         String[] names = getCryptoImpls( "Mac" );
102         List ciphers = Arrays.asList( names );
103 
104         assertTrue( ciphers.contains( "HmacMD5" ) );
105         assertTrue( ciphers.contains( "HmacSHA1" ) );
106     }
107 
108 
109     /**
110      * Tests that DES keys can be generated from bytes.
111      *
112      * @throws Exception
113      */
114     public void generateDes() throws Exception
115     {
116         byte[] desKeyData =
117             { ( byte ) 0x01, ( byte ) 0x02, ( byte ) 0x03, ( byte ) 0x04, ( byte ) 0x05, ( byte ) 0x06, ( byte ) 0x07,
118                 ( byte ) 0x08 };
119         DESKeySpec desKeySpec = new DESKeySpec( desKeyData );
120         SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
121         SecretKey desKey = keyFactory.generateSecret( desKeySpec );
122         assertEquals( "DES key size", 8, desKey.getEncoded().length );
123         assertTrue( DESKeySpec.isParityAdjusted( desKey.getEncoded(), 0 ) );
124     }
125 
126 
127     /**
128      * Tests that a CBC-mode DES cipher can be initialized.
129      *
130      * @throws Exception
131      */
132     public void testDesCipher() throws Exception
133     {
134         KeyGenerator keygen = KeyGenerator.getInstance( "DES" );
135         SecretKey desKey = keygen.generateKey();
136 
137         Cipher ecipher = Cipher.getInstance( "DES/CBC/NoPadding" );
138         ecipher.init( Cipher.ENCRYPT_MODE, desKey );
139         assertEquals( "Block size", 8, ecipher.getBlockSize() );
140     }
141 
142 
143     /**
144      * Tests that a CBC-mode Triple-DES cipher can be initialized.
145      *
146      * @throws Exception
147      */
148     public void testTripleDesCipher() throws Exception
149     {
150         KeyGenerator keygen = KeyGenerator.getInstance( "DESede" );
151         SecretKey desKey = keygen.generateKey();
152 
153         Cipher ecipher = Cipher.getInstance( "DESede/CBC/NoPadding" );
154         ecipher.init( Cipher.ENCRYPT_MODE, desKey );
155         assertEquals( "Block size", 8, ecipher.getBlockSize() );
156     }
157 
158 
159     /**
160      * Tests that a CBC-mode Triple-DES cipher can be initialized.
161      *
162      * @throws Exception
163      */
164     public void testArcFourCipher() throws Exception
165     {
166         KeyGenerator keygen = KeyGenerator.getInstance( "ARCFOUR" );
167         SecretKey desKey = keygen.generateKey();
168 
169         Cipher ecipher = Cipher.getInstance( "ARCFOUR" );
170         ecipher.init( Cipher.ENCRYPT_MODE, desKey );
171         assertEquals( "Block size", 0, ecipher.getBlockSize() );
172     }
173 
174 
175     /**
176      * Tests that a CTS-mode AES cipher can be initialized
177      * with an AES-128 key.
178      *
179      * @throws Exception
180      */
181     public void testAes128Cipher() throws Exception
182     {
183         KeyGenerator keyGenerator = KeyGenerator.getInstance( "AES" );
184         keyGenerator.init( 128 );
185 
186         SecretKey key = keyGenerator.generateKey();
187 
188         try
189         {
190             Cipher ecipher = Cipher.getInstance( "AES/CTS/NoPadding" );
191             ecipher.init( Cipher.ENCRYPT_MODE, key );
192             assertEquals( "Block size", 16, ecipher.getBlockSize() );
193         }
194         catch ( NoSuchAlgorithmException nsae )
195         {
196             // Without CTS mode this will throw an Exception.
197         }
198     }
199 
200 
201     /**
202      * Tests that a CTS-mode AES cipher can be initialized
203      * with an AES-256 key.
204      *
205      * @throws Exception
206      */
207     public void testAes256Cipher() throws Exception
208     {
209         KeyGenerator keyGenerator = KeyGenerator.getInstance( "AES" );
210         keyGenerator.init( 256 );
211 
212         SecretKey key = keyGenerator.generateKey();
213 
214         try
215         {
216             Cipher ecipher = Cipher.getInstance( "AES/CTS/NoPadding" );
217             ecipher.init( Cipher.ENCRYPT_MODE, key );
218             assertEquals( "Block size", 16, ecipher.getBlockSize() );
219         }
220         catch ( InvalidKeyException ike )
221         {
222             // Without unlimited-strength crypto this will throw an exception.
223         }
224         catch ( NoSuchAlgorithmException nsae )
225         {
226             // Without CTS mode this will throw an Exception.
227         }
228     }
229 
230 
231     /**
232      * Tests the generation of an HMAC-MD5 MAC.
233      * 
234      * @throws Exception
235      */
236     public void testGenerateHmacMd5() throws Exception
237     {
238         KeyGenerator kg = KeyGenerator.getInstance( "HmacMD5" );
239         SecretKey sk = kg.generateKey();
240 
241         Mac mac = Mac.getInstance( "HmacMD5" );
242         mac.init( sk );
243         byte[] result = mac.doFinal( "Hello world!".getBytes() );
244 
245         assertEquals( "HmacMD5 size", 16, result.length );
246     }
247 
248 
249     /**
250      * Tests the generation of an HMAC-SHA1 MAC.
251      * 
252      * @throws Exception
253      */
254     public void testGenerateHmacSha1() throws Exception
255     {
256         KeyGenerator kg = KeyGenerator.getInstance( "HmacSHA1" );
257         SecretKey sk = kg.generateKey();
258 
259         Mac mac = Mac.getInstance( "HmacSHA1" );
260         mac.init( sk );
261         byte[] result = mac.doFinal( "Hi There".getBytes() );
262 
263         assertEquals( "HmacSHA1 size", 20, result.length );
264     }
265 
266 
267     /**
268      * This method returns the available implementations for a service type.
269      * 
270      * @param serviceType The type of the service.
271      * @return Array of the service types as Strings.
272      */
273     private static String[] getCryptoImpls( String serviceType )
274     {
275         Set<String> result = new HashSet<String>();
276 
277         Provider[] providers = Security.getProviders();
278         for ( int i = 0; i < providers.length; i++ )
279         {
280             // Get services provided by each provider
281             Set keys = providers[i].keySet();
282             for ( Iterator it = keys.iterator(); it.hasNext(); )
283             {
284                 String key = ( String ) it.next();
285                 key = key.split( " " )[0];
286 
287                 if ( key.startsWith( serviceType + "." ) )
288                 {
289                     result.add( key.substring( serviceType.length() + 1 ) );
290                 }
291                 else if ( key.startsWith( "Alg.Alias." + serviceType + "." ) )
292                 {
293                     // This is an alias
294                     result.add( key.substring( serviceType.length() + 11 ) );
295                 }
296             }
297         }
298 
299         return result.toArray( new String[result.size()] );
300     }
301 }