/**
* Copyright 2012, Board of Regents of the University of
* Wisconsin System. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Board of Regents of the University of Wisconsin
* System licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package edu.wisc.doit.tcrypt.ant;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.util.Vector;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.io.IOUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FilterSetCollection;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.GlobPatternMapper;
import org.apache.tools.ant.util.ResourceUtils;
import org.bouncycastle.crypto.InvalidCipherTextException;
import edu.wisc.doit.tcrypt.BouncyCastleFileEncrypter;
import edu.wisc.doit.tcrypt.FileEncrypter;
/**
* Extends the base {@link Copy} task to provide on the fly encryption of files. The
* encryption is hooked into the process fairly early on so filtering and other transformations
* that copy can do should all operate on the encrypted version of the destFile.
* <br>
* Defaults to adding the .tar extension to encrypted files
*
* @author Eric Dalquist
*/
public class EncryptCopy extends Copy {
private Resource publicKey;
private FileEncrypter fileEncrypter;
/**
* @param publicKey The public key used to encrypt
*/
public void setPublicKey(Resource publicKey) {
this.publicKey = publicKey;
}
public EncryptCopy() {
this.fileUtils = new EncryptFileUtils();
//Setup adding .tar to destFile names
final GlobPatternMapper fileNameMapper = new GlobPatternMapper();
fileNameMapper.setFrom("*");
fileNameMapper.setTo("*.tar");
this.add(fileNameMapper);
}
private FileEncrypter getFileEncrypter() {
if (this.fileEncrypter != null) {
return this.fileEncrypter;
}
Reader publicKeyReader = null;
try {
publicKeyReader = new InputStreamReader(new BufferedInputStream(this.publicKey.getInputStream()), FileEncrypter.CHARSET);
fileEncrypter = new BouncyCastleFileEncrypter(publicKeyReader);
}
catch (IOException e) {
throw new BuildException("Failed to create BouncyCastleFileEncrypter for private key: " + this.publicKey, e);
}
finally {
IOUtils.closeQuietly(publicKeyReader);
}
return this.fileEncrypter;
}
private final class EncryptFileUtils extends FileUtils {
public void copyFile(File sourceFile, File destFile,
FilterSetCollection filters, Vector filterChains,
boolean overwrite, boolean preserveLastModified,
boolean append,
String inputEncoding, String outputEncoding,
Project project, boolean force) throws IOException {
final FileEncrypter fe = getFileEncrypter();
//Swap out the standard FileResource for a EncryptingFileResource
ResourceUtils.copyResource(new FileResource(sourceFile),
new EncryptingFileResource(sourceFile, fe, destFile),
filters, filterChains, overwrite,
preserveLastModified, append, inputEncoding,
outputEncoding, project, force);
}
}
private final class EncryptingFileResource extends Resource {
private final File sourceFile;
private final FileEncrypter fileEncrypter;
private final File destFile;
private final File baseDir;
public EncryptingFileResource(File sourceFile, FileEncrypter fileEncrypter, File destFile) {
this.sourceFile = sourceFile;
this.fileEncrypter = fileEncrypter;
this.destFile = destFile;
this.baseDir = destFile.getParentFile();
}
public String getName() {
return this.baseDir == null ? this.destFile.getName()
: fileUtils.removeLeadingPath(this.baseDir, this.destFile);
}
public boolean isExists() {
return this.destFile.exists();
}
public long getLastModified() {
return this.destFile.lastModified();
}
public boolean isDirectory() {
return this.destFile.isDirectory();
}
public long getSize() {
return this.destFile.length();
}
public InputStream getInputStream() throws IOException {
throw new UnsupportedOperationException("EncryptingFileResource is read only");
}
public OutputStream getOutputStream() throws IOException {
final String fileName = this.sourceFile.getName();
final long size = this.sourceFile.length();
try {
return this.fileEncrypter.encrypt(fileName, (int)size, new BufferedOutputStream(new FileOutputStream(this.destFile)));
}
catch (InvalidCipherTextException e) {
throw new BuildException("Invalid key '" + publicKey + "' for: " + this.destFile, e);
}
catch (DecoderException e) {
throw new BuildException("Invalid key '" + publicKey + "' for: " + this.destFile, e);
}
}
public String toString() {
String absolutePath = destFile.getAbsolutePath();
return fileUtils.normalize(absolutePath).getAbsolutePath();
}
}
}