From 698393b617792fa727bb1dbcc5d0b1eefb21876b Mon Sep 17 00:00:00 2001 From: AssahBismarkabah Date: Tue, 24 Sep 2024 17:49:19 +0100 Subject: [PATCH] example with partialpathEncryption and doc --- .../filesystem/PartialPathEncryptionTest.java | 95 ++++++++++++++ ...PathEncryptionOverriddenWithMovedFile.java | 116 ++++++++++++++++++ docs/readme/partial-path-encryption.md | 77 ++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/PartialPathEncryptionTest.java create mode 100644 datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/TestPathEncryptionOverriddenWithMovedFile.java create mode 100644 docs/readme/partial-path-encryption.md diff --git a/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/PartialPathEncryptionTest.java b/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/PartialPathEncryptionTest.java new file mode 100644 index 000000000..dc2b55c55 --- /dev/null +++ b/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/PartialPathEncryptionTest.java @@ -0,0 +1,95 @@ +package de.adorsys.datasafe.examples.business.filesystem; + +import de.adorsys.datasafe.business.impl.service.DaggerDefaultDatasafeServices; +import de.adorsys.datasafe.business.impl.service.DefaultDatasafeServices; +import de.adorsys.datasafe.directory.impl.profile.config.DefaultDFSConfig; +import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth; +import de.adorsys.datasafe.encrypiton.impl.pathencryption.PathEncryptionImpl; +import de.adorsys.datasafe.encrypiton.impl.pathencryption.PathEncryptionImplRuntimeDelegatable; +import de.adorsys.datasafe.storage.impl.fs.FileSystemStorageService; +import de.adorsys.datasafe.types.api.actions.ListRequest; +import de.adorsys.datasafe.types.api.actions.ReadRequest; +import de.adorsys.datasafe.types.api.actions.WriteRequest; +import de.adorsys.datasafe.types.api.context.BaseOverridesRegistry; +import de.adorsys.datasafe.types.api.context.overrides.OverridesRegistry; +import de.adorsys.datasafe.types.api.resource.Uri; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Function; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class PartialPathEncryptionTest { + + @Test + @SneakyThrows + void testPathEncryptionOverridden(@TempDir Path root) { + // BEGIN_SNIPPET:Create overridable Datasafe services without recompilation + // This shows how to override path encryption service, in particular we are going to disable it + OverridesRegistry registry = new BaseOverridesRegistry(); + + // PathEncryptionImpl now will have completely different functionality + // instead of calling PathEncryptionImpl methods we will call PathEncryptionImplOverridden methods + PathEncryptionImplRuntimeDelegatable.overrideWith(registry, PathEncryptionImplOverridden::new); + + // Customized service, without creating complete module and building it: + DefaultDatasafeServices datasafeServices = DaggerDefaultDatasafeServices.builder() + .config(new DefaultDFSConfig(root.toAbsolutePath().toUri(), "secret"::toCharArray)) + .storage(new FileSystemStorageService(root)) + .overridesRegistry(registry) + .build(); + + // registering user + UserIDAuth user = new UserIDAuth("user", "passwrd"::toCharArray); + datasafeServices.userProfile().registerUsingDefaults(user); + // writing into user privatespace, note that with default implementation `file.txt` would be encrypted + OutputStream os = datasafeServices.privateService().write(WriteRequest.forDefaultPrivate(user, "folder/file.txt")); + os.write("HELLO".getBytes()); + os.close(); + // we can read file by its path + assertThat(datasafeServices.privateService().read(ReadRequest.forDefaultPrivate(user, "folder/file.txt"))).hasContent("HELLO"); + // we can list file + assertThat(datasafeServices.privateService().list(ListRequest.forDefaultPrivate(user, "folder/"))) + .extracting(it -> it.getResource().asPrivate().decryptedPath().asString()) + .contains("folder/file.txt"); + // but we see raw folder name here: + assertThat(Files.walk(root)).asString().contains("folder"); + // but filename is encrypted: + assertThat(Files.walk(root)).asString().doesNotContain("file.txt"); + // END_SNIPPET + } + + + // Path encryption that encrypts only the part after the first segment + static class PathEncryptionImplOverridden extends PathEncryptionImpl { + PathEncryptionImplOverridden(PathEncryptionImplRuntimeDelegatable.ArgumentsCaptor captor) { + super(captor.getSymmetricPathEncryptionService(), captor.getPrivateKeyService()); + } + + @Override + public Uri encrypt(UserIDAuth forUser, Uri path) { + if (path.asString().contains("/")) { + String[] rootAndInRoot = path.asString().split("/", 2); + return new Uri(rootAndInRoot[0] + "/" + super.encrypt(forUser, new Uri(rootAndInRoot[1])).asString()); + } + return path; + } + + @Override + public Function decryptor(UserIDAuth forUser) { + return rootWithEncrypted -> { + if (rootWithEncrypted.asString().contains("/")) { + String[] rootAndInRoot = rootWithEncrypted.asString().split("/", 2); + return new Uri(rootAndInRoot[0] + "/" + super.decryptor(forUser).apply(new Uri(rootAndInRoot[1])).asString()); + } + return rootWithEncrypted; + }; + } + } +} \ No newline at end of file diff --git a/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/TestPathEncryptionOverriddenWithMovedFile.java b/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/TestPathEncryptionOverriddenWithMovedFile.java new file mode 100644 index 000000000..1f1a0d3bd --- /dev/null +++ b/datasafe-examples/datasafe-examples-business/src/test/java/de/adorsys/datasafe/examples/business/filesystem/TestPathEncryptionOverriddenWithMovedFile.java @@ -0,0 +1,116 @@ +package de.adorsys.datasafe.examples.business.filesystem; + +import de.adorsys.datasafe.business.impl.service.DaggerDefaultDatasafeServices; +import de.adorsys.datasafe.business.impl.service.DefaultDatasafeServices; +import de.adorsys.datasafe.directory.impl.profile.config.DefaultDFSConfig; +import de.adorsys.datasafe.encrypiton.api.types.UserIDAuth; +import de.adorsys.datasafe.encrypiton.impl.pathencryption.PathEncryptionImpl; +import de.adorsys.datasafe.encrypiton.impl.pathencryption.PathEncryptionImplRuntimeDelegatable; +import de.adorsys.datasafe.storage.impl.fs.FileSystemStorageService; +import de.adorsys.datasafe.types.api.actions.ListRequest; +import de.adorsys.datasafe.types.api.actions.ReadRequest; +import de.adorsys.datasafe.types.api.actions.WriteRequest; +import de.adorsys.datasafe.types.api.context.BaseOverridesRegistry; +import de.adorsys.datasafe.types.api.context.overrides.OverridesRegistry; +import de.adorsys.datasafe.types.api.resource.Uri; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.OutputStream; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Function; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class TestPathEncryptionOverriddenWithMovedFile { + + @Test + @SneakyThrows + void testPathEncryptionOverridden(@TempDir Path root) { + // BEGIN_SNIPPET:Create overridable Datasafe services without recompilation + // This shows how to override path encryption service, in particular we are going to disable it + OverridesRegistry registry = new BaseOverridesRegistry(); + + // PathEncryptionImpl now will have completely different functionality + // instead of calling PathEncryptionImpl methods we will call PathEncryptionImplOverridden methods + PathEncryptionImplRuntimeDelegatable.overrideWith(registry, PathEncryptionImplOverridden::new); + + // registering user + UserIDAuth user = new UserIDAuth("user", "passwrd"::toCharArray); + + DefaultDatasafeServices defaultDatasafeServices = DaggerDefaultDatasafeServices.builder() + .config(new DefaultDFSConfig(root.toAbsolutePath().toUri(), "secret"::toCharArray)) + .storage(new FileSystemStorageService(root)) + .build(); + + defaultDatasafeServices.userProfile().registerUsingDefaults(user); + OutputStream defOs = defaultDatasafeServices.privateService().write(WriteRequest.forDefaultPrivate(user, "file-old.txt")); + defOs.write("HELLO".getBytes(UTF_8)); + defOs.close(); + Path file = Files.walk(root.resolve("users/user/private/files/SIV/")).filter(it -> !it.toFile().isDirectory()).findFirst().get(); + + root.resolve("users/user/private/files/folder/SIV").toFile().mkdirs(); + Files.move(file, root.resolve("users/user/private/files/folder/SIV").resolve(file.getFileName())); + root.resolve("users/user/private/files/SIV").toFile().delete(); + + // Customized service, without creating complete module and building it: + DefaultDatasafeServices datasafeServices = DaggerDefaultDatasafeServices.builder() + .config(new DefaultDFSConfig(root.toAbsolutePath().toUri(), "secret"::toCharArray)) + .storage(new FileSystemStorageService(root)) + .overridesRegistry(registry) + .build(); + + + datasafeServices.userProfile().registerUsingDefaults(user); + // writing into user privatespace, note that with default implementation `file.txt` would be encrypted + OutputStream os = datasafeServices.privateService().write(WriteRequest.forDefaultPrivate(user, "folder/file.txt")); + os.write("HELLO".getBytes()); + os.close(); + // we can read file by its path + assertThat(datasafeServices.privateService().read(ReadRequest.forDefaultPrivate(user, "folder/file.txt"))).hasContent("HELLO"); + assertThat(datasafeServices.privateService().read(ReadRequest.forDefaultPrivate(user, "folder/file-old.txt"))).hasContent("HELLO"); + // we can list file + assertThat(datasafeServices.privateService().list(ListRequest.forDefaultPrivate(user, "folder/"))) + .extracting(it -> it.getResource().asPrivate().decryptedPath().asString()) + .contains("folder/file.txt", "folder/file-old.txt"); + // but we see raw folder name here: + assertThat(Files.walk(root)).asString().contains("folder"); + // but filename is encrypted: + assertThat(Files.walk(root)).asString().doesNotContain("file.txt"); + // END_SNIPPET + } + + // Path encryption that does not encrypt paths + class PathEncryptionImplOverridden extends PathEncryptionImpl { + + PathEncryptionImplOverridden(PathEncryptionImplRuntimeDelegatable.ArgumentsCaptor captor) { + super(captor.getSymmetricPathEncryptionService(), captor.getPrivateKeyService()); + } + + @Override + public Uri encrypt(UserIDAuth forUser, Uri path) { + if (path.asString().contains("/")) { + String[] rootAndInRoot = path.asString().split("/", 2); + return new Uri(URI.create(rootAndInRoot[0] + "/" + super.encrypt(forUser, new Uri(rootAndInRoot[1])).asString())); + } + // encryption disabled for root folder: + return path; + } + + @Override + public Function decryptor(UserIDAuth forUser) { + return rootWithEncrypted -> { + if (rootWithEncrypted.asString().contains("/")) { + String[] rootAndInRoot = rootWithEncrypted.asString().split("/", 2); + return new Uri(rootAndInRoot[0] + "/" + super.decryptor(forUser).apply(new Uri(URI.create(rootAndInRoot[1]))).asString()); + } + // encryption disabled for root folder: + return rootWithEncrypted; + }; + } + } +} diff --git a/docs/readme/partial-path-encryption.md b/docs/readme/partial-path-encryption.md new file mode 100644 index 000000000..f77e9e536 --- /dev/null +++ b/docs/readme/partial-path-encryption.md @@ -0,0 +1,77 @@ +### Datasafe Partial Path Encryption + +### Overview +Partial path encryption allows encrypting only specific parts of a file path while keeping other parts unencrypted. This feature is useful when you want to maintain some readable structure in your storage while still protecting sensitive information. + +#### How It Works +1. The `PathEncryptionImplOverridden` class extends `PathEncryptionImpl` to provide custom encryption logic. +2. In the `encrypt` method: + - If the path contains a "/", it splits the path into two parts: the root (first segment) and the rest. + - The root remains unencrypted, while the rest is encrypted using the superclass method. +3. In the `decryptor` method: + - It follows a similar pattern, keeping the root unencrypted and decrypting the rest. + +### Implementation +```java +class PathEncryptionImplOverridden extends PathEncryptionImpl { + PathEncryptionImplOverridden(PathEncryptionImplRuntimeDelegatable.ArgumentsCaptor captor) { + super(captor.getSymmetricPathEncryptionService(), captor.getPrivateKeyService()); + } + + @Override + public Uri encrypt(UserIDAuth forUser, Uri path) { + if (path.asString().contains("/")) { + String[] rootAndInRoot = path.asString().split("/", 2); + return new Uri(rootAndInRoot + "/" + super.encrypt(forUser, new Uri(rootAndInRoot[1])).asString()); + } + return path; + } + + @Override + public Function decryptor(UserIDAuth forUser) { + return rootWithEncrypted -> { + if (rootWithEncrypted.asString().contains("/")) { + String[] rootAndInRoot = rootWithEncrypted.asString().split("/", 2); + return new Uri(rootAndInRoot + "/" + super.decryptor(forUser).apply(new Uri(rootAndInRoot[1])).asString()); + } + return rootWithEncrypted; + }; + } +} +``` +### Usage +- To use partial path encryption +Create an OverridesRegistry and override the PathEncryptionImpl: +java +```java +OverridesRegistry registry = new BaseOverridesRegistry(); +PathEncryptionImplRuntimeDelegatable.overrideWith(registry, PathEncryptionImplOverridden::new); +``` + + +Build the Datasafe service with the custom registry: +```java +DefaultDatasafeServices datasafeServices = DaggerDefaultDatasafeServices.builder() +.config(new DefaultDFSConfig(root.toAbsolutePath().toUri(), "secret"::toCharArray)) +.storage(new FileSystemStorageService(root)) +.overridesRegistry(registry) +.build(); +``` + +- Use the service as usual. Paths like "folder/file.txt" will be partially encrypted: +```text +"folder" remains unencrypted +"file.txt" gets encrypted +``` + +- Example +```java +UserIDAuth user = new UserIDAuth("user", "passwrd"::toCharArray); +datasafeServices.userProfile().registerUsingDefaults(user); +datasafeServices.privateService().write(WriteRequest.forDefaultPrivate(user, "folder/file.txt")); + +// The folder name "folder" will be visible in the file system +assertThat(Files.walk(root)).asString().contains("folder"); +// But "file.txt" will be encrypted +assertThat(Files.walk(root)).asString().doesNotContain("file.txt"); +```