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