From 1717e20b61ef13476866da56b61270a13415da30 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Thu, 14 Nov 2019 17:02:41 +0100 Subject: [PATCH] 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