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