feat(filestore): wire AeadEncryptor into MsgBlock for at-rest encryption
Add FileStoreEncryptionTests covering ChaCha20-Poly1305 and AES-GCM round-trips and wrong-key rejection for the FSV2 AEAD path. Fix RestorePayload to wrap CryptographicException from AEAD decryption as InvalidDataException so RecoverBlocks correctly propagates key-mismatch failures instead of silently swallowing them.
This commit is contained in:
104
tests/NATS.Server.Tests/FileStoreEncryptionTests.cs
Normal file
104
tests/NATS.Server.Tests/FileStoreEncryptionTests.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
// 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 or return no messages
|
||||
var act = () => new FileStore(new FileStoreOptions
|
||||
{
|
||||
Directory = dir.FullName,
|
||||
Cipher = StoreCipher.ChaCha,
|
||||
EncryptionKey = key2,
|
||||
});
|
||||
Should.Throw<Exception>(act);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user