diff --git a/pom.xml b/pom.xml
index 50a90b72..5e5387e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
org.cryptomator
cryptofs
- 2.1.0
+ 2.1.1
Cryptomator Crypto Filesystem
This library provides the Java filesystem provider used by Cryptomator.
https://github.com/cryptomator/cryptofs
diff --git a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java
index 0aeb37af..4a1392ac 100644
--- a/src/main/java/org/cryptomator/cryptofs/VaultConfig.java
+++ b/src/main/java/org/cryptomator/cryptofs/VaultConfig.java
@@ -6,7 +6,6 @@
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
-import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptolib.api.CryptorProvider;
@@ -84,12 +83,12 @@ public String toToken(String keyId, byte[] rawKey) {
/**
* Convenience wrapper for {@link #decode(String)} and {@link UnverifiedVaultConfig#verify(byte[], int)}
*
- * @param token The token
- * @param keyLoader A key loader capable of providing a key for this token
+ * @param token The token
+ * @param keyLoader A key loader capable of providing a key for this token
* @param expectedVaultVersion The vault version this token should contain
* @return The decoded configuration
* @throws MasterkeyLoadingFailedException If the key loader was unable to provide a key for this vault configuration
- * @throws VaultConfigLoadException When loading the configuration fails
+ * @throws VaultConfigLoadException When loading the configuration fails
*/
public static VaultConfig load(String token, MasterkeyLoader keyLoader, int expectedVaultVersion) throws MasterkeyLoadingFailedException, VaultConfigLoadException {
var configLoader = decode(token);
@@ -143,6 +142,7 @@ public URI getKeyId() {
/**
* Gets a value from the tokens header
+ *
* @param key Which key to read
* @param clazz Type of the value
* @param Type of the value
@@ -171,19 +171,30 @@ public int allegedShorteningThreshold() {
return unverifiedConfig.getClaim(JSON_KEY_SHORTENING_THRESHOLD).asInt();
}
+ private Algorithm initAlgorithm(byte[] rawKey) throws VaultConfigLoadException {
+ var algo = unverifiedConfig.getAlgorithm();
+ return switch (algo) {
+ case "HS256" -> Algorithm.HMAC256(rawKey);
+ case "HS384" -> Algorithm.HMAC384(rawKey);
+ case "HS512" -> Algorithm.HMAC512(rawKey);
+ default -> throw new VaultConfigLoadException("Unsupported signature algorithm: " + algo);
+ };
+ }
+
/**
* Decodes a vault configuration stored in JWT format.
*
- * @param rawKey The key matching the id in {@link #getKeyId()}
+ * @param rawKey The key matching the id in {@link #getKeyId()}
* @param expectedVaultVersion The vault version this token should contain
* @return The decoded configuration
- * @throws VaultKeyInvalidException If the provided key was invalid
+ * @throws VaultKeyInvalidException If the provided key was invalid
* @throws VaultVersionMismatchException If the token did not match the expected vault version
- * @throws VaultConfigLoadException Generic parse error
+ * @throws VaultConfigLoadException Generic parse error
*/
public VaultConfig verify(byte[] rawKey, int expectedVaultVersion) throws VaultKeyInvalidException, VaultVersionMismatchException, VaultConfigLoadException {
try {
- var verifier = JWT.require(Algorithm.HMAC256(rawKey)) //
+ unverifiedConfig.getAlgorithm();
+ var verifier = JWT.require(initAlgorithm(rawKey)) //
.withClaim(JSON_KEY_VAULTVERSION, expectedVaultVersion) //
.build();
var verifiedConfig = verifier.verify(unverifiedConfig);
diff --git a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java
index a7abbda7..0f6068d2 100644
--- a/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java
+++ b/src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java
@@ -11,10 +11,13 @@
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
@@ -34,6 +37,7 @@ public void setup() throws MasterkeyLoadingFailedException {
}
@Test
+ @DisplayName("test VaultConfig.load() with invalid token")
public void testLoadMalformedToken() {
Assertions.assertThrows(VaultConfigLoadException.class, () -> {
VaultConfig.load("hello world", masterkeyLoader, 42);
@@ -41,41 +45,73 @@ public void testLoadMalformedToken() {
}
@Nested
- public class WithValidToken {
+ @DisplayName("VaultConfig ...")
+ public class WithExistingConfig {
private VaultConfig originalConfig;
- private String token;
@BeforeEach
public void setup() throws MasterkeyLoadingFailedException {
originalConfig = VaultConfig.createNew().cipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).shorteningThreshold(220).build();
- token = originalConfig.toToken("TEST_KEY", rawKey);
}
@Test
- public void testSuccessfulLoad() throws VaultConfigLoadException, MasterkeyLoadingFailedException {
- var loaded = VaultConfig.load(token, masterkeyLoader, originalConfig.getVaultVersion());
+ @DisplayName("toToken() is HS256-signed")
+ public void testToToken() {
+ var token = originalConfig.toToken("TEST_KEY", rawKey);
+
+ Assertions.assertNotNull(token);
+ var decoded = JWT.decode(token);
+ Assertions.assertEquals("HS256", decoded.getAlgorithm());
+ }
+
+ }
+
+ @Nested
+ @DisplayName("Using valid tokens...")
+ public class WithValidToken {
+
+ private static final String TOKEN_NONE = "eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6Im5vbmUifQ.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.";
+ private static final String TOKEN_HS256 = "eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.V7pqSXX1tBRgmntL1sXovnhNR4Z1_7z3Jzrq7NMqPO8";
+ private static final String TOKEN_HS384 = "eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6IkhTMzg0In0.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.rx03sCVAyrCmT6halPaFU46lu-DOd03iwDgvdw362hfgJj782q6xPXjAxdKeVKxG";
+ private static final String TOKEN_HS512 = "eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6IkhTNTEyIn0.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.fzkVI34Ou3z7RaFarS9VPCaA0NX9z7My14gAISTXJGKGNSID7xEcoaY56SBdWbU7Ta17KhxcHhbXffxk3Mzing";
+
+ @Test
+ public void testUnsupportedSignature() {
+ VaultConfigLoadException thrown = Assertions.assertThrows(VaultConfigLoadException.class, () -> {
+ VaultConfig.load(TOKEN_NONE, masterkeyLoader, 8);
+ });
+ Assertions.assertEquals("Unsupported signature algorithm: none", thrown.getMessage());
+ }
+
+ @DisplayName("load token")
+ @ParameterizedTest(name = "signed with {0}")
+ @CsvSource({"HS256," + TOKEN_HS256, "HS384," + TOKEN_HS384, "HS512," + TOKEN_HS512})
+ public void testSuccessfulLoad(String algo, String token) throws VaultConfigLoadException, MasterkeyLoadingFailedException {
+ Assumptions.assumeTrue(JWT.decode(token).getAlgorithm().equals(algo));
+
+ var loaded = VaultConfig.load(token, masterkeyLoader, 8);
- Assertions.assertEquals(originalConfig.getId(), loaded.getId());
- Assertions.assertEquals(originalConfig.getVaultVersion(), loaded.getVaultVersion());
- Assertions.assertEquals(originalConfig.getCipherCombo(), loaded.getCipherCombo());
- Assertions.assertEquals(originalConfig.getShorteningThreshold(), loaded.getShorteningThreshold());
+ Assertions.assertEquals(8, loaded.getVaultVersion());
+ Assertions.assertEquals(CryptorProvider.Scheme.SIV_CTRMAC, loaded.getCipherCombo());
+ Assertions.assertEquals(220, loaded.getShorteningThreshold());
}
- @ParameterizedTest
+ @DisplayName("load using key with...")
+ @ParameterizedTest(name = "invalid byte at position {0}")
@ValueSource(ints = {0, 1, 2, 3, 10, 20, 30, 63})
public void testLoadWithInvalidKey(int pos) {
rawKey[pos] = (byte) 0x77;
- Mockito.when(key.getEncoded()).thenReturn(rawKey);
Assertions.assertThrows(VaultKeyInvalidException.class, () -> {
- VaultConfig.load(token, masterkeyLoader, originalConfig.getVaultVersion());
+ VaultConfig.load(TOKEN_HS256, masterkeyLoader, 8);
});
}
}
@Test
+ @DisplayName("test VaultConfig.createNew()...")
public void testCreateNew() {
var config = VaultConfig.createNew().cipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).shorteningThreshold(220).build();
@@ -86,6 +122,7 @@ public void testCreateNew() {
}
@Test
+ @DisplayName("test VaultConfig.load(...)")
public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoadingFailedException {
var decodedJwt = Mockito.mock(DecodedJWT.class);
var formatClaim = Mockito.mock(Claim.class);
@@ -95,6 +132,7 @@ public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoading
var verification = Mockito.mock(Verification.class);
var verifier = Mockito.mock(JWTVerifier.class);
Mockito.when(decodedJwt.getKeyId()).thenReturn("test:key");
+ Mockito.when(decodedJwt.getAlgorithm()).thenReturn("HS256");
Mockito.when(decodedJwt.getClaim("format")).thenReturn(formatClaim);
Mockito.when(decodedJwt.getClaim("cipherCombo")).thenReturn(cipherComboClaim);
Mockito.when(decodedJwt.getClaim("shorteningThreshold")).thenReturn(maxFilenameLenClaim);