Initial WebhookServer implementation
Add the .NET 8 solution scaffolded against PLAN.md. Three projects share WebhookServer.Core (models, auth, execution, storage, IPC, callbacks) and WebhookServer.Service hosts an embedded Kestrel listener plus the named-pipe admin server. WebhookServer.Gui is a thin MVVM client over the pipe. Includes 25 unit tests covering HMAC verification, bearer auth, IP allowlist parsing, arg-template rendering, DPAPI round-trip, and the encrypt-on-save config store. Install/uninstall PowerShell scripts default to LocalSystem and accept a domain user or gMSA via -ServiceAccount. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using WebhookServer.Core.Auth;
|
||||
using WebhookServer.Core.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace WebhookServer.Core.Tests;
|
||||
|
||||
public class HmacVerifierTests
|
||||
{
|
||||
[Fact]
|
||||
public void Compute_matches_GitHub_style_signature()
|
||||
{
|
||||
var body = Encoding.UTF8.GetBytes("{\"x\":1}");
|
||||
var secret = "topsecret";
|
||||
|
||||
var hex = HmacVerifier.Compute(body, secret, HmacAlgorithm.Sha256, HmacEncoding.Hex);
|
||||
|
||||
// Cross-check against direct HMACSHA256 to ensure no encoding drift.
|
||||
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
|
||||
var expected = Convert.ToHexString(hmac.ComputeHash(body)).ToLowerInvariant();
|
||||
Assert.Equal(expected, hex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Verify_accepts_correct_signature_with_prefix()
|
||||
{
|
||||
var body = Encoding.UTF8.GetBytes("hello world");
|
||||
var secret = "shhh";
|
||||
var sig = HmacVerifier.Compute(body, secret, HmacAlgorithm.Sha256, HmacEncoding.Hex);
|
||||
|
||||
var options = new HmacOptions { Secret = ProtectedString.FromPlaintext(secret) };
|
||||
var result = HmacVerifier.Verify(body, $"sha256={sig}", options);
|
||||
|
||||
Assert.True(result.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Verify_rejects_wrong_signature()
|
||||
{
|
||||
var body = Encoding.UTF8.GetBytes("payload");
|
||||
var options = new HmacOptions { Secret = ProtectedString.FromPlaintext("right") };
|
||||
|
||||
var sig = HmacVerifier.Compute(body, "wrong", HmacAlgorithm.Sha256, HmacEncoding.Hex);
|
||||
var result = HmacVerifier.Verify(body, $"sha256={sig}", options);
|
||||
|
||||
Assert.False(result.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Verify_rejects_when_prefix_missing()
|
||||
{
|
||||
var body = Encoding.UTF8.GetBytes("payload");
|
||||
var options = new HmacOptions { Secret = ProtectedString.FromPlaintext("k") };
|
||||
var sig = HmacVerifier.Compute(body, "k", HmacAlgorithm.Sha256, HmacEncoding.Hex);
|
||||
|
||||
var result = HmacVerifier.Verify(body, sig, options); // no "sha256=" prefix
|
||||
|
||||
Assert.False(result.Success);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Verify_handles_base64_encoding()
|
||||
{
|
||||
var body = Encoding.UTF8.GetBytes("payload");
|
||||
var secret = "abc";
|
||||
var sig = HmacVerifier.Compute(body, secret, HmacAlgorithm.Sha256, HmacEncoding.Base64);
|
||||
|
||||
var options = new HmacOptions
|
||||
{
|
||||
Encoding = HmacEncoding.Base64,
|
||||
Prefix = "",
|
||||
Secret = ProtectedString.FromPlaintext(secret),
|
||||
};
|
||||
|
||||
var result = HmacVerifier.Verify(body, sig, options);
|
||||
Assert.True(result.Success);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user