Browse Source

Add JWT verifier

Sebastian Stenzel 5 years ago
parent
commit
1717e20b61

+ 11 - 5
main/pom.xml

@@ -23,26 +23,25 @@
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
-		<!-- dependency versions -->
+		<!-- cryptomator dependencies -->
 		<cryptomator.cryptofs.version>1.9.0-rc2</cryptomator.cryptofs.version>
 		<cryptomator.jni.version>2.2.1</cryptomator.jni.version>
 		<cryptomator.fuse.version>1.2.1</cryptomator.fuse.version>
 		<cryptomator.dokany.version>1.1.11</cryptomator.dokany.version>
 		<cryptomator.webdav.version>1.0.10</cryptomator.webdav.version>
 
+		<!-- 3rd party dependencies -->
 		<javafx.version>13.0.1</javafx.version>
-
 		<commons-lang3.version>3.9</commons-lang3.version>
-
+		<jwt.version>3.8.3</jwt.version>
 		<easybind.version>1.0.3</easybind.version>
-
 		<guava.version>28.1-jre</guava.version>
 		<dagger.version>2.25.2</dagger.version>
 		<gson.version>2.8.6</gson.version>
-
 		<slf4j.version>1.7.29</slf4j.version>
 		<logback.version>1.2.3</logback.version>
 
+		<!-- test dependencies -->
 		<junit.jupiter.version>5.5.2</junit.jupiter.version>
 		<mockito.version>3.1.0</mockito.version>
 		<hamcrest.version>2.2</hamcrest.version>
@@ -156,6 +155,13 @@
 				<artifactId>commons-lang3</artifactId>
 				<version>${commons-lang3.version}</version>
 			</dependency>
+			
+			<!-- JWT -->
+			<dependency>
+				<groupId>com.auth0</groupId>
+				<artifactId>java-jwt</artifactId>
+				<version>${jwt.version}</version>
+			</dependency>
 
 			<!-- EasyBind -->
 			<dependency>

+ 7 - 1
main/ui/pom.xml

@@ -37,7 +37,13 @@
 		<dependency>
 			<groupId>org.fxmisc.easybind</groupId>
 			<artifactId>easybind</artifactId>
-		</dependency>	
+		</dependency>
+		
+		<!-- JWT -->
+		<dependency>
+			<groupId>com.auth0</groupId>
+			<artifactId>java-jwt</artifactId>
+		</dependency>
 
 		<!-- Google -->
 		<dependency>

+ 54 - 0
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<DecodedJWT> check(String licenseKey) {
+		try {
+			return Optional.of(verifier.verify(licenseKey));
+		} catch (JWTVerificationException exception) {
+			return Optional.empty();
+		}
+	}
+
+}

+ 61 - 0
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<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());
+	}
+
+}