1/*
2 * Copyright (c) 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
24/*
25 * @test
26 * @bug 8141039
27 * @library /lib/testlibrary
28 * @summary This test do API coverage for SecureRandom. It covers most of
29 *          supported operations along with possible positive and negative
30 *          parameters for DRBG mechanism.
31 * @run main/othervm ApiTest Hash_DRBG
32 * @run main/othervm ApiTest HMAC_DRBG
33 * @run main/othervm ApiTest CTR_DRBG
34 * @run main/othervm ApiTest SHA1PRNG
35 * @run main/othervm ApiTest NATIVE
36 */
37import java.security.NoSuchAlgorithmException;
38import java.security.SecureRandom;
39import java.security.Security;
40import java.security.SecureRandomParameters;
41import java.security.DrbgParameters;
42import java.security.DrbgParameters.Instantiation;
43import java.security.DrbgParameters.Capability;
44import javax.crypto.Cipher;
45
46public class ApiTest {
47
48    private static final boolean SHOULD_PASS = true;
49    private static final long SEED = 1l;
50    private static final String INVALID_ALGO = "INVALID";
51    private static final String DRBG_CONFIG = "securerandom.drbg.config";
52    private static final String DRBG_CONFIG_VALUE
53            = Security.getProperty(DRBG_CONFIG);
54
55    public static void main(String[] args) throws Exception {
56        System.setProperty("java.security.egd", "file:/dev/urandom");
57
58        if (args == null || args.length < 1) {
59            throw new RuntimeException("No mechanism available to run test.");
60        }
61        String mech
62                = "NATIVE".equals(args[0]) ? supportedNativeAlgo() : args[0];
63        String[] algs = null;
64        boolean success = true;
65
66        try {
67            if (!isDRBG(mech)) {
68                SecureRandom random = SecureRandom.getInstance(mech);
69                verifyAPI(random, mech);
70                return;
71            } else if (mech.equals("CTR_DRBG")) {
72                algs = new String[]{"AES-128", "AES-192", "AES-256",
73                    INVALID_ALGO};
74            } else if (mech.equals("Hash_DRBG") || mech.equals("HMAC_DRBG")) {
75                algs = new String[]{"SHA-224", "SHA-256", "SHA-512/224",
76                    "SHA-512/256", "SHA-384", "SHA-512", INVALID_ALGO};
77            } else {
78                throw new RuntimeException(
79                        String.format("Not a valid mechanism '%s'", mech));
80            }
81            runForEachMech(mech, algs);
82        } catch (Exception e) {
83            e.printStackTrace(System.out);
84            success = false;
85        }
86
87        if (!success) {
88            throw new RuntimeException("At least one test failed.");
89        }
90    }
91
92    /**
93     * Run the test for a DRBG mechanism with a possible set of parameter
94     * combination.
95     * @param mech DRBG mechanism name
96     * @param algs Algorithm supported by each mechanism
97     * @throws Exception
98     */
99    private static void runForEachMech(String mech, String[] algs)
100            throws Exception {
101        for (String alg : algs) {
102            runForEachAlg(mech, alg);
103        }
104    }
105
106    private static void runForEachAlg(String mech, String alg)
107            throws Exception {
108        for (int strength : new int[]{-1, 0, 1, 223, 224,
109            192, 255, 256}) {
110            for (Capability cp : Capability.values()) {
111                for (byte[] pr : new byte[][]{null, new byte[]{},
112                    "personal".getBytes()}) {
113                    SecureRandomParameters param
114                            = DrbgParameters.instantiation(strength, cp, pr);
115                    runForEachParam(mech, alg, param);
116                }
117            }
118        }
119    }
120
121    private static void runForEachParam(String mech, String alg,
122            SecureRandomParameters param) throws Exception {
123
124        for (boolean df : new Boolean[]{true, false}) {
125            try {
126                Security.setProperty(DRBG_CONFIG, mech + "," + alg + ","
127                        + (df ? "use_df" : "no_df"));
128                System.out.printf("%nParameter for SecureRandom "
129                        + "mechanism: %s is (param:%s, algo:%s, df:%s)",
130                        mech, param, alg, df);
131                SecureRandom sr = SecureRandom.getInstance("DRBG", param);
132                verifyAPI(sr, mech);
133            } catch (NoSuchAlgorithmException e) {
134                // Verify exception status for current test.
135                checkException(getDefaultAlg(mech, alg), param, e);
136            } finally {
137                Security.setProperty(DRBG_CONFIG, DRBG_CONFIG_VALUE);
138            }
139        }
140    }
141
142    /**
143     * Returns the algorithm supported for input mechanism.
144     * @param mech Mechanism name
145     * @param alg Algorithm name
146     * @return Algorithm name
147     */
148    private static String getDefaultAlg(String mech, String alg)
149            throws NoSuchAlgorithmException {
150        if (alg == null) {
151            switch (mech) {
152                case "Hash_DRBG":
153                case "HMAC_DRBG":
154                    return "SHA-256";
155                case "CTR_DRBG":
156                    return (Cipher.getMaxAllowedKeyLength("AES") < 256)
157                            ? "AES-128" : "AES-256";
158                default:
159                    throw new RuntimeException("Mechanism not supported");
160            }
161        }
162        return alg;
163    }
164
165    /**
166     * Verify the exception type either it is expected to occur or not.
167     * @param alg Algorithm name
168     * @param param DRBG parameter
169     * @param e Exception to verify
170     * @throws NoSuchAlgorithmException
171     */
172    private static void checkException(String alg, SecureRandomParameters param,
173            NoSuchAlgorithmException e) throws NoSuchAlgorithmException {
174
175        int strength = ((Instantiation) param).getStrength();
176        boolean error = true;
177        switch (alg) {
178            case INVALID_ALGO:
179                error = false;
180                break;
181            case "SHA-224":
182            case "SHA-512/224":
183                if (strength > 192) {
184                    error = false;
185                }
186                break;
187            case "SHA-256":
188            case "SHA-512/256":
189            case "SHA-384":
190            case "SHA-512":
191                if (strength > 256) {
192                    error = false;
193                }
194                break;
195            case "AES-128":
196            case "AES-192":
197            case "AES-256":
198                int algoStrength = Integer.parseInt(alg.substring("AES-".length()));
199                int maxAESStrength = Cipher.getMaxAllowedKeyLength("AES");
200                if (strength > algoStrength
201                        || algoStrength > maxAESStrength) {
202                    error = false;
203                }
204                break;
205        }
206        if (error) {
207            throw new RuntimeException("Unknown :", e);
208        }
209    }
210
211    /**
212     * Find if the mechanism is a DRBG mechanism.
213     * @param mech Mechanism name
214     * @return True for DRBG mechanism else False
215     */
216    private static boolean isDRBG(String mech) {
217        return mech.contains("_DRBG");
218    }
219
220    /**
221     * Find the name of supported native mechanism name for current platform.
222     */
223    private static String supportedNativeAlgo() {
224        String nativeSr = "Windows-PRNG";
225        try {
226            SecureRandom.getInstance(nativeSr);
227        } catch (NoSuchAlgorithmException e) {
228            nativeSr = "NativePRNG";
229        }
230        return nativeSr;
231    }
232
233    /**
234     * Test a possible set of SecureRandom API for a SecureRandom instance.
235     * @param random SecureRandom instance
236     * @param mech Mechanism used to create SecureRandom instance
237     */
238    private static void verifyAPI(SecureRandom random, String mech)
239            throws Exception {
240
241        System.out.printf("%nTest SecureRandom mechanism: %s for provider: %s",
242                mech, random.getProvider().getName());
243        byte[] output = new byte[2];
244
245        // Generate random number.
246        random.nextBytes(output);
247
248        // Seed the SecureRandom with a generated seed value of lesser size.
249        byte[] seed = random.generateSeed(1);
250        random.setSeed(seed);
251        random.nextBytes(output);
252
253        // Seed the SecureRandom with a fixed seed value.
254        random.setSeed(SEED);
255        random.nextBytes(output);
256
257        // Seed the SecureRandom with a larger seed value.
258        seed = random.generateSeed(128);
259        random.setSeed(seed);
260        random.nextBytes(output);
261
262        // Additional operation only supported for DRBG based SecureRandom.
263        // Execute the code block and expect to pass for DRBG. If it will fail
264        // then it should fail with specified exception type. Else the case
265        // will be considered as a test case failure.
266        matchExc(() -> {
267            random.reseed();
268            random.nextBytes(output);
269        },
270                isDRBG(mech),
271                UnsupportedOperationException.class,
272                String.format("PASS - Unsupported reseed() method for "
273                        + "SecureRandom Algorithm %s ", mech));
274
275        matchExc(() -> {
276            random.reseed(DrbgParameters.reseed(false, new byte[]{}));
277            random.nextBytes(output);
278        },
279                isDRBG(mech),
280                UnsupportedOperationException.class,
281                String.format("PASS - Unsupported reseed(param) method for "
282                        + "SecureRandom Algorithm %s ", mech));
283
284        matchExc(() -> {
285            random.reseed(DrbgParameters.reseed(true, new byte[]{}));
286            random.nextBytes(output);
287        },
288                isDRBG(mech),
289                !isSupportPR(mech, random) ? IllegalArgumentException.class
290                        : UnsupportedOperationException.class,
291                String.format("PASS - Unsupported or illegal reseed(param) "
292                        + "method for SecureRandom Algorithm %s ", mech));
293
294        matchExc(() -> random.nextBytes(output,
295                DrbgParameters.nextBytes(-1, false, new byte[]{})),
296                isDRBG(mech),
297                UnsupportedOperationException.class,
298                String.format("PASS - Unsupported nextBytes(out, nextByteParam)"
299                        + " method for SecureRandom Algorithm %s ", mech));
300
301        matchExc(() -> random.nextBytes(output,
302                DrbgParameters.nextBytes(-1, true, new byte[]{})),
303                isDRBG(mech),
304                !isSupportPR(mech, random) ? IllegalArgumentException.class
305                        : UnsupportedOperationException.class,
306                String.format("PASS - Unsupported or illegal "
307                        + "nextBytes(out, nextByteParam) method for "
308                        + "SecureRandom Algorithm %s ", mech));
309
310        matchExc(() -> {
311            random.reseed(null);
312            random.nextBytes(output);
313        },
314                !SHOULD_PASS,
315                IllegalArgumentException.class,
316                "PASS - Test is expected to fail when parameter for reseed() "
317                + "is null");
318
319        matchExc(() -> random.nextBytes(output, null),
320                !SHOULD_PASS,
321                IllegalArgumentException.class,
322                "PASS - Test is expected to fail when parameter for nextBytes()"
323                + " is null");
324
325    }
326
327    private static boolean isSupportPR(String mech, SecureRandom random) {
328        return (isDRBG(mech) && ((Instantiation) random.getParameters())
329                .getCapability()
330                .supportsPredictionResistance());
331    }
332
333    private interface RunnableCode {
334
335        void run() throws Exception;
336    }
337
338    /**
339     * Execute a given code block and verify, if the exception type is expected.
340     * @param r Code block to run
341     * @param ex Expected exception type
342     * @param shouldPass If the code execution expected to pass without failure
343     * @param msg Message to log in case of expected failure
344     */
345    private static void matchExc(RunnableCode r, boolean shouldPass, Class ex,
346            String msg) {
347        try {
348            r.run();
349            if (!shouldPass) {
350                throw new RuntimeException("Excecution should fail here.");
351            }
352        } catch (Exception e) {
353            System.out.printf("%nOccured exception: %s - Expected exception: "
354                    + "%s : ", e.getClass(), ex.getCanonicalName());
355            if (ex.isAssignableFrom(e.getClass())) {
356                System.out.printf("%n%s : Expected Exception occured: %s : ",
357                        e.getClass(), msg);
358            } else if (shouldPass) {
359                throw new RuntimeException(e);
360            } else {
361                System.out.printf("Ignore the following exception: %s%n",
362                        e.getMessage());
363            }
364        }
365    }
366}
367