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 }