Skip to content

Commit e85dbdc

Browse files
committed
Fix encodings UvfMetadataPayload. WiP root directory name encryption.
1 parent 43bd184 commit e85dbdc

File tree

7 files changed

+100
-84
lines changed

7 files changed

+100
-84
lines changed

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

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@
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

1917
import java.net.URI;
18+
import java.nio.charset.StandardCharsets;
2019
import java.security.NoSuchAlgorithmException;
2120
import java.security.spec.InvalidKeySpecException;
2221
import java.text.ParseException;
@@ -27,7 +26,6 @@
2726
import java.util.Objects;
2827
import java.util.UUID;
2928

30-
import at.favre.lib.hkdf.HKDF;
3129
import ch.iterate.hub.crypto.exceptions.NotECKeyException;
3230
import ch.iterate.hub.model.JWEPayload;
3331
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -101,10 +99,13 @@ public String toJSON() throws JsonProcessingException {
10199

102100
public static UvfMetadataPayload create() {
103101
final String kid = Base64URL.encode(new AlphanumericRandomStringService(4).random()).toString();
104-
final byte[] rawSeed = new byte[32];
105-
FastSecureRandomProvider.get().provide().nextBytes(rawSeed);
106-
final byte[] kdfSalt = new byte[32];
107-
FastSecureRandomProvider.get().provide().nextBytes(kdfSalt);
102+
// TODO hashDirectoryId String/byte[]? byte[] -> UTF-8 -> byte[] not 1:1! See UvfMetadataPayloadTest -> we should use byte array directly going into hashDirectoryId -> do we need to write own CryptoDirectory?
103+
// final byte[] rawSeed = new byte[32];
104+
// FastSecureRandomProvider.get().provide().nextBytes(rawSeed);
105+
// final byte[] kdfSalt = new byte[32];
106+
// FastSecureRandomProvider.get().provide().nextBytes(kdfSalt);
107+
final byte[] rawSeed = new AlphanumericRandomStringService(4).random().getBytes(StandardCharsets.UTF_8);
108+
final byte[] kdfSalt = new AlphanumericRandomStringService(4).random().getBytes(StandardCharsets.UTF_8);
108109
return new UvfMetadataPayload()
109110
.withFileFormat("AES-256-GCM-32k")
110111
.withNameFormat("AES-SIV-512-B64URL")
@@ -117,20 +118,13 @@ public static UvfMetadataPayload create() {
117118
.withKdfSalt(Base64.getEncoder().encodeToString(kdfSalt));
118119
}
119120

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

136130

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
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;
1113
import ch.cyberduck.core.Path;
14+
import ch.cyberduck.core.PathAttributes;
1215
import ch.cyberduck.core.Session;
1316
import ch.cyberduck.core.cryptomator.ContentWriter;
1417
import ch.cyberduck.core.cryptomator.UVFVault;
@@ -20,30 +23,46 @@
2023

2124
import org.apache.logging.log4j.LogManager;
2225
import org.apache.logging.log4j.Logger;
26+
import org.cryptomator.cryptolib.api.Masterkey;
27+
import org.cryptomator.cryptolib.api.UVFMasterkey;
2328

2429
import java.nio.charset.StandardCharsets;
30+
import java.util.Base64;
2531
import java.util.EnumSet;
2632

2733
/**
2834
* Cryptomator vault implementation for Cipherduck (without masterkey file).
2935
*/
3036
public class HubCryptoVault extends UVFVault {
3137
private static final Logger log = LogManager.getLogger(HubCryptoVault.class);
38+
private final String decryptedPayload;
3239

3340

3441
public HubCryptoVault(final Path home) {
35-
super(home, null, null, null); // TODO cleanup
42+
this(home, null, null, null); // TODO cleanup
3643
}
3744

38-
public HubCryptoVault(final Path home, final String masterkey, final String config, final byte[] pepper) {
39-
super(home, masterkey, config, pepper);
45+
public HubCryptoVault(final Path home, final String decryptedPayload, final String config, final byte[] pepper) {
46+
super(home, decryptedPayload, config, pepper);
47+
this.decryptedPayload=decryptedPayload;
4048
}
4149

50+
@Override
51+
public Path getHome() {
52+
// TODO WiP trying to guide AbstractVault.encrypt() -> CryptoDirectoryV7Provider.toEncrypted(final Session<?> session, final String directoryId, final Path directory) -> do we need to write own CryptoDirectory?
53+
final Path home = super.getHome();
54+
final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(this.decryptedPayload);
55+
PathAttributes pathAttributes = new PathAttributes();
56+
pathAttributes.setDirectoryId(new String(masterKey.rootDirId()));
57+
return home.withAttributes(pathAttributes);
58+
}
59+
60+
4261
/**
4362
* Upload vault template into existing bucket (permanent credentials)
4463
*/
4564
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 review @dko check method signature?
46-
public synchronized Path create(final Session<?> session, final String region, final VaultCredentials credentials, final int version, final String metadata, final String rootDirHash) throws BackgroundException {
65+
public synchronized Path create(final Session<?> session, final String region, final VaultCredentials credentials, final int version, final String metadata, final String hashedRootDirId) throws BackgroundException {
4766
final Path home = new Path(session.getHost().getDefaultPath(), EnumSet.of(AbstractPath.Type.directory));
4867
log.debug("Uploading vault template {} in {} ", home, session.getHost());
4968

@@ -60,7 +79,7 @@ public synchronized Path create(final Session<?> session, final String region, f
6079

6180
// TODO https://github.com/shift7-ch/cipherduck-hub/issues/19 implement CryptoDirectory for uvf
6281
// Path secondLevel = this.directoryProvider.toEncrypted(session, this.home.attributes().getDirectoryId(), this.home);
63-
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));
82+
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));
6483
final Path firstLevel = secondLevel.getParent();
6584
final Path dataDir = firstLevel.getParent();
6685
log.debug("Create vault root directory at {}", secondLevel);

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import ch.cyberduck.core.HostPasswordStore;
1414
import ch.cyberduck.core.LocaleFactory;
1515
import ch.cyberduck.core.LoginCallback;
16+
import ch.cyberduck.core.LoginOptions;
1617
import ch.cyberduck.core.PasswordStoreFactory;
1718
import ch.cyberduck.core.Path;
1819
import ch.cyberduck.core.exception.BackgroundException;
@@ -26,11 +27,13 @@
2627
import ch.cyberduck.core.ssl.X509KeyManager;
2728
import ch.cyberduck.core.ssl.X509TrustManager;
2829
import ch.cyberduck.core.threading.CancelCallback;
30+
import ch.cyberduck.core.vault.VaultCredentials;
2931
import ch.cyberduck.core.vault.VaultFactory;
3032

3133
import org.apache.logging.log4j.LogManager;
3234
import org.apache.logging.log4j.Logger;
3335

36+
import java.util.Base64;
3437
import java.util.UUID;
3538

3639
import ch.iterate.hub.client.ApiException;
@@ -81,8 +84,14 @@ public void login(final LoginCallback prompt, final CancelCallback cancel) throw
8184
log.debug("Attempting to locate vault in {}", home);
8285
final UvfMetadataPayload vaultMetadata = new VaultServiceImpl(backend).getVaultMetadataJWE(
8386
UUID.fromString(host.getUuid()), new UserKeysServiceImpl(backend).getUserKeys(backend.getHost(), FirstLoginDeviceSetupCallbackFactory.get()));
84-
final Vault vault = VaultFactory.get(home, vaultMetadata.toJSON(), "", new byte[0]);
85-
vault.load(this, new DisabledPasswordCallback());
87+
final String decryptedPayload = vaultMetadata.toJSON();
88+
final Vault vault = VaultFactory.get(home, decryptedPayload, "", new byte[0]);
89+
registry.add(vault.load(this, new DisabledPasswordCallback() {
90+
@Override
91+
public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) {
92+
return new VaultCredentials(decryptedPayload);
93+
}
94+
}));
8695
backend.close();
8796
}
8897
catch(ApiException | SecurityFailure | AccessException | JsonProcessingException e) {

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

hub/src/test/java/ch/iterate/hub/core/AbstractHubSynchronizeTest.java

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,7 @@
44

55
package ch.iterate.hub.core;
66

7-
import ch.cyberduck.core.AbstractPath;
8-
import ch.cyberduck.core.AttributedList;
9-
import ch.cyberduck.core.DisabledCancelCallback;
10-
import ch.cyberduck.core.DisabledHostKeyCallback;
11-
import ch.cyberduck.core.DisabledListProgressListener;
12-
import ch.cyberduck.core.DisabledLoginCallback;
13-
import ch.cyberduck.core.DisabledPasswordCallback;
14-
import ch.cyberduck.core.Host;
15-
import ch.cyberduck.core.ListService;
16-
import ch.cyberduck.core.Path;
17-
import ch.cyberduck.core.Protocol;
18-
import ch.cyberduck.core.ProtocolFactory;
19-
import ch.cyberduck.core.Session;
20-
import ch.cyberduck.core.SimplePathPredicate;
7+
import ch.cyberduck.core.*;
218
import ch.cyberduck.core.features.Home;
229
import ch.cyberduck.core.features.Vault;
2310
import ch.cyberduck.core.preferences.PreferencesFactory;
@@ -26,6 +13,16 @@
2613
import ch.cyberduck.core.ssl.DisabledX509TrustManager;
2714
import ch.cyberduck.core.vault.DefaultVaultRegistry;
2815

16+
import ch.iterate.hub.client.api.DeviceResourceApi;
17+
import ch.iterate.hub.client.api.UsersResourceApi;
18+
import ch.iterate.hub.client.api.VaultResourceApi;
19+
20+
import ch.iterate.hub.crypto.UserKeys;
21+
import ch.iterate.hub.crypto.uvf.UvfMetadataPayload;
22+
import ch.iterate.hub.workflows.UserKeysService;
23+
import ch.iterate.hub.workflows.UserKeysServiceImpl;
24+
import ch.iterate.hub.workflows.VaultServiceImpl;
25+
2926
import org.apache.commons.lang3.StringUtils;
3027
import org.apache.logging.log4j.LogManager;
3128
import org.apache.logging.log4j.Logger;
@@ -239,17 +236,21 @@ public void test03AddVault(final HubTestConfig config) throws Exception {
239236
session.open(new DisabledProxyFinder(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback());
240237
session.login(new DisabledLoginCallback(), new DisabledCancelCallback());
241238

239+
// listing decrypted file names
242240
assertFalse(vaultRegistry.isEmpty());
243241
assertEquals(1, vaultRegistry.size());
244-
final Path bucket = new Path(vaultBookmark.getDefaultPath(), EnumSet.of(Path.Type.directory, Path.Type.volume));
245-
assertNotSame(Vault.DISABLED, vaultRegistry.find(session, bucket));
246242

243+
// TODO WiP trying to guide AbstractVault.encrypt() -> CryptoDirectoryV7Provider.toEncrypted(final Session<?> session, final String directoryId, final Path directory) -> do we need to write own CryptoDirectory?
244+
final Path bucket = new Path(vaultBookmark.getDefaultPath(), EnumSet.of(Path.Type.directory, Path.Type.volume, Path.Type.vault));
245+
assertNotSame(Vault.DISABLED, vaultRegistry.find(session, bucket));
247246
// {
248247
// final AttributedList<Path> list = session.getFeature(ListService.class).list(bucket, new DisabledListProgressListener());
249248
// assertTrue(list.isEmpty());
250249
// }
251250

251+
// raw listing encrypted file names
252252
vaultRegistry.close(bucket);
253+
assertSame(Vault.DISABLED, vaultRegistry.find(session, bucket));
253254
assertTrue(vaultRegistry.isEmpty());
254255
{
255256
final AttributedList<Path> list = session.getFeature(ListService.class).list(bucket, new DisabledListProgressListener());

0 commit comments

Comments
 (0)