View Javadoc

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.GeneralSecurityException;
24  import java.security.spec.AlgorithmParameterSpec;
25  import java.util.Arrays;
26  
27  import javax.crypto.Cipher;
28  import javax.crypto.Mac;
29  import javax.crypto.SecretKey;
30  import javax.crypto.spec.IvParameterSpec;
31  import javax.crypto.spec.SecretKeySpec;
32  
33  import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumEngine;
34  import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumType;
35  import org.apache.directory.server.kerberos.shared.exceptions.ErrorType;
36  import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
37  import org.apache.directory.server.kerberos.shared.messages.value.EncryptedData;
38  import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
39  
40  
41  /**
42   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
43   * @version $Rev$, $Date$
44   */
45  public class Des3CbcSha1KdEncryption extends EncryptionEngine implements ChecksumEngine
46  {
47      private static final byte[] iv = new byte[]
48          { ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00,
49              ( byte ) 0x00 };
50  
51  
52      public EncryptionType getEncryptionType()
53      {
54          return EncryptionType.DES3_CBC_SHA1_KD;
55      }
56  
57  
58      public int getConfounderLength()
59      {
60          return 8;
61      }
62  
63  
64      public int getChecksumLength()
65      {
66          return 20;
67      }
68  
69  
70      public ChecksumType checksumType()
71      {
72          return ChecksumType.HMAC_SHA1_DES3_KD;
73      }
74  
75  
76      public byte[] calculateChecksum( byte[] data, byte[] key, KeyUsage usage )
77      {
78          byte[] Kc = deriveKey( key, getUsageKc( usage ), 64, 168 );
79  
80          return processChecksum( data, Kc );
81      }
82  
83  
84      public byte[] calculateIntegrity( byte[] data, byte[] key, KeyUsage usage )
85      {
86          byte[] Ki = deriveKey( key, getUsageKi( usage ), 64, 168 );
87  
88          return processChecksum( data, Ki );
89      }
90  
91  
92      public byte[] getDecryptedData( EncryptionKey key, EncryptedData data, KeyUsage usage ) throws KerberosException
93      {
94          byte[] Ke = deriveKey( key.getKeyValue(), getUsageKe( usage ), 64, 168 );
95  
96          byte[] encryptedData = data.getCipher();
97  
98          // extract the old checksum
99          byte[] oldChecksum = new byte[getChecksumLength()];
100         System
101             .arraycopy( encryptedData, encryptedData.length - getChecksumLength(), oldChecksum, 0, oldChecksum.length );
102 
103         // remove trailing checksum
104         encryptedData = removeTrailingBytes( encryptedData, 0, getChecksumLength() );
105 
106         // decrypt the data
107         byte[] decryptedData = decrypt( encryptedData, Ke );
108 
109         // remove leading confounder
110         byte[] withoutConfounder = removeLeadingBytes( decryptedData, getConfounderLength(), 0 );
111 
112         // calculate a new checksum
113         byte[] newChecksum = calculateIntegrity( decryptedData, key.getKeyValue(), usage );
114 
115         // compare checksums
116         if ( !Arrays.equals( oldChecksum, newChecksum ) )
117         {
118             throw new KerberosException( ErrorType.KRB_AP_ERR_BAD_INTEGRITY );
119         }
120 
121         return withoutConfounder;
122     }
123 
124 
125     public EncryptedData getEncryptedData( EncryptionKey key, byte[] plainText, KeyUsage usage )
126     {
127         byte[] Ke = deriveKey( key.getKeyValue(), getUsageKe( usage ), 64, 168 );
128 
129         // build the ciphertext structure
130         byte[] conFounder = getRandomBytes( getConfounderLength() );
131         byte[] paddedPlainText = padString( plainText );
132         byte[] dataBytes = concatenateBytes( conFounder, paddedPlainText );
133         byte[] checksumBytes = calculateIntegrity( dataBytes, key.getKeyValue(), usage );
134 
135         //byte[] encryptedData = encrypt( paddedDataBytes, key.getKeyValue() );
136         byte[] encryptedData = encrypt( dataBytes, Ke );
137 
138         byte[] cipherText = concatenateBytes( encryptedData, checksumBytes );
139 
140         return new EncryptedData( getEncryptionType(), key.getKeyVersion(), cipherText );
141     }
142 
143 
144     public byte[] encrypt( byte[] plainText, byte[] keyBytes )
145     {
146         return processCipher( true, plainText, keyBytes );
147     }
148 
149 
150     public byte[] decrypt( byte[] cipherText, byte[] keyBytes )
151     {
152         return processCipher( false, cipherText, keyBytes );
153     }
154 
155 
156     /**
157      * Derived Key = DK(Base Key, Well-Known Constant)
158      * DK(Key, Constant) = random-to-key(DR(Key, Constant))
159      * DR(Key, Constant) = k-truncate(E(Key, Constant, initial-cipher-state))
160      */
161     protected byte[] deriveKey( byte[] baseKey, byte[] usage, int n, int k )
162     {
163         byte[] result = deriveRandom( baseKey, usage, n, k );
164         result = randomToKey( result );
165 
166         return result;
167     }
168 
169 
170     protected byte[] randomToKey( byte[] seed )
171     {
172         int kBytes = 24;
173         byte[] result = new byte[kBytes];
174 
175         byte[] fillingKey = new byte[0];
176 
177         int pos = 0;
178 
179         for ( int i = 0; i < kBytes; i++ )
180         {
181             if ( pos < fillingKey.length )
182             {
183                 result[i] = fillingKey[pos];
184                 pos++;
185             }
186             else
187             {
188                 fillingKey = getBitGroup( seed, i / 8 );
189                 fillingKey = setParity( fillingKey );
190                 pos = 0;
191                 result[i] = fillingKey[pos];
192                 pos++;
193             }
194         }
195 
196         return result;
197     }
198 
199 
200     protected byte[] getBitGroup( byte[] seed, int group )
201     {
202         int srcPos = group * 7;
203 
204         byte[] result = new byte[7];
205 
206         System.arraycopy( seed, srcPos, result, 0, 7 );
207 
208         return result;
209     }
210 
211 
212     protected byte[] setParity( byte[] in )
213     {
214         byte[] expandedIn = new byte[8];
215 
216         System.arraycopy( in, 0, expandedIn, 0, in.length );
217 
218         setBit( expandedIn, 62, getBit( in, 7 ) );
219         setBit( expandedIn, 61, getBit( in, 15 ) );
220         setBit( expandedIn, 60, getBit( in, 23 ) );
221         setBit( expandedIn, 59, getBit( in, 31 ) );
222         setBit( expandedIn, 58, getBit( in, 39 ) );
223         setBit( expandedIn, 57, getBit( in, 47 ) );
224         setBit( expandedIn, 56, getBit( in, 55 ) );
225 
226         byte[] out = new byte[8];
227 
228         int bitCount = 0;
229         int index = 0;
230 
231         for ( int i = 0; i < 64; i++ )
232         {
233             if ( ( i + 1 ) % 8 == 0 )
234             {
235                 if ( bitCount % 2 == 0 )
236                 {
237                     setBit( out, i, 1 );
238                 }
239 
240                 index++;
241                 bitCount = 0;
242             }
243             else
244             {
245                 int val = getBit( expandedIn, index );
246                 boolean bit = val > 0;
247 
248                 if ( bit )
249                 {
250                     setBit( out, i, val );
251                     bitCount++;
252                 }
253 
254                 index++;
255             }
256         }
257 
258         return out;
259     }
260 
261 
262     private byte[] processCipher( boolean isEncrypt, byte[] data, byte[] keyBytes )
263     {
264         try
265         {
266             Cipher cipher = Cipher.getInstance( "DESede/CBC/NoPadding" );
267             SecretKey key = new SecretKeySpec( keyBytes, "DESede" );
268 
269             AlgorithmParameterSpec paramSpec = new IvParameterSpec( iv );
270 
271             if ( isEncrypt )
272             {
273                 cipher.init( Cipher.ENCRYPT_MODE, key, paramSpec );
274             }
275             else
276             {
277                 cipher.init( Cipher.DECRYPT_MODE, key, paramSpec );
278             }
279 
280             return cipher.doFinal( data );
281         }
282         catch ( GeneralSecurityException nsae )
283         {
284             nsae.printStackTrace();
285             return null;
286         }
287     }
288 
289 
290     private byte[] processChecksum( byte[] data, byte[] key )
291     {
292         try
293         {
294             SecretKey sk = new SecretKeySpec( key, "DESede" );
295 
296             Mac mac = Mac.getInstance( "HmacSHA1" );
297             mac.init( sk );
298 
299             return mac.doFinal( data );
300         }
301         catch ( GeneralSecurityException nsae )
302         {
303             nsae.printStackTrace();
304             return null;
305         }
306     }
307 }