/** * 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.File; import java.io.FileInputStream; 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.BouncyCastleFileDecrypter; import edu.wisc.doit.tcrypt.FileDecrypter; import edu.wisc.doit.tcrypt.FileEncrypter; /** * Extends the base {@link Copy} task to provide on the fly decryption of files. The * decryption is hooked into the process fairly early on so filtering and other transformations * that copy can do should all operate on the decrypted version of the file. * <br> * Defaults to removing the .tar extension from decrypted files * * @author Eric Dalquist */ public class DecryptCopy extends Copy { private Resource privateKey; private FileDecrypter fileDecrypter; /** * @param privateKey The private key used to decrypt */ public void setPrivateKey(Resource privateKey) { this.privateKey = privateKey; } public DecryptCopy() { this.fileUtils = new DecryptFileUtils(); //Setup adding .tar to file names final GlobPatternMapper fileNameMapper = new GlobPatternMapper(); fileNameMapper.setFrom("*.tar"); fileNameMapper.setTo("*"); this.add(fileNameMapper); } private FileDecrypter getFileDecrypter() { if (this.fileDecrypter != null) { return this.fileDecrypter; } Reader privateKeyReader = null; try { privateKeyReader = new InputStreamReader(new BufferedInputStream(this.privateKey.getInputStream()), FileEncrypter.CHARSET); fileDecrypter = new BouncyCastleFileDecrypter(privateKeyReader); } catch (IOException e) { throw new BuildException("Failed to create BouncyCastleFileDecrypter for private key: " + this.privateKey, e); } finally { IOUtils.closeQuietly(privateKeyReader); } return this.fileDecrypter; } private final class DecryptFileUtils 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 FileDecrypter fd = getFileDecrypter(); //Swap out the standard FileResource for a DecryptingFileResource ResourceUtils.copyResource(new DecryptingFileResource(fd, sourceFile), new FileResource(destFile), filters, filterChains, overwrite, preserveLastModified, append, inputEncoding, outputEncoding, project, force); } } private final class DecryptingFileResource extends Resource { private final FileDecrypter fileDecrypter; private final File file; private final File baseDir; public DecryptingFileResource(FileDecrypter fileDecrypter, File file) { this.fileDecrypter = fileDecrypter; this.file = file; this.baseDir = file.getParentFile(); } public String getName() { return this.baseDir == null ? this.file.getName() : fileUtils.removeLeadingPath(this.baseDir, this.file); } public boolean isExists() { return this.file.exists(); } public long getLastModified() { return this.file.lastModified(); } public boolean isDirectory() { return this.file.isDirectory(); } public long getSize() { return this.file.length(); } public InputStream getInputStream() throws IOException { try { return this.fileDecrypter.decrypt(new BufferedInputStream(new FileInputStream(this.file))); } catch (InvalidCipherTextException e) { throw new BuildException("Invalid key '" + privateKey + "' for: " + this.file, e); } catch (DecoderException e) { throw new BuildException("Invalid key '" + privateKey + "' for: " + this.file, e); } } public OutputStream getOutputStream() throws IOException { throw new UnsupportedOperationException("DecryptingFileResource is read only"); } public String toString() { String absolutePath = file.getAbsolutePath(); return fileUtils.normalize(absolutePath).getAbsolutePath(); } } }