Skip to content

Commit 1665a2f

Browse files
ylangiscdkocher
authored andcommitted
Add UVF directory feature.
1 parent 4d4af58 commit 1665a2f

File tree

2 files changed

+127
-1
lines changed

2 files changed

+127
-1
lines changed

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323
import ch.cyberduck.core.Permission;
2424
import ch.cyberduck.core.Session;
2525
import ch.cyberduck.core.SimplePathPredicate;
26+
import ch.cyberduck.core.cryptomator.features.CryptoDirectoryUVFFeature;
2627
import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryUVFProvider;
2728
import ch.cyberduck.core.cryptomator.impl.CryptoFilenameV7Provider;
2829
import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider;
2930
import ch.cyberduck.core.exception.BackgroundException;
31+
import ch.cyberduck.core.features.Directory;
32+
import ch.cyberduck.core.features.Write;
3033
import ch.cyberduck.core.vault.VaultCredentials;
3134

3235
import org.apache.logging.log4j.LogManager;
@@ -193,7 +196,7 @@ public Path encrypt(Session<?> session, Path file, boolean metadata) throws Back
193196
if(file.getType().contains(Path.Type.encrypted)) {
194197
final Path decrypted = file.attributes().getDecrypted();
195198
parent = this.getDirectoryProvider().toEncrypted(session, decrypted.getParent());
196-
filename = this.getDirectoryProvider().toEncrypted(session, parent, decrypted.getName(), decrypted.getType());
199+
filename = this.getDirectoryProvider().toEncrypted(session, decrypted.getParent(), decrypted.getName(), decrypted.getType());
197200
}
198201
else {
199202
parent = this.getDirectoryProvider().toEncrypted(session, file.getParent());
@@ -361,6 +364,17 @@ public byte[] getRootDirId() {
361364
return rootDirId;
362365
}
363366

367+
@Override
368+
public <T> T getFeature(final Session<?> session, final Class<T> type, final T delegate) {
369+
370+
if(type == Directory.class) {
371+
return (T) new CryptoDirectoryUVFFeature(session, (Directory) delegate, session._getFeature(Write.class), this
372+
);
373+
}
374+
375+
return super.getFeature(session, type, delegate);
376+
}
377+
364378
@Override
365379
public boolean equals(final Object o) {
366380
if(this == o) {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package ch.cyberduck.core.cryptomator.features;
2+
3+
/*
4+
* Copyright (c) 2002-2025 iterate GmbH. All rights reserved.
5+
* https://cyberduck.io/
6+
*
7+
* This program is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*/
17+
18+
import ch.cyberduck.core.Path;
19+
import ch.cyberduck.core.Session;
20+
import ch.cyberduck.core.cryptomator.AbstractVault;
21+
import ch.cyberduck.core.cryptomator.ContentWriter;
22+
import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator;
23+
import ch.cyberduck.core.exception.BackgroundException;
24+
import ch.cyberduck.core.features.Directory;
25+
import ch.cyberduck.core.features.Find;
26+
import ch.cyberduck.core.features.Write;
27+
import ch.cyberduck.core.transfer.TransferStatus;
28+
29+
import org.apache.logging.log4j.LogManager;
30+
import org.apache.logging.log4j.Logger;
31+
import org.cryptomator.cryptolib.api.FileHeader;
32+
33+
import java.nio.ByteBuffer;
34+
import java.util.EnumSet;
35+
36+
public class CryptoDirectoryUVFFeature<Reply> extends CryptoDirectoryV7Feature<Reply> {
37+
private static final Logger log = LogManager.getLogger(CryptoDirectoryUVFFeature.class);
38+
39+
private final Session<?> session;
40+
private final Write<Reply> writer;
41+
private final Directory<Reply> delegate;
42+
private final AbstractVault vault;
43+
44+
public CryptoDirectoryUVFFeature(final Session<?> session, final Directory<Reply> delegate,
45+
final Write<Reply> writer, final AbstractVault vault) {
46+
super(session, delegate, writer, vault);
47+
this.session = session;
48+
this.writer = writer;
49+
this.delegate = delegate;
50+
this.vault = vault;
51+
}
52+
53+
@Override
54+
public Path mkdir(final Path folder, final TransferStatus status) throws BackgroundException {
55+
final byte[] directoryId = vault.getDirectoryProvider().createDirectoryId(folder);
56+
// Create metadata file for directory
57+
final Path directoryMetadataFolder = session._getFeature(Directory.class).mkdir(vault.encrypt(session, folder, true),
58+
new TransferStatus().setRegion(status.getRegion()));
59+
final Path directoryMetadataFile = new Path(directoryMetadataFolder,
60+
vault.getDirectoryMetadataFilename(),
61+
EnumSet.of(Path.Type.file));
62+
log.debug("Write metadata {} for folder {}", directoryMetadataFile, folder);
63+
new ContentWriter(session).write(directoryMetadataFile, this.encryptDirectoryMetadataWithCurrentRevision(directoryId));
64+
final Path encrypt = vault.encrypt(session, folder, false);
65+
final Path intermediate = encrypt.getParent();
66+
if(!session._getFeature(Find.class).find(intermediate)) {
67+
session._getFeature(Directory.class).mkdir(intermediate, new TransferStatus().setRegion(status.getRegion()));
68+
}
69+
// Write metadata
70+
final FileHeader header = vault.getFileHeaderCryptor().create();
71+
status.setHeader(vault.getFileHeaderCryptor().encryptHeader(header));
72+
status.setNonces(new RandomNonceGenerator(vault.getNonceSize()));
73+
final Path target = delegate.withWriter(new CryptoWriteFeature<>(session, writer, vault)).mkdir(encrypt, status);
74+
//TODO kopie von dir.uvf auch hier noch anlegen
75+
final Path recoveryDirectoryMetadataFile = new Path(target,
76+
vault.getDirectoryMetadataFilename(),
77+
EnumSet.of(Path.Type.file));
78+
log.debug("Write recovery metadata {} for folder {}", recoveryDirectoryMetadataFile, folder);
79+
new ContentWriter(session).write(directoryMetadataFile, this.encryptDirectoryMetadataWithCurrentRevision(directoryId));
80+
// Implementation may return new copy of attributes without encryption attributes
81+
82+
target.attributes().setDirectoryId(directoryId);
83+
target.attributes().setDecrypted(folder);
84+
// Make reference of encrypted path in attributes of decrypted file point to metadata file
85+
final Path decrypt = vault.decrypt(session, vault.encrypt(session, target, true));
86+
decrypt.attributes().setFileId(directoryMetadataFolder.attributes().getFileId());
87+
decrypt.attributes().setVersionId(directoryMetadataFolder.attributes().getVersionId());
88+
return decrypt;
89+
}
90+
91+
// TODO replace with DirectoryContentCryptor#encryptDirectoryMetadata once we have access to dirId
92+
private byte[] encryptDirectoryMetadataWithCurrentRevision(final byte[] dirId) {
93+
final ByteBuffer cleartextBuf = ByteBuffer.wrap(dirId);
94+
final FileHeader header = vault.getCryptor().fileHeaderCryptor().create();
95+
final ByteBuffer headerBuf = vault.getCryptor().fileHeaderCryptor().encryptHeader(header);
96+
final ByteBuffer contentBuf = vault.getCryptor().fileContentCryptor().encryptChunk(cleartextBuf, 0, header);
97+
final byte[] result = new byte[headerBuf.remaining() + contentBuf.remaining()];
98+
headerBuf.get(result, 0, headerBuf.remaining());
99+
contentBuf.get(result, headerBuf.limit(), contentBuf.remaining());
100+
return result;
101+
}
102+
103+
@Override
104+
public String toString() {
105+
final StringBuilder sb = new StringBuilder("CryptoDirectoryUVFFeature{");
106+
sb.append("proxy=").append(delegate);
107+
sb.append('}');
108+
return sb.toString();
109+
}
110+
111+
112+
}

0 commit comments

Comments
 (0)