/* * 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()); } } } }