001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *  
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *  
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License. 
018     *  
019     */
020    package org.apache.directory.server.kerberos.shared.crypto.encryption;
021    
022    
023    import java.security.GeneralSecurityException;
024    import java.security.spec.AlgorithmParameterSpec;
025    import java.util.Arrays;
026    
027    import javax.crypto.Cipher;
028    import javax.crypto.Mac;
029    import javax.crypto.SecretKey;
030    import javax.crypto.spec.IvParameterSpec;
031    import javax.crypto.spec.SecretKeySpec;
032    
033    import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumEngine;
034    import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumType;
035    import org.apache.directory.server.kerberos.shared.exceptions.ErrorType;
036    import org.apache.directory.server.kerberos.shared.exceptions.KerberosException;
037    import org.apache.directory.server.kerberos.shared.messages.value.EncryptedData;
038    import org.apache.directory.server.kerberos.shared.messages.value.EncryptionKey;
039    
040    
041    /**
042     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043     * @version $Rev$, $Date$
044     */
045    public class Des3CbcSha1KdEncryption extends EncryptionEngine implements ChecksumEngine
046    {
047        private static final byte[] iv = new byte[]
048            { ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00, ( byte ) 0x00,
049                ( byte ) 0x00 };
050    
051    
052        public EncryptionType getEncryptionType()
053        {
054            return EncryptionType.DES3_CBC_SHA1_KD;
055        }
056    
057    
058        public int getConfounderLength()
059        {
060            return 8;
061        }
062    
063    
064        public int getChecksumLength()
065        {
066            return 20;
067        }
068    
069    
070        public ChecksumType checksumType()
071        {
072            return ChecksumType.HMAC_SHA1_DES3_KD;
073        }
074    
075    
076        public byte[] calculateChecksum( byte[] data, byte[] key, KeyUsage usage )
077        {
078            byte[] Kc = deriveKey( key, getUsageKc( usage ), 64, 168 );
079    
080            return processChecksum( data, Kc );
081        }
082    
083    
084        public byte[] calculateIntegrity( byte[] data, byte[] key, KeyUsage usage )
085        {
086            byte[] Ki = deriveKey( key, getUsageKi( usage ), 64, 168 );
087    
088            return processChecksum( data, Ki );
089        }
090    
091    
092        public byte[] getDecryptedData( EncryptionKey key, EncryptedData data, KeyUsage usage ) throws KerberosException
093        {
094            byte[] Ke = deriveKey( key.getKeyValue(), getUsageKe( usage ), 64, 168 );
095    
096            byte[] encryptedData = data.getCipher();
097    
098            // extract the old checksum
099            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    }