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 020package org.apache.james.mime4j.storage; 021 022import java.io.IOException; 023import java.io.InputStream; 024import java.security.GeneralSecurityException; 025import java.security.NoSuchAlgorithmException; 026 027import javax.crypto.Cipher; 028import javax.crypto.CipherInputStream; 029import javax.crypto.CipherOutputStream; 030import javax.crypto.KeyGenerator; 031import 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 */ 047public 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}