From deb10c1256e5f438a42c5486c5807b6e2477798f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Wed, 7 Jan 2015 20:00:09 +0100 Subject: [PATCH] - Allows the user to configure optional MAC verification before decrypting content (Fixes #17) --- .../org/cryptomator/webdav/WebDavServer.java | 7 ++-- .../jackrabbit/WebDavResourceFactory.java | 8 ++-- .../webdav/jackrabbit/WebDavServlet.java | 8 ++-- .../jackrabbit/resources/EncryptedFile.java | 8 +++- .../resources/EncryptedFilePart.java | 7 +++- .../exceptions/DecryptFailedException.java | 2 +- .../org/cryptomator/ui/UnlockController.java | 6 +++ .../org/cryptomator/ui/model/Directory.java | 12 +++++- .../ui/model/DirectoryDeserializer.java | 5 ++- .../ui/model/DirectorySerializer.java | 1 + .../org/cryptomator/ui/settings/Settings.java | 11 +---- main/ui/src/main/resources/css/mac_theme.css | 40 ++++++++++++++----- main/ui/src/main/resources/css/win_theme.css | 40 ++++++++++++++----- main/ui/src/main/resources/fxml/unlock.fxml | 9 ++++- .../main/resources/localization.properties | 2 + 15 files changed, 117 insertions(+), 49 deletions(-) diff --git a/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java b/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java index 1e066b752..9f67a4a74 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java +++ b/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java @@ -45,7 +45,7 @@ public final class WebDavServer { * @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams. * @return true upon success */ - public synchronized boolean start(final String workDir, final Cryptor cryptor) { + public synchronized boolean start(final String workDir, final boolean checkFileIntegrity, final Cryptor cryptor) { final ServerConnector connector = new ServerConnector(server); connector.setHost(LOCALHOST); @@ -53,7 +53,7 @@ public final class WebDavServer { final String servletPathSpec = "/*"; final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.addServlet(getWebDavServletHolder(workDir, contextPath, cryptor), servletPathSpec); + context.addServlet(getWebDavServletHolder(workDir, contextPath, checkFileIntegrity, cryptor), servletPathSpec); context.setContextPath(contextPath); server.setHandler(context); @@ -82,10 +82,11 @@ public final class WebDavServer { return server.isStopped(); } - private ServletHolder getWebDavServletHolder(final String workDir, final String contextPath, final Cryptor cryptor) { + private ServletHolder getWebDavServletHolder(final String workDir, final String contextPath, final boolean checkFileIntegrity, final Cryptor cryptor) { final ServletHolder result = new ServletHolder("Cryptomator-WebDAV-Servlet", new WebDavServlet(cryptor)); result.setInitParameter(WebDavServlet.CFG_FS_ROOT, workDir); result.setInitParameter(WebDavServlet.CFG_HTTP_ROOT, contextPath); + result.setInitParameter(WebDavServlet.CFG_CHECK_FILE_INTEGRITY, Boolean.toString(checkFileIntegrity)); return result; } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavResourceFactory.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavResourceFactory.java index a4a80fe5d..d4dabcd73 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavResourceFactory.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavResourceFactory.java @@ -34,9 +34,11 @@ class WebDavResourceFactory implements DavResourceFactory { private final LockManager lockManager = new SimpleLockManager(); private final Cryptor cryptor; + private final boolean checkFileIntegrity; - WebDavResourceFactory(Cryptor cryptor) { + WebDavResourceFactory(Cryptor cryptor, boolean checkFileIntegrity) { this.cryptor = cryptor; + this.checkFileIntegrity = checkFileIntegrity; } @Override @@ -70,11 +72,11 @@ class WebDavResourceFactory implements DavResourceFactory { } private EncryptedFile createFilePart(DavResourceLocator locator, DavSession session, DavServletRequest request) { - return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor); + return new EncryptedFilePart(this, locator, session, request, lockManager, cryptor, checkFileIntegrity); } private EncryptedFile createFile(DavResourceLocator locator, DavSession session) { - return new EncryptedFile(this, locator, session, lockManager, cryptor); + return new EncryptedFile(this, locator, session, lockManager, cryptor, checkFileIntegrity); } private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session) { diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java index 5b0ddb9ca..8c5c6c96e 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java @@ -22,8 +22,9 @@ import org.cryptomator.crypto.Cryptor; public class WebDavServlet extends AbstractWebdavServlet { private static final long serialVersionUID = 7965170007048673022L; - public static final String CFG_FS_ROOT = "oce.fs.root"; - public static final String CFG_HTTP_ROOT = "oce.http.root"; + public static final String CFG_FS_ROOT = "cfg.fs.root"; + public static final String CFG_HTTP_ROOT = "cfg.http.root"; + public static final String CFG_CHECK_FILE_INTEGRITY = "cfg.checkFileIntegrity"; private DavSessionProvider davSessionProvider; private DavLocatorFactory davLocatorFactory; private DavResourceFactory davResourceFactory; @@ -42,9 +43,10 @@ public class WebDavServlet extends AbstractWebdavServlet { final String fsRoot = config.getInitParameter(CFG_FS_ROOT); final String httpRoot = config.getInitParameter(CFG_HTTP_ROOT); + final boolean checkFileIntegrity = Boolean.parseBoolean(config.getInitParameter(CFG_CHECK_FILE_INTEGRITY)); this.davLocatorFactory = new WebDavLocatorFactory(fsRoot, httpRoot, cryptor); - this.davResourceFactory = new WebDavResourceFactory(cryptor); + this.davResourceFactory = new WebDavResourceFactory(cryptor, checkFileIntegrity); } @Override diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java index 83d017a8f..2d7590fbf 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFile.java @@ -40,8 +40,11 @@ public class EncryptedFile extends AbstractEncryptedNode { private static final Logger LOG = LoggerFactory.getLogger(EncryptedFile.class); - public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) { + protected final boolean checkIntegrity; + + public EncryptedFile(DavResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, boolean checkIntegrity) { super(factory, locator, session, lockManager, cryptor); + this.checkIntegrity = checkIntegrity; } @Override @@ -73,6 +76,9 @@ public class EncryptedFile extends AbstractEncryptedNode { SeekableByteChannel channel = null; try { channel = Files.newByteChannel(path, StandardOpenOption.READ); + if (checkIntegrity && !cryptor.authenticateContent(channel)) { + throw new DecryptFailedException("File content compromised: " + path.toString()); + } outputContext.setContentLength(cryptor.decryptedContentLength(channel)); if (outputContext.hasStream()) { cryptor.decryptedFile(channel, outputContext.getOutputStream()); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java index c257b39fb..9253f3f7d 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java @@ -50,8 +50,8 @@ public class EncryptedFilePart extends EncryptedFile { private final Set> requestedContentRanges = new HashSet>(); - public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor) { - super(factory, locator, session, lockManager, cryptor); + public EncryptedFilePart(DavResourceFactory factory, DavResourceLocator locator, DavSession session, DavServletRequest request, LockManager lockManager, Cryptor cryptor, boolean checkIntegrity) { + super(factory, locator, session, lockManager, cryptor, checkIntegrity); final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString()); if (rangeHeader == null) { throw new IllegalArgumentException("HTTP request doesn't contain a range header"); @@ -116,6 +116,9 @@ public class EncryptedFilePart extends EncryptedFile { SeekableByteChannel channel = null; try { channel = Files.newByteChannel(path, StandardOpenOption.READ); + if (checkIntegrity && !cryptor.authenticateContent(channel)) { + throw new DecryptFailedException("File content compromised: " + path.toString()); + } final Long fileSize = cryptor.decryptedContentLength(channel); final Pair range = getUnionRange(fileSize); final Long rangeLength = range.getRight() - range.getLeft() + 1; diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java index f90a97a4b..5619463ee 100644 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java +++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java @@ -7,7 +7,7 @@ public class DecryptFailedException extends StorageCryptingException { super("Decryption failed.", t); } - protected DecryptFailedException(String msg) { + public DecryptFailedException(String msg) { super(msg); } } \ No newline at end of file diff --git a/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java index 3aea37168..387bc1e8b 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/UnlockController.java @@ -24,6 +24,7 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; @@ -55,6 +56,9 @@ public class UnlockController implements Initializable { @FXML private SecPasswordField passwordField; + @FXML + private CheckBox checkIntegrity; + @FXML private Button unlockButton; @@ -96,6 +100,7 @@ public class UnlockController implements Initializable { try { progressIndicator.setVisible(true); masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ); + directory.setVerifyFileIntegrity(checkIntegrity.isSelected()); directory.getCryptor().decryptMasterKey(masterKeyInputStream, password); if (!directory.startServer()) { messageLabel.setText(rb.getString("unlock.messageLabel.startServerFailed")); @@ -170,6 +175,7 @@ public class UnlockController implements Initializable { public void setDirectory(Directory directory) { this.directory = directory; this.findExistingUsernames(); + this.checkIntegrity.setSelected(directory.shouldVerifyFileIntegrity()); } public UnlockListener getListener() { diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java b/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java index 66112bc6e..3c3d81d18 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/Directory.java @@ -34,7 +34,7 @@ public class Directory implements Serializable { private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor()); private final ObjectProperty unlocked = new SimpleObjectProperty(this, "unlocked", Boolean.FALSE); private final Path path; - // private boolean unlocked; + private boolean verifyFileIntegrity; private WebDavMount webDavMount; private final Runnable shutdownTask = new ShutdownTask(); @@ -50,7 +50,7 @@ public class Directory implements Serializable { } public synchronized boolean startServer() { - if (server.start(path.toString(), cryptor)) { + if (server.start(path.toString(), verifyFileIntegrity, cryptor)) { MainApplication.addShutdownTask(shutdownTask); return true; } else { @@ -96,6 +96,14 @@ public class Directory implements Serializable { return path; } + public boolean shouldVerifyFileIntegrity() { + return verifyFileIntegrity; + } + + public void setVerifyFileIntegrity(boolean verifyFileIntegrity) { + this.verifyFileIntegrity = verifyFileIntegrity; + } + /** * @return Directory name without preceeding path components */ diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/DirectoryDeserializer.java b/main/ui/src/main/java/org/cryptomator/ui/model/DirectoryDeserializer.java index a5d942688..9caa74c84 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/DirectoryDeserializer.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/DirectoryDeserializer.java @@ -17,7 +17,10 @@ public class DirectoryDeserializer extends JsonDeserializer { final JsonNode node = jp.readValueAsTree(); final String pathStr = node.get("path").asText(); final Path path = FileSystems.getDefault().getPath(pathStr); - return new Directory(path); + final Directory dir = new Directory(path); + final boolean verifyFileIntegrity = node.has("checkIntegrity") ? node.get("checkIntegrity").asBoolean() : false; + dir.setVerifyFileIntegrity(verifyFileIntegrity); + return dir; } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/model/DirectorySerializer.java b/main/ui/src/main/java/org/cryptomator/ui/model/DirectorySerializer.java index 83eef3839..ad64952ae 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/model/DirectorySerializer.java +++ b/main/ui/src/main/java/org/cryptomator/ui/model/DirectorySerializer.java @@ -13,6 +13,7 @@ public class DirectorySerializer extends JsonSerializer { public void serialize(Directory value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeStartObject(); jgen.writeStringField("path", value.getPath().toString()); + jgen.writeBooleanField("checkIntegrity", value.shouldVerifyFileIntegrity()); jgen.writeEndObject(); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java b/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java index 3b49165f6..61831295b 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java +++ b/main/ui/src/main/java/org/cryptomator/ui/settings/Settings.java @@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.ObjectMapper; -@JsonPropertyOrder(value = {"webdavWorkDir"}) +@JsonPropertyOrder(value = {"directories"}) public class Settings implements Serializable { private static final long serialVersionUID = 7609959894417878744L; @@ -55,7 +55,6 @@ public class Settings implements Serializable { } private List directories; - private String username; private Settings() { // private constructor @@ -107,12 +106,4 @@ public class Settings implements Serializable { this.directories = directories; } - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - } diff --git a/main/ui/src/main/resources/css/mac_theme.css b/main/ui/src/main/resources/css/mac_theme.css index 85327baeb..c654f688a 100644 --- a/main/ui/src/main/resources/css/mac_theme.css +++ b/main/ui/src/main/resources/css/mac_theme.css @@ -175,8 +175,6 @@ .button, .toggle-button, -.radio-button > .radio, -.check-box > .box, .menu-button, .choice-box, .color-picker.split-button > .color-picker-label, @@ -194,7 +192,6 @@ .button:hover, .toggle-button:hover, .radio-button:hover > .radio, -.check-box:hover > .box, .menu-button:hover, .split-menu-button > .label:hover, .split-menu-button > .arrow-button:hover, @@ -212,8 +209,6 @@ .button:armed, .button:default:armed, .toggle-button:armed, -.radio-button:armed > .radio, -.check-box:armed .box, .menu-button:armed, .split-menu-button:armed > .label, .split-menu-button > .arrow-button:pressed, @@ -228,8 +223,6 @@ } .button:focused, .toggle-button:focused, -.radio-button:focused > .radio, -.check-box:focused > .box, .menu-button:focused, .choice-box:focused, .color-picker.split-button:focused > .color-picker-label { @@ -242,8 +235,6 @@ .button:disabled, .toggle-button:disabled, -.radio-button:disabled, -.check-box:disabled, .hyperlink:disabled, .menu-button:disabled, .split-menu-button:disabled, @@ -270,8 +261,6 @@ .button:show-mnemonics .mnemonic-underline, .toggle-button:show-mnemonics .mnemonic-underline, -.radio-button:show-mnemonics .mnemonic-underline, -.check-box:show-mnemonics .mnemonic-underline, .hyperlink:show-mnemonics > .mnemonic-underline, .split-menu-button:show-mnemonics > .mnemonic-underline, .menu-button:show-mnemonics > .mnemonic-underline { @@ -334,6 +323,35 @@ -fx-text-fill: -fx-mid-text-color; } +/******************************************************************************* + * * + * CheckBox * + * * + ******************************************************************************/ + +.check-box { + -fx-label-padding: 0 0 0 3px; + -fx-text-fill: -fx-text-background-color; +} +.check-box > .box { + -fx-padding: 3px; + -fx-background-color: linear-gradient(to bottom, #A5A5A5 0%, #B8B8B8 100%), #F3F3F3, #FFFFFF; + -fx-background-radius: 2.5, 2.5, 2.5; + -fx-background-insets: 0, 1, 2 1 1 1; +} +.check-box > .box > .mark { + -fx-background-color: transparent; + -fx-padding: 4px; + -fx-shape: "M-1,4, L-1,5.5 L3.5,8.5 L9,0 L9,-1 L7,-1 L3,6 L1,4 Z"; +} +.check-box:selected > .box { + -fx-background-color: #2C90FC, #3B99FC; + -fx-background-insets: 0, 1; +} +.check-box:selected > .box > .mark { + -fx-background-color: white; +} + /******************************************************************************* * * * ToolBar * diff --git a/main/ui/src/main/resources/css/win_theme.css b/main/ui/src/main/resources/css/win_theme.css index 4993e0184..20998db5a 100644 --- a/main/ui/src/main/resources/css/win_theme.css +++ b/main/ui/src/main/resources/css/win_theme.css @@ -174,8 +174,6 @@ .button, .toggle-button, -.radio-button > .radio, -.check-box > .box, .menu-button, .choice-box, .color-picker.split-button > .color-picker-label, @@ -192,8 +190,6 @@ } .button:hover, .toggle-button:hover, -.radio-button:hover > .radio, -.check-box:hover > .box, .menu-button:hover, .split-menu-button > .label:hover, .split-menu-button > .arrow-button:hover, @@ -208,8 +204,6 @@ .button:armed, .button:default:armed, .toggle-button:armed, -.radio-button:armed > .radio, -.check-box:armed .box, .menu-button:armed, .split-menu-button:armed > .label, .split-menu-button > .arrow-button:pressed, @@ -235,8 +229,6 @@ .button:disabled, .toggle-button:disabled, -.radio-button:disabled, -.check-box:disabled, .hyperlink:disabled, .menu-button:disabled, .split-menu-button:disabled, @@ -261,8 +253,6 @@ .button:show-mnemonics .mnemonic-underline, .toggle-button:show-mnemonics .mnemonic-underline, -.radio-button:show-mnemonics .mnemonic-underline, -.check-box:show-mnemonics .mnemonic-underline, .hyperlink:show-mnemonics > .mnemonic-underline, .split-menu-button:show-mnemonics > .mnemonic-underline, .menu-button:show-mnemonics > .mnemonic-underline { @@ -323,6 +313,36 @@ -fx-text-fill: -fx-mid-text-color; } +/******************************************************************************* + * * + * CheckBox * + * * + ******************************************************************************/ + +/* TODO win L&F */ +.check-box { + -fx-label-padding: 0 0 0 3px; + -fx-text-fill: -fx-text-background-color; +} +.check-box > .box { + -fx-padding: 3px; + -fx-background-color: linear-gradient(to bottom, #A5A5A5 0%, #B8B8B8 100%), #F3F3F3, #FFFFFF; + -fx-background-radius: 2.5, 2.5, 2.5; + -fx-background-insets: 0, 1, 2 1 1 1; +} +.check-box > .box > .mark { + -fx-background-color: transparent; + -fx-padding: 4px; + -fx-shape: "M-1,4, L-1,5.5 L3.5,8.5 L9,0 L9,-1 L7,-1 L3,6 L1,4 Z"; +} +.check-box:selected > .box { + -fx-background-color: #2C90FC, #3B99FC; + -fx-background-insets: 0, 1; +} +.check-box:selected > .box > .mark { + -fx-background-color: white; +} + /******************************************************************************* * * * ToolBar * diff --git a/main/ui/src/main/resources/fxml/unlock.fxml b/main/ui/src/main/resources/fxml/unlock.fxml index 8c5ccbe28..9f3e788dd 100644 --- a/main/ui/src/main/resources/fxml/unlock.fxml +++ b/main/ui/src/main/resources/fxml/unlock.fxml @@ -16,6 +16,7 @@ + @@ -38,10 +39,14 @@ -