View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.james.mime4j.storage;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.security.GeneralSecurityException;
25  import java.security.NoSuchAlgorithmException;
26  
27  import javax.crypto.Cipher;
28  import javax.crypto.CipherInputStream;
29  import javax.crypto.CipherOutputStream;
30  import javax.crypto.KeyGenerator;
31  import javax.crypto.spec.SecretKeySpec;
32  
33  /**
34   * A {@link StorageProvider} that transparently scrambles and unscrambles the
35   * data stored by another <code>StorageProvider</code>.
36   *
37   * <p>
38   * Example usage:
39   *
40   * <pre>
41   * StorageProvider mistrusted = new TempFileStorageProvider();
42   * StorageProvider enciphered = new CipherStorageProvider(mistrusted);
43   * StorageProvider provider = new ThresholdStorageProvider(enciphered);
44   * DefaultStorageProvider.setInstance(provider);
45   * </pre>
46   */
47  public class CipherStorageProvider extends AbstractStorageProvider {
48  
49      private final StorageProvider backend;
50      private final String algorithm;
51      private final KeyGenerator keygen;
52  
53      /**
54       * Creates a new <code>CipherStorageProvider</code> for the given back-end
55       * using the Blowfish cipher algorithm.
56       *
57       * @param backend
58       *            back-end storage strategy to encrypt.
59       */
60      public CipherStorageProvider(StorageProvider backend) {
61          this(backend, "Blowfish");
62      }
63  
64      /**
65       * Creates a new <code>CipherStorageProvider</code> for the given back-end
66       * and cipher algorithm.
67       *
68       * @param backend
69       *            back-end storage strategy to encrypt.
70       * @param algorithm
71       *            the name of the symmetric block cipher algorithm such as
72       *            "Blowfish", "AES" or "RC2".
73       */
74      public CipherStorageProvider(StorageProvider backend, String algorithm) {
75          if (backend == null)
76              throw new IllegalArgumentException();
77  
78          try {
79              this.backend = backend;
80              this.algorithm = algorithm;
81              this.keygen = KeyGenerator.getInstance(algorithm);
82          } catch (NoSuchAlgorithmException e) {
83              throw new IllegalArgumentException(e);
84          }
85      }
86  
87      public StorageOutputStream createStorageOutputStream() throws IOException {
88          SecretKeySpec skeySpec = getSecretKeySpec();
89  
90          return new CipherStorageOutputStream(backend
91                  .createStorageOutputStream(), algorithm, skeySpec);
92      }
93  
94      private SecretKeySpec getSecretKeySpec() {
95          byte[] raw = keygen.generateKey().getEncoded();
96          return new SecretKeySpec(raw, algorithm);
97      }
98  
99      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 }