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.cipher;
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 CryptoCipher} instances.
030 */
031public class CryptoCipherFactory {
032
033    /**
034     * The configuration key of the provider class for JCE cipher.
035     */
036    public static final String JCE_PROVIDER_KEY = Crypto.CONF_PREFIX
037            + "cipher.jce.provider";
038    /**
039     * The configuration key of the CryptoCipher implementation class.
040     * <p>
041     * The value of CLASSES_KEY needs to be the full name of a
042     * class that implements the
043     * {@link org.apache.commons.crypto.cipher.CryptoCipher CryptoCipher} interface
044     * The internal classes are listed in the enum
045     * {@link CipherProvider CipherProvider}
046     * which can be used to obtain the full class name.
047     * <p>
048     * The value can also be a comma-separated list of class names in
049     * order of descending priority.
050     */
051
052    public static final String CLASSES_KEY = Crypto.CONF_PREFIX
053            + "cipher.classes";
054
055    /**
056     * Defines the internal CryptoCipher implementations.
057     * <p>
058     * Usage:
059     * <blockquote><pre>
060     * props.setProperty(CryptoCipherFactory.CLASSES_KEY, CipherProvider.OPENSSL.getClassName());
061     * props.setProperty(...); // if required by the implementation
062     * cipher = CryptoCipherFactory.getInstance(transformation, props);
063     * </pre></blockquote>
064     */
065    public enum CipherProvider {
066
067        /**
068         * The OpenSSL cipher implementation (using JNI)
069         * <p>
070         * This implementation does not use any properties
071         */
072        // Please ensure the property description agrees with the implementation
073        OPENSSL(OpenSslCipher.class),
074
075        /**
076         * The JCE cipher implementation from the JVM
077         * <p>
078         * uses the property {@link #JCE_PROVIDER_KEY}
079         * to define the provider name, if present.
080         */
081        // Please ensure the property description agrees with the implementation
082        JCE(JceCipher.class);
083
084        private final Class<? extends CryptoCipher> klass;
085
086        private final String className;
087
088        /**
089         * The private constructor.
090         * @param klass the Class of CryptoCipher
091         */
092        private CipherProvider(final Class<? extends CryptoCipher> klass) {
093            this.klass = klass;
094            this.className = klass.getName();
095        }
096
097        /**
098         * Gets the class name of the provider.
099         *
100         * @return the fully qualified name of the provider class
101         */
102        public String getClassName() {
103            return className;
104        }
105
106        /**
107         * Gets the implementation class of the provider.
108         *
109         * @return the implementation class of the provider
110         */
111        public Class<? extends CryptoCipher> getImplClass() {
112            return klass;
113        }
114    }
115
116    /**
117     * For AES, the algorithm block is fixed size of 128 bits.
118     *
119     * @see <a href="http://en.wikipedia.org/wiki/Advanced_Encryption_Standard">
120     *      http://en.wikipedia.org/wiki/Advanced_Encryption_Standard</a>
121     */
122    public static final int AES_BLOCK_SIZE = 16;
123
124    /**
125     * The default value (OPENSSL,JCE) for crypto cipher.
126     */
127    private static final String CLASSES_DEFAULT =
128            CipherProvider.OPENSSL.getClassName()
129            .concat(",")
130            .concat(CipherProvider.JCE.getClassName());
131
132    /**
133     * The private Constructor of {@link CryptoCipherFactory}.
134     */
135    private CryptoCipherFactory() {
136    }
137
138    /**
139     * Gets a cipher instance for specified algorithm/mode/padding.
140     *
141     * @param properties  the configuration properties - uses {@link #CLASSES_KEY}
142     * @param transformation  algorithm/mode/padding
143     * @return CryptoCipher  the cipher  (defaults to OpenSslCipher)
144     * @throws GeneralSecurityException if cipher initialize failed
145     * @throws IllegalArgumentException if no classname(s) were provided
146     */
147    public static CryptoCipher getCryptoCipher(final String transformation, final Properties properties)
148        throws GeneralSecurityException {
149
150        final List<String> names = Utils.splitClassNames(getCipherClassString(properties), ",");
151        if (names.size() == 0) {
152            throw new IllegalArgumentException("No classname(s) provided");
153        }
154        CryptoCipher cipher = null;
155        Exception lastException = null;
156
157        final StringBuilder errorMessage = new StringBuilder("CryptoCipher ");
158        for (final String klass : names) {
159            try {
160                final Class<?> cls = ReflectionUtils.getClassByName(klass);
161                cipher = ReflectionUtils.newInstance(cls.asSubclass
162                        (CryptoCipher.class), properties, transformation);
163                if (cipher != null) {
164                    break;
165                }
166            } catch (final Exception e) {
167                lastException = e;
168                errorMessage.append("{" + klass + "}");
169            }
170        }
171
172        if (cipher != null) {
173            return cipher;
174        }
175        errorMessage.append(" is not available or transformation " +
176                transformation + " is not supported.");
177        throw new GeneralSecurityException(errorMessage.toString(), lastException);
178    }
179
180    /**
181     * Gets a cipher for algorithm/mode/padding in config value
182     * commons.crypto.cipher.transformation
183     *
184     * @param transformation the name of the transformation, e.g.,
185     * <i>AES/CBC/PKCS5Padding</i>.
186     * See the Java Cryptography Architecture Standard Algorithm Name Documentation
187     * for information about standard transformation names.
188     * @return CryptoCipher the cipher object (defaults to OpenSslCipher if available, else JceCipher)
189     * @throws GeneralSecurityException if JCE cipher initialize failed
190     */
191    public static CryptoCipher getCryptoCipher(final String transformation)
192            throws GeneralSecurityException {
193        return getCryptoCipher(transformation, new Properties());
194    }
195
196    /**
197     * Gets the cipher class.
198     *
199     * @param props The {@code Properties} class represents a set of
200     *        properties.
201     * @return the cipher class based on the props.
202     */
203    private static String getCipherClassString(final Properties props) {
204        String cipherClassString = props.getProperty(CryptoCipherFactory.CLASSES_KEY, CLASSES_DEFAULT);
205        if (cipherClassString.isEmpty()) { // TODO does it make sense to treat the empty string as the default?
206            cipherClassString = CLASSES_DEFAULT;
207        }
208        return cipherClassString;
209    }
210
211}