Skip to content

Commit e864c20

Browse files
ylangiscchenkins
authored andcommitted
Inherit from UVFVault.
1 parent 713d156 commit e864c20

File tree

7 files changed

+120
-130
lines changed

7 files changed

+120
-130
lines changed

hub/src/main/java/ch/iterate/hub/crypto/uvf/UvfMetadataPayload.java

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@
88
import ch.cyberduck.core.AlphanumericRandomStringService;
99
import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider;
1010

11-
import org.bouncycastle.crypto.Digest;
12-
import org.bouncycastle.crypto.digests.SHA256Digest;
13-
import org.bouncycastle.crypto.macs.HMac;
14-
import org.bouncycastle.crypto.params.KeyParameter;
15-
import org.bouncycastle.util.encoders.Base32;
11+
import org.cryptomator.cryptolib.api.Cryptor;
12+
import org.cryptomator.cryptolib.api.CryptorProvider;
13+
import org.cryptomator.cryptolib.api.UVFMasterkey;
1614
import org.cryptomator.cryptolib.common.P384KeyPair;
1715
import org.openapitools.jackson.nullable.JsonNullableModule;
1816

@@ -27,7 +25,6 @@
2725
import java.util.Objects;
2826
import java.util.UUID;
2927

30-
import at.favre.lib.hkdf.HKDF;
3128
import ch.iterate.hub.crypto.exceptions.NotECKeyException;
3229
import ch.iterate.hub.model.JWEPayload;
3330
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -93,8 +90,14 @@ public static UvfMetadataPayload fromJWE(final String jwe) throws JsonProcessing
9390
return mapper.readValue(jwe, UvfMetadataPayload.class);
9491
}
9592

