From 1717e20b61ef13476866da56b61270a13415da30 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 14 Nov 2019 17:02:41 +0100 Subject: [PATCH 01/64] Add JWT verifier --- main/pom.xml | 16 +++-- main/ui/pom.xml | 8 ++- .../ui/preferences/LicenseChecker.java | 54 ++++++++++++++++ .../ui/preferences/LicenseCheckerTest.java | 61 +++++++++++++++++++ 4 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/preferences/LicenseChecker.java create mode 100644 main/ui/src/test/java/org/cryptomator/ui/preferences/LicenseCheckerTest.java diff --git a/main/pom.xml b/main/pom.xml index 5ab371fb3..fc263cd3a 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -23,26 +23,25 @@ UTF-8 - + 1.9.0-rc2 2.2.1 1.2.1 1.1.11 1.0.10 + 13.0.1 - 3.9 - + 3.8.3 1.0.3 - 28.1-jre 2.25.2 2.8.6 - 1.7.29 1.2.3 + 5.5.2 3.1.0 2.2 @@ -156,6 +155,13 @@ commons-lang3 ${commons-lang3.version} + + + + com.auth0 + java-jwt + ${jwt.version} + diff --git a/main/ui/pom.xml b/main/ui/pom.xml index 1c37e76d5..cf253cce6 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -37,7 +37,13 @@ org.fxmisc.easybind easybind - + + + + + com.auth0 + java-jwt + diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/LicenseChecker.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/LicenseChecker.java new file mode 100644 index 000000000..e99a1bb03 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/LicenseChecker.java @@ -0,0 +1,54 @@ +package org.cryptomator.ui.preferences; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.auth0.jwt.interfaces.JWTVerifier; +import com.google.common.io.BaseEncoding; + +import javax.inject.Inject; +import javax.inject.Named; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Optional; + +public class LicenseChecker { + + private final JWTVerifier verifier; + + @Inject + public LicenseChecker(@Named("licensePublicKey") String pemEncodedPublicKey) { + Algorithm algorithm = Algorithm.ECDSA512(decodePublicKey(pemEncodedPublicKey), null); + this.verifier = JWT.require(algorithm).build(); + } + + private static ECPublicKey decodePublicKey(String pemEncodedPublicKey) { + try { + byte[] keyBytes = BaseEncoding.base64().decode(pemEncodedPublicKey); + PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(keyBytes)); + if (key instanceof ECPublicKey) { + return (ECPublicKey) key; + } else { + throw new IllegalStateException("Key not an EC public key."); + } + } catch (InvalidKeySpecException e) { + throw new IllegalArgumentException("Invalid license public key", e); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + } + + public Optional check(String licenseKey) { + try { + return Optional.of(verifier.verify(licenseKey)); + } catch (JWTVerificationException exception) { + return Optional.empty(); + } + } + +} diff --git a/main/ui/src/test/java/org/cryptomator/ui/preferences/LicenseCheckerTest.java b/main/ui/src/test/java/org/cryptomator/ui/preferences/LicenseCheckerTest.java new file mode 100644 index 000000000..ebf40d018 --- /dev/null +++ b/main/ui/src/test/java/org/cryptomator/ui/preferences/LicenseCheckerTest.java @@ -0,0 +1,61 @@ +package org.cryptomator.ui.preferences; + +import com.auth0.jwt.interfaces.DecodedJWT; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +class LicenseCheckerTest { + + private static final String PUBLIC_KEY = "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ" // + + "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" // + + "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" // + + "Al8G7CqwoJOsW7Kddns="; + + private LicenseChecker licenseChecker; + + @BeforeEach + public void setup() { + licenseChecker = new LicenseChecker(PUBLIC_KEY); + } + + @Test + public void testCheckValidLicense() { + String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj"; + + Optional decoded = licenseChecker.check(license); + + Assertions.assertTrue(decoded.isPresent()); + Assertions.assertEquals("cryptobot@example.com", decoded.get().getSubject()); + } + + @Test + public void testCheckInvalidLicenseHeader() { + String license = "EyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj"; + + Optional decoded = licenseChecker.check(license); + + Assertions.assertFalse(decoded.isPresent()); + } + + @Test + public void testCheckInvalidLicensePayload() { + String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.EyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.AQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj"; + + Optional decoded = licenseChecker.check(license); + + Assertions.assertFalse(decoded.isPresent()); + } + + @Test + public void testCheckInvalidLicenseSignature() { + String license = "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6InhaRGZacHJ5NFA5dlpQWnlHMmZOQlJqLTdMejVvbVZkbTd0SG9DZ1NOZlkifQ.eyJzdWIiOiJjcnlwdG9ib3RAZXhhbXBsZS5jb20iLCJpYXQiOjE1MTYyMzkwMjJ9.aQaBIKQdNCxmRJi2wLOcbagTgi39WhdWwgdpKTYSPicg-aPr_tst_RjmnqMemx3cBe0Blr4nEbj_lAtSKHz_i61fAUyI1xCIAZYbK9Q3ICHIHQl3AiuCpBwFl-k81OB4QDYiKpEc9gLN5dhW_VymJMsgOvyiC0UjC91f2AM7s46byDNj"; + + Optional decoded = licenseChecker.check(license); + + Assertions.assertFalse(decoded.isPresent()); + } + +} \ No newline at end of file From b884ea7ddc7f8f74d1bbfb9a0c7bdc10ae090484 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 15 Nov 2019 15:56:35 +0100 Subject: [PATCH 02/64] added registration key to preferences --- main/commons/pom.xml | 6 ++ .../org/cryptomator/common/CommonsModule.java | 11 +++ .../cryptomator/common}/LicenseChecker.java | 4 +- .../org/cryptomator/common/Optionals.java | 28 ------- .../cryptomator/common/settings/Settings.java | 8 ++ .../common}/LicenseCheckerTest.java | 3 +- main/ui/pom.xml | 6 -- .../ui/controls/FontAwesome5Icon.java | 3 +- .../ui/preferences/PreferencesController.java | 1 - .../ui/preferences/PreferencesModule.java | 5 ++ .../RegistrationPreferencesController.java | 77 +++++++++++++++++++ .../src/main/resources/fxml/preferences.fxml | 8 ++ .../fxml/preferences_registration.fxml | 29 +++++++ .../main/resources/i18n/strings.properties | 3 + 14 files changed, 154 insertions(+), 38 deletions(-) rename main/{ui/src/main/java/org/cryptomator/ui/preferences => commons/src/main/java/org/cryptomator/common}/LicenseChecker.java (95%) delete mode 100644 main/commons/src/main/java/org/cryptomator/common/Optionals.java rename main/{ui/src/test/java/org/cryptomator/ui/preferences => commons/src/test/java/org/cryptomator/common}/LicenseCheckerTest.java (97%) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/preferences/RegistrationPreferencesController.java create mode 100644 main/ui/src/main/resources/fxml/preferences_registration.fxml diff --git a/main/commons/pom.xml b/main/commons/pom.xml index 261ab2ee8..f16aaccfc 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -48,6 +48,12 @@ easybind + + + com.auth0 + java-jwt + + com.google.guava diff --git a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java index 5d86ca6ce..81bc5f01e 100644 --- a/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java +++ b/main/commons/src/main/java/org/cryptomator/common/CommonsModule.java @@ -35,6 +35,17 @@ public abstract class CommonsModule { private static final int NUM_SCHEDULER_THREADS = 4; + @Provides + @Singleton + @Named("licensePublicKey") + static String provideLicensePublicKey() { + // TODO replace + return "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ" // + + "PDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47" // + + "6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM" // + + "Al8G7CqwoJOsW7Kddns="; + } + @Provides @Singleton @Named("SemVer") diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/LicenseChecker.java b/main/commons/src/main/java/org/cryptomator/common/LicenseChecker.java similarity index 95% rename from main/ui/src/main/java/org/cryptomator/ui/preferences/LicenseChecker.java rename to main/commons/src/main/java/org/cryptomator/common/LicenseChecker.java index e99a1bb03..82134f77f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/preferences/LicenseChecker.java +++ b/main/commons/src/main/java/org/cryptomator/common/LicenseChecker.java @@ -1,4 +1,4 @@ -package org.cryptomator.ui.preferences; +package org.cryptomator.common; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; @@ -9,6 +9,7 @@ import com.google.common.io.BaseEncoding; import javax.inject.Inject; import javax.inject.Named; +import javax.inject.Singleton; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; @@ -17,6 +18,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; import java.util.Optional; +@Singleton public class LicenseChecker { private final JWTVerifier verifier; diff --git a/main/commons/src/main/java/org/cryptomator/common/Optionals.java b/main/commons/src/main/java/org/cryptomator/common/Optionals.java deleted file mode 100644 index 4ccea7d8b..000000000 --- a/main/commons/src/main/java/org/cryptomator/common/Optionals.java +++ /dev/null @@ -1,28 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE file. - *******************************************************************************/ -package org.cryptomator.common; - -import java.util.Optional; -import java.util.function.Function; - -public final class Optionals { - - private Optionals() { - } - - /** - * Returns a function that is equivalent to the input function but immediately gets the value of the returned optional when invoked. - * - * @param the type of the input to the function - * @param the type of the result of the function - * @param function An {@code Optional}-bearing input function {@code Function>} - * @return A {@code Function}, that may throw a NoSuchElementException, if the original function returns an empty optional. - */ - public static Function unwrap(Function> function) { - return t -> function.apply(t).get(); - } - -} diff --git a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java index ff5613f1e..863e359e2 100644 --- a/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java +++ b/main/commons/src/main/java/org/cryptomator/common/settings/Settings.java @@ -15,6 +15,8 @@ import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.NodeOrientation; @@ -35,6 +37,7 @@ public class Settings { public static final VolumeImpl DEFAULT_PREFERRED_VOLUME_IMPL = System.getProperty("os.name").toLowerCase().contains("windows") ? VolumeImpl.DOKANY : VolumeImpl.FUSE; public static final UiTheme DEFAULT_THEME = UiTheme.LIGHT; public static final NodeOrientation DEFAULT_USER_INTERFACE_ORIENTATION = NodeOrientation.LEFT_TO_RIGHT; + private static final String DEFAULT_LICENSE_KEY = ""; private final ObservableList directories = FXCollections.observableArrayList(VaultSettings::observables); private final BooleanProperty askedForUpdateCheck = new SimpleBooleanProperty(DEFAULT_ASKED_FOR_UPDATE_CHECK); @@ -47,6 +50,7 @@ public class Settings { private final ObjectProperty preferredVolumeImpl = new SimpleObjectProperty<>(DEFAULT_PREFERRED_VOLUME_IMPL); private final ObjectProperty theme = new SimpleObjectProperty<>(DEFAULT_THEME); private final ObjectProperty userInterfaceOrientation = new SimpleObjectProperty<>(DEFAULT_USER_INTERFACE_ORIENTATION); + private final StringProperty licenseKey = new SimpleStringProperty(DEFAULT_LICENSE_KEY); private Consumer saveCmd; @@ -126,4 +130,8 @@ public class Settings { public ObjectProperty userInterfaceOrientation() { return userInterfaceOrientation; } + + public StringProperty licenseKey() { + return licenseKey; + } } diff --git a/main/ui/src/test/java/org/cryptomator/ui/preferences/LicenseCheckerTest.java b/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java similarity index 97% rename from main/ui/src/test/java/org/cryptomator/ui/preferences/LicenseCheckerTest.java rename to main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java index ebf40d018..5ae8f0fb9 100644 --- a/main/ui/src/test/java/org/cryptomator/ui/preferences/LicenseCheckerTest.java +++ b/main/commons/src/test/java/org/cryptomator/common/LicenseCheckerTest.java @@ -1,6 +1,7 @@ -package org.cryptomator.ui.preferences; +package org.cryptomator.common; import com.auth0.jwt.interfaces.DecodedJWT; +import org.cryptomator.common.LicenseChecker; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/main/ui/pom.xml b/main/ui/pom.xml index cf253cce6..514288e89 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -38,12 +38,6 @@ org.fxmisc.easybind easybind - - - - com.auth0 - java-jwt - diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java index f5b62ae2b..41f8b80e2 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java @@ -15,17 +15,18 @@ public enum FontAwesome5Icon { EYE_SLASH("\uF070"), // FILE_IMPORT("\uF56F"), // FOLDER_OPEN("\uF07C"), // + HEART("\uF004"), // HDD("\uF0A0"), // KEY("\uF084"), // LOCK_ALT("\uF30D"), // LOCK_OPEN_ALT("\uF3C2"), // - MINUS("\uF068"), // PLUS("\uF067"), // QUESTION("\uF128"), // SPARKLES("\uF890"), // SPINNER("\uF110"), // SYNC("\uF021"), // TIMES("\uF00D"), // + USER_CHECK("\uf4fc"), // WRENCH("\uF0AD"), // ; diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java index e63505e4b..7ce4793cc 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesController.java @@ -17,7 +17,6 @@ public class PreferencesController implements FxController { private final Stage window; private final BooleanBinding updateAvailable; public TabPane tabPane; - public Tab generalTab; public Tab updatesTab; @Inject diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java index ae4233cb8..793617352 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java @@ -70,4 +70,9 @@ abstract class PreferencesModule { @FxControllerKey(VolumePreferencesController.class) abstract FxController bindVolumePreferencesController(VolumePreferencesController controller); + @Binds + @IntoMap + @FxControllerKey(RegistrationPreferencesController.class) + abstract FxController bindRegistrationPreferencesController(RegistrationPreferencesController controller); + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/RegistrationPreferencesController.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/RegistrationPreferencesController.java new file mode 100644 index 000000000..64de38d63 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/RegistrationPreferencesController.java @@ -0,0 +1,77 @@ +package org.cryptomator.ui.preferences; + +import com.auth0.jwt.interfaces.DecodedJWT; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.scene.control.TextArea; +import org.cryptomator.common.LicenseChecker; +import org.cryptomator.common.settings.Settings; +import org.cryptomator.ui.common.FxController; + +import javax.inject.Inject; +import java.util.Optional; + +@PreferencesScoped +public class RegistrationPreferencesController implements FxController { + + private final Settings settings; + private final LicenseChecker licenseChecker; + private final ObjectProperty validJwtClaims; + private final StringBinding licenseSubject; + private final BooleanBinding registeredProperty; + public TextArea registrationKeyField; + + @Inject + RegistrationPreferencesController(Settings settings, LicenseChecker licenseChecker) { + this.settings = settings; + this.licenseChecker = licenseChecker; + this.validJwtClaims = new SimpleObjectProperty<>(); + this.licenseSubject = Bindings.createStringBinding(this::getLicenseSubject, validJwtClaims); + this.registeredProperty = validJwtClaims.isNotNull(); + + Optional claims = licenseChecker.check(settings.licenseKey().get()); + validJwtClaims.set(claims.orElse(null)); + } + + @FXML + public void initialize() { + registrationKeyField.textProperty().addListener(this::registrationKeyChanged); + } + + private void registrationKeyChanged(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") String oldValue, String newValue) { + Optional claims = licenseChecker.check(newValue); + validJwtClaims.set(claims.orElse(null)); + if (claims.isPresent()) { + settings.licenseKey().set(newValue); + } + } + + /* Observable Properties */ + + public StringBinding licenseSubjectProperty() { + return licenseSubject; + } + + public String getLicenseSubject() { + DecodedJWT claims = validJwtClaims.get(); + if (claims != null) { + return claims.getSubject(); + } else { + return null; + } + } + + public BooleanBinding registeredProperty() { + return registeredProperty; + } + + public boolean isRegistered() { + return registeredProperty.get(); + } + +} diff --git a/main/ui/src/main/resources/fxml/preferences.fxml b/main/ui/src/main/resources/fxml/preferences.fxml index 34ea44235..4a5363883 100644 --- a/main/ui/src/main/resources/fxml/preferences.fxml +++ b/main/ui/src/main/resources/fxml/preferences.fxml @@ -35,5 +35,13 @@ + + + + + + + + diff --git a/main/ui/src/main/resources/fxml/preferences_registration.fxml b/main/ui/src/main/resources/fxml/preferences_registration.fxml new file mode 100644 index 000000000..0620ad9ed --- /dev/null +++ b/main/ui/src/main/resources/fxml/preferences_registration.fxml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + +