/* * 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. */ import sun.security.provider.EntropySource; import sun.security.provider.MoreDrbgParameters; import javax.crypto.Cipher; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.lang.*; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.DrbgParameters; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Queue; import java.util.stream.Stream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; import static java.security.DrbgParameters.Capability.*; /** * The Known-output DRBG test. The test vector can be obtained from * http://csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors.zip. * * Manually run this test with * * java DrbgCavp drbgtestvectors.zip * */ public class DrbgCavp { // the current nonce private static byte[] nonce; // A buffer to store test materials for the current call and // can be printed out of an error occurs. private static ByteArrayOutputStream bout = new ByteArrayOutputStream(); // Save err for restoring private static PrintStream err = System.err; private static final int AES_LIMIT; static { try { AES_LIMIT = Cipher.getMaxAllowedKeyLength("AES"); } catch (Exception e) { // should not happen throw new AssertionError("Cannot detect AES"); } } public static void main(String[] args) throws Exception { if (args.length != 1) { System.out.println("Usage: java DrbgCavp drbgtestvectors.zip"); return; } File tv = new File(args[0]); EntropySource es = new TestEntropySource(); System.setErr(new PrintStream(bout)); // The testsuite is a zip file containing more zip files for different // working modes. Each internal zip file contains test materials for // different mechanisms. try (ZipFile zf = new ZipFile(tv)) { String[] modes = {"no_reseed", "pr_false", "pr_true"}; for (String mode : modes) { try (ZipInputStream zis = new ZipInputStream(zf.getInputStream( zf.getEntry("drbgvectors_" + mode + ".zip")))) { while (true) { ZipEntry ze = zis.getNextEntry(); if (ze == null) { break; } String fname = ze.getName(); if (fname.equals("Hash_DRBG.txt") || fname.equals("HMAC_DRBG.txt") || fname.equals("CTR_DRBG.txt")) { String algorithm = fname.substring(0, fname.length() - 4); test(mode, algorithm, es, zis); } } } } } finally { System.setErr(err); } } /** * A special entropy source you can set entropy input at will. */ private static class TestEntropySource implements EntropySource { private static Queue<byte[]> data = new ArrayDeque<>(); @Override public byte[] getEntropy(int minEntropy, int minLength, int maxLength, boolean pr) { byte[] result = data.poll(); if (result == null || result.length < minLength || result.length > maxLength) { throw new RuntimeException("Invalid entropy: " + "need [" + minLength + ", " + maxLength + "], " + (result == null ? "none" : "has " + result.length)); } return result; } private static void setEntropy(byte[] input) { data.offer(input); } private static void clearEntropy() { data.clear(); } } /** * The test. * * // Algorithm line, might contain usedf flag * [AES-128 use df] * // Ignored, use mode argument * [PredictionResistance = True] * // Ignored, just read EntropyInput * [EntropyInputLen = 128] * // Ignored, just read Nonce * [NonceLen = 64] * // Ignored, just read PersonalizationString * [PersonalizationStringLen = 128] * // Ignored, just read AdditionalInput * [AdditionalInputLen = 128] * // Used to allocate buffer for nextBytes() call * [ReturnedBitsLen = 512] * * // A sign we can ignore old unused entropy input * COUNT = 0 * * // Instantiate * EntropyInput = 92898f... * Nonce = c2a4d9... * PersonalizationString = ea65ee... // Enough to call getInstance() * * // Reseed * EntropyInputReseed = bfd503... * AdditionalInputReseed = 009e0b... // Enough to call reseed() * * // Generation * AdditionalInput = 1a40fa.... // Enough to call nextBytes() for PR off * EntropyInputPR = 20728a... // Enough to call nextBytes() for PR on * ReturnedBits = 5a3539... // Compare this to last nextBytes() output * * @param mode one of "no_reseed", "pr_false", "pr_true" * @param mech one of "Hash_DRBG", "HMAC_DRBG", "CTR_DRBG" * @param es our own entropy source * @param is test material */ private static void test(String mode, String mech, EntropySource es, InputStream is) throws Exception { SecureRandom hd = null; // Expected output length in bits as in [ReturnedBitsLen] int outLen = 0; // DRBG algorithm as in the algorithm line String algorithm = null; // When CTR_DRBG uses a derivation function as in the algorithm line boolean usedf = false; // Additional input as in "AdditionalInput" byte[] additional = null; // Random bits generated byte[] output = null; // Prediction resistance flag, determined by mode boolean isPr = false; StringBuilder sb = new StringBuilder(); int lineno = 0; System.out.println(mode + "/" + mech); try (Stream<String> lines = new BufferedReader(new InputStreamReader(is)).lines()) { for (String s: (Iterable<String>) lines::iterator) { lineno++; err.print(hd == null ? '-' : '*'); Line l = new Line(s); if (l.key.contains("no df") || l.key.contains("use df") || l.key.startsWith("SHA-")) { sb = new StringBuilder(); bout.reset(); } sb.append(String.format( "%9s %4s %5d %s\n", mode, mech, lineno, s)); switch (l.key) { case "3KeyTDEA no df": case "AES-128 no df": case "AES-192 no df": case "AES-256 no df": case "3KeyTDEA use df": case "AES-128 use df": case "AES-192 use df": case "AES-256 use df": algorithm = l.key.split(" ")[0]; usedf = l.key.contains("use df"); break; case "ReturnedBitsLen": outLen = l.vint(); output = new byte[outLen / 8]; break; case "EntropyInput": TestEntropySource.setEntropy(l.vdata()); break; case "Nonce": nonce = l.vdata(); break; case "COUNT": // Remove unused entropy (say, when AES-256 is skipped) TestEntropySource.clearEntropy(); break; case "PersonalizationString": try { isPr = mode.equals("pr_true"); byte[] ps = null; if (l.vdata().length != 0) { ps = l.vdata(); } // MoreDrbgParameters must be used because we // want to set entropy input and nonce. Since // it can also set mechanism, algorithm and usedf, // we don't need to touch securerandom.drbg.config. hd = SecureRandom.getInstance("DRBG", new MoreDrbgParameters(es, mech, algorithm, nonce, usedf, DrbgParameters.instantiation( -1, isPr ? PR_AND_RESEED : RESEED_ONLY, ps)), "SUN"); } catch (NoSuchAlgorithmException iae) { // We don't support SHA-1 and 3KeyTDEA. AES-192 or // AES-256 might not be available. This is OK. if (algorithm.equals("SHA-1") || algorithm.equals("3KeyTDEA") || ((algorithm.equals("AES-192") || algorithm.equals("AES-256")) && AES_LIMIT == 128)) { hd = null; } else { throw iae; } } break; case "EntropyInputReseed": TestEntropySource.setEntropy(l.vdata()); break; case "AdditionalInputReseed": if (l.vdata().length == 0) { additional = null; } else { additional = l.vdata(); } if (hd != null) { if (additional == null) { hd.reseed(); } else { hd.reseed(DrbgParameters.reseed( isPr, additional)); } } break; case "EntropyInputPR": if (l.vdata().length != 0) { TestEntropySource.setEntropy(l.vdata()); } if (mode.equals("pr_true")) { if (hd != null) { if (additional == null) { hd.nextBytes(output); } else { hd.nextBytes(output, DrbgParameters.nextBytes( -1, isPr, additional)); } } } break; case "AdditionalInput": if (l.vdata().length == 0) { additional = null; } else { additional = l.vdata(); } if (!mode.equals("pr_true")) { if (hd != null) { if (additional == null) { hd.nextBytes(output); } else { hd.nextBytes(output, DrbgParameters.nextBytes( -1, isPr, additional)); } } } break; case "ReturnedBits": if (hd != null) { if (!Arrays.equals(output, l.vdata())) { throw new Exception("\nExpected: " + l.value + "\n Actual: " + hex(output)); } } break; default: // Algorithm line for Hash_DRBG and HMAC_DRBG if (l.key.startsWith("SHA-")) { algorithm = l.key; } } } err.println(); } catch (Exception e) { err.println(); err.println(sb.toString()); err.println(bout.toString()); throw e; } } /** * Parse a line from test material. * * Brackets are removed. Key and value separated. */ static class Line { final String key; final String value; Line(String s) { s = s.trim(); if (s.length() >= 2) { if (s.charAt(0) == '[') { s = s.substring(1, s.length() - 1); } } if (s.indexOf('=') < 0) { key = s; value = null; } else { key = s.substring(0, s.indexOf('=')).trim(); value = s.substring(s.indexOf('=') + 1).trim(); } } int vint() { return Integer.parseInt(value); } byte[] vdata() { return xeh(value); } } // Bytes to HEX private static String hex(byte[] in) { StringBuilder sb = new StringBuilder(); for (byte b: in) { sb.append(String.format("%02x", b&0xff)); } return sb.toString(); } // HEX to bytes private static byte[] xeh(String in) { in = in.replaceAll(" ", ""); int len = in.length() / 2; byte[] out = new byte[len]; for (int i = 0; i < len; i++) { out[i] = (byte) Integer.parseInt( in.substring(i * 2, i * 2 + 2), 16); } return out; } }