001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.commons.crypto.random;
019
020import java.security.GeneralSecurityException;
021import java.util.List;
022import java.util.Properties;
023
024import org.apache.commons.crypto.Crypto;
025import org.apache.commons.crypto.utils.ReflectionUtils;
026import org.apache.commons.crypto.utils.Utils;
027
028/**
029 * This is the factory class used for creating {@link CryptoRandom} instances
030 */
031public class CryptoRandomFactory {
032
033    // security random related configuration keys
034    /**
035     * The configuration key of the file path for secure random device.
036     */
037    public static final String DEVICE_FILE_PATH_KEY = Crypto.CONF_PREFIX
038            + "secure.random.device.file.path";
039
040    /**
041     * The default value ({@value}) of the file path for secure random device.
042     */
043    // Note: this is public mainly for use by the Javadoc
044    public static final String DEVICE_FILE_PATH_DEFAULT = "/dev/urandom";
045
046    /**
047     * The configuration key of the algorithm of secure random.
048     */
049    public static final String JAVA_ALGORITHM_KEY = Crypto.CONF_PREFIX
050            + "secure.random.java.algorithm";
051
052    /**
053     * The default value ({@value}) of the algorithm of secure random.
054     */
055    // Note: this is public mainly for use by the Javadoc
056    public static final String JAVA_ALGORITHM_DEFAULT = "SHA1PRNG";
057
058    /**
059     * The configuration key of the CryptoRandom implementation class.
060     * <p>
061     * The value of the CLASSES_KEY needs to be the full name of a
062     * class that implements the
063     * {@link org.apache.commons.crypto.random.CryptoRandom CryptoRandom} interface
064     * The internal classes are listed in the enum
065     * {@link RandomProvider RandomProvider}
066     * which can be used to obtain the full class name.
067     * <p>
068     * The value can also be a comma-separated list of class names in
069     * order of descending priority.
070     */
071    public static final String CLASSES_KEY = Crypto.CONF_PREFIX
072            + "secure.random.classes";
073    /**
074     * Defines the internal CryptoRandom implementations.
075     * <p>
076     * Usage:
077     * <blockquote><pre>
078     * props.setProperty(CryptoRandomFactory.CLASSES_KEY, RandomProvider.OPENSSL.getClassName());
079     * props.setProperty(...); // if required by the implementation
080     * random = CryptoRandomFactory.getCryptoRandom(transformation, props);
081     * </pre></blockquote>
082     */
083    public enum RandomProvider {
084
085        /**
086         * The OpenSSL Random implementation (using JNI)
087         * <p>
088         * No properties are used for configuration, but they
089         * are passed to the {@link RandomProvider#JAVA} backup implementation
090         */
091        // Please ensure the property description agrees with the implementation
092        OPENSSL(OpenSslCryptoRandom.class),
093
094        /**
095         * The SecureRandom implementation from the JVM
096         * <p>
097         * Uses the property with key
098         * {@link #JAVA_ALGORITHM_KEY}
099         * with the default of
100         * {@link #JAVA_ALGORITHM_DEFAULT}
101         */
102        // Please ensure the property description agrees with the implementation
103        JAVA(JavaCryptoRandom.class),
104
105        /**
106         * The OS random device implementation. May not be available on some OSes.
107         * <p>
108         * Uses {@link #DEVICE_FILE_PATH_KEY} to determine the
109         * path to the random device, default is
110         * {@link #DEVICE_FILE_PATH_DEFAULT}
111         */
112        // Please ensure the property description agrees with the implementation
113        OS(OsCryptoRandom.class);
114
115        private final Class<? extends CryptoRandom> klass;
116
117        private final String className;
118
119        /**
120         * The private constructor.
121         * @param klass the Class of CryptoRandom
122         */
123        private RandomProvider(final Class<? extends CryptoRandom> klass) {
124            this.klass = klass;
125            this.className = klass.getName();
126        }
127
128        /**
129         * Gets the class name of the provider.
130         *
131         * @return the name of the provider class
132         */
133        public String getClassName() {
134            return className;
135        }
136
137        /**
138         * Gets the implementation class of the provider.
139         *
140         * @return the implementation class of the provider
141         */
142        public Class<? extends CryptoRandom> getImplClass() {
143            return klass;
144        }
145    }
146
147    /**
148     * The default value (OPENSSL,JAVA) used when creating a {@link org.apache.commons.crypto.cipher.CryptoCipher}.
149     */
150    private static final String CLASSES_DEFAULT =
151        RandomProvider.OPENSSL.getClassName()
152        .concat(",")
153        .concat(RandomProvider.JAVA.getClassName());
154
155    /**
156     * The private constructor of {@link CryptoRandomFactory}.
157     */
158    private CryptoRandomFactory() {
159    }
160
161    /**
162     * Gets a CryptoRandom instance using the default implementation
163     * as defined by {@link #CLASSES_DEFAULT}
164     *
165     * @return CryptoRandom  the cryptoRandom object.
166     * @throws GeneralSecurityException if cannot create the {@link CryptoRandom} class
167     */
168    public static CryptoRandom getCryptoRandom() throws GeneralSecurityException {
169        final Properties properties = new Properties();
170        return getCryptoRandom(properties);
171    }
172
173    /**
174     * Gets a CryptoRandom instance for specified props.
175     * Uses the SECURE_RANDOM_CLASSES_KEY from the provided
176     * properties.
177     * If it is not set, then it checks the System properties.
178     * Failing that, it defaults to OpenSslCryptoRandom,JavaCryptoRandom
179     * The properties are passed to the generated class.
180     *
181     * @param props the configuration properties.
182     * @return CryptoRandom  the cryptoRandom object.
183     * @throws GeneralSecurityException if cannot create the {@link CryptoRandom} class
184     * @throws IllegalArgumentException if no classname(s) are provided
185     */
186    public static CryptoRandom getCryptoRandom(final Properties props)
187            throws GeneralSecurityException {
188        final List<String> names = Utils.splitClassNames(getRandomClassString(props), ",");
189        if (names.size() == 0) {
190            throw new IllegalArgumentException("No class name(s) provided");
191        }
192        final StringBuilder errorMessage = new StringBuilder();
193        CryptoRandom random = null;
194        Exception lastException = null;
195        for (final String klassName : names) {
196            try {
197                final Class<?> klass = ReflectionUtils.getClassByName(klassName);
198                random = (CryptoRandom) ReflectionUtils.newInstance(klass, props);
199                if (random != null) {
200                    break;
201                }
202            } catch (final ClassCastException e) {
203                lastException = e;
204                errorMessage.append("Class: [" + klassName + "] is not a CryptoRandom.");
205            } catch (final ClassNotFoundException e) {
206                lastException = e;
207                errorMessage.append("CryptoRandom: [" + klassName + "] not found.");
208            } catch (final Exception e) {
209                lastException = e;
210                errorMessage.append("CryptoRandom: [" + klassName + "] failed with " + e.getMessage());
211            }
212        }
213
214        if (random != null) {
215            return random;
216        }
217        throw new GeneralSecurityException(errorMessage.toString(), lastException);
218    }
219
220    /**
221     * Gets the CryptoRandom class.
222     *
223     * @param props The {@code Properties} class represents a set of
224     *        properties.
225     * @return the CryptoRandom class based on the props.
226     */
227    private static String getRandomClassString(final Properties props) {
228        String randomClassString = props.getProperty(CryptoRandomFactory.CLASSES_KEY, CLASSES_DEFAULT);
229        if (randomClassString.isEmpty()) { // TODO does it make sense to treat the empty string as the default?
230            randomClassString = CLASSES_DEFAULT;
231        }
232        return randomClassString;
233    }
234}