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 24import sun.security.provider.EntropySource; 25import sun.security.provider.MoreDrbgParameters; 26 27import javax.crypto.Cipher; 28import java.io.BufferedReader; 29import java.io.ByteArrayOutputStream; 30import java.io.File; 31import java.io.InputStream; 32import java.io.InputStreamReader; 33import java.io.PrintStream; 34import java.lang.*; 35import java.security.NoSuchAlgorithmException; 36import java.security.SecureRandom; 37import java.security.DrbgParameters; 38import java.util.ArrayDeque; 39import java.util.Arrays; 40import java.util.Queue; 41import java.util.stream.Stream; 42import java.util.zip.ZipEntry; 43import java.util.zip.ZipFile; 44import java.util.zip.ZipInputStream; 45 46import static java.security.DrbgParameters.Capability.*; 47 48/** 49 * The Known-output DRBG test. The test vector can be obtained from 50 * http://csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors.zip. 51 * 52 * Manually run this test with 53 * 54 * java DrbgCavp drbgtestvectors.zip 55 * 56 */ 57public class DrbgCavp { 58 59 // the current nonce 60 private static byte[] nonce; 61 62 // A buffer to store test materials for the current call and 63 // can be printed out of an error occurs. 64 private static ByteArrayOutputStream bout = new ByteArrayOutputStream(); 65 66 // Save err for restoring 67 private static PrintStream err = System.err; 68 69 private static final int AES_LIMIT; 70 71 static { 72 try { 73 AES_LIMIT = Cipher.getMaxAllowedKeyLength("AES"); 74 } catch (Exception e) { 75 // should not happen 76 throw new AssertionError("Cannot detect AES"); 77 } 78 } 79 80 public static void main(String[] args) throws Exception { 81 82 if (args.length != 1) { 83 System.out.println("Usage: java DrbgCavp drbgtestvectors.zip"); 84 return; 85 } 86 File tv = new File(args[0]); 87 88 EntropySource es = new TestEntropySource(); 89 System.setErr(new PrintStream(bout)); 90 91 // The testsuite is a zip file containing more zip files for different 92 // working modes. Each internal zip file contains test materials for 93 // different mechanisms. 94 95 try (ZipFile zf = new ZipFile(tv)) { 96 String[] modes = {"no_reseed", "pr_false", "pr_true"}; 97 for (String mode : modes) { 98 try (ZipInputStream zis = new ZipInputStream(zf.getInputStream( 99 zf.getEntry("drbgvectors_" + mode + ".zip")))) { 100 while (true) { 101 ZipEntry ze = zis.getNextEntry(); 102 if (ze == null) { 103 break; 104 } 105 String fname = ze.getName(); 106 if (fname.equals("Hash_DRBG.txt") 107 || fname.equals("HMAC_DRBG.txt") 108 || fname.equals("CTR_DRBG.txt")) { 109 String algorithm 110 = fname.substring(0, fname.length() - 4); 111 test(mode, algorithm, es, zis); 112 } 113 } 114 } 115 } 116 } finally { 117 System.setErr(err); 118 } 119 } 120 121 /** 122 * A special entropy source you can set entropy input at will. 123 */ 124 private static class TestEntropySource implements EntropySource { 125 126 private static Queue<byte[]> data = new ArrayDeque<>(); 127 128 @Override 129 public byte[] getEntropy(int minEntropy, int minLength, 130 int maxLength, boolean pr) { 131 byte[] result = data.poll(); 132 if (result == null 133 || result.length < minLength 134 || result.length > maxLength) { 135 throw new RuntimeException("Invalid entropy: " + 136 "need [" + minLength + ", " + maxLength + "], " + 137 (result == null ? "none" : "has " + result.length)); 138 } 139 return result; 140 } 141 142 private static void setEntropy(byte[] input) { 143 data.offer(input); 144 } 145 146 private static void clearEntropy() { 147 data.clear(); 148 } 149 } 150 151 /** 152 * The test. 153 * 154 * // Algorithm line, might contain usedf flag 155 * [AES-128 use df] 156 * // Ignored, use mode argument 157 * [PredictionResistance = True] 158 * // Ignored, just read EntropyInput 159 * [EntropyInputLen = 128] 160 * // Ignored, just read Nonce 161 * [NonceLen = 64] 162 * // Ignored, just read PersonalizationString 163 * [PersonalizationStringLen = 128] 164 * // Ignored, just read AdditionalInput 165 * [AdditionalInputLen = 128] 166 * // Used to allocate buffer for nextBytes() call 167 * [ReturnedBitsLen = 512] 168 * 169 * // A sign we can ignore old unused entropy input 170 * COUNT = 0 171 * 172 * // Instantiate 173 * EntropyInput = 92898f... 174 * Nonce = c2a4d9... 175 * PersonalizationString = ea65ee... // Enough to call getInstance() 176 * 177 * // Reseed 178 * EntropyInputReseed = bfd503... 179 * AdditionalInputReseed = 009e0b... // Enough to call reseed() 180 * 181 * // Generation 182 * AdditionalInput = 1a40fa.... // Enough to call nextBytes() for PR off 183 * EntropyInputPR = 20728a... // Enough to call nextBytes() for PR on 184 * ReturnedBits = 5a3539... // Compare this to last nextBytes() output 185 * 186 * @param mode one of "no_reseed", "pr_false", "pr_true" 187 * @param mech one of "Hash_DRBG", "HMAC_DRBG", "CTR_DRBG" 188 * @param es our own entropy source 189 * @param is test material 190 */ 191 private static void test(String mode, String mech, EntropySource es, 192 InputStream is) throws Exception { 193 194 SecureRandom hd = null; 195 196 // Expected output length in bits as in [ReturnedBitsLen] 197 int outLen = 0; 198 199 // DRBG algorithm as in the algorithm line 200 String algorithm = null; 201 202 // When CTR_DRBG uses a derivation function as in the algorithm line 203 boolean usedf = false; 204 205 // Additional input as in "AdditionalInput" 206 byte[] additional = null; 207 208 // Random bits generated 209 byte[] output = null; 210 211 // Prediction resistance flag, determined by mode 212 boolean isPr = false; 213 214 StringBuilder sb = new StringBuilder(); 215 216 int lineno = 0; 217 218 System.out.println(mode + "/" + mech); 219 220 try (Stream<String> lines = 221 new BufferedReader(new InputStreamReader(is)).lines()) { 222 for (String s: (Iterable<String>) lines::iterator) { 223 lineno++; 224 err.print(hd == null ? '-' : '*'); 225 Line l = new Line(s); 226 if (l.key.contains("no df") || l.key.contains("use df") || 227 l.key.startsWith("SHA-")) { 228 sb = new StringBuilder(); 229 bout.reset(); 230 } 231 sb.append(String.format( 232 "%9s %4s %5d %s\n", mode, mech, lineno, s)); 233 switch (l.key) { 234 case "3KeyTDEA no df": 235 case "AES-128 no df": 236 case "AES-192 no df": 237 case "AES-256 no df": 238 case "3KeyTDEA use df": 239 case "AES-128 use df": 240 case "AES-192 use df": 241 case "AES-256 use df": 242 algorithm = l.key.split(" ")[0]; 243 usedf = l.key.contains("use df"); 244 break; 245 case "ReturnedBitsLen": 246 outLen = l.vint(); 247 output = new byte[outLen / 8]; 248 break; 249 case "EntropyInput": 250 TestEntropySource.setEntropy(l.vdata()); 251 break; 252 case "Nonce": 253 nonce = l.vdata(); 254 break; 255 case "COUNT": 256 // Remove unused entropy (say, when AES-256 is skipped) 257 TestEntropySource.clearEntropy(); 258 break; 259 case "PersonalizationString": 260 try { 261 isPr = mode.equals("pr_true"); 262 byte[] ps = null; 263 if (l.vdata().length != 0) { 264 ps = l.vdata(); 265 } 266 267 // MoreDrbgParameters must be used because we 268 // want to set entropy input and nonce. Since 269 // it can also set mechanism, algorithm and usedf, 270 // we don't need to touch securerandom.drbg.config. 271 hd = SecureRandom.getInstance("DRBG", 272 new MoreDrbgParameters(es, mech, algorithm, 273 nonce, usedf, 274 DrbgParameters.instantiation( 275 -1, 276 isPr ? PR_AND_RESEED 277 : RESEED_ONLY, 278 ps)), 279 "SUN"); 280 } catch (NoSuchAlgorithmException iae) { 281 // We don't support SHA-1 and 3KeyTDEA. AES-192 or 282 // AES-256 might not be available. This is OK. 283 if (algorithm.equals("SHA-1") || 284 algorithm.equals("3KeyTDEA") || 285 ((algorithm.equals("AES-192") 286 || algorithm.equals("AES-256")) 287 && AES_LIMIT == 128)) { 288 hd = null; 289 } else { 290 throw iae; 291 } 292 } 293 break; 294 case "EntropyInputReseed": 295 TestEntropySource.setEntropy(l.vdata()); 296 break; 297 case "AdditionalInputReseed": 298 if (l.vdata().length == 0) { 299 additional = null; 300 } else { 301 additional = l.vdata(); 302 } 303 if (hd != null) { 304 if (additional == null) { 305 hd.reseed(); 306 } else { 307 hd.reseed(DrbgParameters.reseed( 308 isPr, additional)); 309 } 310 } 311 break; 312 case "EntropyInputPR": 313 if (l.vdata().length != 0) { 314 TestEntropySource.setEntropy(l.vdata()); 315 } 316 if (mode.equals("pr_true")) { 317 if (hd != null) { 318 if (additional == null) { 319 hd.nextBytes(output); 320 } else { 321 hd.nextBytes(output, 322 DrbgParameters.nextBytes( 323 -1, isPr, additional)); 324 } 325 } 326 } 327 break; 328 case "AdditionalInput": 329 if (l.vdata().length == 0) { 330 additional = null; 331 } else { 332 additional = l.vdata(); 333 } 334 if (!mode.equals("pr_true")) { 335 if (hd != null) { 336 if (additional == null) { 337 hd.nextBytes(output); 338 } else { 339 hd.nextBytes(output, 340 DrbgParameters.nextBytes( 341 -1, isPr, additional)); 342 } 343 } 344 } 345 break; 346 case "ReturnedBits": 347 if (hd != null) { 348 if (!Arrays.equals(output, l.vdata())) { 349 throw new Exception("\nExpected: " + 350 l.value + "\n Actual: " + hex(output)); 351 } 352 } 353 break; 354 default: 355 // Algorithm line for Hash_DRBG and HMAC_DRBG 356 if (l.key.startsWith("SHA-")) { 357 algorithm = l.key; 358 } 359 } 360 } 361 err.println(); 362 } catch (Exception e) { 363 err.println(); 364 err.println(sb.toString()); 365 err.println(bout.toString()); 366 throw e; 367 } 368 } 369 370 /** 371 * Parse a line from test material. 372 * 373 * Brackets are removed. Key and value separated. 374 */ 375 static class Line { 376 377 final String key; 378 final String value; 379 380 Line(String s) { 381 s = s.trim(); 382 if (s.length() >= 2) { 383 if (s.charAt(0) == '[') { 384 s = s.substring(1, s.length() - 1); 385 } 386 } 387 if (s.indexOf('=') < 0) { 388 key = s; 389 value = null; 390 } else { 391 key = s.substring(0, s.indexOf('=')).trim(); 392 value = s.substring(s.indexOf('=') + 1).trim(); 393 } 394 } 395 396 int vint() { 397 return Integer.parseInt(value); 398 } 399 400 byte[] vdata() { 401 return xeh(value); 402 } 403 } 404 405 // Bytes to HEX 406 private static String hex(byte[] in) { 407 StringBuilder sb = new StringBuilder(); 408 for (byte b: in) { 409 sb.append(String.format("%02x", b&0xff)); 410 } 411 return sb.toString(); 412 } 413 414 // HEX to bytes 415 private static byte[] xeh(String in) { 416 in = in.replaceAll(" ", ""); 417 int len = in.length() / 2; 418 byte[] out = new byte[len]; 419 for (int i = 0; i < len; i++) { 420 out[i] = (byte) Integer.parseInt( 421 in.substring(i * 2, i * 2 + 2), 16); 422 } 423 return out; 424 } 425} 426