Skip to content

Commit e577456

Browse files
committed
Implement metadata provider to create vault.
1 parent ea118c9 commit e577456

File tree

7 files changed

+143
-107
lines changed

7 files changed

+143
-107
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright (c) 2025 shift7 GmbH. All rights reserved.
3+
*/
4+
5+
package cloud.katta.crypto.uvf;
6+
7+
import ch.cyberduck.core.Host;
8+
import ch.cyberduck.core.HostUrlProvider;
9+
import ch.cyberduck.core.cryptomator.impl.uvf.VaultMetadataUVFProvider;
10+
import ch.cyberduck.core.vault.VaultMetadataProvider;
11+
12+
import java.nio.charset.StandardCharsets;
13+
import java.util.UUID;
14+
15+
import com.fasterxml.jackson.core.JsonProcessingException;
16+
import com.nimbusds.jose.JOSEException;
17+
18+
public class VaultIdMetadataUVFProvider implements VaultMetadataUVFProvider {
19+
20+
private final UUID vaultId;
21+
private final UvfMetadataPayload.UniversalVaultFormatJWKS jwks;
22+
private final byte[] vaultMetadata;
23+
private final byte[] rootDirectoryMetadata;
24+
private final String hashedRootDirId;
25+
26+
public VaultIdMetadataUVFProvider(final Host storage, final UUID vaultId,
27+
final UvfMetadataPayload.UniversalVaultFormatJWKS jwks, final UvfMetadataPayload vaultMetadata) throws JOSEException, JsonProcessingException {
28+
this(vaultId, jwks, vaultMetadata.encrypt(
29+
String.format("%s/api", new HostUrlProvider(false, true).get(storage)),
30+
vaultId,
31+
jwks.toJWKSet()
32+
).getBytes(StandardCharsets.US_ASCII),
33+
vaultMetadata.computeRootDirUvf(),
34+
vaultMetadata.computeRootDirIdHash()
35+
);
36+
}
37+
38+
public VaultIdMetadataUVFProvider(final UUID vaultId, final UvfMetadataPayload.UniversalVaultFormatJWKS jwks, final byte[] vaultMetadata, final byte[] rootDirectoryMetadata, final String hashedRootDirId) {
39+
this.vaultId = vaultId;
40+
this.jwks = jwks;
41+
this.vaultMetadata = vaultMetadata;
42+
this.rootDirectoryMetadata = rootDirectoryMetadata;
43+
this.hashedRootDirId = hashedRootDirId;
44+
}
45+
46+
public static VaultIdMetadataUVFProvider cast(VaultMetadataProvider provider) {
47+
if(provider instanceof VaultIdMetadataUVFProvider) {
48+
return (VaultIdMetadataUVFProvider) provider;
49+
}
50+
else {
51+
throw new IllegalArgumentException("Unsupported metadata type " + provider.getClass());
52+
}
53+
}
54+
55+
public UUID getVaultId() {
56+
return vaultId;
57+
}
58+
59+
public UvfMetadataPayload.UniversalVaultFormatJWKS getJwks() {
60+
return jwks;
61+
}
62+
63+
@Override
64+
public byte[] getMetadata() {
65+
return vaultMetadata;
66+
}
67+
68+
@Override
69+
public byte[] getRootDirectoryMetadata() {
70+
return rootDirectoryMetadata;
71+
}
72+
73+
@Override
74+
public String getDirPath() {
75+
return hashedRootDirId;
76+
}
77+
}

hub/src/main/java/cloud/katta/protocols/hub/HubStorageLocationService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public UvfMetadataPayload toUvfMetadataPayload(final Path bucket) {
121121
.provider(this.getProfile())
122122
.defaultPath(bucket.getAbsolute())
123123
.region(this.getRegion())
124-
.nickname(null != bucket.attributes().getDisplayname() ? bucket.attributes().getDisplayname() : "Vault"))
124+
.nickname(bucket.attributes().getDisplayname()))
125125
.withAutomaticAccessGrant(new VaultMetadataJWEAutomaticAccessGrantDto()
126126
.enabled(true)
127127
.maxWotDepth(null));

hub/src/main/java/cloud/katta/protocols/hub/HubUVFVault.java

