Files
scadaproj/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Tests/MasterKey/MasterKeyProviderTests.cs
T

230 lines
7.1 KiB
C#

using System.Security.Cryptography;
using ZB.MOM.WW.Secrets.Abstractions;
using ZB.MOM.WW.Secrets.MasterKey;
namespace ZB.MOM.WW.Secrets.Tests.MasterKey;
public class MasterKeyProviderTests
{
// ---- Environment ----------------------------------------------------------------------
[Fact]
public void Environment_ReturnsDecodedKey()
{
byte[] key = RandomNumberGenerator.GetBytes(32);
string envVar = UniqueEnvVarName();
Environment.SetEnvironmentVariable(envVar, Convert.ToBase64String(key));
try
{
var provider = new EnvironmentMasterKeyProvider(new MasterKeyOptions { EnvVarName = envVar });
Assert.True(key.AsSpan().SequenceEqual(provider.GetMasterKey().Span));
}
finally
{
Environment.SetEnvironmentVariable(envVar, null);
}
}
[Fact]
public void Environment_KekId_IsStableAcrossCalls()
{
byte[] key = RandomNumberGenerator.GetBytes(32);
string envVar = UniqueEnvVarName();
Environment.SetEnvironmentVariable(envVar, Convert.ToBase64String(key));
try
{
var provider = new EnvironmentMasterKeyProvider(new MasterKeyOptions { EnvVarName = envVar });
string first = provider.KekId;
string second = provider.KekId;
Assert.Equal(first, second);
Assert.StartsWith("sha256:", first, StringComparison.Ordinal);
}
finally
{
Environment.SetEnvironmentVariable(envVar, null);
}
}
[Fact]
public void Environment_ShortKey_Throws()
{
byte[] shortKey = RandomNumberGenerator.GetBytes(16);
string envVar = UniqueEnvVarName();
Environment.SetEnvironmentVariable(envVar, Convert.ToBase64String(shortKey));
try
{
var provider = new EnvironmentMasterKeyProvider(new MasterKeyOptions { EnvVarName = envVar });
Assert.Throws<MasterKeyUnavailableException>(() => provider.GetMasterKey());
}
finally
{
Environment.SetEnvironmentVariable(envVar, null);
}
}
[Fact]
public void Environment_MissingVariable_Throws()
{
string envVar = UniqueEnvVarName();
Environment.SetEnvironmentVariable(envVar, null); // ensure absent
var provider = new EnvironmentMasterKeyProvider(new MasterKeyOptions { EnvVarName = envVar });
Assert.Throws<MasterKeyUnavailableException>(() => provider.GetMasterKey());
}
// ---- File -----------------------------------------------------------------------------
[Fact]
public void File_Raw32Bytes_ReturnsKey()
{
byte[] key = RandomNumberGenerator.GetBytes(32);
string path = Path.GetTempFileName();
File.WriteAllBytes(path, key);
try
{
var provider = new FileMasterKeyProvider(new MasterKeyOptions { FilePath = path });
Assert.True(key.AsSpan().SequenceEqual(provider.GetMasterKey().Span));
}
finally
{
File.Delete(path);
}
}
[Fact]
public void File_Base64Text_ReturnsKey()
{
byte[] key = RandomNumberGenerator.GetBytes(32);
string path = Path.GetTempFileName();
File.WriteAllText(path, Convert.ToBase64String(key) + "\n");
try
{
var provider = new FileMasterKeyProvider(new MasterKeyOptions { FilePath = path });
Assert.True(key.AsSpan().SequenceEqual(provider.GetMasterKey().Span));
}
finally
{
File.Delete(path);
}
}
[Fact]
public void File_TenBytes_Throws()
{
// 10 raw bytes: not 32, and not valid base64 text either → fail closed.
string path = Path.GetTempFileName();
File.WriteAllBytes(path, RandomNumberGenerator.GetBytes(10));
try
{
var provider = new FileMasterKeyProvider(new MasterKeyOptions { FilePath = path });
Assert.Throws<MasterKeyUnavailableException>(() => provider.GetMasterKey());
}
finally
{
File.Delete(path);
}
}
[Fact]
public void File_MissingFile_Throws()
{
var provider = new FileMasterKeyProvider(
new MasterKeyOptions { FilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")) });
Assert.Throws<MasterKeyUnavailableException>(() => provider.GetMasterKey());
}
// ---- KekId derivation -----------------------------------------------------------------
[Fact]
public void ExplicitKekId_IsUsedVerbatim()
{
byte[] key = RandomNumberGenerator.GetBytes(32);
string path = Path.GetTempFileName();
File.WriteAllBytes(path, key);
try
{
var provider = new FileMasterKeyProvider(
new MasterKeyOptions { FilePath = path, KekId = "prod-kek-2026" });
Assert.Equal("prod-kek-2026", provider.KekId);
}
finally
{
File.Delete(path);
}
}
[Fact]
public void DerivedKekId_StartsWithSha256()
{
byte[] key = RandomNumberGenerator.GetBytes(32);
string path = Path.GetTempFileName();
File.WriteAllBytes(path, key);
try
{
var provider = new FileMasterKeyProvider(new MasterKeyOptions { FilePath = path });
Assert.StartsWith("sha256:", provider.KekId, StringComparison.Ordinal);
}
finally
{
File.Delete(path);
}
}
// ---- Factory --------------------------------------------------------------------------
[Fact]
public void Factory_Environment_ReturnsEnvironmentProvider()
{
IMasterKeyProvider provider = MasterKeyProviderFactory.Create(
new MasterKeyOptions { Source = MasterKeySource.Environment });
Assert.IsType<EnvironmentMasterKeyProvider>(provider);
}
[Fact]
public void Factory_File_ReturnsFileProvider()
{
IMasterKeyProvider provider = MasterKeyProviderFactory.Create(
new MasterKeyOptions { Source = MasterKeySource.File, FilePath = "unused" });
Assert.IsType<FileMasterKeyProvider>(provider);
}
// ---- DPAPI (Windows-only) -------------------------------------------------------------
[SkippableFact]
public void Dpapi_RoundTripsOnWindows()
{
Skip.IfNot(OperatingSystem.IsWindows(), "DPAPI is only supported on Windows.");
byte[] key = RandomNumberGenerator.GetBytes(32);
#pragma warning disable CA1416 // Whole body is guarded by Skip.IfNot(IsWindows) above.
byte[] blob = ProtectedData.Protect(key, optionalEntropy: null, DataProtectionScope.CurrentUser);
string path = Path.GetTempFileName();
File.WriteAllBytes(path, blob);
try
{
var provider = new DpapiMasterKeyProvider(new MasterKeyOptions { FilePath = path });
Assert.True(key.AsSpan().SequenceEqual(provider.GetMasterKey().Span));
}
finally
{
File.Delete(path);
}
#pragma warning restore CA1416
}
private static string UniqueEnvVarName() => "ZB_SECRETS_TEST_" + Guid.NewGuid().ToString("N");
}