Skip to content

Commit db6ec89

Browse files
ylangiscchenkins
authored andcommitted
More extraction.
1 parent 909981f commit db6ec89

File tree

3 files changed

+220
-225
lines changed

3 files changed

+220
-225
lines changed

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

Lines changed: 203 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717

1818
import ch.cyberduck.core.ListService;
1919
import ch.cyberduck.core.Path;
20+
import ch.cyberduck.core.PathAttributes;
21+
import ch.cyberduck.core.Permission;
2022
import ch.cyberduck.core.Session;
23+
import ch.cyberduck.core.SimplePathPredicate;
2124
import ch.cyberduck.core.UrlProvider;
2225
import ch.cyberduck.core.cryptomator.features.*;
2326
import ch.cyberduck.core.exception.BackgroundException;
@@ -26,19 +29,38 @@
2629
import ch.cyberduck.core.shared.DefaultTouchFeature;
2730
import ch.cyberduck.core.transfer.TransferStatus;
2831

32+
import org.apache.logging.log4j.LogManager;
33+
import org.apache.logging.log4j.Logger;
34+
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
2935
import org.cryptomator.cryptolib.api.Cryptor;
3036
import org.cryptomator.cryptolib.api.FileContentCryptor;
3137
import org.cryptomator.cryptolib.api.FileHeaderCryptor;
3238

39+
import java.nio.charset.StandardCharsets;
40+
import java.util.EnumSet;
41+
import java.util.regex.Matcher;
42+
import java.util.regex.Pattern;
43+
44+
import com.google.common.io.BaseEncoding;
45+
3346
public abstract class AbstractVault implements Vault {
3447

48+
private static final Logger log = LogManager.getLogger(AbstractVault.class);
49+
3550
public static final int VAULT_VERSION_DEPRECATED = 6;
3651
public static final int VAULT_VERSION = PreferencesFactory.get().getInteger("cryptomator.vault.version");
3752

53+
public static final String DIR_PREFIX = "0";
54+
55+
private static final Pattern BASE32_PATTERN = Pattern.compile("^0?(([A-Z2-7]{8})*[A-Z2-7=]{8})");
56+
private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+).c9r");
57+
3858
public abstract Path getMasterkey();
3959

4060
public abstract Path getConfig();
4161

62+
public abstract Path gethHome();
63+
4264
public abstract int getVersion();
4365

4466
public abstract FileHeaderCryptor getFileHeaderCryptor();
@@ -82,6 +104,26 @@ public long toCleartextSize(final long cleartextFileOffset, final long ciphertex
82104
}
83105
}
84106

107+
@Override
108+
public State getState() {
109+
return this.isUnlocked() ? State.open : State.closed;
110+
}
111+
112+
@Override
113+
public long toCiphertextSize(final long cleartextFileOffset, final long cleartextFileSize) {
114+
if(TransferStatus.UNKNOWN_LENGTH == cleartextFileSize) {
115+
return TransferStatus.UNKNOWN_LENGTH;
116+
}
117+
final int headerSize;
118+
if(0L == cleartextFileOffset) {
119+
headerSize = this.getCryptor().fileHeaderCryptor().headerSize();
120+
}
121+
else {
122+
headerSize = 0;
123+
}
124+
return headerSize + this.getCryptor().fileContentCryptor().ciphertextSize(cleartextFileSize);
125+
}
126+
85127
@Override
86128
public Path encrypt(Session<?> session, Path file) throws BackgroundException {
87129
return this.encrypt(session, file, file.attributes().getDirectoryId(), false);
@@ -92,12 +134,172 @@ public Path encrypt(Session<?> session, Path file, boolean metadata) throws Back
92134
return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata);
93135
}
94136

