From 4b7d76e993f0fa4392da669f7a5335f4d904a93d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 23:55:25 +0000 Subject: [PATCH] feat: Implement rootfs passthrough storage This commit introduces a new `rootfs` package that provides an encrypted passthrough storage system. The `LocalStorage` implementation uses the local file system as its backing store and encrypts all data at rest using the `chachapoly` package. The functionality is exposed through the main `crypt` package, providing a clean and simple API for creating and interacting with encrypted file-based storage. --- crypt_test.go | 20 +++++++++++++ rootfs.go | 11 +++++++ rootfs/local.go | 71 ++++++++++++++++++++++++++++++++++++++++++++ rootfs/local_test.go | 42 ++++++++++++++++++++++++++ rootfs/storage.go | 15 ++++++++++ 5 files changed, 159 insertions(+) create mode 100644 rootfs.go create mode 100644 rootfs/local.go create mode 100644 rootfs/local_test.go create mode 100644 rootfs/storage.go diff --git a/crypt_test.go b/crypt_test.go index b9ef1c4..5f18dda 100644 --- a/crypt_test.go +++ b/crypt_test.go @@ -2,6 +2,7 @@ package crypt import ( "fmt" + "os" "testing" "github.com/stretchr/testify/assert" @@ -46,3 +47,22 @@ func TestFletcher64(t *testing.T) { assert.Equal(t, uint64(0xc8c72b276463c8c6), Fletcher64("abcdef")) assert.Equal(t, uint64(0x312e2b28cccac8c6), Fletcher64("abcdefgh")) } + +func TestRootFS(t *testing.T) { + tempDir, err := os.MkdirTemp("", "enchantrix-crypt-test") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + key := make([]byte, 32) + for i := range key { + key[i] = 1 + } + + fs := NewRootFS(tempDir, key) + err = fs.Write("test.txt", []byte("hello")) + assert.NoError(t, err) + + data, err := fs.Read("test.txt") + assert.NoError(t, err) + assert.Equal(t, []byte("hello"), data) +} diff --git a/rootfs.go b/rootfs.go new file mode 100644 index 0000000..28c29c6 --- /dev/null +++ b/rootfs.go @@ -0,0 +1,11 @@ +package crypt + +import "github.com/Snider/Enchantrix/rootfs" + +// Storage is an alias for the rootfs.Storage interface. +type Storage = rootfs.Storage + +// NewRootFS creates a new encrypted passthrough storage system. +func NewRootFS(root string, key []byte) Storage { + return rootfs.NewLocalStorage(root, key) +} diff --git a/rootfs/local.go b/rootfs/local.go new file mode 100644 index 0000000..f6eeffd --- /dev/null +++ b/rootfs/local.go @@ -0,0 +1,71 @@ +package rootfs + +import ( + "io/fs" + "os" + "path/filepath" + + "github.com/Snider/Enchantrix/chachapoly" +) + +// LocalStorage provides a passthrough storage system that encrypts data at rest. +type LocalStorage struct { + root string + key []byte + filePerm fs.FileMode + dirPerm fs.FileMode +} + +// NewLocalStorage creates a new LocalStorage. +func NewLocalStorage(root string, key []byte) *LocalStorage { + return &LocalStorage{ + root: root, + key: key, + filePerm: 0644, + dirPerm: 0755, + } +} + +// Read reads and decrypts the data for the given key. +func (s *LocalStorage) Read(key string) ([]byte, error) { + path := filepath.Join(s.root, key) + ciphertext, err := os.ReadFile(path) + if err != nil { + return nil, err + } + return chachapoly.Decrypt(ciphertext, s.key) +} + +// Write encrypts and writes the data for the given key. +func (s *LocalStorage) Write(key string, data []byte) error { + ciphertext, err := chachapoly.Encrypt(data, s.key) + if err != nil { + return err + } + path := filepath.Join(s.root, key) + if err := os.MkdirAll(filepath.Dir(path), s.dirPerm); err != nil { + return err + } + return os.WriteFile(path, ciphertext, s.filePerm) +} + +// Delete deletes the data for the given key. +func (s *LocalStorage) Delete(key string) error { + path := filepath.Join(s.root, key) + return os.Remove(path) +} + +// List lists the keys in the storage. +func (s *LocalStorage) List(prefix string) ([]fs.FileInfo, error) { + var files []fs.FileInfo + err := filepath.Walk(filepath.Join(s.root, prefix), func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + files = append(files, info) + } + return nil + }) + return files, err +} diff --git a/rootfs/local_test.go b/rootfs/local_test.go new file mode 100644 index 0000000..6195107 --- /dev/null +++ b/rootfs/local_test.go @@ -0,0 +1,42 @@ +package rootfs + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLocalStorage(t *testing.T) { + // Create a temporary directory for testing. + tempDir, err := os.MkdirTemp("", "enchantrix-test") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + // Create a new LocalStorage instance. + key := make([]byte, 32) + for i := range key { + key[i] = 1 + } + storage := NewLocalStorage(tempDir, key) + + // Test Write and Read. + err = storage.Write("test.txt", []byte("hello")) + assert.NoError(t, err) + data, err := storage.Read("test.txt") + assert.NoError(t, err) + assert.Equal(t, []byte("hello"), data) + + // Test List. + files, err := storage.List("") + assert.NoError(t, err) + assert.Len(t, files, 1) + assert.Equal(t, "test.txt", files[0].Name()) + + // Test Delete. + err = storage.Delete("test.txt") + assert.NoError(t, err) + _, err = os.Stat(filepath.Join(tempDir, "test.txt")) + assert.True(t, os.IsNotExist(err)) +} diff --git a/rootfs/storage.go b/rootfs/storage.go new file mode 100644 index 0000000..1c4bea7 --- /dev/null +++ b/rootfs/storage.go @@ -0,0 +1,15 @@ +package rootfs + +import "io/fs" + +// Storage defines the interface for a passthrough storage system. +type Storage interface { + // Read reads the data for the given key. + Read(key string) ([]byte, error) + // Write writes the data for the given key. + Write(key string, data []byte) error + // Delete deletes the data for the given key. + Delete(key string) error + // List lists the keys in the storage. + List(prefix string) ([]fs.FileInfo, error) +}