up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-12-03 00:10:19 +02:00
parent ea1d58a89b
commit 37cba83708
158 changed files with 147438 additions and 867 deletions

View File

@@ -0,0 +1,116 @@
using System;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Scanner.Worker.Options;
using StellaOps.Scanner.Worker.Processing.Surface;
using Xunit;
namespace StellaOps.Scanner.Worker.Tests;
public sealed class HmacDsseEnvelopeSignerTests
{
[Fact]
public async Task SignAsync_UsesHmac_WhenSecretProvided()
{
var options = BuildOptions(signing =>
{
signing.EnableDsseSigning = true;
signing.SharedSecret = "a2V5LXNlY3JldA=="; // base64("key-secret")
signing.KeyId = "scanner-hmac";
});
var signer = new HmacDsseEnvelopeSigner(options, NullLogger<HmacDsseEnvelopeSigner>.Instance);
var payload = Encoding.UTF8.GetBytes("{\"hello\":\"world\"}");
var envelope = await signer.SignAsync("application/json", payload, "test.kind", "root", view: null, CancellationToken.None);
var json = JsonDocument.Parse(envelope.Content.Span);
var sig = json.RootElement.GetProperty("signatures")[0].GetProperty("sig").GetString();
var expectedSig = ComputeExpectedSignature("application/json", payload, "a2V5LXNlY3JldA==");
Assert.Equal(expectedSig, sig);
Assert.Equal("application/vnd.dsse+json", envelope.MediaType);
Assert.Equal("scanner-hmac", json.RootElement.GetProperty("signatures")[0].GetProperty("keyid").GetString());
}
[Fact]
public async Task SignAsync_FallsBackToDeterministic_WhenSecretMissing()
{
var options = BuildOptions(signing =>
{
signing.EnableDsseSigning = true;
signing.SharedSecret = null;
signing.SharedSecretFile = null;
signing.AllowDeterministicFallback = true;
});
var signer = new HmacDsseEnvelopeSigner(options, NullLogger<HmacDsseEnvelopeSigner>.Instance);
var payload = Encoding.UTF8.GetBytes("abc");
var envelope = await signer.SignAsync("text/plain", payload, "kind", "root", view: null, CancellationToken.None);
var json = JsonDocument.Parse(envelope.Content.Span);
var sig = json.RootElement.GetProperty("signatures")[0].GetProperty("sig").GetString();
// Deterministic signer encodes sha256 hex of payload as signature.
var expected = Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(payload)).ToLowerInvariant();
var expectedBase64Url = Base64UrlEncode(Encoding.UTF8.GetBytes(expected));
Assert.Equal(expectedBase64Url, sig);
}
private static IOptions<ScannerWorkerOptions> BuildOptions(Action<ScannerWorkerOptions.SigningOptions> configure)
{
var options = new ScannerWorkerOptions();
configure(options.Signing);
return Microsoft.Extensions.Options.Options.Create(options);
}
private static string ComputeExpectedSignature(string payloadType, byte[] payload, string base64Secret)
{
var secret = Convert.FromBase64String(base64Secret);
using var hmac = new System.Security.Cryptography.HMACSHA256(secret);
var pae = BuildPae(payloadType, payload);
var signature = hmac.ComputeHash(pae);
return Base64UrlEncode(signature);
}
private static byte[] BuildPae(string payloadType, byte[] payload)
{
const string prefix = "DSSEv1";
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
var typeLen = Encoding.UTF8.GetBytes(typeBytes.Length.ToString());
var payloadLen = Encoding.UTF8.GetBytes(payload.Length.ToString());
var total = prefix.Length + 1 + typeLen.Length + 1 + typeBytes.Length + 1 + payloadLen.Length + 1 + payload.Length;
var buffer = new byte[total];
var offset = 0;
Encoding.UTF8.GetBytes(prefix, buffer.AsSpan(offset));
offset += prefix.Length;
buffer[offset++] = 0x20;
typeLen.CopyTo(buffer.AsSpan(offset));
offset += typeLen.Length;
buffer[offset++] = 0x20;
typeBytes.CopyTo(buffer.AsSpan(offset));
offset += typeBytes.Length;
buffer[offset++] = 0x20;
payloadLen.CopyTo(buffer.AsSpan(offset));
offset += payloadLen.Length;
buffer[offset++] = 0x20;
payload.CopyTo(buffer.AsSpan(offset));
return buffer;
}
private static string Base64UrlEncode(ReadOnlySpan<byte> data)
{
var base64 = Convert.ToBase64String(data);
return base64.TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
}