Lines changed: 31 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,19 @@
77
import ch.cyberduck.core.DisabledCancelCallback;
88
import ch.cyberduck.core.DisabledHostKeyCallback;
99
import ch.cyberduck.core.Host;
10-
import ch.cyberduck.core.HostUrlProvider;
1110
import ch.cyberduck.core.LoginCallback;
1211
import ch.cyberduck.core.PasswordCallback;
1312
import ch.cyberduck.core.Path;
14-
import ch.cyberduck.core.PathAttributes;
1513
import ch.cyberduck.core.Session;
16-
import ch.cyberduck.core.cryptomator.ContentWriter;
17-
import ch.cyberduck.core.cryptomator.UVFVault;
14+
import ch.cyberduck.core.cryptomator.AbstractVault;
15+
import ch.cyberduck.core.cryptomator.impl.uvf.CryptoVault;
1816
import ch.cyberduck.core.exception.BackgroundException;
1917
import ch.cyberduck.core.exception.InteroperabilityException;
2018
import ch.cyberduck.core.exception.UnsupportedException;
21-
import ch.cyberduck.core.features.AttributesFinder;
22-
import ch.cyberduck.core.features.Directory;
23-
import ch.cyberduck.core.features.Write;
2419
import ch.cyberduck.core.preferences.HostPreferencesFactory;
25-
import ch.cyberduck.core.preferences.PreferencesFactory;
2620
import ch.cyberduck.core.proxy.ProxyFactory;
2721
import ch.cyberduck.core.s3.S3Session;
28-
import ch.cyberduck.core.transfer.TransferStatus;
29-
import ch.cyberduck.core.vault.VaultCredentials;
22+
import ch.cyberduck.core.vault.VaultMetadataProvider;
3023
import ch.cyberduck.core.vault.VaultUnlockCancelException;
3124

3225
import org.apache.logging.log4j.LogManager;
@@ -35,7 +28,6 @@
3528

3629
import java.nio.charset.StandardCharsets;
3730
import java.util.Collections;
38-
import java.util.EnumSet;
3931
import java.util.UUID;
4032

