From 2e443c72a9aaf1593ed96f2889716f8fc1fab5e3 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 20 Jan 2024 13:12:59 +0100 Subject: [PATCH] add new API `encryptVaultKey(vaultKey, userKey)` and `decodeECPublicKey(byte[])` --- .../ui/keyloading/hub/JWEHelper.java | 37 +++++++++++++++++++ .../ui/keyloading/hub/JWEHelperTest.java | 28 ++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java b/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java index 6ef60610a..313ec48c9 100644 --- a/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java +++ b/src/main/java/org/cryptomator/ui/keyloading/hub/JWEHelper.java @@ -24,6 +24,7 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Base64; import java.util.Map; @@ -92,6 +93,42 @@ class JWEHelper { throw new KeyDecodeFailedException(e); } } + + /** + * Attempts to decode a DER-encoded EC public key. + * + * @param encoded DER-encoded EC public key + * @return the decoded key + * @throws KeyDecodeFailedException On malformed input + */ + public static ECPublicKey decodeECPublicKey(byte[] encoded) throws KeyDecodeFailedException { + try { + KeyFactory factory = KeyFactory.getInstance(EC_ALG); + var publicKey = factory.generatePublic(new X509EncodedKeySpec(encoded)); + if (publicKey instanceof ECPublicKey ecPublicKey) { + return ecPublicKey; + } else { + throw new IllegalStateException(EC_ALG + " key factory not generating ECPublicKeys"); + } + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(EC_ALG + " not supported"); + } catch (InvalidKeySpecException e) { + throw new KeyDecodeFailedException(e); + } + } + + public static JWEObject encryptVaultKey(Masterkey vaultKey, ECPublicKey userKey) { + try { + var encodedVaultKey = Base64.getEncoder().encodeToString(vaultKey.getEncoded()); + var keyGen = new ECKeyGenerator(Curve.P_384); + var ephemeralKeyPair = keyGen.generate(); + var header = new JWEHeader.Builder(JWEAlgorithm.ECDH_ES, EncryptionMethod.A256GCM).ephemeralPublicKey(ephemeralKeyPair.toPublicJWK()).build(); + var payload = new Payload(Map.of(JWE_PAYLOAD_KEY_FIELD, encodedVaultKey)); + var jwe = new JWEObject(header, payload); + jwe.encrypt(new ECDHEncrypter(userKey)); + return jwe; + } catch (JOSEException e) { + throw new RuntimeException(e); } } diff --git a/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java b/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java index a2e46cc28..7ff9df83f 100644 --- a/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java +++ b/src/test/java/org/cryptomator/ui/keyloading/hub/JWEHelperTest.java @@ -1,6 +1,7 @@ package org.cryptomator.ui.keyloading.hub; import com.nimbusds.jose.JWEObject; +import org.cryptomator.cryptolib.api.Masterkey; import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException; import org.cryptomator.cryptolib.common.P384KeyPair; import org.junit.jupiter.api.Assertions; @@ -140,4 +141,31 @@ public class JWEHelperTest { Assertions.assertThrows(MasterkeyLoadingFailedException.class, () -> JWEHelper.decryptVaultKey(jwe, privateKey)); } + @Test + @DisplayName("decrypt(encrypt(vaultKey, userPublicKey), userPrivateKey) == vaultKey") + public void testEncryptAndDecryptVaultKey() { + var keyBytes = new byte[64]; + Arrays.fill(keyBytes, 0, 32, (byte) 0x55); + Arrays.fill(keyBytes, 32, 64, (byte) 0x77); + var vaultKey = new Masterkey(keyBytes); + var userKey = P384KeyPair.generate(); + + var encrypted = JWEHelper.encryptVaultKey(vaultKey, userKey.getPublic()); + var decrypted = JWEHelper.decryptVaultKey(encrypted, userKey.getPrivate()); + + Assertions.assertArrayEquals(keyBytes, decrypted.getEncoded()); + } + + @Test + @DisplayName("decrypt(encrypt(userKey, devicePublicKey), devicePrivateKey) == userKey") + public void testEncryptAndDecryptUserKey() { + var userKey = P384KeyPair.generate(); + var deviceKey = P384KeyPair.generate(); + + var encrypted = JWEHelper.encryptUserKey(userKey.getPrivate(), deviceKey.getPublic()); + var decrypted = JWEHelper.decryptUserKey(encrypted, deviceKey.getPrivate()); + + Assertions.assertArrayEquals(userKey.getPrivate().getEncoded(), decrypted.getEncoded()); + } + } \ No newline at end of file