test/jdk/java/security/SecureRandom/ApiTest.java
author shade
Tue, 15 Oct 2019 19:38:59 +0200
changeset 58604 791217cdc433
parent 47216 71c04702a3d5
permissions -rw-r--r--
8232205: Shenandoah: missing "Update References" -> "Update Roots" tracing Reviewed-by: rkennke, zgu

/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 8141039
 * @library /lib/testlibrary
 * @summary This test do API coverage for SecureRandom. It covers most of
 *          supported operations along with possible positive and negative
 *          parameters for DRBG mechanism.
 * @run main/othervm ApiTest Hash_DRBG
 * @run main/othervm ApiTest HMAC_DRBG
 * @run main/othervm ApiTest CTR_DRBG
 * @run main/othervm ApiTest SHA1PRNG
 * @run main/othervm ApiTest NATIVE
 */
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.SecureRandomParameters;
import java.security.DrbgParameters;
import java.security.DrbgParameters.Instantiation;
import java.security.DrbgParameters.Capability;
import javax.crypto.Cipher;

public class ApiTest {

    private static final boolean SHOULD_PASS = true;
    private static final long SEED = 1l;
    private static final String INVALID_ALGO = "INVALID";
    private static final String DRBG_CONFIG = "securerandom.drbg.config";
    private static final String DRBG_CONFIG_VALUE
            = Security.getProperty(DRBG_CONFIG);

    public static void main(String[] args) throws Exception {
        System.setProperty("java.security.egd", "file:/dev/urandom");

        if (args == null || args.length < 1) {
            throw new RuntimeException("No mechanism available to run test.");
        }
        String mech
                = "NATIVE".equals(args[0]) ? supportedNativeAlgo() : args[0];
        String[] algs = null;
        boolean success = true;

        try {
            if (!isDRBG(mech)) {
                SecureRandom random = SecureRandom.getInstance(mech);
                verifyAPI(random, mech);
                return;
            } else if (mech.equals("CTR_DRBG")) {
                algs = new String[]{"AES-128", "AES-192", "AES-256",
                    INVALID_ALGO};
            } else if (mech.equals("Hash_DRBG") || mech.equals("HMAC_DRBG")) {
                algs = new String[]{"SHA-224", "SHA-256", "SHA-512/224",
                    "SHA-512/256", "SHA-384", "SHA-512", INVALID_ALGO};
            } else {
                throw new RuntimeException(
                        String.format("Not a valid mechanism '%s'", mech));
            }
            runForEachMech(mech, algs);
        } catch (Exception e) {
            e.printStackTrace(System.out);
            success = false;
        }

        if (!success) {
            throw new RuntimeException("At least one test failed.");
        }
    }

    /**
     * Run the test for a DRBG mechanism with a possible set of parameter
     * combination.
     * @param mech DRBG mechanism name
     * @param algs Algorithm supported by each mechanism
     * @throws Exception
     */
    private static void runForEachMech(String mech, String[] algs)
            throws Exception {
        for (String alg : algs) {
            runForEachAlg(mech, alg);
        }
    }

    private static void runForEachAlg(String mech, String alg)
            throws Exception {
        for (int strength : new int[]{-1, 0, 1, 223, 224,
            192, 255, 256}) {
            for (Capability cp : Capability.values()) {
                for (byte[] pr : new byte[][]{null, new byte[]{},
                    "personal".getBytes()}) {
                    SecureRandomParameters param
                            = DrbgParameters.instantiation(strength, cp, pr);
                    runForEachParam(mech, alg, param);
                }
            }
        }
    }

    private static void runForEachParam(String mech, String alg,
            SecureRandomParameters param) throws Exception {

        for (boolean df : new Boolean[]{true, false}) {
            try {
                Security.setProperty(DRBG_CONFIG, mech + "," + alg + ","
                        + (df ? "use_df" : "no_df"));
                System.out.printf("%nParameter for SecureRandom "
                        + "mechanism: %s is (param:%s, algo:%s, df:%s)",
                        mech, param, alg, df);
                SecureRandom sr = SecureRandom.getInstance("DRBG", param);
                verifyAPI(sr, mech);
            } catch (NoSuchAlgorithmException e) {
                // Verify exception status for current test.
                checkException(getDefaultAlg(mech, alg), param, e);
            } finally {
                Security.setProperty(DRBG_CONFIG, DRBG_CONFIG_VALUE);
            }
        }
    }

    /**
     * Returns the algorithm supported for input mechanism.
     * @param mech Mechanism name
     * @param alg Algorithm name
     * @return Algorithm name
     */
    private static String getDefaultAlg(String mech, String alg)
            throws NoSuchAlgorithmException {
        if (alg == null) {
            switch (mech) {
                case "Hash_DRBG":
                case "HMAC_DRBG":
                    return "SHA-256";
                case "CTR_DRBG":
                    return (Cipher.getMaxAllowedKeyLength("AES") < 256)
                            ? "AES-128" : "AES-256";
                default:
                    throw new RuntimeException("Mechanism not supported");
            }
        }
        return alg;
    }

    /**
     * Verify the exception type either it is expected to occur or not.
     * @param alg Algorithm name
     * @param param DRBG parameter
     * @param e Exception to verify
     * @throws NoSuchAlgorithmException
     */
    private static void checkException(String alg, SecureRandomParameters param,
            NoSuchAlgorithmException e) throws NoSuchAlgorithmException {

        int strength = ((Instantiation) param).getStrength();
        boolean error = true;
        switch (alg) {
            case INVALID_ALGO:
                error = false;
                break;
            case "SHA-224":
            case "SHA-512/224":
                if (strength > 192) {
                    error = false;
                }
                break;
            case "SHA-256":
            case "SHA-512/256":
            case "SHA-384":
            case "SHA-512":
                if (strength > 256) {
                    error = false;
                }
                break;
            case "AES-128":
            case "AES-192":
            case "AES-256":
                int algoStrength = Integer.parseInt(alg.substring("AES-".length()));
                int maxAESStrength = Cipher.getMaxAllowedKeyLength("AES");
                if (strength > algoStrength
                        || algoStrength > maxAESStrength) {
                    error = false;
                }
                break;
        }
        if (error) {
            throw new RuntimeException("Unknown :", e);
        }
    }