4133
import cloud.katta.client.ApiException;
@@ -44,8 +36,7 @@
4436
import cloud.katta.client.model.VaultDto;
4537
import cloud.katta.core.DeviceSetupCallback;
4638
import cloud.katta.crypto.UserKeys;
47-
import cloud.katta.crypto.uvf.UvfMetadataPayload;
48-
import cloud.katta.crypto.uvf.UvfMetadataPayloadPasswordCallback;
39+
import cloud.katta.crypto.uvf.VaultIdMetadataUVFProvider;
4940
import cloud.katta.protocols.hub.exceptions.HubExceptionMappingService;
5041
import cloud.katta.protocols.s3.S3AssumeRoleProtocol;
5142
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -54,12 +45,9 @@
5445
/**
5546
* Unified vault format (UVF) implementation for Katta
5647
*/
57-
public class HubUVFVault extends UVFVault {
48+
public class HubUVFVault extends CryptoVault {
5849
private static final Logger log = LogManager.getLogger(HubUVFVault.class);
5950

60-
private final UUID vaultId;
61-
private final UvfMetadataPayload vaultMetadata;
62-
6351
/**
6452
* Storage connection only available after loading vault
6553
*/
@@ -68,17 +56,13 @@ public class HubUVFVault extends UVFVault {
6856

6957
/**
7058
*
71-
* @param storage Storage connection
72-
* @param vaultId Vault Id
73-
* @param vaultMetadata Vault UVF metadata
74-
* @param prompt Login prompt to access storage
59+
* @param storage Storage connection
60+
* @param bucket Vault UVF metadata
61+
* @param prompt Login prompt to access storage
7562
*/
76-
public HubUVFVault(final Session<?> storage, final UUID vaultId, final UvfMetadataPayload vaultMetadata, final LoginCallback prompt) {
77-
super(new Path(vaultMetadata.storage().getDefaultPath(), EnumSet.of(Path.Type.directory, Path.Type.volume),
78-
new PathAttributes().setDisplayname(vaultMetadata.storage().getNickname())));
63+
public HubUVFVault(final Session<?> storage, final Path bucket, final LoginCallback prompt) {
64+
super(bucket);
7965
this.storage = storage;
80-
this.vaultId = vaultId;
81-
this.vaultMetadata = vaultMetadata;
8266
this.login = prompt;
8367
}
8468

@@ -115,36 +99,32 @@ public synchronized void close() {
11599
}
116100

117101
@Override
118-
public Path create(final Session<?> session, final String region, final VaultCredentials noop) throws BackgroundException {
102+
public AbstractVault create(final Session<?> session, final String region, final VaultMetadataProvider metadata) throws BackgroundException {
119103
try {
120104
final HubSession hub = HubSession.coerce(session);
121-
log.debug("Created metadata JWE {}", vaultMetadata);
122-
final UvfMetadataPayload.UniversalVaultFormatJWKS jwks = UvfMetadataPayload.createKeys();
105+
final Path home = this.getHome();
106+
final UUID vaultId = VaultIdMetadataUVFProvider.cast(metadata).getVaultId();
123107
final VaultDto vaultDto = new VaultDto()
124108
.id(vaultId)
125-
.name(vaultMetadata.storage().getNickname())
109+
.name(home.attributes().getDisplayname())
126110
.description(null)
127111
.archived(false)
128112
.creationTime(DateTime.now())
129-
.uvfMetadataFile(vaultMetadata.encrypt(
130-
String.format("%s/api", new HostUrlProvider(false, true).get(session.getHost())),
131-
vaultId,
132-
jwks.toJWKSet()
133-
))
134-
.uvfKeySet(jwks.serializePublicRecoverykey());
113+
.uvfMetadataFile(new String(VaultIdMetadataUVFProvider.cast(metadata).getMetadata(), StandardCharsets.US_ASCII))
114+
.uvfKeySet(VaultIdMetadataUVFProvider.cast(metadata).getJwks().serializePublicRecoverykey());
135115
// Create vault in Hub
136116
final VaultResourceApi vaultResourceApi = new VaultResourceApi(hub.getClient());
137117
log.debug("Create vault {}", vaultDto);
138-
vaultResourceApi.apiVaultsVaultIdPut(vaultDto.getId(), vaultDto,
118+
vaultResourceApi.apiVaultsVaultIdPut(vaultId, vaultDto,
139119
storage.getHost().getProtocol().isRoleConfigurable() && !S3Session.isAwsHostname(storage.getHost().getHostname()),
140120
storage.getHost().getProtocol().isRoleConfigurable() && S3Session.isAwsHostname(storage.getHost().getHostname()));
141121
// Upload JWE
142122
log.debug("Grant access to vault {}", vaultDto);
143123
final UserDto userDto = hub.getMe();
144124
final DeviceSetupCallback setup = login.getFeature(DeviceSetupCallback.class);
145125
final UserKeys userKeys = hub.getUserKeys(setup);
146-
vaultResourceApi.apiVaultsVaultIdAccessTokensPost(vaultDto.getId(),
147-
Collections.singletonMap(userDto.getId(), jwks.toOwnerAccessToken().encryptForUser(userKeys.ecdhKeyPair().getPublic())));
126+
vaultResourceApi.apiVaultsVaultIdAccessTokensPost(vaultId,
127+
Collections.singletonMap(userDto.getId(), VaultIdMetadataUVFProvider.cast(metadata).getJwks().toOwnerAccessToken().encryptForUser(userKeys.ecdhKeyPair().getPublic())));
148128
// Upload vault template to storage
149129
log.debug("Connect to {}", storage);
150130
final Host configuration = storage.getHost();
@@ -156,36 +136,8 @@ public Path create(final Session<?> session, final String region, final VaultCre
156136
// No role chaining when creating vault
157137
configuration.setProperty(S3AssumeRoleProtocol.S3_ASSUMEROLE_ROLEARN_TAG, null);
158138
storage.open(ProxyFactory.get(), new DisabledHostKeyCallback(), login, new DisabledCancelCallback());
159-
final Path vault;
160-
if(false) {
161-
log.debug("Upload vault template to {}", storage);
162-
return super.create(storage,
163-
HubStorageLocationService.StorageLocation.fromIdentifier(region).getRegion(), noop);
164-
}
165-
else { // Obsolete when implemented in super
166-
final Directory<?> directory = (Directory<?>) storage._getFeature(Directory.class);
167-
final Path home = this.getHome();
168-
log.debug("Create vault root directory at {}", home);
169-
final TransferStatus status = (new TransferStatus()).setRegion(HubStorageLocationService.StorageLocation.fromIdentifier(region).getRegion());
170-
vault = directory.mkdir(storage._getFeature(Write.class), home, status);
171-
172-
final String hashedRootDirId = vaultMetadata.computeRootDirIdHash();
173-
final Path dataDir = new Path(vault, "d", EnumSet.of(Path.Type.directory));
174-
final Path firstLevel = new Path(dataDir, hashedRootDirId.substring(0, 2), EnumSet.of(Path.Type.directory));
175-
final Path secondLevel = new Path(firstLevel, hashedRootDirId.substring(2), EnumSet.of(Path.Type.directory));
176-
177-
directory.mkdir(storage._getFeature(Write.class), dataDir, status);
178-
directory.mkdir(storage._getFeature(Write.class), firstLevel, status);
179-
directory.mkdir(storage._getFeature(Write.class), secondLevel, status);
180-
181-
// vault.uvf
182-
new ContentWriter(storage).write(new Path(home, PreferencesFactory.get().getProperty("cryptomator.vault.config.filename"),
183-
EnumSet.of(Path.Type.file, Path.Type.vault)), vaultDto.getUvfMetadataFile().getBytes(StandardCharsets.US_ASCII));
184-
// dir.uvf
185-
new ContentWriter(storage).write(new Path(secondLevel, "dir.uvf", EnumSet.of(Path.Type.file)),
186-
vaultMetadata.computeRootDirUvf());
187-
}
188-
return vault;
139+
log.debug("Upload vault template to {}", storage);
140+
return super.create(storage, HubStorageLocationService.StorageLocation.fromIdentifier(region).getRegion(), metadata);
189141
}
190142
catch(JOSEException | JsonProcessingException e) {
191143
throw new InteroperabilityException(e.getMessage(), e);
@@ -203,34 +155,24 @@ public Path create(final Session<?> session, final String region, final VaultCre
203155
*/
204156
@Override
205157
public HubUVFVault load(final Session<?> session, final PasswordCallback prompt) throws BackgroundException {
158+
log.debug("Connect to {}", storage);
206159
try {
207-
log.debug("Connect to {}", storage);
208-
try {
209-
storage.open(ProxyFactory.get(), new DisabledHostKeyCallback(), login, new DisabledCancelCallback());
210-
}
211-
catch(BackgroundException e) {
212-
log.warn("Skip loading vault with failure {} connecting to storage", e.toString());
213-
throw new VaultUnlockCancelException(this, e);
214-
}
215-
final Path home = this.getHome();
216-
home.setAttributes(storage.getFeature(AttributesFinder.class).find(home)
217-
.setDisplayname(vaultMetadata.storage().getNickname()));
218-
log.debug("Initialize vault {} with metadata {}", this, vaultMetadata);
219-
// Initialize cryptors
220-
super.load(storage, new UvfMetadataPayloadPasswordCallback(vaultMetadata.toJSON()));
221-
return this;
160+
storage.open(ProxyFactory.get(), new DisabledHostKeyCallback(), login, new DisabledCancelCallback());
222161
}
223-
catch(JsonProcessingException e) {
224-
throw new InteroperabilityException(e.getMessage(), e);
162+
catch(BackgroundException e) {
163+
log.warn("Skip loading vault with failure {} connecting to storage", e.toString());
164+
throw new VaultUnlockCancelException(this, e);
225165
}
166+
log.debug("Initialize vault {}", this);
167+
// Initialize cryptors
168+
super.load(storage, prompt);
169+
return this;
226170
}
227171

228172
@Override
229173
public String toString() {
230174
final StringBuilder sb = new StringBuilder("HubUVFVault{");
231-
sb.append("vaultId=").append(vaultId);
232-
sb.append(", vaultMetadata=").append(vaultMetadata);
233-
sb.append(", storage=").append(storage);
175+
sb.append("storage=").append(storage);
234176
sb.append('}');
235177
return sb.toString();
236178
}

hub/src/main/java/cloud/katta/protocols/hub/HubVaultListService.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import ch.cyberduck.core.LocaleFactory;
1111
import ch.cyberduck.core.LoginCallback;
1212
import ch.cyberduck.core.Path;
13+
import ch.cyberduck.core.PathAttributes;
1314
import ch.cyberduck.core.Session;
1415
import ch.cyberduck.core.exception.AccessDeniedException;
1516
import ch.cyberduck.core.exception.BackgroundException;
@@ -22,16 +23,19 @@
2223
import org.apache.logging.log4j.Logger;
2324

2425
import java.text.MessageFormat;
26+
import java.util.EnumSet;
2527

2628
import cloud.katta.client.ApiException;
2729
import cloud.katta.client.api.VaultResourceApi;
2830
import cloud.katta.client.model.VaultDto;
2931
import cloud.katta.core.DeviceSetupCallback;
3032
import cloud.katta.crypto.uvf.UvfMetadataPayload;
33+
import cloud.katta.crypto.uvf.UvfMetadataPayloadPasswordCallback;
3134
import cloud.katta.protocols.hub.exceptions.HubExceptionMappingService;
3235
import cloud.katta.workflows.VaultServiceImpl;
3336
import cloud.katta.workflows.exceptions.AccessException;
3437
import cloud.katta.workflows.exceptions.SecurityFailure;
38+
import com.fasterxml.jackson.core.JsonProcessingException;
3539

3640
public class HubVaultListService implements ListService {
3741
private static final Logger log = LogManager.getLogger(HubVaultListService.class);
@@ -62,15 +66,21 @@ public AttributedList<Path> list(final Path directory, final ListProgressListene
6266
final DeviceSetupCallback setup = prompt.getFeature(DeviceSetupCallback.class);
6367
final UvfMetadataPayload vaultMetadata = vaultService.getVaultMetadataJWE(vaultDto.getId(), session.getUserKeys(setup));
6468
final Session<?> storage = vaultService.getVaultStorageSession(session, vaultDto.getId(), vaultMetadata);
65-
final HubUVFVault vault = new HubUVFVault(storage, vaultDto.getId(), vaultMetadata, prompt);
69+
final Path bucket = new Path(vaultMetadata.storage().getDefaultPath(), EnumSet.of(Path.Type.directory, Path.Type.volume),
70+
new PathAttributes().setDisplayname(vaultMetadata.storage().getNickname()));
6671
try {
67-
registry.add(vault.load(session, prompt));
72+
final HubUVFVault vault = new HubUVFVault(storage, bucket, prompt).load(session, new UvfMetadataPayloadPasswordCallback(vaultMetadata.toJSON()));
73+
log.info("Loaded vault {}", vault);
74+
registry.add(vault);
6875
vaults.add(vault.getHome());
6976
listener.chunk(directory, vaults);
7077
}
7178
catch(VaultUnlockCancelException e) {
7279
log.warn("Skip vault {} with failure {} loading", vaultDto, e);
7380
}
81+
catch(JsonProcessingException e) {
82+
throw new SecurityFailure(e);
83+
}
7484
}
7585
catch(ApiException e) {
7686
if(HttpStatus.SC_FORBIDDEN == e.getCode()) {

hub/src/test/java/cloud/katta/crypto/uvf/UvfMetadataPayloadTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import ch.cyberduck.core.AlphanumericRandomStringService;
99
import ch.cyberduck.core.Host;
1010
import ch.cyberduck.core.Path;
11-
import ch.cyberduck.core.cryptomator.UVFVault;
11+
import ch.cyberduck.core.cryptomator.impl.uvf.CryptoVault;
1212
import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider;
1313
import ch.cyberduck.core.exception.BackgroundException;
1414
import ch.cyberduck.core.ssl.DefaultX509KeyManager;
@@ -171,7 +171,7 @@ void testUVFMasterkeyFromUvfMetadataPayload() throws JsonProcessingException {
171171
@Test
172172
void testUvfVaultLoadFromMetadataPayload() throws JsonProcessingException, BackgroundException {
173173
final UvfMetadataPayload uvfMetadataPayload = UvfMetadataPayload.create();
174-
final UVFVault uvfVault = new UVFVault(new Path("/", EnumSet.of(AbstractPath.Type.directory)));
174+
final CryptoVault uvfVault = new CryptoVault(new Path("/", EnumSet.of(AbstractPath.Type.directory)));
175175
uvfVault.load(new HubSession(new Host(new HubProtocol()), new DisabledX509TrustManager(), new DefaultX509KeyManager()),
176176
new UvfMetadataPayloadPasswordCallback(uvfMetadataPayload));
177177
}

0 commit comments

Comments
 (0)