/* * 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ package sun.security.provider; import sun.security.util.Debug; import java.security.*; import java.util.Arrays; import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import static java.security.DrbgParameters.Capability.*; /** * The abstract base class for all DRBGs. *

* This class creates 5 new abstract methods. 3 are defined by the SP800-90A: *

    *
  1. {@link #generateAlgorithm(byte[], byte[])} *
  2. {@link #reseedAlgorithm(byte[], byte[])} (might not be supported) *
  3. {@link #instantiateAlgorithm(byte[])} *
* and 2 for implementation purpose: *
    *
  1. {@link #initEngine()} *
  2. {@link #chooseAlgorithmAndStrength} *
* All existing {@link SecureRandomSpi} methods are implemented based on the * methods above as final. The instantiate process is divided into 2 phases: * configuration is eagerly called to set up parameters, and instantiation * is lazily called only when nextBytes or reseed is called. */ public abstract class AbstractDrbg extends SecureRandomSpi { // synchronized keyword should be added to all externally callable engine // methods including engineGenerateSeed, engineReseed, engineSetSeed // and engineNextBytes. Internally engine methods like engineConfigure // and instantiateAlgorithm are not synchronized. They will either be // called in a constructor or in another synchronized method. // Precisely engineGenerateSeed does not need to be synchronized since // it does not modify any internal states after configuration is // complete, but the configuration itself can be called by other engine // methods and should be synchronized. My understanding is that the // engineGenerateSeed method is not used much and it's not worth // creating a lock for the configuration itself. private static final long serialVersionUID = 9L; /** * This field is not null if {@code -Djava.security.debug=securerandom} is * specified on the command line. An implementation can print useful * debug info. */ protected static final Debug debug = Debug.getInstance( "securerandom", "drbg"); // Common working status private boolean instantiated = false; /** * Reseed counter of a DRBG instance. A mechanism should increment it * after each random bits generation and reset it in reseed. A mechanism * does not need to compare it to {@link #reseedInterval}. */ protected int reseedCounter = 0; // Mech features. If not same as below, must be redefined in constructor. /** * Default strength of a DRBG instance if it is not configured. * 128 is considered secure enough now. A mechanism * can change it in a constructor. * * The default here is described in comments of the "drbg" security property. */ protected static final int defaultStrength = 128; /** * Mechanism name, say, {@code HashDRBG}. Must be set in constructor. * This value will be used in {@code toString}. */ protected String mechName = "DRBG"; /** * highest_supported_security_strength of this mechanism for all algorithms * it supports. A mechanism should update the value in its constructor * if the value is not 256. */ protected int highestSecurity = 256; /** * Whether prediction resistance is supported. A mechanism should update * the value in its constructor if it is not supported. */ protected boolean supportPr = true; /** * Whether reseed is supported. A mechanism should update * the value in its constructor if it is not supported. */ protected boolean supportReseed = true; // Strength features. If not same as below, must be redefined in // chooseAlgorithmAndStrength. Among these, minLength and seedLen have no // default value and must be redefined. If personalization string or // additional input is not supported, set maxPsLength or // maxAiLength to -1. /** * Minimum entropy input length in bytes for this DRBG instance. * Must be assigned in {@link #chooseAlgorithmAndStrength}. */ protected int minLength; /** * Maximum entropy input length in bytes for this DRBG instance. * Should be assigned in {@link #chooseAlgorithmAndStrength} if it is not * {@link Integer#MAX_VALUE}. */ protected int maxLength = Integer.MAX_VALUE; /** * Maximum personalization string length in bytes for this DRBG instance. * Should be assigned in {@link #chooseAlgorithmAndStrength} if it is not * {@link Integer#MAX_VALUE}. */ protected int maxPsLength = Integer.MAX_VALUE; /** * Maximum additional input length in bytes for this DRBG instance. * Should be assigned in {@link #chooseAlgorithmAndStrength} if it is not * {@link Integer#MAX_VALUE}. */ protected int maxAiLength = Integer.MAX_VALUE; /** * max_number_of_bits_per_request in bytes for this DRBG instance. * Should be assigned in {@link #chooseAlgorithmAndStrength} if it is not * {@link Integer#MAX_VALUE}. */ protected int maxNbLength = Integer.MAX_VALUE; /** * Maximum number of requests between reseeds for this DRBG instance. * Should be assigned in {@link #chooseAlgorithmAndStrength} if it is not * {@link Integer#MAX_VALUE}. */ protected int reseedInterval = Integer.MAX_VALUE; /** * Algorithm used by this instance (SHA-512 or AES-256). Must be assigned * in {@link #chooseAlgorithmAndStrength}. This field is used in * {@link #toString()}. */ protected String algorithm; // Configurable parameters /** * Security strength for this instance. Must be assigned in * {@link #chooseAlgorithmAndStrength}. Should be at least the requested * strength. Might be smaller than the highest strength * {@link #algorithm} supports. Must not be -1. */ protected int strength; // in bits /** * Strength requested in {@link DrbgParameters.Instantiate}. * The real strength is based on it. Do not modify it in a mechanism. */ protected int requestedStrength = -1; /** * The personalization string used by this instance. Set inside * {@link #engineConfigure(SecureRandomParameters)} and * can be used in a mechanism. Do not modify it in a mechanism. */ protected byte[] ps; /** * The prediction resistance flag used by this instance. Set inside * {@link #engineConfigure(SecureRandomParameters)}. */ private boolean isPr; // Non-standard configurable parameters /** * Whether a derivation function is used. Requested in * {@link MoreDrbgParameters}. Only CtrDRBG uses it. * Do not modify it in a mechanism. */ protected boolean usedf; /** * The nonce for this instance. Set in {@link #instantiateIfNecessary}. * After instantiation, this field is not null. Do not modify it * in a mechanism. */ protected byte[] nonce; /** * Requested nonce in {@link MoreDrbgParameters}. If set to null, * nonce will be chosen by system, and a reinstantiated DRBG will get a * new system-provided nonce. */ private byte[] requestedNonce; /** * Requested algorithm in {@link MoreDrbgParameters}. * Do not modify it in a mechanism. */ protected String requestedAlgorithm; /** * The prediction resistance flag used by this instance. Set inside * {@link #engineConfigure(SecureRandomParameters)}. This field * can be null. {@link #getEntropyInput} will take care of null check. */ private transient EntropySource es; // Five abstract methods for SP 800-90A DRBG /** * Decides what algorithm and strength to use (SHA-256 or AES-256, * 128 or 256). Strength related fields must also be defined or redefined * here. Called in {@link #engineConfigure}. A mechanism uses * {@link #requestedAlgorithm}, {@link #requestedStrength}, and * {@link #defaultStrength} to decide which algorithm and strength to use. *

* If {@code requestedAlgorithm} is provided, it will always be used. * If {@code requestedStrength} is also provided, the algorithm will use * the strength (an exception will be thrown if the strength is not * supported), otherwise, the smaller one of the highest supported strength * of the algorithm and the default strength will be used. *

* If {@code requestedAlgorithm} is not provided, an algorithm will be * chosen that supports {@code requestedStrength} * (or {@code defaultStrength} if there is no request). *

* Since every call to {@link #engineConfigure} will call this method, * make sure to the calls do not contradict with each other. *

* Here are some examples of the algorithm and strength chosen (suppose * {@code defaultStrength} is 128) for HashDRBG: *

     * (requestedAlg,requestedStrength)   Algorithm   Strength
     * (SHA-1, -1)                        SHA-1       128
     * (SHA-1, 112)                       SHA-1       112
     * (SHA-1, 192)                       error       error
     * (SHA-256, -1)                      SHA-256     128
     * (SHA-256, 128)                     SHA-256     128
     * (SHA-3, -1)                        error       error
     * (null, -1)                         SHA-256     128
     * (null, 112)                        SHA-256     112
     * (null, 192)                        SHA-256     192
     * (null, 256)                        SHA-256     256
     * (null, 384)                        error       error
     * 
* * @throws IllegalArgumentException if the requested parameters * can not be supported or contradict with each other. */ protected abstract void chooseAlgorithmAndStrength(); /** * Initiates security engines ({@code MessageDigest}, {@code Mac}, * or {@code Cipher}). Must be called in deserialization. Please note * that before instantiation the algorithm might not be available yet. * In this case, just return and this method will be called * automatically at instantiation. */ protected abstract void initEngine(); /** * Instantiates a DRBG. Called automatically before the first * {@code nextBytes} call. *

* Note that the other parameters (nonce, strength, ps) are already * stored inside at configuration. * * @param ei the entropy input, its length is already conditioned to be * between {@link #minLength} and {@link #maxLength}. */ protected abstract void instantiateAlgorithm(byte[] ei); /** * The generate function. * * @param result fill result here, not null * @param additionalInput additional input, can be null. If not null, * its length is smaller than {@link #maxAiLength} */ protected abstract void generateAlgorithm( byte[] result, byte[] additionalInput); /** * The reseed function. * * @param ei the entropy input, its length is already conditioned to be * between {@link #minLength} and {@link #maxLength}. * @param additionalInput additional input, can be null. If not null, * its length is smaller than {@link #maxAiLength} * @throws UnsupportedOperationException if reseed is not supported */ protected void reseedAlgorithm( byte[] ei, byte[] additionalInput) { throw new UnsupportedOperationException("No reseed function"); } // SecureRandomSpi methods taken care of here. All final. @Override protected final void engineNextBytes(byte[] result) { engineNextBytes(result, DrbgParameters.nextBytes(-1, isPr, null)); } @Override protected final void engineNextBytes( byte[] result, SecureRandomParameters params) { if (debug != null) { debug.println("nextBytes"); } if (params instanceof DrbgParameters.NextBytes) { DrbgParameters.NextBytes dp = (DrbgParameters.NextBytes) params; if (!isPr && dp.getPredictionResistance()) { throw new IllegalArgumentException("pr not available"); } if (dp.getStrength() > strength) { throw new IllegalArgumentException("strength too high: " + dp.getStrength()); } if (result.length > maxNbLength) { throw new IllegalArgumentException("result too long: " + result.length); } byte[] ai = dp.getAdditionalInput(); if (ai != null && ai.length > maxAiLength) { throw new IllegalArgumentException("ai too long: " + ai.length); } instantiateIfNecessary(null); if (reseedCounter > reseedInterval || dp.getPredictionResistance()) { reseedAlgorithm(getEntropyInput(dp.getPredictionResistance()), ai); ai = null; } generateAlgorithm(result, ai); } else { throw new IllegalArgumentException("unknown params type:" + params.getClass()); } } @Override public final void engineReseed(SecureRandomParameters params) { if (debug != null) { debug.println("reseed with params"); } if (!supportReseed) { throw new UnsupportedOperationException("Reseed not supported"); } if (params == null) { params = DrbgParameters.reseed(isPr, null); } if (params instanceof DrbgParameters.Reseed) { DrbgParameters.Reseed dp = (DrbgParameters.Reseed) params; if (!isPr && dp.getPredictionResistance()) { throw new IllegalArgumentException("pr not available"); } byte[] ai = dp.getAdditionalInput(); if (ai != null && ai.length > maxAiLength) { throw new IllegalArgumentException("ai too long: " + ai.length); } instantiateIfNecessary(null); reseedAlgorithm(getEntropyInput(dp.getPredictionResistance()), ai); } else { throw new IllegalArgumentException("unknown params type: " + params.getClass()); } } /** * Returns the given number of seed bytes. A DRBG always uses its * {@code EntropySource} to get an array with full-entropy. * * @param numBytes the number of seed bytes to generate. * @return the seed bytes. */ @Override public final synchronized byte[] engineGenerateSeed(int numBytes) { return getEntropyInput(numBytes, numBytes, numBytes, isPr); } /** * Reseeds this random object with the given seed. A DRBG always expands * or truncates the input to be between {@link #minLength} and * {@link #maxLength} and uses it to instantiate or reseed itself * (depending on whether the DRBG is instantiated). * * @param input the seed */ @Override public final synchronized void engineSetSeed(byte[] input) { // TODO: Will truncate be insecure? hashDF? if (debug != null) { debug.println("setSeed"); } if (input.length < minLength) { input = Arrays.copyOf(input, minLength); } else if (input.length > maxLength) { input = Arrays.copyOf(input, maxLength); } if (!instantiated) { instantiateIfNecessary(input); } else { reseedAlgorithm(input, null); } } // get_entropy_input private byte[] getEntropyInput(boolean isPr) { // Should the 1st arg be strength or minLength? // // Precisely I think it should be strength, but CtrDRBG // (not using derivation function) is so confusing // (does it need only strength or seedlen of entropy?) // that maybe it's safer to assume minLength. return getEntropyInput(minLength, minLength, maxLength, isPr); } private byte[] getEntropyInput(int security, int min, int max, boolean pr) { if (debug != null) { debug.println("getEntropy(" + security + "," + min + "," + max + "," + pr + ")"); } EntropySource esNow = es; if (esNow == null) { esNow = pr? SeederHolder.prseeder: SeederHolder.seeder; } return esNow.getEntropy(security, min, max, pr); } // Defaults /** * The default {@code EntropySource} determined by system property * "java.security.egd" or security property "securerandom.source". *

* This object uses {@link SeedGenerator#generateSeed(byte[])} to * return a byte array containing {@code minLength} bytes. It is * assumed to support prediction resistance and always contains * full-entropy. A trusted application can update this field. */ public static EntropySource defaultES = (entropy, minLen, maxLen, pr) -> { byte[] result = new byte[minLen]; SeedGenerator.generateSeed(result); return result; }; private static class SeederHolder { /** * Default EntropySource for SecureRandom with prediction resistance, */ static final EntropySource prseeder; /** * Default EntropySource for SecureRandom without prediction resistance, * which is backed by a DRBG whose EntropySource is {@link #prseeder}. */ static final EntropySource seeder; static { prseeder = defaultES; HashDrbg drbg = new HashDrbg(null); // According to SP800-90C section 7, a DRBG without live // entropy (drbg here, with pr being false) can instantiate // another DRBG with weaker strength. So we choose highest // strength we support. drbg.engineConfigure(new MoreDrbgParameters( prseeder, null, null, null, false, DrbgParameters.instantiate( 256, NONE, SeedGenerator.getSystemEntropy()))); seeder = (entropy, minLen, maxLen, pr) -> { if (pr) { // This SEI does not support pr throw new IllegalArgumentException("pr not supported"); } byte[] result = new byte[minLen]; drbg.engineNextBytes(result); return result; }; } } // Constructor called by overridden methods, initializer... /** * A constructor without argument so that an implementation does not * need to always write {@code super(params)}. */ protected AbstractDrbg() { // Nothing } /** * A mechanism shall override this constructor to setup {@link #mechName}, * {@link #highestSecurity}, {@link #supportPr}, {@link #supportReseed} * or other features like {@link #defaultStrength}. Finally it shall * call {@link #configureInternal} on {@code params}. * * @param params the {@link SecureRandomParameters} object. * This argument can be {@code null}. * @throws IllegalArgumentException if {@code params} is * inappropriate for this SecureRandom. */ protected AbstractDrbg(SecureRandomParameters params) { // Nothing } protected void register(Map map) { String name = "SecureRandom." + mechName; map.put(name, this.getClass().getName()); map.put(name + " ImplementedIn", "Software"); map.put(name + " DRBG.supportPr", Boolean.toString(supportPr)); map.put(name + " DRBG.highestSecurity", Integer.toString(highestSecurity)); map.put(name + " DRBG.supportReseed", Boolean.toString(supportReseed)); map.put(name + " DRBG.defaultStrength", Integer.toString(defaultStrength)); } /** * Returns the current configuration as a {@link DrbgParameters.Instantiate} * object. * * @return the curent configuration */ @Override protected SecureRandomParameters engineGetParameters() { // Or read from variable. return DrbgParameters.instantiate( strength, isPr? PR_AND_RESEED: (supportReseed? RESEED_ONLY: NONE), ps); } /** * A helper function that calls {@link #engineConfigure}. * * @param params if null, default configuration (default strength, * pr_false, no personalization string) will be used. * @throws IllegalArgumentException if {@code params} is * inappropriate for this SecureRandom. */ protected void configureInternal(SecureRandomParameters params) { if (params == null) { params = DrbgParameters.instantiate(-1, RESEED_ONLY, null); } engineConfigure(params); } /** * Configure this DRBG. This method calls {@link #chooseAlgorithmAndStrength()} * and {@link #initEngine()} but does not do the actual instantiation. * * @param params configuration, cannot be null * @throws IllegalArgumentException if {@code params} is * inappropriate for this SecureRandom. */ //@Override protected final synchronized void engineConfigure( SecureRandomParameters params) { if (debug != null) { debug.println("configure " + this + " with " + params); } if (params instanceof MoreDrbgParameters) { MoreDrbgParameters m = (MoreDrbgParameters)params; this.requestedNonce = m.nonce; this.es = m.es; this.requestedAlgorithm = m.algorithm; this.usedf = m.usedf; params = m.config; } if (params != null) { if (params instanceof DrbgParameters.Instantiate) { DrbgParameters.Instantiate config = (DrbgParameters.Instantiate) params; if (config.getStrength() > highestSecurity) { throw new IllegalArgumentException("strength too big: " + config.getStrength()); } byte[] ps = config.getPersonalizationString(); if (ps != null && ps.length > maxPsLength) { throw new IllegalArgumentException("ps too long: " + ps.length); } if (config.getCapability().supportsPredictionResistance() && !supportPr) { throw new IllegalArgumentException("pr not supported"); } if (config.getCapability().supportsReseeding() && !supportReseed) { throw new IllegalArgumentException("reseed not supported"); } this.ps = ps; this.isPr = config.getCapability().supportsPredictionResistance(); this.requestedStrength = config.getStrength(); } else { throw new IllegalArgumentException("unknown params: " + params.getClass()); } } chooseAlgorithmAndStrength(); instantiated = false; if (debug != null) { debug.println("configured " + this); } } /** * Instantiate if necessary, * * @param entropy a user-provided entropy, the length is already good. * If null, will fetch entropy input automatically. */ private synchronized void instantiateIfNecessary(byte[] entropy) { if (!instantiated) { if (debug != null) { debug.println("instantiate"); } if (strength <= 0) { // not configured at all chooseAlgorithmAndStrength(); } // 1-4. strength/ps/pr check // 5. Null // 6/7. Get entropy. TODO: 1st arg security strength? if (entropy == null) { entropy = getEntropyInput(isPr); } // 8. nonce if (requestedNonce != null) { nonce = requestedNonce; } else { nonce = longToByteArray(cc.incrementAndGet()); } // 9. instantiate initEngine(); instantiateAlgorithm(entropy); instantiated = true; } } // Nonce provider /** * Helper function to convert a long into a byte array (least significant * byte first). */ private static byte[] longToByteArray(long l) { byte[] retVal = new byte[16]; retVal[0] = 's'; retVal[1] = 'u'; retVal[2] = 'n'; retVal[3] = '.'; retVal[4] = 'd'; retVal[5] = 'r'; retVal[6] = 'b'; retVal[7] = 'g'; for (int i = 0; i < 8; i++) { retVal[i+8] = (byte) l; l >>= 8; } return retVal; } private static AtomicLong cc = new AtomicLong(0); // Misc /** A handy method returning hexdump string with no colon or new line. * * @param in input byte array * @return the hexdump string */ protected static String hex(byte[] in) { StringBuilder sb = new StringBuilder(); for (byte b: in) { sb.append(String.format("%02x", b&0xff)); } return sb.toString(); } /** * Returns the smallest standard strength (112, 128, 192, 256) that is * greater or equal to the input. * * @param input the input strength * @return the standard strength */ protected static int getStandardStrength(int input) { if (input <= 112) return 112; if (input <= 128) return 128; if (input <= 192) return 192; if (input <= 256) return 256; throw new IllegalArgumentException("input too big: " + input); } @Override public String toString() { return mechName + "," + algorithm + "," + strength + "," + (isPr?"pr_and_reseed":(supportReseed?"reseed_only":"none")); } }