1/*
2 * Copyright (c) 2007, 2015, 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.io.ByteArrayInputStream;
27import java.io.IOException;
28import java.security.InvalidAlgorithmParameterException;
29import java.security.InvalidKeyException;
30import java.security.NoSuchAlgorithmException;
31import java.security.Provider;
32import java.security.Security;
33import java.security.spec.AlgorithmParameterSpec;
34import java.security.spec.InvalidKeySpecException;
35import javax.crypto.Cipher;
36import javax.crypto.CipherInputStream;
37import javax.crypto.KeyGenerator;
38import javax.crypto.NoSuchPaddingException;
39import javax.crypto.SecretKey;
40import javax.crypto.SecretKeyFactory;
41import javax.crypto.spec.IvParameterSpec;
42import javax.crypto.spec.PBEKeySpec;
43import javax.crypto.spec.PBEParameterSpec;
44
45/*
46 * @test
47 * @bug 8048604
48 * @summary This test verifies the assertion "The skip feature of Filter
49 *    streams should be supported." for feature
50 *    CipherInputStream and CipherOutputStream
51 */
52public class CICOSkipTest {
53    /**
54     * Block length.
55     */
56    private static final int BLOCK = 50;
57
58    /**
59     * Saving bytes length.
60     */
61    private static final int SAVE = 45;
62
63    /**
64     * Plain text length.
65     */
66    private static final int PLAIN_TEXT_LENGTH = 800;
67
68    /**
69     * Skip reading byte size. This should be same to BLOCK - SAVE
70     */
71    private static final int DISCARD = BLOCK - SAVE;
72
73    private static final String[] ALGOS = {"DES", "DESede", "Blowfish"};
74    private static final String[] MODES = {"ECB", "CBC", "CFB", "CFB32",
75        "OFB", "OFB64", "PCBC"};
76    private static final String[] PADDINGS = {"NoPadding", "Pkcs5Padding"};
77    private static final String[] PBE_ALGOS = {"PBEWithMD5AndDES",
78        "PBEWithMD5AndDES/CBC/PKCS5Padding"};
79
80    public static void main(String[] args) throws Exception {
81        // how many kinds of padding mode such as PKCS5padding and NoPadding
82        for (String algo : ALGOS) {
83            for (String mode : MODES) {
84                int padKinds = 1;
85                if (mode.equalsIgnoreCase("ECB")
86                        || mode.equalsIgnoreCase("PCBC")
87                        || mode.equalsIgnoreCase("CBC")) {
88                    padKinds = PADDINGS.length;
89                }
90                // PKCS5padding is meaningful only for ECB, CBC, PCBC
91                for (int k = 0; k < padKinds; k++) {
92                    String info = algo + "/" + mode + "/" + PADDINGS[k];
93                    try {
94                        CipherGenerator cg = new CipherGenerator(algo, mode,
95                                PADDINGS[k]);
96                        for (ReadMethod model : ReadMethod.values()) {
97                            runTest(cg.getPair(), info, model);
98                        }
99                    } catch (LengthLimitException exp) {
100                        // skip this if this key length is larger than what's
101                        // configured in the jce jurisdiction policy files
102                        out.println(exp.getMessage() + " is expected.");
103                    }
104                }
105            }
106        }
107        for (String pbeAlgo : PBE_ALGOS) {
108            for (ReadMethod model : ReadMethod.values()) {
109                System.out.println("Testing Algorithm : " + pbeAlgo
110                        + " ReadMethod : " + model);
111                runTest(new CipherGenerator(pbeAlgo).getPair(), pbeAlgo, model);
112            }
113        }
114    }
115
116    private static void runTest(Cipher[] pair, String info, ReadMethod whichRead)
117            throws IOException {
118        byte[] plainText = TestUtilities.generateBytes(PLAIN_TEXT_LENGTH);
119        out.println("Testing: " + info + "/" + whichRead);
120        try (ByteArrayInputStream baInput = new ByteArrayInputStream(plainText);
121                CipherInputStream ciInput1 = new CipherInputStream(baInput,
122                        pair[0]);
123                CipherInputStream ciInput2 = new CipherInputStream(ciInput1,
124                        pair[1]);) {
125            // Skip 5 bytes after read 45 bytes and repeat until finish
126            // (Read from the input and write to the output using 2 types
127            // of buffering : byte[] and int)
128            // So output has size:
129            // (OVERALL/BLOCK)* SAVE = (800 / 50) * 45 = 720 bytes
130            int numOfBlocks = plainText.length / BLOCK;
131
132            // Output buffer.
133            byte[] outputText = new byte[numOfBlocks * SAVE];
134            int index = 0;
135            for (int i = 0; i < numOfBlocks; i++) {
136                index = whichRead.readByte(ciInput2, outputText, SAVE, index);
137                // If available is more than expected discard byte size. Skip
138                // discard bytes, otherwise try to read discard bytes by read.
139                if (ciInput2.available() >= DISCARD) {
140                    ciInput2.skip(DISCARD);
141                } else {
142                    for (int k = 0; k < DISCARD; k++) {
143                        ciInput2.read();
144                    }
145                }
146            }
147            // Verify output is same as input
148            if (!TestUtilities
149                    .equalsBlockPartial(plainText, outputText, BLOCK, SAVE)) {
150                throw new RuntimeException("Test failed with compare fail");
151            }
152        }
153    }
154}
155
156class CipherGenerator {
157    /**
158     * Initialization vector  length.
159     */
160    private static final int IV_LENGTH = 8;
161
162    private static final String PASSWD = "Sesame!(@#$%^&*)";
163
164    private final Cipher[] pair = new Cipher[2];
165
166    // For DES/DESede ciphers
167    CipherGenerator(String algo, String mo, String pad)
168            throws NoSuchAlgorithmException,
169            InvalidAlgorithmParameterException, InvalidKeyException,
170            NoSuchPaddingException, SecurityException, LengthLimitException {
171        // Do initialization
172        KeyGenerator kg = KeyGenerator.getInstance(algo);
173        SecretKey key = kg.generateKey();
174        if (key.getEncoded().length * 8 > Cipher.getMaxAllowedKeyLength(algo)) {
175            // skip this if this key length is larger than what's
176            // configured in the jce jurisdiction policy files
177            throw new LengthLimitException(
178                    "Skip this test if key length is larger than what's"
179                    + "configured in the jce jurisdiction policy files");
180        }
181        AlgorithmParameterSpec aps = null;
182        if (!mo.equalsIgnoreCase("ECB")) {
183            byte[] iv = TestUtilities.generateBytes(IV_LENGTH);
184            aps = new IvParameterSpec(iv);
185        }
186        initCiphers(algo + "/" + mo + "/" + pad, key, aps);
187    }
188
189    // For PBE ciphers
190    CipherGenerator(String algo) throws NoSuchAlgorithmException,
191            InvalidAlgorithmParameterException, InvalidKeyException,
192            NoSuchPaddingException, InvalidKeySpecException {
193        // Do initialization
194        byte[] salt = TestUtilities.generateBytes(IV_LENGTH);
195        int iterCnt = 6;
196        SecretKeyFactory skf = SecretKeyFactory.getInstance(algo.split("/")[0]);
197        SecretKey key = skf
198                .generateSecret(new PBEKeySpec(PASSWD.toCharArray()));
199        AlgorithmParameterSpec aps = new PBEParameterSpec(salt, iterCnt);
200        initCiphers(algo, key, aps);
201    }
202
203    private void initCiphers(String algo, SecretKey key,
204            AlgorithmParameterSpec aps) throws NoSuchAlgorithmException,
205            NoSuchPaddingException, InvalidKeyException,
206            InvalidAlgorithmParameterException {
207        Provider provider = Security.getProvider("SunJCE");
208        if (provider == null) {
209            throw new RuntimeException("SunJCE provider does not exist.");
210        }
211        Cipher ci1 = Cipher.getInstance(algo, provider);
212        ci1.init(Cipher.ENCRYPT_MODE, key, aps);
213        pair[0] = ci1;
214        Cipher ci2 = Cipher.getInstance(algo, provider);
215        ci2.init(Cipher.DECRYPT_MODE, key, aps);
216        pair[1] = ci2;
217    }
218
219    Cipher[] getPair() {
220        return pair;
221    }
222}
223
224enum ReadMethod {
225    // read one byte at a time for save times
226    READ_ONE_BYTE {
227        @Override
228        int readByte(CipherInputStream ciIn2, byte[] outputText, int save,
229                int index) throws IOException {
230            for (int j = 0; j < save; j++, index++) {
231                int buffer0 = ciIn2.read();
232                if (buffer0 != -1) {
233                    outputText[index] = (byte) buffer0;
234                } else {
235                    break;
236                }
237            }
238            return index;
239        }
240    },
241    // read a chunk of save bytes if possible
242    READ_BLOCK {
243        @Override
244        int readByte(CipherInputStream ciIn2, byte[] outputText, int save,
245                int index) throws IOException {
246            int len1 = ciIn2.read(outputText, index, save);
247            out.println("Init: index=" + index + ",len=" + len1);
248            // read more until save bytes
249            index += len1;
250            int len2 = 0;
251            while (len1 != save && len2 != -1) {
252                len2 = ciIn2.read(outputText, index, save - len1);
253                out.println("Cont: index=" + index + ",len=" + len2);
254                len1 += len2;
255                index += len2;
256            }
257            return index;
258        }
259    };
260
261    abstract int readByte(CipherInputStream ciIn2, byte[] outputText, int save,
262            int index) throws IOException;
263};
264
265class LengthLimitException extends Exception {
266
267    public LengthLimitException(String string) {
268        super(string);
269    }
270}
271