diff --git a/pom.xml b/pom.xml
index 3ae31ef15..d52701afb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
com.github.serceman,com.github.jnr,org.ow2.asm,net.java.dev.jna,org.apache.jackrabbit,org.apache.httpcomponents,de.swiesend,org.purejava,com.github.hypfvieh
- 2.1.0-beta2
+ 2.1.0-beta3
2.2.0
1.0.0
1.0.0
@@ -157,6 +157,11 @@
java-jwt
${jwt.version}
+
+ com.nimbusds
+ nimbus-jose-jwt
+ 9.15.2
+
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 920c3bdc8..6627f5528 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -35,6 +35,7 @@ module org.cryptomator.desktop {
requires static javax.inject; /* ugly dagger/guava crap */
requires logback.classic;
requires logback.core;
+ requires com.nimbusds.jose.jwt;
uses AutoStartProvider;
uses KeychainAccessProvider;
diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java b/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java
new file mode 100644
index 000000000..2c2b9baa4
--- /dev/null
+++ b/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java
@@ -0,0 +1,55 @@
+package org.cryptomator.ui.keyloading.hub;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWEObject;
+import com.nimbusds.jose.crypto.ECDHDecrypter;
+import org.cryptomator.cryptolib.api.Masterkey;
+import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.interfaces.ECPrivateKey;
+import java.util.Arrays;
+
+class JWEHelper {
+
+ private static final Logger LOG = LoggerFactory.getLogger(JWEHelper.class);
+ private static final String JWE_PAYLOAD_MASTERKEY_FIELD = "key";
+
+ private JWEHelper(){}
+
+ public static Masterkey decrypt(JWEObject jwe, ECPrivateKey privateKey) throws MasterkeyLoadingFailedException {
+ try {
+ jwe.decrypt(new ECDHDecrypter(privateKey));
+ return readKey(jwe);
+ } catch (JOSEException e) {
+ LOG.warn("Failed to decrypt JWE: {}", jwe);
+ throw new MasterkeyLoadingFailedException("Failed to decrypt JWE", e);
+ }
+ }
+
+ private static Masterkey readKey(JWEObject jwe) throws MasterkeyLoadingFailedException {
+ Preconditions.checkArgument(jwe.getState() == JWEObject.State.DECRYPTED);
+ var fields = jwe.getPayload().toJSONObject();
+ if (fields == null) {
+ LOG.error("Expected JWE payload to be JSON: {}", jwe.getPayload());
+ throw new MasterkeyLoadingFailedException("Expected JWE payload to be JSON");
+ }
+ var keyBytes = new byte[0];
+ try {
+ if (fields.get(JWE_PAYLOAD_MASTERKEY_FIELD) instanceof String key) {
+ keyBytes = BaseEncoding.base64().decode(key);
+ return new Masterkey(keyBytes);
+ } else {
+ throw new IllegalArgumentException("JWE payload doesn't contain field " + JWE_PAYLOAD_MASTERKEY_FIELD);
+ }
+ } catch (IllegalArgumentException e) {
+ LOG.error("Unexpected JWE payload: {}", jwe.getPayload());
+ throw new MasterkeyLoadingFailedException("Unexpected JWE payload", e);
+ } finally {
+ Arrays.fill(keyBytes, (byte) 0x00);
+ }
+ }
+}
diff --git a/src/main/resources/license/THIRD-PARTY.txt b/src/main/resources/license/THIRD-PARTY.txt
index e58486ddd..5344f58e8 100644
--- a/src/main/resources/license/THIRD-PARTY.txt
+++ b/src/main/resources/license/THIRD-PARTY.txt
@@ -11,16 +11,18 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
-Cryptomator uses 43 third-party dependencies under the following licenses:
+Cryptomator uses 45 third-party dependencies under the following licenses:
Apache License v2.0:
- jffi (com.github.jnr:jffi:1.3.5 - http://github.com/jnr/jffi)
- jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm)
- jnr-constants (com.github.jnr:jnr-constants:0.10.2 - http://github.com/jnr/jnr-constants)
- jnr-ffi (com.github.jnr:jnr-ffi:2.2.7 - http://github.com/jnr/jnr-ffi)
+ - JCIP Annotations under Apache License (com.github.stephenc.jcip:jcip-annotations:1.0-1 - http://stephenc.github.com/jcip-annotations)
- Gson (com.google.code.gson:gson:2.8.8 - https://github.com/google/gson/gson)
- Dagger (com.google.dagger:dagger:2.39 - https://github.com/google/dagger)
- Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess)
- Guava: Google Core Libraries for Java (com.google.guava:guava:31.0-jre - https://github.com/google/guava)
+ - Nimbus JOSE+JWT (com.nimbusds:nimbus-jose-jwt:9.15.2 - https://bitbucket.org/connect2id/nimbus-jose-jwt)
- Apache Commons CLI (commons-cli:commons-cli:1.4 - http://commons.apache.org/proper/commons-cli/)
- javax.inject (javax.inject:javax.inject:1 - http://code.google.com/p/atinject/)
- Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/)
diff --git a/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java b/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java
new file mode 100644
index 000000000..3d495e8c1
--- /dev/null
+++ b/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java
@@ -0,0 +1,56 @@
+package org.cryptomator.ui.keyloading.hub;
+
+import com.nimbusds.jose.JWEObject;
+import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
+import org.cryptomator.cryptolib.common.P384KeyPair;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Base64;
+
+public class JWEHelperTest {
+
+ private static final String JWE = "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IllUcEY3bGtTc3JvZVVUVFdCb21LNzBTN0FhVTJyc0ptMURpZ1ZzbjRMY2F5eUxFNFBabldkYmFVcE9jQVV5a1ciLCJ5IjoiLU5pS3loUktjSk52Nm02Z0ZJUWc4cy1Xd1VXUW9uT3A5dkQ4cHpoa2tUU3U2RzFlU2FUTVlhZGltQ2Q4V0ExMSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..BECWGzd9UvhHcTJC.znt4TlS-qiNEjxiu2v-du_E1QOBnyBR6LCt865SHxD-kwRc1JwX_Lq9XVoFj2GnK9-9CgxhCLGurg5Jt9g38qv2brGAzWL7eSVeY1fIqdO_kUhLpGslRTN6h2U0NHJi2-iE.WDVI2kOk9Dy3PWHyIg8gKA";
+ private static final String PRIV_KEY = "ME8CAQAwEAYHKoZIzj0CAQYFK4EEACIEODA2AgEBBDEA6QybmBitf94veD5aCLr7nlkF5EZpaXHCfq1AXm57AKQyGOjTDAF9EQB28fMywTDQ";
+ private static final String PUB_KEY = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAERxQR+NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0aq6uqmUy4jUhuxnKxsv59A6JeK7Unn+mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu";
+
+ @Test
+ public void testDecrypt() throws ParseException, InvalidKeySpecException {
+ var jwe = JWEObject.parse(JWE);
+ var keyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY)));
+
+ var masterkey = JWEHelper.decrypt(jwe, keyPair.getPrivate());
+
+ var expectedEncKey = new byte[32];
+ var expectedMacKey = new byte[32];
+ Arrays.fill(expectedEncKey, (byte) 0x55);
+ Arrays.fill(expectedMacKey, (byte) 0x77);
+ Assertions.assertArrayEquals(expectedEncKey, masterkey.getEncKey().getEncoded());
+ Assertions.assertArrayEquals(expectedMacKey, masterkey.getMacKey().getEncoded());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6ImdodGR3VnNoUU8wRGFBdjVBOXBiZ1NCTW0yYzZKWVF4dkloR3p6RVdQTncxczZZcEFYeTRQTjBXRFJUWExtQ2wiLCJ5IjoiN3Rncm1Gd016NGl0ZmVQNzBndkpLcjRSaGdjdENCMEJHZjZjWE9WZ2M0bjVXMWQ4dFgxZ1RQakdrczNVSm1zUiJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..x6JWRGSojUJUJYpp.5BRuzcaV.lLIhGH7Wz0n_iTBAubDFZA", // wrong key
+ "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IkM2bWhsNE5BTHhEdHMwUlFlNXlyZWxQVDQyOGhDVzJNeUNYS3EwdUI0TDFMdnpXRHhVaVk3YTdZcEhJakJXcVoiLCJ5IjoiakM2dWc1NE9tbmdpNE9jUk1hdkNrczJpcFpXQjdkUmotR3QzOFhPSDRwZ2tpQ0lybWNlUnFxTnU3Z0c3Qk1yOSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..HNJJghL-SvERFz2v.N0z8YwFg.rYw29iX4i8XujdM4P4KKWg", // payload is not json
+ "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6InB3R05vcXRnY093MkJ6RDVmSnpBWDJvMzUwSWNsY3A5cFdVTHZ5VDRqRWVCRWdCc3hhTVJXQ1ZyNlJMVUVXVlMiLCJ5IjoiZ2lIVEE5MlF3VU5lbmg1OFV1bWFfb09BX3hnYmFDVWFXSlRnb3Z4WjU4R212TnN4eUlQRElLSm9WV1h5X0R6OSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..jDbzdI7d67_cUjGD.01BPnMq_tQ.aG_uFA6FYqoPS64QAJ4VBQ", // json payload doesn't contain "key"
+ "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IkJyYm9UQkl5Y0NDUEdJQlBUekU2RjBnbTRzRjRCamZPN1I0a2x0aWlCaThKZkxxcVdXNVdUSVBLN01yMXV5QVUiLCJ5IjoiNUpGVUI0WVJiYjM2RUZpN2Y0TUxMcFFyZXd2UV9Tc3dKNHRVbFd1a2c1ZU04X1ZyM2pkeml2QXI2WThRczVYbSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..QEq4Z2m6iwBx2ioS.IBo8TbKJTS4pug.61Z-agIIXgP8bX10O_yEMA", // json payload field "key" not a string
+ "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6ImNZdlVFZm9LYkJjenZySE5zQjUxOGpycUxPMGJDOW5lZjR4NzFFMUQ5dk95MXRqd1piZzV3cFI0OE5nU1RQdHgiLCJ5IjoiaWRJekhCWERzSzR2NTZEeU9yczJOcDZsSG1zb29fMXV0VTlzX3JNdVVkbkxuVXIzUXdLZkhYMWdaVXREM1RKayJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..0VZqu5ei9U3blGtq.eDvhU6drw7mIwvXu6Q.f05QnhI7JWG3IYHvexwdFQ" // json payload field "key" invalid base64 data
+ })
+ public void testDecryptInvalid(String malformed) throws ParseException, InvalidKeySpecException {
+ var jwe = JWEObject.parse(malformed);
+ var keyPair = P384KeyPair.create(new X509EncodedKeySpec(Base64.getDecoder().decode(PUB_KEY)), new PKCS8EncodedKeySpec(Base64.getDecoder().decode(PRIV_KEY)));
+
+ Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> {
+ JWEHelper.decrypt(jwe, keyPair.getPrivate());
+ });
+ }
+
+}
\ No newline at end of file