mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-09-04 15:18:09 +00:00
added registration key to preferences
This commit is contained in:
@@ -38,12 +38,6 @@
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
<artifactId>easybind</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT -->
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>java-jwt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Google -->
|
||||
<dependency>
|
||||
|
||||
@@ -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"), //
|
||||
;
|
||||
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
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<DecodedJWT> check(String licenseKey) {
|
||||
try {
|
||||
return Optional.of(verifier.verify(licenseKey));
|
||||
} catch (JWTVerificationException exception) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
+77
@@ -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<DecodedJWT> 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<DecodedJWT> 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<? extends String> observable, @SuppressWarnings("unused") String oldValue, String newValue) {
|
||||
Optional<DecodedJWT> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,5 +35,13 @@
|
||||
<fx:include source="/fxml/preferences_updates.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab fx:id="registrationTab" text="%preferences.registration">
|
||||
<graphic>
|
||||
<FontAwesome5IconView glyph="USER_CHECK"/>
|
||||
</graphic>
|
||||
<content>
|
||||
<fx:include source="/fxml/preferences_registration.fxml"/>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.shape.Circle?>
|
||||
<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
|
||||
<?import org.cryptomator.ui.controls.FormattedLabel?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.preferences.RegistrationPreferencesController"
|
||||
spacing="18">
|
||||
<padding>
|
||||
<Insets topRightBottomLeft="12"/>
|
||||
</padding>
|
||||
<children>
|
||||
<HBox spacing="12" alignment="CENTER_LEFT" VBox.vgrow="NEVER" visible="${controller.registered}">
|
||||
<StackPane alignment="CENTER" HBox.hgrow="NEVER">
|
||||
<Circle styleClass="glyph-icon-primary" radius="24"/>
|
||||
<FontAwesome5IconView styleClass="glyph-icon-white" glyph="HEART" glyphSize="24"/>
|
||||
</StackPane>
|
||||
<FormattedLabel format="%preferences.registration.registeredFor" arg1="${controller.licenseSubject}" textAlignment="CENTER" wrapText="true"/>
|
||||
</HBox>
|
||||
|
||||
<TextArea fx:id="registrationKeyField" wrapText="true" VBox.vgrow="ALWAYS"/>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -123,6 +123,9 @@ preferences.updates.currentVersion=Current Version: %s
|
||||
preferences.updates.autoUpdateCheck=Check for updates automatically
|
||||
preferences.updates.checkNowBtn=Check Now
|
||||
preferences.updates.updateAvailable=Update to version %s available.
|
||||
## Registration
|
||||
preferences.registration=Registration
|
||||
preferences.registration.registeredFor=Registered for %s
|
||||
|
||||
# Main Window
|
||||
main.closeBtn.tooltip=Close
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
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<DecodedJWT> 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<DecodedJWT> 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<DecodedJWT> 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<DecodedJWT> decoded = licenseChecker.check(license);
|
||||
|
||||
Assertions.assertFalse(decoded.isPresent());
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user