/* * * Panbox - encryption for cloud storage * Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Additonally, third party code may be provided with notices and open source * licenses from communities and third parties that govern the use of those * portions, and any licenses granted hereunder do not alter any rights and * obligations you may have under such open source licenses, however, the * disclaimer of warranty and limitation of liability provisions of the GPLv3 * will apply to all the product. * */ package org.panbox.desktop.common.vfs.backend; import java.io.File; import java.io.IOException; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.util.Arrays; import javax.crypto.SecretKey; import org.apache.log4j.Logger; import org.panbox.core.crypto.CryptCore; import org.panbox.core.crypto.FileObfuscatorFactory; import org.panbox.core.crypto.Obfuscator; import org.panbox.core.exception.ObfuscationException; import org.panbox.core.exception.SymmetricKeyDecryptionException; import org.panbox.core.exception.SymmetricKeyNotFoundException; import org.panbox.core.keymgmt.EncryptedShareKey; import org.panbox.core.keymgmt.IVolume; import org.panbox.core.keymgmt.ShareKey; import org.panbox.core.vfs.backend.VirtualFile; import org.panbox.core.vfs.backend.VirtualVolume; import org.panbox.desktop.common.ex.DeviceKeyException; /** * VFSShare represents a share that can be mounted in the Panbox virtual * filesystem component, containing a share name, the share key used for * encryption and obfuscation and the VirtualFile backend storage, which is used * to access the concrete data files. * * @author Clemens A. Schulz <c.schulz@sirrix.com> */ public class VFSShare { private static final Logger log = Logger.getLogger(VFSShare.class); private final SecretKey obfuscationKey; private final String shareName; private final VirtualVolume backend; private final IVolume volume; private final PublicKey publicDeviceKey; private final PrivateKey privateDeviceKey; private ShareKey[] shareKeyCache = new ShareKey[20]; private final Obfuscator obfuscator; /** * Returns the share key of this share. * * @return Share key as SecretKey instance. */ public VFSShare(String shareName, String path, VirtualVolume backend, IVolume volume, KeyPair deviceKeys) throws DeviceKeyException { this.backend = backend; this.shareName = shareName; this.volume = volume; this.publicDeviceKey = deviceKeys.getPublic(); this.privateDeviceKey = deviceKeys.getPrivate(); try { this.obfuscationKey = CryptCore.decryptSymmertricKey( volume.getEncryptedObfuscationKey(publicDeviceKey), privateDeviceKey); } catch (SymmetricKeyDecryptionException e) { throw new RuntimeException("Could not decrypt obfuscation key.", e); } catch (SymmetricKeyNotFoundException e) { throw new DeviceKeyException(e); } try { this.obfuscator = org.panbox.core.crypto.AbstractObfuscatorFactory .getFactory(FileObfuscatorFactory.class).getInstance(path, shareName); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | ObfuscationException e) { log.fatal("obfuscator intialization failed, quitting!", e); throw new RuntimeException("Obfuscator intialization failed, quitting!", e); } } public Obfuscator getObfuscator() { return obfuscator; } /** * To decrypt a file, this method can be used to retrieve the sharekey * specified in the header of the corresponding file. * * @param version * the version of the sharekey as extracted from the file's * metadata * @return the SecretKey that can be used to decrypt the file's FEK */ public SecretKey getShareKey(int version) { if (version >= this.shareKeyCache.length) { growCache(); } ShareKey shareKey = this.shareKeyCache[version]; if (shareKey == null) { // Get key from backend try { log.debug("Retrieving ShareKey version " + version + "from Backend..."); EncryptedShareKey key = volume.getEncryptedShareKey(version, publicDeviceKey); SecretKey secretKey = CryptCore.decryptSymmertricKey( key.encryptedKey, privateDeviceKey); shareKey = new ShareKey(secretKey, key.version); } catch (SymmetricKeyNotFoundException | SymmetricKeyDecryptionException e) { throw new RuntimeException(e); } // Store in cache log.debug("Using ShareKey version " + version); this.shareKeyCache[version] = shareKey; } return shareKey.key; } /** * To decrypt a file, this method can be used to retrieve the sharekey * specified in the header of the corresponding file. * * @return the SecretKey that can be used to decrypt the file's FEK */ public ShareKey getLatestShareKey() { try { EncryptedShareKey key = volume .getLatestEncryptedShareKey(publicDeviceKey); if (key.version >= this.shareKeyCache.length) { growCache(); } ShareKey shareKey = this.shareKeyCache[key.version]; if (shareKey == null) { log.debug("Retrieving ShareKey from Backend..."); SecretKey secKey = CryptCore.decryptSymmertricKey( key.encryptedKey, privateDeviceKey); shareKey = new ShareKey(secKey, key.version); this.shareKeyCache[key.version] = shareKey; } log.debug("Using latest sharekey: Version " + shareKey.version); return shareKey; } catch (SymmetricKeyNotFoundException | SymmetricKeyDecryptionException e) { log.fatal("Decryption of ShareKey failed", e); throw new RuntimeException("Decryption of ShareKey failed!", e); } } private void growCache() { this.shareKeyCache = Arrays.copyOf(this.shareKeyCache, this.shareKeyCache.length + 10); } public SecretKey getObfuscationKey() { return this.obfuscationKey; } /** * Returns the backend storage of this share. * * @return VirtualVolume instance of the backend. */ public VirtualVolume getBackend() { return backend; } /** * Returns the name of this share. * * @return Share name string. */ public String getShareName() { return shareName; } /** * This method decides whether the specified fileName is a file in this * share or not. * * @param fileName * Relative path to a file. * @return true if the file is part of this share, else false. */ public boolean contains(String fileName) { // NOTE: share names may not be prefix free if (fileName.startsWith(shareName + File.separator) || fileName.substring(1).startsWith(shareName + File.separator) || fileName.substring(1).toUpperCase().startsWith(shareName + File.separator) || fileName.equals(shareName) || fileName.toUpperCase().equals(shareName) || fileName.substring(1).equals(shareName)) { return true; // file is part of this share! } return false; // file is not part of this share! } /** * This method decides whether the specified VirtualFile is a file in this * share or not. * * @param file * VirtualFile instance of a file. * @return true if the file is part of this share, else false. */ public boolean contains(VirtualFile file) { if (file.getFileName().equals(shareName) || file.getFileName().equals(File.separator + shareName)) { return true; // share-file of the share is always included! } if (file.getFile() == null) { return false; // root-file is never included! } // NOTE: filenames are not necessarily prefix-free String vfilename = file.getFile().getAbsolutePath(); String vrootname = backend.getRoot().getFile().getAbsolutePath(); if (vfilename.startsWith(vrootname + File.separator) || vfilename.equals(vrootname)) { return true; // file is part of this share! } return false; } /** * Returns the relative Path in this share to the specified file. * * @param file * VirtualFile instance of a file. * @return Relative path of the file as String. */ public String getRelativePath(VirtualFile file) { if (file.getRelativePath().length() > 0 && file.getRelativePath().startsWith("/")) return File.separator + shareName + file.getRelativePath(); else return File.separator + shareName + File.separator + file.getRelativePath(); } /** * Returns a VirtualFile instance for the containing file with the specified * file path. * * @param fileName * Relative path to a file. * @return VirtualFile instance of the specified file. * @throws IOException */ public VirtualFile getFile(String fileName) throws IOException { return backend.getFile(getRelativePathInVolume(fileName)); } // returns the relative path in volume for the specified filename. This // means especially a path WITHOUT the sharename! private String getRelativePathInVolume(String fileName) { if (fileName.startsWith(File.separator)) { if (fileName.substring(1).equals(shareName)) { return File.separator; } else { fileName = fileName.substring(1); return fileName.substring(fileName.indexOf(File.separator)); } } if (fileName.equals(shareName)) { return File.separator; } else { return fileName.substring(fileName.indexOf(File.separator)); } } @Override public String toString() { return getShareName(); } }