    /**
     * Find if the mechanism is a DRBG mechanism.
     * @param mech Mechanism name
     * @return True for DRBG mechanism else False
     */
    private static boolean isDRBG(String mech) {
        return mech.contains("_DRBG");
    }

    /**
     * Find the name of supported native mechanism name for current platform.
     */
    private static String supportedNativeAlgo() {
        String nativeSr = "Windows-PRNG";
        try {
            SecureRandom.getInstance(nativeSr);
        } catch (NoSuchAlgorithmException e) {
            nativeSr = "NativePRNG";
        }
        return nativeSr;
    }

    /**
     * Test a possible set of SecureRandom API for a SecureRandom instance.
     * @param random SecureRandom instance
     * @param mech Mechanism used to create SecureRandom instance
     */
    private static void verifyAPI(SecureRandom random, String mech)
            throws Exception {

        System.out.printf("%nTest SecureRandom mechanism: %s for provider: %s",
                mech, random.getProvider().getName());
        byte[] output = new byte[2];

        // Generate random number.
        random.nextBytes(output);

        // Seed the SecureRandom with a generated seed value of lesser size.
        byte[] seed = random.generateSeed(1);
        random.setSeed(seed);
        random.nextBytes(output);

        // Seed the SecureRandom with a fixed seed value.
        random.setSeed(SEED);
        random.nextBytes(output);

        // Seed the SecureRandom with a larger seed value.
        seed = random.generateSeed(128);
        random.setSeed(seed);
        random.nextBytes(output);

        // Additional operation only supported for DRBG based SecureRandom.
        // Execute the code block and expect to pass for DRBG. If it will fail
        // then it should fail with specified exception type. Else the case
        // will be considered as a test case failure.
        matchExc(() -> {
            random.reseed();
            random.nextBytes(output);
        },
                isDRBG(mech),
                UnsupportedOperationException.class,
                String.format("PASS - Unsupported reseed() method for "
                        + "SecureRandom Algorithm %s ", mech));

        matchExc(() -> {
            random.reseed(DrbgParameters.reseed(false, new byte[]{}));
            random.nextBytes(output);
        },
                isDRBG(mech),
                UnsupportedOperationException.class,
                String.format("PASS - Unsupported reseed(param) method for "
                        + "SecureRandom Algorithm %s ", mech));

        matchExc(() -> {
            random.reseed(DrbgParameters.reseed(true, new byte[]{}));
            random.nextBytes(output);
        },
                isDRBG(mech),
                !isSupportPR(mech, random) ? IllegalArgumentException.class
                        : UnsupportedOperationException.class,
                String.format("PASS - Unsupported or illegal reseed(param) "
                        + "method for SecureRandom Algorithm %s ", mech));

        matchExc(() -> random.nextBytes(output,
                DrbgParameters.nextBytes(-1, false, new byte[]{})),
                isDRBG(mech),
                UnsupportedOperationException.class,
                String.format("PASS - Unsupported nextBytes(out, nextByteParam)"
                        + " method for SecureRandom Algorithm %s ", mech));

        matchExc(() -> random.nextBytes(output,
                DrbgParameters.nextBytes(-1, true, new byte[]{})),
                isDRBG(mech),
                !isSupportPR(mech, random) ? IllegalArgumentException.class
                        : UnsupportedOperationException.class,
                String.format("PASS - Unsupported or illegal "
                        + "nextBytes(out, nextByteParam) method for "
                        + "SecureRandom Algorithm %s ", mech));

        matchExc(() -> {
            random.reseed(null);
            random.nextBytes(output);
        },
                !SHOULD_PASS,
                IllegalArgumentException.class,
                "PASS - Test is expected to fail when parameter for reseed() "
                + "is null");

        matchExc(() -> random.nextBytes(output, null),
                !SHOULD_PASS,
                IllegalArgumentException.class,
                "PASS - Test is expected to fail when parameter for nextBytes()"
                + " is null");

    }

    private static boolean isSupportPR(String mech, SecureRandom random) {
        return (isDRBG(mech) && ((Instantiation) random.getParameters())
                .getCapability()
                .supportsPredictionResistance());
    }

    private interface RunnableCode {

        void run() throws Exception;
    }

    /**
     * Execute a given code block and verify, if the exception type is expected.
     * @param r Code block to run
     * @param ex Expected exception type
     * @param shouldPass If the code execution expected to pass without failure
     * @param msg Message to log in case of expected failure
     */
    private static void matchExc(RunnableCode r, boolean shouldPass, Class ex,
            String msg) {
        try {
            r.run();
            if (!shouldPass) {
                throw new RuntimeException("Excecution should fail here.");
            }
        } catch (Exception e) {
            System.out.printf("%nOccured exception: %s - Expected exception: "
                    + "%s : ", e.getClass(), ex.getCanonicalName());
            if (ex.isAssignableFrom(e.getClass())) {
                System.out.printf("%n%s : Expected Exception occured: %s : ",
                        e.getClass(), msg);
            } else if (shouldPass) {
                throw new RuntimeException(e);
            } else {
                System.out.printf("Ignore the following exception: %s%n",
                        e.getMessage());
            }
        }
    }
}