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 * Creates {@link CryptoRandom} instances
030 */
031public class CryptoRandomFactory {
032
033    /**
034     * Defines the internal CryptoRandom implementations.
035     * <p>
036     * Usage:
037     * <blockquote><pre>
038     * props.setProperty(CryptoRandomFactory.CLASSES_KEY, RandomProvider.OPENSSL.getClassName());
039     * props.setProperty(...); // if required by the implementation
040     * random = CryptoRandomFactory.getCryptoRandom(transformation, props);
041     * </pre></blockquote>
042     */
043    public enum RandomProvider {
044
045        /**
046         * The OpenSSL Random implementation (using JNI)
047         * <p>
048         * No properties are used for configuration, but they
049         * are passed to the {@link RandomProvider#JAVA} backup implementation
050         */
051        // Please ensure the property description agrees with the implementation
052        OPENSSL(OpenSslCryptoRandom.class),
053
054        /**
055         * The SecureRandom implementation from the JVM
056         * <p>
057         * Uses the property with key
058         * {@link #JAVA_ALGORITHM_KEY}
059         * with the default of
060         * {@link #JAVA_ALGORITHM_DEFAULT}
061         */
062        // Please ensure the property description agrees with the implementation
063        JAVA(JavaCryptoRandom.class),
064
065        /**
066         * The OS random device implementation. May not be available on some OSes.
067         * <p>
068         * Uses {@link #DEVICE_FILE_PATH_KEY} to determine the
069         * path to the random device, default is
070         * {@link #DEVICE_FILE_PATH_DEFAULT}
071         */
072        // Please ensure the property description agrees with the implementation
073        OS(OsCryptoRandom.class);
074
075        private final Class<? extends CryptoRandom> klass;
076
077        private final String className;
078
079        /**
080         * The private constructor.
081         * @param klass the Class of CryptoRandom
082         */
083        RandomProvider(final Class<? extends CryptoRandom> klass) {
084            this.klass = klass;
085            this.className = klass.getName();
086        }
087
088        /**
089         * Gets the class name of the provider.
090         *
091         * @return the name of the provider class
092         */
093        public String getClassName() {
094            return className;
095        }
096
097        /**
098         * Gets the implementation class of the provider.
099         *
100         * @return the implementation class of the provider
101         */
102        public Class<? extends CryptoRandom> getImplClass() {
103            return klass;
104        }
105    }
106
107    // security random related configuration keys
108    /**
109     * The configuration key of the file path for secure random device.
110     */
111    public static final String DEVICE_FILE_PATH_KEY = Crypto.CONF_PREFIX + "secure.random.device.file.path";
112
113    /**
114     * The default value ({@value}) of the file path for secure random device.
115     */
116    // Note: this is public mainly for use by the Javadoc
117    public static final String DEVICE_FILE_PATH_DEFAULT = "/dev/urandom";
118
119    /**
120     * The configuration key of the algorithm of secure random.
121     */
122    public static final String JAVA_ALGORITHM_KEY = Crypto.CONF_PREFIX + "secure.random.java.algorithm";
123
124    /**
125     * The default value ({@value}) of the algorithm of secure random.
126     */
127    // Note: this is public mainly for use by the Javadoc
128    public static final String JAVA_ALGORITHM_DEFAULT = "SHA1PRNG";
129
130    /**
131     * The configuration key of the CryptoRandom implementation class.
132     * <p>
133     * The value of the CLASSES_KEY needs to be the full name of a
134     * class that implements the
135     * {@link org.apache.commons.crypto.random.CryptoRandom CryptoRandom} interface
136     * The internal classes are listed in the enum
137     * {@link RandomProvider RandomProvider}
138     * which can be used to obtain the full class name.
139     * <p>
140     * The value can also be a comma-separated list of class names in
141     * order of descending priority.
142     */
143    public static final String CLASSES_KEY = Crypto.CONF_PREFIX + "secure.random.classes";
144
145    /**
146     * The default value (OPENSSL,JAVA) used when creating a {@link org.apache.commons.crypto.cipher.CryptoCipher}.
147     */
148    private static final String CLASSES_DEFAULT =
149        RandomProvider.OPENSSL.getClassName()
150        .concat(",")
151        .concat(RandomProvider.JAVA.getClassName());
152
153    /**
154     * Gets a CryptoRandom instance using the default implementation
155     * as defined by {@link #CLASSES_DEFAULT}
156     *
157     * @return CryptoRandom  the cryptoRandom object.
158     * @throws GeneralSecurityException if cannot create the {@link CryptoRandom} class
159     */
160    public static CryptoRandom getCryptoRandom() throws GeneralSecurityException {
161        final Properties properties = new Properties();
162        return getCryptoRandom(properties);
163    }
164
165    /**
166     * Gets a CryptoRandom instance for specified props.
167     * Uses the SECURE_RANDOM_CLASSES_KEY from the provided
168     * properties.
169     * If it is not set, then it checks the System properties.
170     * Failing that, it defaults to OpenSslCryptoRandom,JavaCryptoRandom
171     * The properties are passed to the generated class.
172     *
173     * @param props the configuration properties.
174     * @return CryptoRandom  the cryptoRandom object.
175     * @throws GeneralSecurityException if cannot create the {@link CryptoRandom} class
176     * @throws IllegalArgumentException if no classname(s) are provided
177     */
178    public static CryptoRandom getCryptoRandom(final Properties props)
179            throws GeneralSecurityException {
180        final List<String> names = Utils.splitClassNames(getRandomClassString(props), ",");
181        if (names.isEmpty()) {
182            throw new IllegalArgumentException("No class name(s) provided");
183        }
184        final StringBuilder errorMessage = new StringBuilder();
185        CryptoRandom random = null;
186        Exception lastException = null;
187        for (final String klassName : names) {
188            try {
189                final Class<?> klass = ReflectionUtils.getClassByName(klassName);
190                random = (CryptoRandom) ReflectionUtils.newInstance(klass, props);
191                break;
192            } catch (final ClassCastException e) {
193                lastException = e;
194                errorMessage.append("Class: [" + klassName + "] is not a CryptoRandom.");
195            } catch (final ClassNotFoundException e) {
196                lastException = e;
197                errorMessage.append("CryptoRandom: [" + klassName + "] not found.");
198            } catch (final Exception e) {
199                lastException = e;
200                errorMessage.append("CryptoRandom: [" + klassName + "] failed with " + e.getMessage());
201            }
202        }
203
204        if (random != null) {
205            return random;
206        }
207        throw new GeneralSecurityException(errorMessage.toString(), lastException);
208    }
209
210    /**
211     * Gets the CryptoRandom class.
212     *
213     * @param props The {@code Properties} class represents a set of
214     *        properties.
215     * @return the CryptoRandom class based on the props.
216     */
217    private static String getRandomClassString(final Properties props) {
218        String randomClassString = props.getProperty(CryptoRandomFactory.CLASSES_KEY, CLASSES_DEFAULT);
219        if (randomClassString.isEmpty()) { // TODO does it make sense to treat the empty string as the default?
220            randomClassString = CLASSES_DEFAULT;
221        }
222        return randomClassString;
223    }
224
225    /**
226     * The private constructor of {@link CryptoRandomFactory}.
227     */
228    private CryptoRandomFactory() {
229    }
230}