/* * * 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.sharemgmt; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.rmi.RemoteException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.UnrecoverableKeyException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.UUID; import org.apache.commons.codec.digest.DigestUtils; import org.apache.log4j.Logger; import org.panbox.PanboxConstants; import org.panbox.Settings; import org.panbox.core.Utils; import org.panbox.core.crypto.KeyConstants; import org.panbox.core.csp.StorageBackendType; import org.panbox.core.exception.DeviceListException; import org.panbox.core.exception.ObfuscationException; import org.panbox.core.exception.ShareMetaDataException; import org.panbox.core.identitymgmt.AbstractIdentity; import org.panbox.core.identitymgmt.IPerson; import org.panbox.core.identitymgmt.PanboxContact; import org.panbox.core.keymgmt.VolumeParams; import org.panbox.core.keymgmt.VolumeParams.VolumeParamsFactory; import org.panbox.desktop.common.ex.DeviceKeyException; import org.panbox.desktop.common.gui.PasswordEnterDialog; import org.panbox.desktop.common.gui.PasswordEnterDialog.PermissionType; import org.panbox.desktop.common.gui.shares.PanboxShare; import org.panbox.desktop.common.utils.FileUtils; public class ShareManagerImpl implements IShareManager { private static final Logger logger = Logger.getLogger("org.panbox"); private final String SHARESDB = Settings.getInstance().getSharesDBPath(); private final String SHARESDB_CONNECT_STRING = "jdbc:sqlite:" + SHARESDB; private static final String TABLE_SHARES = "shares"; private static ShareManagerImpl instance = null; private Connection connection = null; private AbstractIdentity identity; private final VolumeParamsFactory paramsFactory = VolumeParamsFactory .getFactory(); public final IPanboxService service; private ShareManagerImpl(IPanboxService service) throws ShareManagerException { this.service = service; // create a database connection try { connection = DriverManager.getConnection(SHARESDB_CONNECT_STRING); } catch (SQLException ex) { throw new ShareManagerException( "Could not get connection for SQL DB: " + SHARESDB_CONNECT_STRING, ex); } init(); } public static ShareManagerImpl getInstance() throws ShareManagerException { if (instance == null) { throw new RuntimeException( "Service implementation has not been set yet!"); } return instance; } public static ShareManagerImpl getInstance(IPanboxService service) throws ShareManagerException { if (instance == null) { instance = new ShareManagerImpl(service); // init(); } return instance; } private void init() throws ShareManagerException { Connection con = null; try { con = DriverManager.getConnection(SHARESDB_CONNECT_STRING); Statement s = con.createStatement(); ResultSet rs = s .executeQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='" + TABLE_SHARES + "';"); if (!rs.next()) { logger.debug("ShareManager database did not exist. Creating a new one now..."); System.out.println("new ShareManager, creating table..."); createTables(s); } else { logger.debug("ShareManager database exists. Will use that one..."); } } catch (SQLException ex) { throw new ShareManagerException("Failed to run SQL command init: ", ex); } finally { if (con != null) { try { con.close(); } catch (SQLException ex) { throw new ShareManagerException( "Failed to run SQL command init: ", ex); } } } } private static void createTables(Statement statement) throws ShareManagerException { logger.debug("ShareManager : createTables"); try { statement.executeUpdate("drop table if exists " + TABLE_SHARES); statement .executeUpdate("create table " + TABLE_SHARES + " (id INTEGER PRIMARY KEY AUTOINCREMENT, uuid string, name string, type string, backendURL string)"); } catch (SQLException ex) { throw new ShareManagerException( "Failed to run SQL command during createTables: ", ex); } } @Override public List<String> getInstalledShares() throws ShareManagerException { logger.debug("ShareManager : getInstalledShares"); List<String> shares = new ArrayList<String>(); ResultSet rs = null; Statement statement = null; try { statement = connection.createStatement(); statement.setQueryTimeout(30); // set timeout to 30 sec. rs = statement.executeQuery("select * from " + TABLE_SHARES); while (rs.next()) { String shareName = rs.getString("name"); shares.add(shareName); } } catch (SQLException ex) { throw new ShareManagerException( "Failed to run getInstalledShares: ", ex); } finally { try { if (rs != null) rs.close(); } catch (Exception e) { } try { if (statement != null) statement.close(); } catch (Exception e) { } } return shares; } private StorageBackendType getShareType(String shareType) { if (shareType.equals(StorageBackendType.DROPBOX.toString())) { return StorageBackendType.DROPBOX; } else if (shareType.equals(StorageBackendType.FOLDER.toString())) { return StorageBackendType.FOLDER; } // should not have gotten here logger.warn("Could not find correct StorageBackenType for type description: " + shareType); logger.warn("Returning StorageBackendType FOLDER as fallback!"); return StorageBackendType.FOLDER; } @Override public boolean shareNameAvailable(String shareName) throws ShareManagerException, UnrecoverableKeyException, ShareMetaDataException { logger.debug("ShareManager : isShareNameAvailable(" + shareName + ")"); ResultSet rs = null; String sql = "select * from " + TABLE_SHARES + " where name=?"; PreparedStatement pstatement = null; try { pstatement = connection.prepareStatement(sql); pstatement.setString(1, shareName); pstatement.setQueryTimeout(30); // set timeout to 30 sec. rs = pstatement.executeQuery(); if (rs.next()) { return false; } else { return true; } } catch (SQLException ex) { throw new ShareManagerException( "Failed to run shareNameAvailable: ", ex); } finally { try { if (rs != null) rs.close(); } catch (Exception e) { } try { if (pstatement != null) pstatement.close(); } catch (Exception e) { } } } @Override public PanboxShare getShareForName(String shareName) throws ShareDoesNotExistException, ShareManagerException, UnrecoverableKeyException, ShareMetaDataException { logger.debug("ShareManager : getShareForName(" + shareName + ")"); ResultSet rs = null; String sql = "select * from " + TABLE_SHARES + " where name=?"; PreparedStatement pstatement = null; try { pstatement = connection.prepareStatement(sql); pstatement.setString(1, shareName); pstatement.setQueryTimeout(30); // set timeout to 30 sec. rs = pstatement.executeQuery(); if (rs.next()) { String shareType = rs.getString("type"); String shareURL = rs.getString("backendURL"); String shareUUID = rs.getString("uuid"); try { PanboxShare share = nameTypeUrlToVolumeData(shareName, shareURL, getShareType(shareType), UUID.fromString(shareUUID)); return share; } catch (FileNotFoundException ex) { throw new ShareInaccessibleException( "A share with the specified share name (" + shareName + ") does exist, but is not accessible anymore!", ex); } } else { throw new ShareDoesNotExistException( "A share with the specified share name (" + shareName + ") does not exist."); } } catch (SQLException | IOException ex) { throw new ShareManagerException("Failed to run getShareForName: ", ex); } finally { try { if (rs != null) rs.close(); } catch (Exception e) { } try { if (pstatement != null) pstatement.close(); } catch (Exception e) { } } } @Override public PanboxShare getShareForPath(String sharePath) throws UnrecoverableKeyException, ShareManagerException, ShareMetaDataException { logger.debug("ShareManager: getShareForPath(" + sharePath + ")"); ResultSet rs = null; String sql = "select * from " + TABLE_SHARES + " where backendURL=?"; PreparedStatement pstatement = null; try { pstatement = connection.prepareStatement(sql); pstatement.setString(1, sharePath); pstatement.setQueryTimeout(30); // set timeout to 30 sec. rs = pstatement.executeQuery(); if (rs.next()) { String shareName = rs.getString("name"); String shareType = rs.getString("type"); String shareUUID = rs.getString("uuid"); try { PanboxShare share = nameTypeUrlToVolumeData(shareName, sharePath, getShareType(shareType), UUID.fromString(shareUUID)); return share; } catch (FileNotFoundException ex) { throw new ShareInaccessibleException( "A share with the specified share path (" + sharePath + ") does exist, but is not accessible anymore!", ex); } } else { logger.warn("A share with the specified share path (" + sharePath + ") does not exist."); return null; } } catch (SQLException | IOException ex) { throw new ShareManagerException("Failed to run getShareForPath: ", ex); } finally { try { if (rs != null) rs.close(); } catch (Exception e) { } try { if (pstatement != null) pstatement.close(); } catch (Exception e) { } } } @Override public boolean sharePathAvailable(String path) throws UnrecoverableKeyException, ShareManagerException, ShareMetaDataException { logger.debug("ShareManager : isSharePathAvailable(" + path + ")"); ResultSet rs = null; String sql = "select * from " + TABLE_SHARES + " where backendURL=?"; PreparedStatement pstatement = null; try { pstatement = connection.prepareStatement(sql); pstatement.setString(1, path); pstatement.setQueryTimeout(30); // set timeout to 30 sec. rs = pstatement.executeQuery(); if (rs.next()) { return false; } else { return true; } } catch (SQLException ex) { throw new ShareManagerException( "Failed to run sharePathAvailable: ", ex); } finally { try { if (rs != null) rs.close(); } catch (Exception e) { } try { if (pstatement != null) pstatement.close(); } catch (Exception e) { } } } @Override public PanboxShare editShare(String shareName, String newShareName, StorageBackendType newShareType, String newSharePath, char[] password) throws ShareManagerException, ShareNameAlreadyExistsException, SharePathAlreadyExistsException, UnrecoverableKeyException, ShareMetaDataException { logger.debug("ShareManager : editShare(" + shareName + "," + newShareName + "," + newShareType + "," + newSharePath + ")"); String sql = "UPDATE " + TABLE_SHARES + " SET name = ?, type = ?, backendURL = ?" + "WHERE name = ?;"; // String sqluuid = "SELECT FROM " + TABLE_SHARES + " uuid " + // "WHERE name = ?;"; boolean nameChanged = !shareName.equals(newShareName); boolean pathChanged = false; try { pathChanged = !getShareForName(shareName).getPath().equals( newSharePath); } catch (ShareDoesNotExistException e) { e.printStackTrace(); } if ((!nameChanged || shareNameAvailable(newShareName)) && (!pathChanged || sharePathAvailable(newSharePath))) { PreparedStatement pStatement = null; ResultSet rs = null; try { pStatement = connection.prepareStatement(sql); pStatement.setString(1, newShareName); pStatement.setString(2, newShareType.toString()); pStatement.setString(3, newSharePath); pStatement.setString(4, shareName); pStatement.executeUpdate(); pStatement.close(); return getShareForName(newShareName); } catch (SQLException | ShareDoesNotExistException ex) { // FIXME: consider reverting DB changes if something went wrong throw new ShareManagerException( "Failed to run SQL command editShare: ", ex); } finally { try { if (rs != null) rs.close(); } catch (Exception e) { } try { if (pStatement != null) pStatement.close(); } catch (Exception e) { } } } else { if (nameChanged && !shareNameAvailable(newShareName)) { throw new ShareNameAlreadyExistsException( "A share with the name " + newShareName + " does already exist."); } else { throw new SharePathAlreadyExistsException( "A share with the url " + newSharePath + " does already exist."); } } } @Override public void removeShareFromDB(String shareName) throws ShareManagerException { logger.debug("ShareManager : removeShareFromDB(" + shareName + ")"); PreparedStatement pstatement = null; String sql = "DELETE from " + TABLE_SHARES + " WHERE name=?;"; try { pstatement = connection.prepareStatement(sql); pstatement.setString(1, shareName); pstatement.setQueryTimeout(30); // set timeout to 30 sec. pstatement.executeUpdate(); } catch (SQLException ex) { throw new ShareManagerException( "Failed to run SQL command removeShareFromDB: ", ex); } finally { try { if (pstatement != null) pstatement.close(); } catch (Exception e) { } } } @Override public void removeShare(String shareName, String sharePath, StorageBackendType type) throws ShareManagerException { logger.debug("ShareManager : removeShare(" + shareName + ")"); try { removeShareFromDB(shareName); service.removeShare(paramsFactory.createVolumeParams() .setShareName(shareName).setPath(sharePath).setType(type)); } catch (RemoteException ex) { throw new ShareManagerException( "Failed to run command removeShare: ", ex); } } @Override public PanboxShare addDevicePermission(PanboxShare share, String deviceName, char[] password) throws ShareDoesNotExistException, ShareManagerException, UnrecoverableKeyException, ShareMetaDataException { try { VolumeParams p = paramsFactory .createVolumeParams() .setKeys(identity, password) .setUserAlias(identity.getEmail()) .setDeviceAlias(deviceName) .setPublicDeviceKey( identity.getPublicKeyForDevice(deviceName)) .setShareName(share.getName()).setPath(share.getPath()) .setType(share.getType()); PanboxShare pbShare = service.addDevice(p); return pbShare; } catch (IllegalArgumentException | RemoteException e) { throw new ShareManagerException( "Could not add Device to ShareMetadata", e); } } @Override public PanboxShare removeDevicePermission(PanboxShare share, String deviceName, char[] password) throws ShareDoesNotExistException, ShareManagerException, UnrecoverableKeyException, ShareMetaDataException { try { VolumeParams p = paramsFactory .createVolumeParams() .setUserAlias(identity.getEmail()) .setPublicSignatureKey(identity.getPublicKeySign()) .setPrivateSignatureKey( identity.getPrivateKeySign(password)) .setDeviceAlias(deviceName).setShareName(share.getName()) .setPath(share.getPath()).setType(share.getType()); PanboxShare pbShare = service.removeDevice(p); return pbShare; } catch (IllegalArgumentException | RemoteException e) { throw new ShareManagerException( "Could not add Device to ShareMetadata", e); } } @Override public PanboxShare addContactPermission(PanboxShare share, String email, char[] password) throws ShareDoesNotExistException, ShareManagerException, UnrecoverableKeyException, ShareMetaDataException { PanboxContact contact = identity.getAddressbook().contactExists(email); if (contact == null) { throw new ShareManagerException("Contact " + email + " is not in addressbook."); } try { VolumeParams p = paramsFactory.createVolumeParams() .setKeys(identity, password) .setOwnerAlias(identity.getEmail()) .setOtherSignatureKey(contact.getPublicKeySign()) .setOtherEncryptionKey(contact.getPublicKeyEnc()) .setUserAlias(contact.getEmail()) .setShareName(share.getName()).setPath(share.getPath()) .setType(share.getType()); // Add Invitation fingerPrint so invited user can detect // invitation File invitationFolder = new File(share.getPath() + File.separator + PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY + File.separator + PanboxConstants.PANBOX_SHARE_INVITATION_DIRECTORY); if (invitationFolder.isFile()) { // invitationFolder is not a folder throw new RuntimeException( "invitation folder is a file, not a folder!"); } if (!invitationFolder.exists()) { invitationFolder.mkdir(); } String fingerPrint = DigestUtils.md5Hex(contact.getCertSign() .getPublicKey().getEncoded()); File fpFile = new File(invitationFolder.getAbsolutePath() + File.separator + fingerPrint); fpFile.createNewFile(); PanboxShare pbShare = service.inviteUser(p); pbShare.generatePermissionsModel(identity); return pbShare; } catch (IOException e) { throw new ShareManagerException( "Could not invite Contact to ShareMetadata", e); } } public void setIdentity(AbstractIdentity identity) { this.identity = identity; } @Override public PanboxShare addNewShare(PanboxShare share, char[] password) throws ShareManagerException, ShareNameAlreadyExistsException, SharePathAlreadyExistsException, UnrecoverableKeyException, ShareMetaDataException { logger.debug("ShareManager : addNewShare(" + share.getName() + "," + share.getType() + "," + share.getPath() + ")"); boolean shareDirCreated = false; if (shareNameAvailable(share.getName()) && sharePathAvailable(share.getPath())) { File shareFolder = new File(share.getPath()); String sql = "insert into " + TABLE_SHARES + " VALUES (NULL, (?), (?), (?), (?))"; PreparedStatement pStatement = null; try { if (!shareFolder.exists()) { shareDirCreated = shareFolder.mkdirs(); } PanboxShare resultShare = nameTypeUrlToVolumeData(share, password); pStatement = connection.prepareStatement(sql); pStatement.setString(1, resultShare.getUuid().toString()); pStatement.setString(2, share.getName()); pStatement.setString(3, share.getType().toString()); pStatement.setString(4, share.getPath()); pStatement.executeUpdate(); return resultShare; } catch (SQLException | NoSuchAlgorithmException | IOException e) { logger.error( "ShareManagerImpl : addNewShare : Already logging exception: ", e); try { // in case of error we need to remove the share again removeShare(share.getName(), share.getPath(), share.getType()); if (shareFolder.exists() && shareDirCreated) { FileUtils.deleteDirectoryTree(shareFolder); } } catch (Exception e1) { } throw new ShareManagerException("Failed to run addNewShare: ", e); } catch (ShareMetaDataException | UnrecoverableKeyException | UnknownOwnerException e) { logger.error( "ShareManagerImpl : addNewShare : Already logging exception: ", e); try { // in case of error we need to remove the share again removeShare(share.getName(), share.getPath(), share.getType()); if (shareFolder.exists() && shareDirCreated) { FileUtils.deleteDirectoryTree(shareFolder); } } catch (Exception e1) { } throw e; } finally { try { if (pStatement != null) { pStatement.close(); } } catch (Exception e) { } } } else { if (!shareNameAvailable(share.getName())) { throw new ShareNameAlreadyExistsException( "A share with the name " + share.getName() + " does already exist."); } else { throw new SharePathAlreadyExistsException( "A share with the url " + share.getPath() + " does already exist."); } } } @Override public Collection<PanboxContact> checkShareDeviceListIntegrity( PanboxShare share) { Exception e = null; LinkedList<PanboxContact> corruptedDLContacts = new LinkedList<PanboxContact>(); if ((e = share.getException()) != null) { logger.warn("One or more device lists in share " + share.getName() + " seem to be corrupt... "); if (e instanceof DeviceListException) { DeviceListException ex = (DeviceListException) e; Collection<PublicKey> coll = ex.getUserKeys(); for (Iterator<PublicKey> it = coll.iterator(); it.hasNext();) { PublicKey publicKey = (PublicKey) it.next(); logger.warn("Device list " + Utils.getPubKeyFingerprint(publicKey) + ".db in share " + share.getName() + " seem to be corrupt."); PanboxContact c = identity.getAddressbook() .getContactBySignaturePubKey(publicKey); corruptedDLContacts.add(c); } } } return corruptedDLContacts; } @Override public PanboxShare resetShareInvitation(PanboxShare share, String email, char[] password) throws UnrecoverableKeyException, ShareDoesNotExistException, ShareManagerException, ShareMetaDataException { // TODO: check if corrupt device list file has to be removed physically // first return addContactPermission(share, email, password); } private PanboxShare nameTypeUrlToVolumeData(String shareName, String sharePath, StorageBackendType type, UUID uuid, char[] password) throws IOException, ShareManagerException, ShareMetaDataException, UnrecoverableKeyException { if (!new File(sharePath).exists()) { throw new ShareManagerException( "The specified share path (" + sharePath + ") does not exist or is inaccessible!" ); } String metaDataDir = sharePath + File.separator + PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY + File.separator; File metaDataFile = new File(metaDataDir); File ownerFile = new File(metaDataDir + PanboxConstants.PANBOX_SHARE_OWNER_FILE); String deviceName = Settings.getInstance().getDeviceName(); PanboxShare pbShare = null; // create new share if (!metaDataFile.exists()) { if (password != null) { pbShare = createNewShare(shareName, sharePath, type, password, metaDataFile, ownerFile, deviceName); } else { // empty password field indicates we were trying to load a share // from the DB, but the metadata file was missing throw new ShareInaccessibleException("Metadatafile for share " + shareName + " could not be found!"); } } else { if (!ownerFile.exists() || !ownerFile.canRead()) { throw new ShareManagerException("Can't access owner file at " + metaDataFile.getAbsolutePath()); } MessageDigest md = getMessageDigestForOwnerFile(); byte[] ownerFp = getOwnerFpFromMessageDigest(ownerFile, md); byte[] me = md.digest(identity.getPublicKeySign().getEncoded()); md.reset(); if (Settings.getInstance().isProtectedDeviceKey()) { password = PasswordEnterDialog .invoke(PasswordEnterDialog.PermissionType.SHARE); } VolumeParams p = paramsFactory .createVolumeParams() .setPublicSignatureKey(identity.getPublicKeySign()) .setDeviceAlias(deviceName) .setPublicDeviceKey( identity.getPublicKeyForDevice(deviceName)) .setShareName(shareName).setPath(sharePath).setType(type); if (password != null) { // password was entered try { p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( password, deviceName)); } catch (UnrecoverableKeyException e) { p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName)); } } else { // password was not entered! Try default one! try { p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName)); // Looks like the configuration of deviceKeyProtection // has been changed! We need to set this option to true // for next startup! Settings.getInstance().setProtectedDeviceKey(false); } catch (UnrecoverableKeyException e) { logger.warn("Could not get device key with standard password, but standard password was configured."); password = PasswordEnterDialog .invoke(PasswordEnterDialog.PermissionType.SHARE); try { p = p.setPrivateDeviceKey(identity .getPrivateKeyForDevice( KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName)); // Looks like the configuration of deviceKeyProtection // has been changed! We need to set this option to true // for next startup! Settings.getInstance().setProtectedDeviceKey(true); } catch (UnrecoverableKeyException ex) { logger.error("Entered Password was wrong!"); throw ex; } } } if (Arrays.equals(me, ownerFp)) { // I am the owner try { logger.debug("I am the owner of this preinitialized share, loading..."); pbShare = service.loadShare(p.setOwnerAlias( identity.getEmail()).setOwnerSignatureKey( identity.getPublicKeySign())); } catch (ShareMetaDataException e) { boolean success = false; if (e.getCause() instanceof DeviceKeyException) { // the sharemetadata is ok, but the user's current // device has no keys, possibly because the metadata // state has been reverted due to a file conflict. try // if re-adding the device works, otherwise show error try { logger.warn( "Detected missing device key. This may be because of the metadata state having been reverted due to a file conflict. Will try re-adding the device ...", e); if (password == null) { password = PasswordEnterDialog .invoke(PermissionType.SHARE); } VolumeParams ptmp = paramsFactory .createVolumeParams() .setKeys(identity, password) .setUserAlias(identity.getEmail()) .setDeviceAlias(deviceName) .setPublicDeviceKey( identity.getPublicKeyForDevice(deviceName)) .setShareName(shareName).setPath(sharePath) .setType(type); pbShare = service.addDevice(ptmp); logger.warn("Successfully re-added device key. Trying to re-run loadShare..."); pbShare = service.loadShare(p.setOwnerAlias( identity.getEmail()).setOwnerSignatureKey( identity.getPublicKeySign())); success = true; } catch (Exception e2) { logger.error("Re-addeding device key failed.", e2); } } // else if (e.getCause() instanceof DeviceListException) { // try { // logger.warn( // "Detected corrupt device list. Will try to reset device list by re-inviting user...", // e); // if (password == null) { // password = PasswordEnterDialog // .invoke(PermissionType.SHARE); // } // // PublicKey pk = ((DeviceListException) e.getCause()) // .getUserKey(); // PanboxContact c = identity.getAddressbook() // .getContactBySignaturePubKey(pk); // // if (c != null) { // logger.warn("DeviceListException associated caused by contact list of contact \"" // + c.getEmail() + "\" "); // // // // VolumeParams pinv = paramsFactory.createVolumeParams() // .setKeys(identity, password) // .setOwnerAlias(identity.getEmail()) // .setOtherSignatureKey(c.getPublicKeySign()) // .setOtherEncryptionKey(c.getPublicKeyEnc()) // .setUserAlias(c.getEmail()) // .setShareName(shareName).setPath(sharePath) // .setType(type); // // // Add Invitation fingerPrint so invited user can detect // // invitation // File invitationFolder = new File(sharePath + // File.separator // + PanboxConstants.PANBOX_SHARE_METADATA_DIRECTORY // + File.separator // + PanboxConstants.PANBOX_SHARE_INVITATION_DIRECTORY); // if (invitationFolder.isFile()) { // // invitationFolder is not a folder // throw new RuntimeException( // "invitation folder is a file, not a folder!"); // } // if (!invitationFolder.exists()) { // invitationFolder.mkdir(); // } // String fingerPrint = DigestUtils.md5Hex(c.getCertSign() // .getPublicKey().getEncoded()); // File fpFile = new File(invitationFolder.getAbsolutePath() // + File.separator + fingerPrint); // fpFile.createNewFile(); // // pbShare = service.inviteUser(p); // pbShare.generatePermissionsModel(identity); // pbShare = pbShare = service.loadShare(p.setOwnerAlias( // identity.getEmail()).setOwnerSignatureKey( // identity.getPublicKeySign())); // // success = true; // } else { // logger.warn("DeviceListException could not be attributed to any existing contact. Publiy Key fingerprint: \"" // + Utils.getPubKeyFingerprint(pk) // + "\" "); // } // // } catch (Exception e2) { // logger.error("Re-inviting user failed.", e2); // } // } if (!success) { logger.error("Unable to load preinitialized share!", e); throw e; } } } else { // I am not the owner IPerson owner = getOwnerForShare(md, ownerFp, p); if (owner != null) { // Found the owner, could be previously initialized share or // a new share that i'm invited to // Is there an invitation for me? File invitationFolder = new File(metaDataDir + PanboxConstants.PANBOX_SHARE_INVITATION_DIRECTORY); String fingerPrint = DigestUtils.md5Hex(identity .getPublicKeySign().getEncoded()); File fpFile = new File(invitationFolder.getAbsolutePath() + File.separator + fingerPrint); if (invitationFolder.isDirectory() && fpFile.isFile()) { // I have been invited to this share // accept invitation and delete invitational // fingerprint pbShare = acceptInvitation(password, deviceName, p); logger.debug("Accepted invitation, deleting invitational fingerprint..."); if (!fpFile.delete()) { logger.warn("Could not delete invitational fingerprint file from invitations folder: " + fpFile.getAbsolutePath()); logger.warn("If this share is added again it will try to accept an invitation that has already been accepted."); } } else { // There is no invitation for me lying around, so // i'll just assume that this is a previously // existing and correctly initialized share logger.debug("Found owner, assuming preinitialized Share..."); try { pbShare = service.loadShare(p); } catch (ShareMetaDataException e) { boolean success = false; if (e.getCause() instanceof DeviceKeyException) { // the sharemetadata is ok, but the user's // current device has no keys, possibly because // the metadata state has been reverted due to a // file conflict. try if re-adding the device // works, otherwise show error try { logger.warn( "Detected missing device key. This may be because of the metadata state having been reverted due to a file conflict. Will try re-adding the device ...", e); if (password == null) password = PasswordEnterDialog .invoke(PermissionType.SHARE); VolumeParams ptmp = paramsFactory .createVolumeParams() .setKeys(identity, password) .setUserAlias(identity.getEmail()) .setDeviceAlias(deviceName) .setPublicDeviceKey( identity.getPublicKeyForDevice(deviceName)) .setShareName(shareName) .setPath(sharePath).setType(type); pbShare = service.addDevice(ptmp); logger.warn("Successfully re-added device key. Trying to re-run loadShare..."); pbShare = service.loadShare(p); success = true; } catch (Exception e2) { logger.error( "Re-addeding device key failed.", e); } } if (!success) { logger.error( "Unable to load preinitialized share!", e); throw e; } } } } else { // The Owner Fingerprint in the share did not match // any of my contacts or myself. Either the real owner is // not in my addressbook, or somebody manipulated the // fingerprint in the share // TODO: if somebody messed with the owner fingerprint // in the share, i could just try to load the share with all // my available contacts and myself as owner and see which // initialization actually runs through throw new UnknownOwnerException( "The Owner Fingerprint in the share did not match " + "any of my contacts or myself. Either the real owner is " + "not in my addressbook, or somebody manipulated the " + "fingerprint in the share"); } } // share was loaded successfully - now, we still need to check if // the current users device list has been marked as corrupted. // in this case, accessing the share will fail as no keys will be // available. Thus, an exception needs to be thrown at this point Exception e = null; if ((e = pbShare.getException()) != null) { logger.warn("One or more device lists in share " + pbShare.getName() + " seem to be corrupt... "); if (e instanceof DeviceListException) { DeviceListException ex = (DeviceListException) e; Collection<PublicKey> coll = ex.getUserKeys(); for (Iterator<PublicKey> it = coll.iterator(); it.hasNext();) { PublicKey publicKey = (PublicKey) it.next(); if (Utils.keysEqual(identity.getPublicKeySign(), publicKey)) { // exception was thrown because our own devicelist // was corrupt. this means no sharekey or // obfuscationkeys will be available. logger.fatal("Own device list " + Utils.getPubKeyFingerprint(publicKey) + ".db in share " + pbShare.getName() + " seems to be corrupt. Share will not be available."); throw new ShareMetaDataException( "Could not verify signature of device list", ex); } logger.warn("Device list " + Utils.getPubKeyFingerprint(publicKey) + ".db in share " + pbShare.getName() + " seems to be corrupt."); } } } } pbShare.generatePermissionsModel(identity); return pbShare; } private PanboxShare nameTypeUrlToVolumeData(String shareName, String sharePath, StorageBackendType type, UUID uuid) throws IOException, ShareManagerException, UnrecoverableKeyException, ShareMetaDataException { return nameTypeUrlToVolumeData(shareName, sharePath, type, uuid, null); } private PanboxShare nameTypeUrlToVolumeData(PanboxShare share, char[] password) throws NoSuchAlgorithmException, IOException, ShareManagerException, UnrecoverableKeyException, ShareMetaDataException { return nameTypeUrlToVolumeData(share.getName(), share.getPath(), share.getType(), share.getUuid(), password); } private PanboxShare acceptInvitation(char[] password, String deviceName, VolumeParams p) throws UnrecoverableKeyException, ShareMetaDataException, RemoteException, FileNotFoundException { PanboxShare pbShare; logger.debug("Detected invitation, accepting it..."); if (password == null) { password = PasswordEnterDialog .invoke(PasswordEnterDialog.PermissionType.SHARE); } p.setKeys(identity, password) .setPublicDeviceKey(identity.getPublicKeyForDevice(deviceName)) .setUserAlias(identity.getEmail()); try { if (Settings.getInstance().isProtectedDeviceKey()) { p.setPrivateDeviceKey(identity.getPrivateKeyForDevice(password, deviceName)); } else { p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName)); } } catch (UnrecoverableKeyException e) { if (Settings.getInstance().isProtectedDeviceKey()) { p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName)); Settings.getInstance().setProtectedDeviceKey(false); logger.info("ShareManager : createNewShare : Updated device key protection (disabled)!"); } else { p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( password, deviceName)); Settings.getInstance().setProtectedDeviceKey(true); logger.info("ShareManager : createNewShare : Updated device key protection (enabled)!"); } } pbShare = service.acceptInviation(p); return pbShare; } private IPerson getOwnerForShare(MessageDigest md, byte[] ownerFp, VolumeParams p) { IPerson owner = null; for (PanboxContact contact : identity.getAddressbook().getContacts()) { byte[] c = md.digest(contact.getCertSign().getPublicKey() .getEncoded()); md.reset(); if (Arrays.equals(ownerFp, c)) { owner = contact; p.setOwnerAlias(owner.getEmail()).setOwnerSignatureKey( owner.getPublicKeySign()); break; } } return owner; } private byte[] getOwnerFpFromMessageDigest(File ownerFile, MessageDigest md) throws IOException { byte[] ownerFp = new byte[md.getDigestLength()]; try (BufferedInputStream bis = new BufferedInputStream( new FileInputStream(ownerFile))) { bis.read(ownerFp); } return ownerFp; } private MessageDigest getMessageDigestForOwnerFile() throws ShareManagerException, IOException { MessageDigest md; try { md = MessageDigest.getInstance( KeyConstants.PUBKEY_FINGERPRINT_DIGEST, KeyConstants.PROV_BC); } catch (NoSuchProviderException | NoSuchAlgorithmException e1) { throw new ShareManagerException( "Error initializing message-digest!", e1); } return md; } private PanboxShare createNewShare(String shareName, String sharePath, StorageBackendType type, char[] password, File metaDataFile, File ownerFile, String deviceName) throws IOException, UnrecoverableKeyException, ShareMetaDataException, ShareManagerException { PanboxShare pbShare;// Creating new Share if (Settings.getInstance().isSlave()) { // Slave clients are not allowed to create a new share! throw new CreateShareNotAllowedException( "Slave is not allowed to create a new share."); } try { logger.debug("Creating new Share..."); VolumeParams p = paramsFactory .createVolumeParams() .setOwnerAlias(identity.getEmail()) .setDeviceAlias(deviceName) .setPublicDeviceKey( identity.getPublicKeyForDevice(deviceName)) .setKeys(identity, password).setPath(sharePath) .setType(type).setShareName(shareName); try { if (Settings.getInstance().isProtectedDeviceKey()) { p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( password, deviceName)); } else { p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName)); } } catch (UnrecoverableKeyException e) { if (Settings.getInstance().isProtectedDeviceKey()) { p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( KeyConstants.OPEN_KEYSTORE_PASSWORD, deviceName)); Settings.getInstance().setProtectedDeviceKey(false); logger.info("ShareManager : createNewShare : Updated device key protection (disabled)!"); } else { p = p.setPrivateDeviceKey(identity.getPrivateKeyForDevice( password, deviceName)); Settings.getInstance().setProtectedDeviceKey(true); logger.info("ShareManager : createNewShare : Updated device key protection (enabled)!"); } } metaDataFile.mkdirs(); pbShare = service.createShare(p); // write uuid Files.createFile(Paths.get(metaDataFile.getAbsolutePath() + File.separator + PanboxConstants.PANBOX_SHARE_UUID_PREFIX + pbShare.getUuid())); // Owner ID is the SHA256-Hash of the PublicSignKey MessageDigest md = MessageDigest.getInstance( KeyConstants.PUBKEY_FINGERPRINT_DIGEST, KeyConstants.PROV_BC); try (BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream(ownerFile))) { bos.write(md.digest(identity.getPublicKeySign().getEncoded())); bos.flush(); } } catch (IllegalArgumentException | NoSuchProviderException | NoSuchAlgorithmException e) { throw new ShareManagerException("Could not create share!", e); } return pbShare; } public VolumeParamsFactory getParamsFactory() { return paramsFactory; } public String getOnlineFilename(PanboxShare share, String path) throws ShareManagerException { VolumeParams p = paramsFactory.createVolumeParams() .setShareName(share.getName()).setPath(share.getPath()); try { return service.getOnlineFilename(p, path); } catch (RemoteException | FileNotFoundException | ObfuscationException e) { throw new ShareManagerException( "Failed to resolve online filename for path " + path, e); } } @Override public PanboxShare reloadShareMetadata(PanboxShare share) throws ShareManagerException { VolumeParams p = paramsFactory.createVolumeParams() .setPublicSignatureKey(identity.getPublicKeySign()) .setShareName(share.getName()).setPath(share.getPath()) .setType(share.getType()); try { return service.reloadShareMetaData(p); } catch (RemoteException | FileNotFoundException | ShareMetaDataException e) { throw new ShareManagerException( "Failed to reload share metadata for share " + share.getName(), e); } } }