95-
public abstract Path encrypt(Session<?> session, Path file, String directoryId, boolean metadata) throws BackgroundException;
137+
public Path encrypt(Session<?> session, Path file, String directoryId, boolean metadata) throws BackgroundException {
138+
final Path encrypted;
139+
if(file.isFile() || metadata) {
140+
if(file.getType().contains(Path.Type.vault)) {
141+
log.warn("Skip file {} because it is marked as an internal vault path", file);
142+
return file;
143+
}
144+
if(new SimplePathPredicate(file).test(this.gethHome())) {
145+
log.warn("Skip vault home {} because the root has no metadata file", file);
146+
return file;
147+
}
148+
final Path parent;
149+
final String filename;
150+
if(file.getType().contains(Path.Type.encrypted)) {
151+
final Path decrypted = file.attributes().getDecrypted();
152+
parent = this.getDirectoryProvider().toEncrypted(session, decrypted.getParent().attributes().getDirectoryId(), decrypted.getParent());
153+
filename = this.getDirectoryProvider().toEncrypted(session, parent.attributes().getDirectoryId(), decrypted.getName(), decrypted.getType());
154+
}
155+
else {
156+
parent = this.getDirectoryProvider().toEncrypted(session, file.getParent().attributes().getDirectoryId(), file.getParent());
157+
filename = this.getDirectoryProvider().toEncrypted(session, parent.attributes().getDirectoryId(), file.getName(), file.getType());
158+
}
159+
final PathAttributes attributes = new PathAttributes(file.attributes());
160+
attributes.setDirectoryId(null);
161+
if(!file.isFile() && !metadata) {
162+
// The directory is different from the metadata file used to resolve the actual folder
163+
attributes.setVersionId(null);
164+
attributes.setFileId(null);
165+
}
166+
// Translate file size
167+
attributes.setSize(this.toCiphertextSize(0L, file.attributes().getSize()));
168+
final EnumSet<Path.Type> type = EnumSet.copyOf(file.getType());
169+
if(metadata && this.getVersion() == VAULT_VERSION_DEPRECATED) {
170+
type.remove(Path.Type.directory);
171+
type.add(Path.Type.file);
172+
}
173+
type.remove(Path.Type.decrypted);
174+
type.add(Path.Type.encrypted);
175+
encrypted = new Path(parent, filename, type, attributes);
176+
}
177+
else {
178+
if(file.getType().contains(Path.Type.encrypted)) {
179+
log.warn("Skip file {} because it is already marked as an encrypted path", file);
180+
return file;
181+
}
182+
if(file.getType().contains(Path.Type.vault)) {
183+
return this.getDirectoryProvider().toEncrypted(session, this.gethHome().attributes().getDirectoryId(), this.gethHome());
184+
}
185+
encrypted = this.getDirectoryProvider().toEncrypted(session, directoryId, file);
186+
}
187+
// Add reference to decrypted file
188+
if(!file.getType().contains(Path.Type.encrypted)) {
189+
encrypted.attributes().setDecrypted(file);
190+
}
191+
// Add reference for vault
192+
file.attributes().setVault(this.gethHome());
193+
encrypted.attributes().setVault(this.gethHome());
194+
return encrypted;
195+
}
196+
197+
@Override
198+
public Path decrypt(final Session<?> session, final Path file) throws BackgroundException {
199+
if(file.getType().contains(Path.Type.decrypted)) {
200+
log.warn("Skip file {} because it is already marked as an decrypted path", file);
201+
return file;
202+
}
203+
if(file.getType().contains(Path.Type.vault)) {
204+
log.warn("Skip file {} because it is marked as an internal vault path", file);
205+
return file;
206+
}
207+
final Path inflated = this.inflate(session, file);
208+
final Pattern pattern = this.getVersion() == VAULT_VERSION_DEPRECATED ? BASE32_PATTERN : BASE64URL_PATTERN;
209+
final Matcher m = pattern.matcher(inflated.getName());
210+
if(m.matches()) {
211+
final String ciphertext = m.group(1);
212+
try {
213+
final String cleartextFilename = this.getFileNameCryptor().decryptFilename(
214+
this.getVersion() == VAULT_VERSION_DEPRECATED ? BaseEncoding.base32() : BaseEncoding.base64Url(),
215+
ciphertext, file.getParent().attributes().getDirectoryId().getBytes(StandardCharsets.UTF_8));
216+
final PathAttributes attributes = new PathAttributes(file.attributes());
217+
if(this.isDirectory(inflated)) {
218+
if(Permission.EMPTY != attributes.getPermission()) {
219+
final Permission permission = new Permission(attributes.getPermission());
220+
permission.setUser(permission.getUser().or(Permission.Action.execute));
221+
permission.setGroup(permission.getGroup().or(Permission.Action.execute));
222+
permission.setOther(permission.getOther().or(Permission.Action.execute));
223+
attributes.setPermission(permission);
224+
}
225+
// Reset size for folders
226+
attributes.setSize(-1L);
227+
attributes.setVersionId(null);
228+
attributes.setFileId(null);
229+
}
230+
else {
231+
// Translate file size
232+
attributes.setSize(this.toCleartextSize(0L, file.attributes().getSize()));
233+
}
234+
// Add reference to encrypted file
235+
attributes.setEncrypted(file);
236+
// Add reference for vault
237+
attributes.setVault(this.gethHome());
238+
final EnumSet<Path.Type> type = EnumSet.copyOf(file.getType());
239+
type.remove(this.isDirectory(inflated) ? Path.Type.file : Path.Type.directory);
240+
type.add(this.isDirectory(inflated) ? Path.Type.directory : Path.Type.file);
241+
type.remove(Path.Type.encrypted);
242+
type.add(Path.Type.decrypted);
243+
final Path decrypted = new Path(file.getParent().attributes().getDecrypted(), cleartextFilename, type, attributes);
244+
if(type.contains(Path.Type.symboliclink)) {
245+
decrypted.setSymlinkTarget(file.getSymlinkTarget());
246+
}
247+
return decrypted;
248+
}
249+
catch(AuthenticationFailedException e) {
250+
throw new CryptoAuthenticationException(
251+
"Failure to decrypt due to an unauthentic ciphertext", e);
252+
}
253+
}
254+
else {
255+
throw new CryptoFilenameMismatchException(
256+
String.format("Failure to decrypt %s due to missing pattern match for %s", inflated.getName(), pattern));
257+
}
258+
}
259+
260+
private boolean isDirectory(final Path p) {
261+
if(this.getVersion() == VAULT_VERSION_DEPRECATED) {
262+
return p.getName().startsWith(DIR_PREFIX);
263+
}
264+
return p.isDirectory();
265+
}
266+
267+
private Path inflate(final Session<?> session, final Path file) throws BackgroundException {
268+
final String fileName = file.getName();
269+
if(this.getFilenameProvider().isDeflated(fileName)) {
270+
final String filename = this.getFilenameProvider().inflate(session, fileName);
271+
return new Path(file.getParent(), filename, EnumSet.of(Path.Type.file), file.attributes());
272+
}
273+
return file;
274+
}
96275

97276
public synchronized boolean isUnlocked() {
98277
return this.getCryptor() != null;
99278
}
100279

280+
@Override
281+
public boolean contains(final Path file) {
282+
if(this.isUnlocked()) {
283+
return new SimplePathPredicate(file).test(this.gethHome()) || file.isChild(this.getHome());
284+
}
285+
return false;
286+
}
287+
288+
@Override
289+
public synchronized void close() {
290+
if(this.isUnlocked()) {
291+
if(this.getCryptor() != null) {
292+
getCryptor().destroy();
293+
}
294+
if(this.getDirectoryProvider() != null) {
295+
this.getDirectoryProvider().destroy();
296+
}
297+
if(this.getFilenameProvider() != null) {
298+
this.getFilenameProvider().destroy();
299+
}
300+
}
301+
}
302+
101303
@Override
102304
@SuppressWarnings("unchecked")
103305
public <T> T getFeature(final Session<?> session, final Class<T> type, final T delegate) {

0 commit comments

Comments
 (0)