1/*
2 * Copyright (c) 2004, 2008, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 */
28
29package sun.security.krb5.internal.crypto.dk;
30
31import javax.crypto.Cipher;
32import javax.crypto.Mac;
33import javax.crypto.SecretKeyFactory;
34import javax.crypto.SecretKey;
35import javax.crypto.spec.SecretKeySpec;
36import javax.crypto.spec.DESedeKeySpec;
37import javax.crypto.spec.IvParameterSpec;
38import javax.crypto.spec.PBEKeySpec;
39import java.security.spec.KeySpec;
40import java.security.GeneralSecurityException;
41import sun.security.krb5.KrbCryptoException;
42import sun.security.krb5.Confounder;
43import sun.security.krb5.internal.crypto.KeyUsage;
44import java.util.Arrays;
45
46/**
47 * This class provides the implementation of AES Encryption for Kerberos
48 * as defined RFC 3962.
49 * http://www.ietf.org/rfc/rfc3962.txt
50 *
51 * Algorithm profile described in [KCRYPTO]:
52 * +--------------------------------------------------------------------+
53 * |               protocol key format          128- or 256-bit string  |
54 * |                                                                    |
55 * |            string-to-key function          PBKDF2+DK with variable |
56 * |                                          iteration count (see      |
57 * |                                          above)                    |
58 * |                                                                    |
59 * |  default string-to-key parameters          00 00 10 00             |
60 * |                                                                    |
61 * |        key-generation seed length          key size                |
62 * |                                                                    |
63 * |            random-to-key function          identity function       |
64 * |                                                                    |
65 * |                    hash function, H                SHA-1           |
66 * |                                                                    |
67 * |               HMAC output size, h          12 octets (96 bits)     |
68 * |                                                                    |
69 * |             message block size, m          1 octet                 |
70 * |                                                                    |
71 * |  encryption/decryption functions,          AES in CBC-CTS mode     |
72 * |  E and D                                 (cipher block size 16     |
73 * |                                          octets), with next to     |
74 * |                                          last block as CBC-style   |
75 * |                                          ivec                      |
76 * +--------------------------------------------------------------------+
77 *
78 * Supports AES128 and AES256
79 *
80 * @author Seema Malkani
81 */
82
83public class AesDkCrypto extends DkCrypto {
84
85    private static final boolean debug = false;
86
87    private static final int BLOCK_SIZE = 16;
88    private static final int DEFAULT_ITERATION_COUNT = 4096;
89    private static final byte[] ZERO_IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0,
90                                                       0, 0, 0, 0, 0, 0, 0, 0 };
91    private static final int hashSize = 96/8;
92    private final int keyLength;
93
94    public AesDkCrypto(int length) {
95        keyLength = length;
96    }
97
98    protected int getKeySeedLength() {
99        return keyLength;   // bits; AES key material
100    }
101
102    public byte[] stringToKey(char[] password, String salt, byte[] s2kparams)
103        throws GeneralSecurityException {
104
105        byte[] saltUtf8 = null;
106        try {
107            saltUtf8 = salt.getBytes("UTF-8");
108            return stringToKey(password, saltUtf8, s2kparams);
109        } catch (Exception e) {
110            return null;
111        } finally {
112            if (saltUtf8 != null) {
113                Arrays.fill(saltUtf8, (byte)0);
114            }
115        }
116    }
117
118    private byte[] stringToKey(char[] secret, byte[] salt, byte[] params)
119        throws GeneralSecurityException {
120
121        int iter_count = DEFAULT_ITERATION_COUNT;
122        if (params != null) {
123            if (params.length != 4) {
124                throw new RuntimeException("Invalid parameter to stringToKey");
125            }
126            iter_count = readBigEndian(params, 0, 4);
127        }
128
129        byte[] tmpKey = randomToKey(PBKDF2(secret, salt, iter_count,
130                                        getKeySeedLength()));
131        byte[] result = dk(tmpKey, KERBEROS_CONSTANT);
132        return result;
133    }
134
135    protected byte[] randomToKey(byte[] in) {
136        // simple identity operation
137        return in;
138    }
139
140    protected Cipher getCipher(byte[] key, byte[] ivec, int mode)
141        throws GeneralSecurityException {
142
143        // IV
144        if (ivec == null) {
145           ivec = ZERO_IV;
146        }
147        SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
148        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
149        IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
150        cipher.init(mode, secretKey, encIv);
151        return cipher;
152    }
153
154    // get an instance of the AES Cipher in CTS mode
155    public int getChecksumLength() {
156        return hashSize;  // bytes
157    }
158
159    /**
160     * Get the truncated HMAC
161     */
162    protected byte[] getHmac(byte[] key, byte[] msg)
163        throws GeneralSecurityException {
164
165        SecretKey keyKi = new SecretKeySpec(key, "HMAC");
166        Mac m = Mac.getInstance("HmacSHA1");
167        m.init(keyKi);
168
169        // generate hash
170        byte[] hash = m.doFinal(msg);
171
172        // truncate hash
173        byte[] output = new byte[hashSize];
174        System.arraycopy(hash, 0, output, 0, hashSize);
175        return output;
176    }
177
178    /**
179     * Calculate the checksum
180     */
181    public byte[] calculateChecksum(byte[] baseKey, int usage, byte[] input,
182        int start, int len) throws GeneralSecurityException {
183
184        if (!KeyUsage.isValid(usage)) {
185            throw new GeneralSecurityException("Invalid key usage number: "
186                                                + usage);
187        }
188
189        // Derive keys
190        byte[] constant = new byte[5];
191        constant[0] = (byte) ((usage>>24)&0xff);
192        constant[1] = (byte) ((usage>>16)&0xff);
193        constant[2] = (byte) ((usage>>8)&0xff);
194        constant[3] = (byte) (usage&0xff);
195
196        constant[4] = (byte) 0x99;
197
198        byte[] Kc = dk(baseKey, constant);  // Checksum key
199        if (debug) {
200            System.err.println("usage: " + usage);
201            traceOutput("input", input, start, Math.min(len, 32));
202            traceOutput("constant", constant, 0, constant.length);
203            traceOutput("baseKey", baseKey, 0, baseKey.length);
204            traceOutput("Kc", Kc, 0, Kc.length);
205        }
206
207        try {
208            // Generate checksum
209            // H1 = HMAC(Kc, input)
210            byte[] hmac = getHmac(Kc, input);
211            if (debug) {
212                traceOutput("hmac", hmac, 0, hmac.length);
213            }
214            if (hmac.length == getChecksumLength()) {
215                return hmac;
216            } else if (hmac.length > getChecksumLength()) {
217                byte[] buf = new byte[getChecksumLength()];
218                System.arraycopy(hmac, 0, buf, 0, buf.length);
219                return buf;
220            } else {
221                throw new GeneralSecurityException("checksum size too short: " +
222                        hmac.length + "; expecting : " + getChecksumLength());
223            }
224        } finally {
225            Arrays.fill(Kc, 0, Kc.length, (byte)0);
226        }
227    }
228
229    /**
230     * Performs encryption using derived key; adds confounder.
231     */
232    public byte[] encrypt(byte[] baseKey, int usage,
233        byte[] ivec, byte[] new_ivec, byte[] plaintext, int start, int len)
234        throws GeneralSecurityException, KrbCryptoException {
235
236        if (!KeyUsage.isValid(usage)) {
237            throw new GeneralSecurityException("Invalid key usage number: "
238                                                 + usage);
239        }
240        byte[] output = encryptCTS(baseKey, usage, ivec, new_ivec, plaintext,
241                                        start, len, true);
242        return output;
243    }
244
245    /**
246     * Performs encryption using derived key; does not add confounder.
247     */
248    public byte[] encryptRaw(byte[] baseKey, int usage,
249        byte[] ivec, byte[] plaintext, int start, int len)
250        throws GeneralSecurityException, KrbCryptoException {
251
252        if (!KeyUsage.isValid(usage)) {
253            throw new GeneralSecurityException("Invalid key usage number: "
254                                                + usage);
255        }
256        byte[] output = encryptCTS(baseKey, usage, ivec, null, plaintext,
257                                        start, len, false);
258        return output;
259    }
260
261    /**
262     * @param baseKey key from which keys are to be derived using usage
263     * @param ciphertext  E(Ke, conf | plaintext | padding, ivec) | H1[1..h]
264     */
265    public byte[] decrypt(byte[] baseKey, int usage, byte[] ivec,
266        byte[] ciphertext, int start, int len) throws GeneralSecurityException {
267
268        if (!KeyUsage.isValid(usage)) {
269            throw new GeneralSecurityException("Invalid key usage number: "
270                                                + usage);
271        }
272        byte[] output = decryptCTS(baseKey, usage, ivec, ciphertext,
273                                        start, len, true);
274        return output;
275    }
276
277    /**
278     * Decrypts data using specified key and initial vector.
279     * @param baseKey encryption key to use
280     * @param ciphertext  encrypted data to be decrypted
281     * @param usage ignored
282     */
283    public byte[] decryptRaw(byte[] baseKey, int usage, byte[] ivec,
284        byte[] ciphertext, int start, int len)
285        throws GeneralSecurityException {
286
287        if (!KeyUsage.isValid(usage)) {
288            throw new GeneralSecurityException("Invalid key usage number: "
289                                                + usage);
290        }
291        byte[] output = decryptCTS(baseKey, usage, ivec, ciphertext,
292                                        start, len, false);
293        return output;
294    }
295
296    /**
297     * Encrypt AES in CBC-CTS mode using derived keys.
298     */
299    private byte[] encryptCTS(byte[] baseKey, int usage, byte[] ivec,
300        byte[] new_ivec, byte[] plaintext, int start, int len,
301        boolean confounder_exists)
302        throws GeneralSecurityException, KrbCryptoException {
303
304        byte[] Ke = null;
305        byte[] Ki = null;
306
307        if (debug) {
308            System.err.println("usage: " + usage);
309            if (ivec != null) {
310                traceOutput("old_state.ivec", ivec, 0, ivec.length);
311            }
312            traceOutput("plaintext", plaintext, start, Math.min(len, 32));
313            traceOutput("baseKey", baseKey, 0, baseKey.length);
314        }
315
316        try {
317            // derive Encryption key
318            byte[] constant = new byte[5];
319            constant[0] = (byte) ((usage>>24)&0xff);
320            constant[1] = (byte) ((usage>>16)&0xff);
321            constant[2] = (byte) ((usage>>8)&0xff);
322            constant[3] = (byte) (usage&0xff);
323            constant[4] = (byte) 0xaa;
324            Ke = dk(baseKey, constant);  // Encryption key
325
326            byte[] toBeEncrypted = null;
327            if (confounder_exists) {
328                byte[] confounder = Confounder.bytes(BLOCK_SIZE);
329                toBeEncrypted = new byte[confounder.length + len];
330                System.arraycopy(confounder, 0, toBeEncrypted,
331                                        0, confounder.length);
332                System.arraycopy(plaintext, start, toBeEncrypted,
333                                        confounder.length, len);
334            } else {
335                toBeEncrypted = new byte[len];
336                System.arraycopy(plaintext, start, toBeEncrypted, 0, len);
337            }
338
339            // encryptedData + HMAC
340            byte[] output = new byte[toBeEncrypted.length + hashSize];
341
342            // AES in JCE
343            Cipher cipher = Cipher.getInstance("AES/CTS/NoPadding");
344            SecretKeySpec secretKey = new SecretKeySpec(Ke, "AES");
345            IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
346            cipher.init(Cipher.ENCRYPT_MODE, secretKey, encIv);
347            cipher.doFinal(toBeEncrypted, 0, toBeEncrypted.length, output);
348
349            // Derive integrity key
350            constant[4] = (byte) 0x55;
351            Ki = dk(baseKey, constant);
352            if (debug) {
353                traceOutput("constant", constant, 0, constant.length);
354                traceOutput("Ki", Ki, 0, Ke.length);
355            }
356
357            // Generate checksum
358            // H1 = HMAC(Ki, conf | plaintext | pad)
359            byte[] hmac = getHmac(Ki, toBeEncrypted);
360
361            // encryptedData + HMAC
362            System.arraycopy(hmac, 0, output, toBeEncrypted.length,
363                                hmac.length);
364            return output;
365        } finally {
366            if (Ke != null) {
367                Arrays.fill(Ke, 0, Ke.length, (byte) 0);
368            }
369            if (Ki != null) {
370                Arrays.fill(Ki, 0, Ki.length, (byte) 0);
371            }
372        }
373    }
374
375    /**
376     * Decrypt AES in CBC-CTS mode using derived keys.
377     */
378    private byte[] decryptCTS(byte[] baseKey, int usage, byte[] ivec,
379        byte[] ciphertext, int start, int len, boolean confounder_exists)
380        throws GeneralSecurityException {
381
382        byte[] Ke = null;
383        byte[] Ki = null;
384
385        try {
386            // Derive encryption key
387            byte[] constant = new byte[5];
388            constant[0] = (byte) ((usage>>24)&0xff);
389            constant[1] = (byte) ((usage>>16)&0xff);
390            constant[2] = (byte) ((usage>>8)&0xff);
391            constant[3] = (byte) (usage&0xff);
392
393            constant[4] = (byte) 0xaa;
394            Ke = dk(baseKey, constant);  // Encryption key
395
396            if (debug) {
397                System.err.println("usage: " + usage);
398                if (ivec != null) {
399                    traceOutput("old_state.ivec", ivec, 0, ivec.length);
400                }
401                traceOutput("ciphertext", ciphertext, start, Math.min(len, 32));
402                traceOutput("constant", constant, 0, constant.length);
403                traceOutput("baseKey", baseKey, 0, baseKey.length);
404                traceOutput("Ke", Ke, 0, Ke.length);
405            }
406
407            // Decrypt [confounder | plaintext ] (without checksum)
408
409            // AES in JCE
410            Cipher cipher = Cipher.getInstance("AES/CTS/NoPadding");
411            SecretKeySpec secretKey = new SecretKeySpec(Ke, "AES");
412            IvParameterSpec encIv = new IvParameterSpec(ivec, 0, ivec.length);
413            cipher.init(Cipher.DECRYPT_MODE, secretKey, encIv);
414            byte[] plaintext = cipher.doFinal(ciphertext, start, len-hashSize);
415
416            if (debug) {
417                traceOutput("AES PlainText", plaintext, 0,
418                                Math.min(plaintext.length, 32));
419            }
420
421            // Derive integrity key
422            constant[4] = (byte) 0x55;
423            Ki = dk(baseKey, constant);  // Integrity key
424            if (debug) {
425                traceOutput("constant", constant, 0, constant.length);
426                traceOutput("Ki", Ki, 0, Ke.length);
427            }
428
429            // Verify checksum
430            // H1 = HMAC(Ki, conf | plaintext | pad)
431            byte[] calculatedHmac = getHmac(Ki, plaintext);
432            int hmacOffset = start + len - hashSize;
433            if (debug) {
434                traceOutput("calculated Hmac", calculatedHmac,
435                                0, calculatedHmac.length);
436                traceOutput("message Hmac", ciphertext, hmacOffset, hashSize);
437            }
438            boolean cksumFailed = false;
439            if (calculatedHmac.length >= hashSize) {
440                for (int i = 0; i < hashSize; i++) {
441                    if (calculatedHmac[i] != ciphertext[hmacOffset+i]) {
442                        cksumFailed = true;
443                        if (debug) {
444                            System.err.println("Checksum failed !");
445                        }
446                        break;
447                    }
448                }
449            }
450            if (cksumFailed) {
451                throw new GeneralSecurityException("Checksum failed");
452            }
453
454            if (confounder_exists) {
455                // Get rid of confounder
456                // [ confounder | plaintext ]
457                byte[] output = new byte[plaintext.length - BLOCK_SIZE];
458                System.arraycopy(plaintext, BLOCK_SIZE, output,
459                                        0, output.length);
460                return output;
461            } else {
462                return plaintext;
463            }
464        } finally {
465            if (Ke != null) {
466                Arrays.fill(Ke, 0, Ke.length, (byte) 0);
467            }
468            if (Ki != null) {
469                Arrays.fill(Ki, 0, Ki.length, (byte) 0);
470            }
471        }
472    }
473
474    /*
475     * Invoke the PKCS#5 PBKDF2 algorithm
476     */
477    private static byte[] PBKDF2(char[] secret, byte[] salt,
478        int count, int keyLength) throws GeneralSecurityException {
479
480        PBEKeySpec keySpec = new PBEKeySpec(secret, salt, count, keyLength);
481        SecretKeyFactory skf =
482                SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
483        SecretKey key = skf.generateSecret(keySpec);
484        byte[] result = key.getEncoded();
485
486        return result;
487    }
488
489    public static final int readBigEndian(byte[] data, int pos, int size) {
490        int retVal = 0;
491        int shifter = (size-1)*8;
492        while (size > 0) {
493            retVal += (data[pos] & 0xff) << shifter;
494            shifter -= 8;
495            pos++;
496            size--;
497        }
498        return retVal;
499    }
500
501}
502