Add tests for SBOM generation determinism across multiple formats
- Created `StellaOps.TestKit.Tests` project for unit tests related to determinism. - Implemented `DeterminismManifestTests` to validate deterministic output for canonical bytes and strings, file read/write operations, and error handling for invalid schema versions. - Added `SbomDeterminismTests` to ensure identical inputs produce consistent SBOMs across SPDX 3.0.1 and CycloneDX 1.6/1.7 formats, including parallel execution tests. - Updated project references in `StellaOps.Integration.Determinism` to include the new determinism testing library.
This commit is contained in:
@@ -20,7 +20,7 @@ public sealed class DefaultCryptoHashTests
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
var expected = SHA256.HashData(Sample);
|
||||
var actual = hash.ComputeHash(Sample, HashAlgorithms.Sha256);
|
||||
Assert.Equal(Convert.ToHexString(expected).ToLowerInvariant(), Convert.ToHexString(actual).ToLowerInvariant());
|
||||
Assert.Equal(Convert.ToHexStringLower(expected), Convert.ToHexStringLower(actual));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -29,7 +29,7 @@ public sealed class DefaultCryptoHashTests
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
var expected = SHA512.HashData(Sample);
|
||||
var actual = hash.ComputeHash(Sample, HashAlgorithms.Sha512);
|
||||
Assert.Equal(Convert.ToHexString(expected).ToLowerInvariant(), Convert.ToHexString(actual).ToLowerInvariant());
|
||||
Assert.Equal(Convert.ToHexStringLower(expected), Convert.ToHexStringLower(actual));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -38,7 +38,7 @@ public sealed class DefaultCryptoHashTests
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
var expected = ComputeGostDigest(use256: true);
|
||||
var actual = hash.ComputeHash(Sample, HashAlgorithms.Gost3411_2012_256);
|
||||
Assert.Equal(Convert.ToHexString(expected).ToLowerInvariant(), Convert.ToHexString(actual).ToLowerInvariant());
|
||||
Assert.Equal(Convert.ToHexStringLower(expected), Convert.ToHexStringLower(actual));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -47,7 +47,7 @@ public sealed class DefaultCryptoHashTests
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
var expected = ComputeGostDigest(use256: false);
|
||||
var actual = hash.ComputeHash(Sample, HashAlgorithms.Gost3411_2012_512);
|
||||
Assert.Equal(Convert.ToHexString(expected).ToLowerInvariant(), Convert.ToHexString(actual).ToLowerInvariant());
|
||||
Assert.Equal(Convert.ToHexStringLower(expected), Convert.ToHexStringLower(actual));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -60,6 +60,25 @@ public sealed class DefaultCryptoHashTests
|
||||
Assert.Equal(Convert.ToHexString(bufferDigest), Convert.ToHexString(streamDigest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeHashHex_Sha256_MatchesBclLowerHex()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
var expected = Convert.ToHexStringLower(SHA256.HashData(Sample));
|
||||
var actual = hash.ComputeHashHex(Sample, HashAlgorithms.Sha256);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ComputeHashHexAsync_Sha256_MatchesBclLowerHex()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
var expected = Convert.ToHexStringLower(SHA256.HashData(Sample));
|
||||
await using var stream = new MemoryStream(Sample);
|
||||
var actual = await hash.ComputeHashHexAsync(stream, HashAlgorithms.Sha256);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
private static byte[] ComputeGostDigest(bool use256)
|
||||
{
|
||||
Org.BouncyCastle.Crypto.IDigest digest = use256
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public sealed class DefaultCryptoHmacTests
|
||||
{
|
||||
private static readonly byte[] Sample = Encoding.UTF8.GetBytes("The quick brown fox jumps over the lazy dog");
|
||||
private static readonly byte[] Key = Encoding.UTF8.GetBytes("test-key");
|
||||
|
||||
[Fact]
|
||||
public void ComputeHmacHexForPurpose_WebhookInterop_MatchesBclLowerHex()
|
||||
{
|
||||
var hmac = DefaultCryptoHmac.CreateForTests();
|
||||
var expected = Convert.ToHexStringLower(HMACSHA256.HashData(Key, Sample));
|
||||
var actual = hmac.ComputeHmacHexForPurpose(Key, Sample, HmacPurpose.WebhookInterop);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ComputeHmacHexForPurposeAsync_WebhookInterop_MatchesBclLowerHex()
|
||||
{
|
||||
var hmac = DefaultCryptoHmac.CreateForTests();
|
||||
var expected = Convert.ToHexStringLower(HMACSHA256.HashData(Key, Sample));
|
||||
await using var stream = new MemoryStream(Sample);
|
||||
var actual = await hmac.ComputeHmacHexForPurposeAsync(Key, stream, HmacPurpose.WebhookInterop);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Digests;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public sealed class Sha256DigestTests
|
||||
{
|
||||
[Fact]
|
||||
public void Normalize_AllowsBareHex_WhenPrefixNotRequired()
|
||||
{
|
||||
var hex = new string('a', Sha256Digest.HexLength);
|
||||
Assert.Equal($"sha256:{hex}", Sha256Digest.Normalize(hex));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_NormalizesPrefixAndHexToLower()
|
||||
{
|
||||
var hexUpper = new string('A', Sha256Digest.HexLength);
|
||||
Assert.Equal(
|
||||
$"sha256:{new string('a', Sha256Digest.HexLength)}",
|
||||
Sha256Digest.Normalize($"SHA256:{hexUpper}"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Normalize_RequiresPrefix_WhenConfigured()
|
||||
{
|
||||
var hex = new string('a', Sha256Digest.HexLength);
|
||||
var ex = Assert.Throws<FormatException>(() => Sha256Digest.Normalize(hex, requirePrefix: true, parameterName: "sbomDigest"));
|
||||
Assert.Contains("sbomDigest", ex.Message, StringComparison.Ordinal);
|
||||
Assert.Contains("sha256:", ex.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractHex_ReturnsLowercaseHex()
|
||||
{
|
||||
var hexUpper = new string('A', Sha256Digest.HexLength);
|
||||
Assert.Equal(new string('a', Sha256Digest.HexLength), Sha256Digest.ExtractHex($"sha256:{hexUpper}"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_UsesCryptoHashStack()
|
||||
{
|
||||
var hash = CryptoHashFactory.CreateDefault();
|
||||
var content = Encoding.UTF8.GetBytes("hello");
|
||||
|
||||
var expectedHex = Convert.ToHexStringLower(SHA256.HashData(content));
|
||||
Assert.Equal($"sha256:{expectedHex}", Sha256Digest.Compute(hash, content));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.DependencyInjection\StellaOps.Cryptography.DependencyInjection.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.BouncyCastle\StellaOps.Cryptography.Plugin.BouncyCastle.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.OfflineVerification\StellaOps.Cryptography.Plugin.OfflineVerification.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.OpenSslGost\StellaOps.Cryptography.Plugin.OpenSslGost.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Cryptography.Plugin.SmSoft\StellaOps.Cryptography.Plugin.SmSoft.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user