Code quality review feedback: Should.Throw<Exception> was too broad, changed to Should.Throw<InvalidDataException> to match the actual exception type thrown when AEAD decryption fails with wrong key.
105 lines
3.5 KiB
C#
105 lines
3.5 KiB
C#
// 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<InvalidDataException>(act);
|
|
}
|
|
}
|