Skip to content

Commit

Permalink
Merge branch 'release/2.1.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Oct 20, 2021
2 parents f7046e1 + d93d310 commit 9759128
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 21 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
<version>2.1.0</version>
<version>2.1.1</version>
<name>Cryptomator Crypto Filesystem</name>
<description>This library provides the Java filesystem provider used by Cryptomator.</description>
<url>https://github.com/cryptomator/cryptofs</url>
Expand Down
27 changes: 19 additions & 8 deletions src/main/java/org/cryptomator/cryptofs/VaultConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 <T> Type of the value
Expand Down Expand Up @@ -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);
Expand Down
62 changes: 50 additions & 12 deletions src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,48 +37,81 @@ 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);
});
}

@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();

Expand All @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 9759128

Please sign in to comment.