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  */
  37 import java.security.NoSuchAlgorithmException;
  38 import java.security.SecureRandom;
  39 import java.security.Security;
  40 import java.security.SecureRandomParameters;
  41 import java.security.DrbgParameters;
  42 import java.security.DrbgParameters.Instantiation;
  43 import java.security.DrbgParameters.Capability;
  44 import javax.crypto.Cipher;
  45 
  46 public 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[]{Integer.MIN_VALUE, -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.replaceAll("AES-", ""));
 199                 int maxStrengthSupported = Cipher.getMaxAllowedKeyLength("AES");
 200                 if (strength > maxStrengthSupported
 201                         || algoStrength > maxStrengthSupported) {
 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 }