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,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.mime4j.storage;
021    
022    import java.io.IOException;
023    import java.io.InputStream;
024    import java.security.GeneralSecurityException;
025    import java.security.NoSuchAlgorithmException;
026    
027    import javax.crypto.Cipher;
028    import javax.crypto.CipherInputStream;
029    import javax.crypto.CipherOutputStream;
030    import javax.crypto.KeyGenerator;
031    import javax.crypto.spec.SecretKeySpec;
032    
033    /**
034     * A {@link StorageProvider} that transparently scrambles and unscrambles the
035     * data stored by another <code>StorageProvider</code>.
036     *
037     * <p>
038     * Example usage:
039     *
040     * <pre>
041     * StorageProvider mistrusted = new TempFileStorageProvider();
042     * StorageProvider enciphered = new CipherStorageProvider(mistrusted);
043     * StorageProvider provider = new ThresholdStorageProvider(enciphered);
044     * DefaultStorageProvider.setInstance(provider);
045     * </pre>
046     */
047    public class CipherStorageProvider extends AbstractStorageProvider {
048    
049        private final StorageProvider backend;
050        private final String algorithm;
051        private final KeyGenerator keygen;
052    
053        /**
054         * Creates a new <code>CipherStorageProvider</code> for the given back-end
055         * using the Blowfish cipher algorithm.
056         *
057         * @param backend
058         *            back-end storage strategy to encrypt.
059         */
060        public CipherStorageProvider(StorageProvider backend) {
061            this(backend, "Blowfish");
062        }
063    
064        /**
065         * Creates a new <code>CipherStorageProvider</code> for the given back-end
066         * and cipher algorithm.
067         *
068         * @param backend
069         *            back-end storage strategy to encrypt.
070         * @param algorithm
071         *            the name of the symmetric block cipher algorithm such as
072         *            "Blowfish", "AES" or "RC2".
073         */
074        public CipherStorageProvider(StorageProvider backend, String algorithm) {
075            if (backend == null)
076                throw new IllegalArgumentException();
077    
078            try {
079                this.backend = backend;
080                this.algorithm = algorithm;
081                this.keygen = KeyGenerator.getInstance(algorithm);
082            } catch (NoSuchAlgorithmException e) {
083                throw new IllegalArgumentException(e);
084            }
085        }
086    
087        public StorageOutputStream createStorageOutputStream() throws IOException {
088            SecretKeySpec skeySpec = getSecretKeySpec();
089    
090            return new CipherStorageOutputStream(backend
091                    .createStorageOutputStream(), algorithm, skeySpec);
092        }
093    
094        private SecretKeySpec getSecretKeySpec() {
095            byte[] raw = keygen.generateKey().getEncoded();
096            return new SecretKeySpec(raw, algorithm);
097        }
098    
099        private static final class CipherStorageOutputStream extends
100                StorageOutputStream {
101            private final StorageOutputStream storageOut;
102            private final String algorithm;
103            private final SecretKeySpec skeySpec;
104            private final CipherOutputStream cipherOut;
105    
106            public CipherStorageOutputStream(StorageOutputStream out,
107                    String algorithm, SecretKeySpec skeySpec) throws IOException {
108                try {
109                    this.storageOut = out;
110                    this.algorithm = algorithm;
111                    this.skeySpec = skeySpec;
112    
113                    Cipher cipher = Cipher.getInstance(algorithm);
114                    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
115    
116                    this.cipherOut = new CipherOutputStream(out, cipher);
117                } catch (GeneralSecurityException e) {
118                    throw (IOException) new IOException().initCause(e);
119                }
120            }
121    
122            @Override
123            public void close() throws IOException {
124                super.close();
125                cipherOut.close();
126            }
127    
128            @Override
129            protected void write0(byte[] buffer, int offset, int length)
130                    throws IOException {
131                cipherOut.write(buffer, offset, length);
132            }
133    
134            @Override
135            protected Storage toStorage0() throws IOException {
136                // cipherOut has already been closed because toStorage calls close
137                Storage encrypted = storageOut.toStorage();
138                return new CipherStorage(encrypted, algorithm, skeySpec);
139            }
140        }
141    
142        private static final class CipherStorage implements Storage {
143            private Storage encrypted;
144            private final String algorithm;
145            private final SecretKeySpec skeySpec;
146    
147            public CipherStorage(Storage encrypted, String algorithm,
148                    SecretKeySpec skeySpec) {
149                this.encrypted = encrypted;
150                this.algorithm = algorithm;
151                this.skeySpec = skeySpec;
152            }
153    
154            public void delete() {
155                if (encrypted != null) {
156                    encrypted.delete();
157                    encrypted = null;
158                }
159            }
160    
161            public InputStream getInputStream() throws IOException {
162                if (encrypted == null)
163                    throw new IllegalStateException("storage has been deleted");
164    
165                try {
166                    Cipher cipher = Cipher.getInstance(algorithm);
167                    cipher.init(Cipher.DECRYPT_MODE, skeySpec);
168    
169                    InputStream in = encrypted.getInputStream();
170                    return new CipherInputStream(in, cipher);
171                } catch (GeneralSecurityException e) {
172                    throw (IOException) new IOException().initCause(e);
173                }
174            }
175        }
176    
177    }