// Go: TestFileStoreEncryption server/filestore_test.go // Reference: golang/nats-server/server/filestore.go:816-907 (genEncryptionKeys, recoverAEK, setupAEK) // Tests that block files are encrypted at rest and can be recovered with the same key. using System.Security.Cryptography; using NATS.Server.JetStream.Storage; namespace NATS.Server.Tests; public class FileStoreEncryptionTests { [Fact] public async Task Encrypted_block_round_trips_message() { // Go: TestFileStoreEncryption server/filestore_test.go var dir = Directory.CreateTempSubdirectory(); var key = new byte[32]; RandomNumberGenerator.Fill(key); await using (var store = new FileStore(new FileStoreOptions { Directory = dir.FullName, Cipher = StoreCipher.ChaCha, EncryptionKey = key, })) { await store.AppendAsync("test.subj", "hello encrypted"u8.ToArray(), default); } // Raw block file should NOT contain plaintext var blkFiles = Directory.GetFiles(dir.FullName, "*.blk"); blkFiles.ShouldNotBeEmpty(); var raw = File.ReadAllBytes(blkFiles[0]); System.Text.Encoding.UTF8.GetString(raw).ShouldNotContain("hello encrypted"); // Recover with same key should return plaintext await using var recovered = new FileStore(new FileStoreOptions { Directory = dir.FullName, Cipher = StoreCipher.ChaCha, EncryptionKey = key, }); var msg = await recovered.LoadAsync(1, default); msg.ShouldNotBeNull(); System.Text.Encoding.UTF8.GetString(msg.Payload.Span).ShouldBe("hello encrypted"); } [Fact] public async Task Encrypted_block_with_aes_round_trips() { var dir = Directory.CreateTempSubdirectory(); var key = new byte[32]; RandomNumberGenerator.Fill(key); await using (var store = new FileStore(new FileStoreOptions { Directory = dir.FullName, Cipher = StoreCipher.Aes, EncryptionKey = key, })) { await store.AppendAsync("aes.subj", "aes payload"u8.ToArray(), default); } await using var recovered = new FileStore(new FileStoreOptions { Directory = dir.FullName, Cipher = StoreCipher.Aes, EncryptionKey = key, }); var msg = await recovered.LoadAsync(1, default); msg.ShouldNotBeNull(); System.Text.Encoding.UTF8.GetString(msg.Payload.Span).ShouldBe("aes payload"); } [Fact] public async Task Wrong_key_fails_to_decrypt() { var dir = Directory.CreateTempSubdirectory(); var key1 = new byte[32]; var key2 = new byte[32]; RandomNumberGenerator.Fill(key1); RandomNumberGenerator.Fill(key2); await using (var store = new FileStore(new FileStoreOptions { Directory = dir.FullName, Cipher = StoreCipher.ChaCha, EncryptionKey = key1, })) { await store.AppendAsync("secret", "data"u8.ToArray(), default); } // Recovery with wrong key should throw InvalidDataException (from CryptographicException) var act = () => new FileStore(new FileStoreOptions { Directory = dir.FullName, Cipher = StoreCipher.ChaCha, EncryptionKey = key2, }); Should.Throw(act); } }