93+
public String toJSON() throws JsonProcessingException {
94+
ObjectMapper mapper = new ObjectMapper();
95+
mapper.registerModule(new JsonNullableModule());
96+
return mapper.writeValueAsString(this);
97+
}
98+
9699
public static UvfMetadataPayload create() {
97-
final String kid = new AlphanumericRandomStringService(4).random();
100+
final String kid = Base64URL.encode(new AlphanumericRandomStringService(4).random()).toString();
98101
final byte[] rawSeed = new byte[32];
99102
FastSecureRandomProvider.get().provide().nextBytes(rawSeed);
100103
final byte[] kdfSalt = new byte[32];
@@ -103,28 +106,21 @@ public static UvfMetadataPayload create() {
103106
.withFileFormat("AES-256-GCM-32k")
104107
.withNameFormat("AES-SIV-512-B64URL")
105108
.withSeeds(new HashMap<String, String>() {{
106-
put(kid, Base64URL.encode(rawSeed).toString());
109+
put(kid, Base64.getEncoder().encodeToString(rawSeed));
107110
}})
108111
.withLatestSeed(kid)
109112
.withinitialSeed(kid)
110113
.withKdf("HKDF-SHA512")
111-
.withKdfSalt(Base64URL.encode(kdfSalt).toString());
112-
}
113-
114-
public byte[] computeRootDirId() {
115-
return HKDF.fromHmacSha512().extractAndExpand(Base64URL.from(kdfSalt()).decode(), Base64URL.from(seeds().get(initialSeed())).decode(), "rootDirId".getBytes(), 256 / 8);
114+
.withKdfSalt(Base64.getEncoder().encodeToString(kdfSalt));
116115
}
117116

118-
public String computeRootDirIdHash(final byte[] rootDirId) {
119-
final byte[] hmacKey = HKDF.fromHmacSha512()
120-
.extractAndExpand(Base64URL.from(kdfSalt()).decode(), Base64URL.from(seeds().get(initialSeed())).decode(), "hmac".getBytes(), 512 / 8);
121-
final Digest digest = new SHA256Digest();
122-
final HMac hMac = new HMac(digest);
123-
hMac.init(new KeyParameter(hmacKey));
124-
hMac.update(rootDirId, 0, rootDirId.length);
125-
final byte[] hmacOut = new byte[hMac.getMacSize()];
126-
hMac.doFinal(hmacOut, 0);
127-
return Base32.toBase32String(Arrays.copyOfRange(hmacOut, 0, 20));
117+
public String computeRootDirIdHash() throws JsonProcessingException {
118+
final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(this.toJSON());
119+
final CryptorProvider provider = CryptorProvider.forScheme(CryptorProvider.Scheme.UVF_DRAFT);
120+
final Cryptor cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide());
121+
final byte[] rootDirId = masterKey.rootDirId();
122+
final String hashedRootDirId = cryptor.fileNameCryptor(masterKey.firstRevision()).hashDirectoryId(rootDirId);
123+
return hashedRootDirId;
128124
}
129125

130126

hub/src/main/java/ch/iterate/hub/protocols/hub/HubCryptoVault.java

Lines changed: 29 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@
88
import ch.cyberduck.core.AbstractPath;
99
import ch.cyberduck.core.DisabledListProgressListener;
1010
import ch.cyberduck.core.ListService;
11-
import ch.cyberduck.core.LoginOptions;
12-
import ch.cyberduck.core.PasswordCallback;
1311
import ch.cyberduck.core.Path;
12+
import ch.cyberduck.core.PathAttributes;
1413
import ch.cyberduck.core.Session;
1514
import ch.cyberduck.core.cryptomator.ContentWriter;
16-
import ch.cyberduck.core.cryptomator.CryptoVault;
15+
import ch.cyberduck.core.cryptomator.UVFVault;
1716
import ch.cyberduck.core.exception.BackgroundException;
1817
import ch.cyberduck.core.features.Directory;
1918
import ch.cyberduck.core.preferences.PreferencesFactory;
@@ -22,47 +21,50 @@
2221

2322
import org.apache.logging.log4j.LogManager;
2423
import org.apache.logging.log4j.Logger;
25-
import org.cryptomator.cryptolib.api.CryptorProvider;
26-
import org.cryptomator.cryptolib.api.Masterkey;
24+
import org.cryptomator.cryptolib.api.UVFMasterkey;
2725

2826
import java.nio.charset.StandardCharsets;
29-
import java.util.Base64;
3027
import java.util.EnumSet;
3128

3229
/**
3330
* Cryptomator vault implementation for Cipherduck (without masterkey file).
3431
*/
35-
public class HubCryptoVault extends CryptoVault {
32+
public class HubCryptoVault extends UVFVault {
3633
private static final Logger log = LogManager.getLogger(HubCryptoVault.class);
34+
private final String decryptedPayload;
3735

38-
// See https://github.com/cryptomator/hub/blob/develop/frontend/src/common/vaultconfig.ts
39-
//const jwtPayload: VaultConfigPayload = {
40-
// jti: vaultId,
41-
// format: 8,
42-
// cipherCombo: 'SIV_GCM',
43-
// shorteningThreshold: 220
44-
//};
45-
//const header = JSON.stringify({
46-
// kid: kid,
47-
// typ: 'jwt',
48-
// alg: 'HS256',
49-
// hub: hubConfig
50-
//});
51-
private static final VaultConfig VAULT_CONFIG = new VaultConfig(8, 220, CryptorProvider.Scheme.SIV_GCM, "HS256", null);
5236

5337
public HubCryptoVault(final Path home) {
54-
super(home);
38+
this(home, null, null, null); // TODO cleanup
5539
}
5640

57-
public HubCryptoVault(final Path home, final String masterkey, final String config, final byte[] pepper) {
58-
super(home);
41+
public HubCryptoVault(final Path home, final String decryptedPayload, final String config, final byte[] pepper) {
42+
super(home, decryptedPayload, config, pepper);
43+
this.decryptedPayload = decryptedPayload;
5944
}
6045

46+
public Path encrypt(Session<?> session, Path file, byte[] directoryId, boolean metadata) throws BackgroundException {
47+
log.debug("HubCryptoVault.encrypt. Use directory ID '{}' for folder {}", directoryId, file);
48+
return super.encrypt(session, file, directoryId, metadata);
49+
}
50+
51+
52+
@Override
53+
public Path getHome() {
54+
final Path home = super.getHome();
55+
final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(this.decryptedPayload);
56+
byte[] directoryId = masterKey.rootDirId();
57+
assert directoryId != null;
58+
home.attributes().setDirectoryId(directoryId);
59+
return home;
60+
}
61+
62+
6163
/**
6264
* Upload vault template into existing bucket (permanent credentials)
6365
*/
6466
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 review @dko check method signature?
65-
public synchronized Path create(final Session<?> session, final String region, final VaultCredentials credentials, final int version, final String metadata, final String rootDirHash) throws BackgroundException {
67+
public synchronized Path create(final Session<?> session, final String region, final VaultCredentials credentials, final int version, final String metadata, final String hashedRootDirId) throws BackgroundException {
6668
final Path home = new Path(session.getHost().getDefaultPath(), EnumSet.of(AbstractPath.Type.directory));
6769
log.debug("Uploading vault template {} in {} ", home, session.getHost());
6870

@@ -75,11 +77,11 @@ public synchronized Path create(final Session<?> session, final String region, f
7577
// zip.file('vault.cryptomator', this.vaultConfigToken);
7678
// zip.folder('d')?.folder(this.rootDirHash.substring(0, 2))?.folder(this.rootDirHash.substring(2));
7779
(new ContentWriter(session)).write(new Path(home, PreferencesFactory.get().getProperty("cryptomator.vault.config.filename"), EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.vault)), metadata.getBytes(StandardCharsets.US_ASCII));
78-
Directory<?> directory = (Directory) session._getFeature(Directory.class);
80+
Directory<?> directory = (Directory<?>) session._getFeature(Directory.class);
7981

8082
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 implement CryptoDirectory for uvf
8183
// Path secondLevel = this.directoryProvider.toEncrypted(session, this.home.attributes().getDirectoryId(), this.home);
82-
final Path secondLevel = new Path(String.format("/%s/d/%s/%s/", session.getHost().getDefaultPath(), rootDirHash.substring(0, 2), rootDirHash.substring(2)), EnumSet.of(AbstractPath.Type.directory));
84+
final Path secondLevel = new Path(String.format("/%s/d/%s/%s/", session.getHost().getDefaultPath(), hashedRootDirId.substring(0, 2), hashedRootDirId.substring(2)), EnumSet.of(AbstractPath.Type.directory));
8385
final Path firstLevel = secondLevel.getParent();
8486
final Path dataDir = firstLevel.getParent();
8587
log.debug("Create vault root directory at {}", secondLevel);
@@ -91,19 +93,6 @@ public synchronized Path create(final Session<?> session, final String region, f
9193
return home;
9294
}
9395

94-
@Override
95-
public HubCryptoVault load(final Session<?> session, final PasswordCallback prompt) throws BackgroundException {
96-
// no-interactive prompt in Cipherduck
97-
final String masterkey = prompt.prompt(session.getHost(), "", "", new LoginOptions()).getPassword();
98-
try {
99-
this.open(VAULT_CONFIG, new Masterkey(Base64.getDecoder().decode(masterkey)));
100-
}
101-
catch(IllegalArgumentException e) {
102-
throw new BackgroundException(e);
103-
}
104-
return this;
105-
}
106-
10796
public Path getMasterkey() {
10897
// No master key in vault
10998
return null;

hub/src/main/java/ch/iterate/hub/protocols/s3/S3AutoLoadVaultSession.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@
4444
import ch.iterate.hub.workflows.VaultServiceImpl;
4545
import ch.iterate.hub.workflows.exceptions.AccessException;
4646
import ch.iterate.hub.workflows.exceptions.SecurityFailure;
47-
import com.google.common.primitives.Bytes;
48-
import com.nimbusds.jose.util.Base64URL;
47+
import com.fasterxml.jackson.core.JsonProcessingException;
4948

5049
public class S3AutoLoadVaultSession extends S3AssumeRoleSession {
5150
private static final Logger log = LogManager.getLogger(S3AutoLoadVaultSession.class);
@@ -83,23 +82,19 @@ public void login(final LoginCallback prompt, final CancelCallback cancel) throw
8382
super.login(prompt, cancel);
8483
final Path home = new DelegatingHomeFeature(new DefaultPathHomeFeature(host)).find();
8584
log.debug("Attempting to locate vault in {}", home);
86-
final Vault vault = VaultFactory.get(home);
87-
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! MUST NEVER BE RELEASED LIKE THIS
88-
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 use rawFileKey,rawNameKey as vault key for now (going into cryptolib's Masterkey)
8985
final UvfMetadataPayload vaultMetadata = new VaultServiceImpl(backend).getVaultMetadataJWE(
9086
UUID.fromString(host.getUuid()), new UserKeysServiceImpl(backend).getUserKeys(backend.getHost(), FirstLoginDeviceSetupCallbackFactory.get()));
91-
final byte[] rawFileKey = Base64URL.from(vaultMetadata.seeds().get(vaultMetadata.latestSeed())).decode();
92-
final byte[] rawNameKey = Base64URL.from(vaultMetadata.seeds().get(vaultMetadata.latestSeed())).decode();
93-
final byte[] vaultKey = Bytes.concat(rawFileKey, rawNameKey);
87+
final String decryptedPayload = vaultMetadata.toJSON();
88+
final Vault vault = VaultFactory.get(home, decryptedPayload, "", new byte[0]);
9489
registry.add(vault.load(this, new DisabledPasswordCallback() {
9590
@Override
9691
public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
97-
return new VaultCredentials(Base64.getEncoder().encodeToString(vaultKey));
92+
return new VaultCredentials(decryptedPayload);
9893
}
9994
}));
10095
backend.close();
10196
}
102-
catch(ApiException | SecurityFailure | AccessException e) {
97+
catch(ApiException | SecurityFailure | AccessException | JsonProcessingException e) {
10398
throw new LoginFailureException(LocaleFactory.localizedString("Login failed", "Credentials"), e);
10499
}
105100
}

hub/src/main/java/ch/iterate/hub/workflows/CreateVaultService.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@
1212
import ch.cyberduck.core.HostUrlProvider;
1313
import ch.cyberduck.core.Path;
1414
import ch.cyberduck.core.TemporaryAccessTokens;
15+
import ch.cyberduck.core.cryptomator.CryptorCache;
16+
import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider;
1517
import ch.cyberduck.core.exception.BackgroundException;
1618
import ch.cyberduck.core.proxy.DisabledProxyFinder;
1719
import ch.cyberduck.core.s3.S3Session;
1820

1921
import org.apache.commons.io.IOUtils;
2022
import org.apache.logging.log4j.LogManager;
2123
import org.apache.logging.log4j.Logger;
24+
import org.cryptomator.cryptolib.api.Cryptor;
25+
import org.cryptomator.cryptolib.api.CryptorProvider;
26+
import org.cryptomator.cryptolib.api.UVFMasterkey;
27+
import org.cryptomator.cryptolib.common.HKDFHelper;
2228
import org.joda.time.DateTime;
2329

2430
import java.io.IOException;
@@ -74,7 +80,7 @@ public void createVault(final StorageProfileDtoWrapper storageProfileWrapper, fi
7480
hubSession.getHost(), FirstLoginDeviceSetupCallbackFactory.get());
7581

7682
final UvfMetadataPayload.UniversalVaultFormatJWKS jwks = UvfMetadataPayload.createKeys();
77-
final UvfMetadataPayload metadataJWE = UvfMetadataPayload.create()
83+
final UvfMetadataPayload metadataPayload = UvfMetadataPayload.create()
7884
.withStorage(new VaultMetadataJWEBackendDto()
7985
.provider(storageProfileWrapper.getId().toString())
8086
.defaultPath(storageProfileWrapper.getStsEndpoint() != null ? storageProfileWrapper.getBucketPrefix() + vaultModel.vaultId() : vaultModel.bucketName())
@@ -85,39 +91,40 @@ public void createVault(final StorageProfileDtoWrapper storageProfileWrapper, fi
8591
.enabled(vaultModel.automaticAccessGrant())
8692
.maxWotDepth(vaultModel.maxWotLevel())
8793
);
88-
log.debug("Created metadata JWE {}", metadataJWE);
89-
final String uvfMetadataFile = metadataJWE.encrypt(
94+
log.debug("Created metadata JWE {}", metadataPayload);
95+
final String uvfMetadataFile = metadataPayload.encrypt(
9096
String.format("%s/api", new HostUrlProvider(false, true).get(hubSession.getHost())),
9197
vaultModel.vaultId(),
9298
jwks.toJWKSet()
9399
);
94100
final VaultDto vaultDto = new VaultDto()
95101
.id(vaultModel.vaultId())
96-
.name(metadataJWE.storage().getNickname())
102+
.name(metadataPayload.storage().getNickname())
97103
.description(vaultModel.vaultDescription())
98104
.archived(false)
99105
.creationTime(DateTime.now())
100106
.uvfMetadataFile(uvfMetadataFile)
101107
.uvfKeySet(jwks.serializePublicRecoverykey());
108+
109+
final String hashedRootDirId = metadataPayload.computeRootDirIdHash();
102110
final CreateS3STSBucketDto storageDto = new CreateS3STSBucketDto()
103111
.vaultId(vaultModel.vaultId().toString())
104112
.storageConfigId(storageProfileWrapper.getId())
105-
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 do we need to store here as well? Only in VaultDto?
106113
.vaultUvf(uvfMetadataFile)
107-
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 do we need to store here?
108-
.rootDirHash(metadataJWE.computeRootDirIdHash(metadataJWE.computeRootDirId()))
109-
.region(metadataJWE.storage().getRegion());
114+
.rootDirHash(hashedRootDirId)
115+
.region(metadataPayload.storage().getRegion());
110116
log.debug("Created storage dto {}", storageDto);
111-
final Host bookmark = HubStorageVaultSyncSchedulerService.toBookmark(hubSession.getHost(), vaultDto.getId(), metadataJWE.storage());
117+
final Host bookmark = HubStorageVaultSyncSchedulerService.toBookmark(hubSession.getHost(), vaultDto.getId(), metadataPayload.storage());
112118
if(storageProfileWrapper.getStsEndpoint() == null) {
113-
// permanent: template upload into existing bucket
119+
// permanent: template upload into existing bucket from client (not backend)
114120
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 review @dko
115121
final S3Session session = new S3Session(bookmark);
116122
session.open(new DisabledProxyFinder(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback());
117123
session.login(new DisabledLoginCallback(), new DisabledCancelCallback());
124+
118125
// upload vault template
119-
new HubCryptoVault(new Path(metadataJWE.storage().getDefaultPath(), EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)))
120-
.create(session, metadataJWE.storage().getRegion(), null, 42, storageDto.getVaultUvf(), storageDto.getRootDirHash());
126+
new HubCryptoVault(new Path(metadataPayload.storage().getDefaultPath(), EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)))
127+
.create(session, metadataPayload.storage().getRegion(), null, 42, storageDto.getVaultUvf(), hashedRootDirId);
121128
session.close();
122129
}
123130
else {
@@ -153,6 +160,7 @@ public void createVault(final StorageProfileDtoWrapper storageProfileWrapper, fi
153160
}
154161
}
155162

163+
156164
private static TemporaryAccessTokens getSTSTokensFromAccessTokenWithCreateBucketInlinePoliy(final String token, final String roleArn, final String roleSessionName, final String stsEndpoint, final String bucketName, final Boolean bucketAcceleration) throws IOException {
157165
log.debug("Get STS tokens from {} to pass to backend {} with role {} and session name {}", token, stsEndpoint, roleArn, roleSessionName);
158166

0 commit comments

Comments
 (0)