|
| 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