--- /dev/null 2016-04-15 21:29:47.000000000 +0800 +++ new/src/java.base/share/classes/sun/security/provider/AbstractDrbg.java 2016-04-15 21:29:47.000000000 +0800 @@ -0,0 +1,740 @@ +/* + * 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")); + } +}