Browse Source

- Allows the user to configure optional MAC verification before decrypting content (Fixes #17)

Sebastian Stenzel 10 năm trước cách đây
mục cha
commit
deb10c1256

+ 4 - 3
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 <code>true</code> 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;
 	}
 

+ 5 - 3
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) {

+ 5 - 3
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

+ 7 - 1
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());

+ 5 - 2
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/resources/EncryptedFilePart.java

@@ -50,8 +50,8 @@ public class EncryptedFilePart extends EncryptedFile {
 
 	private final Set<Pair<Long, Long>> requestedContentRanges = new HashSet<Pair<Long, Long>>();
 
-	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<Long, Long> range = getUnionRange(fileSize);
 				final Long rangeLength = range.getRight() - range.getLeft() + 1;

+ 1 - 1
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);
 	}
 }

+ 6 - 0
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() {

+ 10 - 2
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<Boolean> unlocked = new SimpleObjectProperty<Boolean>(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
 	 */

+ 4 - 1
main/ui/src/main/java/org/cryptomator/ui/model/DirectoryDeserializer.java

@@ -17,7 +17,10 @@ public class DirectoryDeserializer extends JsonDeserializer<Directory> {
 		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;
 	}
 
 }

+ 1 - 0
main/ui/src/main/java/org/cryptomator/ui/model/DirectorySerializer.java

@@ -13,6 +13,7 @@ public class DirectorySerializer extends JsonSerializer<Directory> {
 	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();
 	}
 

+ 1 - 10
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<Directory> 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;
-	}
-
 }

+ 29 - 11
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                                                                     *

+ 30 - 10
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                                                                     *

+ 7 - 2
main/ui/src/main/resources/fxml/unlock.fxml

@@ -16,6 +16,7 @@
 <?import org.cryptomator.ui.controls.SecPasswordField?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.control.ProgressIndicator?>
+<?import javafx.scene.control.CheckBox?>
 
 
 <GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" fx:controller="org.cryptomator.ui.UnlockController" xmlns:fx="http://javafx.com/fxml">
@@ -38,10 +39,14 @@
 		<SecPasswordField fx:id="passwordField" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
 		
 		<!-- Row 2 -->
-		<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton"/>
+		<Label text="%unlock.label.checkIntegrity" GridPane.rowIndex="2" GridPane.columnIndex="0" />
+		<CheckBox fx:id="checkIntegrity" wrapText="true" text="%unlock.checkbox.checkIntegrity" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" maxWidth="Infinity" />
 		
 		<!-- Row 3 -->
-		<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" visible="false"/>
+		<Button fx:id="unlockButton" text="%unlock.button.unlock" defaultButton="true" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="RIGHT" prefWidth="150.0" onAction="#didClickUnlockButton"/>
+		
+		<!-- Row 4-->
+		<ProgressIndicator progress="-1" fx:id="progressIndicator" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="CENTER" visible="false"/>
 		
 		<!-- Row 5 -->
 		<Label fx:id="messageLabel" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" />

+ 2 - 0
main/ui/src/main/resources/localization.properties

@@ -32,6 +32,8 @@ initialize.alert.directoryIsNotEmpty.content=All existing files inside this dire
 # unlock.fxml
 unlock.label.username=Username
 unlock.label.password=Password
+unlock.label.checkIntegrity=File integrity
+unlock.checkbox.checkIntegrity=Verify checksums (slower, but detects manipulation)
 unlock.button.unlock=Unlock vault
 unlock.errorMessage.wrongPassword=Wrong password.
 unlock.errorMessage.decryptionFailed=Decryption failed.