1/*
2 * Copyright (c) 2015, 2016, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import static java.lang.System.out;
25
26import java.security.InvalidAlgorithmParameterException;
27import java.security.InvalidKeyException;
28import java.security.NoSuchAlgorithmException;
29import java.security.NoSuchProviderException;
30import java.security.spec.AlgorithmParameterSpec;
31
32import javax.crypto.BadPaddingException;
33import javax.crypto.Cipher;
34import javax.crypto.IllegalBlockSizeException;
35import javax.crypto.KeyGenerator;
36import javax.crypto.NoSuchPaddingException;
37import javax.crypto.SecretKey;
38import javax.crypto.ShortBufferException;
39import javax.crypto.spec.IvParameterSpec;
40import javax.crypto.spec.SecretKeySpec;
41
42/**
43 * This is a abstract class used to test various ciphers
44 */
45public abstract class TestCipher {
46
47    private final String SUNJCE = "SunJCE";
48    private final String ALGORITHM;
49    private final String[] MODES;
50    private final String[] PADDINGS;
51
52    /* Used to test variable-key-length ciphers:
53       Key size tested is increment of KEYCUTTER from minKeySize
54       to min(maxKeySize, Cipher.getMaxAllowedKeyLength(algo)).
55    */
56    private final int KEYCUTTER = 8;
57    private final int minKeySize;
58    private final int maxKeySize;
59
60    // Used to assert that Encryption/Decryption works with same buffer
61    // TEXT_LEN is multiple of blocks in order to work against ciphers w/ NoPadding
62    private final int TEXT_LEN = 800;
63    private final int ENC_OFFSET = 6;
64    private final int STORAGE_OFFSET = 3;
65    private final int PAD_BYTES = 16;
66
67    private final byte[] IV;
68    private final byte[] INPUT_TEXT;
69
70    // for variable-key-length ciphers
71    TestCipher(String algo, String[] modes, String[] paddings,
72            int minKeySize, int maxKeySize) throws NoSuchAlgorithmException {
73        ALGORITHM = algo;
74        MODES = modes;
75        PADDINGS = paddings;
76        this.minKeySize = minKeySize;
77        int maxAllowedKeySize = Cipher.getMaxAllowedKeyLength(ALGORITHM);
78        if (maxKeySize > maxAllowedKeySize) {
79            maxKeySize = maxAllowedKeySize;
80        }
81        this.maxKeySize = maxKeySize;
82        IV = generateBytes(8);
83        INPUT_TEXT = generateBytes(TEXT_LEN + PAD_BYTES + ENC_OFFSET);
84    }
85
86    // for fixed-key-length ciphers
87    TestCipher(String algo, String[] modes, String[] paddings) {
88        ALGORITHM = algo;
89        MODES = modes;
90        PADDINGS = paddings;
91        this.minKeySize = this.maxKeySize = 0;
92
93        IV = generateBytes(8);
94        INPUT_TEXT = generateBytes(TEXT_LEN + PAD_BYTES + ENC_OFFSET);
95    }
96
97    private static byte[] generateBytes(int length) {
98        byte[] bytes = new byte[length];
99        for (int i = 0; i < length; i++) {
100            bytes[i] = (byte) (i & 0xff);
101        }
102        return bytes;
103    }
104
105    private boolean isMultipleKeyLengthSupported() {
106        return (maxKeySize != minKeySize);
107    }
108
109    public void runAll() throws InvalidKeyException,
110            NoSuchPaddingException, InvalidAlgorithmParameterException,
111            ShortBufferException, IllegalBlockSizeException,
112            BadPaddingException, NoSuchAlgorithmException,
113            NoSuchProviderException {
114
115        for (String mode : MODES) {
116            for (String padding : PADDINGS) {
117                if (!isMultipleKeyLengthSupported()) {
118                    runTest(mode, padding, minKeySize);
119                } else {
120                    int keySize = maxKeySize;
121                    while (keySize >= minKeySize) {
122                        out.println("With Key Strength: " + keySize);
123                        runTest(mode, padding, keySize);
124                        keySize -= KEYCUTTER;
125                    }
126                }
127            }
128        }
129    }
130
131    private void runTest(String mo, String pad, int keySize)
132            throws NoSuchPaddingException, BadPaddingException,
133            ShortBufferException, IllegalBlockSizeException,
134            InvalidAlgorithmParameterException, InvalidKeyException,
135            NoSuchAlgorithmException, NoSuchProviderException {
136
137        String TRANSFORMATION = ALGORITHM + "/" + mo + "/" + pad;
138        out.println("Testing: " + TRANSFORMATION);
139
140        // Initialization
141        Cipher ci = Cipher.getInstance(TRANSFORMATION, SUNJCE);
142        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM, SUNJCE);
143        if (keySize != 0) {
144            kg.init(keySize);
145        }
146
147        SecretKey key = kg.generateKey();
148        SecretKeySpec skeySpec = new SecretKeySpec(key.getEncoded(), ALGORITHM);
149
150        AlgorithmParameterSpec aps = new IvParameterSpec(IV);
151        if (mo.equalsIgnoreCase("ECB")) {
152            ci.init(Cipher.ENCRYPT_MODE, key);
153        } else {
154            ci.init(Cipher.ENCRYPT_MODE, key, aps);
155        }
156
157        // Encryption
158        byte[] plainText = INPUT_TEXT.clone();
159
160        // Generate cipher and save to separate buffer
161        byte[] cipherText = ci.doFinal(INPUT_TEXT, ENC_OFFSET, TEXT_LEN);
162
163        // Generate cipher and save to same buffer
164        int enc_bytes = ci.update(
165                INPUT_TEXT, ENC_OFFSET, TEXT_LEN, INPUT_TEXT, STORAGE_OFFSET);
166        enc_bytes += ci.doFinal(INPUT_TEXT, enc_bytes + STORAGE_OFFSET);
167
168        if (!equalsBlock(
169                INPUT_TEXT, STORAGE_OFFSET, enc_bytes,
170                cipherText, 0, cipherText.length)) {
171            throw new RuntimeException(
172                    "Different ciphers generated with same buffer");
173        }
174
175        // Decryption
176        if (mo.equalsIgnoreCase("ECB")) {
177            ci.init(Cipher.DECRYPT_MODE, skeySpec);
178        } else {
179            ci.init(Cipher.DECRYPT_MODE, skeySpec, aps);
180        }
181
182        // Recover text from cipher and save to separate buffer
183        byte[] recoveredText = ci.doFinal(cipherText, 0, cipherText.length);
184
185        if (!equalsBlock(
186                plainText, ENC_OFFSET, TEXT_LEN,
187                recoveredText, 0, recoveredText.length)) {
188            throw new RuntimeException(
189                    "Recovered text not same as plain text");
190        } else {
191            out.println("Recovered and plain text are same");
192        }
193
194        // Recover text from cipher and save to same buffer
195        int dec_bytes = ci.update(
196                INPUT_TEXT, STORAGE_OFFSET, enc_bytes, INPUT_TEXT, ENC_OFFSET);
197        dec_bytes += ci.doFinal(INPUT_TEXT, dec_bytes + ENC_OFFSET);
198
199        if (!equalsBlock(
200                plainText, ENC_OFFSET, TEXT_LEN,
201                INPUT_TEXT, ENC_OFFSET, dec_bytes)) {
202            throw new RuntimeException(
203                    "Recovered text not same as plain text with same buffer");
204        } else {
205            out.println("Recovered and plain text are same with same buffer");
206        }
207
208        out.println("Test Passed.");
209    }
210
211    private static boolean equalsBlock(byte[] b1, int off1, int len1,
212            byte[] b2, int off2, int len2) {
213        if (len1 != len2) {
214            return false;
215        }
216        for (int i = off1, j = off2, k = 0; k < len1; i++, j++, k++) {
217            if (b1[i] != b2[j]) {
218                return false;
219            }
220        }
221        return true;
222    }
223}
224