Bifrost/tests/Bifrost.Web.Tests/OidcKeyMaterialTests.cs
Meta Vens 83daabe512 Bifrost as OIDC issuer for the Matrix chat (gamecult-chat cut bifrost-oidc-issuer, squash of 1ef289a..ec4acfa and abd12b3..)
One configured client (stale clients removed with their tokens and authorizations), Active membership re-checked at token and userinfo through IsStillActiveMemberAsync, PKCE required by the server option, OpenIddict.Server.AspNetCore + EntityFrameworkCore 7.7.1 with no vulnerable transitives. Soul s2, s3 and s4 hold; Low conformance gaps in gamecult-chat:follow_up:oidc-issuer-spec-conformance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 04:57:16 +01:00

104 lines
4.1 KiB
C#

using System.Security.Cryptography;
using Bifrost.Web.Configuration;
using Bifrost.Web.Features.Oidc;
using Bifrost.Web.Tests.Support;
using Microsoft.IdentityModel.Tokens;
namespace Bifrost.Web.Tests;
public sealed class OidcKeyMaterialTests : IDisposable
{
private readonly OidcTestKit _kit = new();
public void Dispose() => _kit.Dispose();
[Fact]
public void A_complete_configuration_loads_the_signing_key_the_secret_and_the_encryption_key()
{
using var material = OidcKeyMaterial.Load(Options());
var signing = Assert.IsType<ECDsaSecurityKey>(material.SigningKey);
Assert.False(string.IsNullOrEmpty(signing.KeyId));
Assert.Equal(_kit.ClientSecret, material.ClientSecret);
Assert.Equal(32, Assert.IsType<SymmetricSecurityKey>(material.EncryptionKey).Key.Length);
}
[Theory]
[InlineData("SigningKeyPath", "Bifrost:Oidc:SigningKeyPath")]
[InlineData("EncryptionKeyPath", "Bifrost:Oidc:EncryptionKeyPath")]
[InlineData("ClientId", "Bifrost:Oidc:Clients:Mas:ClientId")]
[InlineData("ClientSecretPath", "Bifrost:Oidc:Clients:Mas:ClientSecretPath")]
[InlineData("RedirectUri", "Bifrost:Oidc:Clients:Mas:RedirectUri")]
public void A_missing_setting_stops_startup_naming_that_setting(string blank, string expectedField)
{
var failure = Assert.Throws<OidcConfigurationException>(() => OidcKeyMaterial.Load(Options(blank: blank)));
Assert.Contains(expectedField, failure.Message, StringComparison.Ordinal);
}
[Fact]
public void A_redirect_uri_that_is_not_absolute_stops_startup()
{
var failure = Assert.Throws<OidcConfigurationException>(() => OidcKeyMaterial.Load(Options(redirectUri: "/callback")));
Assert.Contains("Bifrost:Oidc:Clients:Mas:RedirectUri", failure.Message, StringComparison.Ordinal);
}
[Theory]
[InlineData("missing")]
[InlineData("not base64 !!")]
[InlineData("AAAAAAAAAAAAAAAAAAAAAA==")]
public void An_unusable_encryption_key_stops_startup_naming_the_setting_not_the_path(string content)
{
if (content == "missing")
{
File.Delete(_kit.EncryptionKeyPath);
}
else
{
File.WriteAllText(_kit.EncryptionKeyPath, content);
}
var failure = Assert.Throws<OidcConfigurationException>(() => OidcKeyMaterial.Load(Options()));
Assert.Contains("Bifrost:Oidc:EncryptionKeyPath", failure.Message, StringComparison.Ordinal);
Assert.DoesNotContain("canary-", failure.Message, StringComparison.Ordinal);
}
[Fact]
public void A_missing_client_secret_file_stops_startup_naming_the_setting_not_the_path()
{
File.Delete(_kit.ClientSecretPath);
var failure = Assert.Throws<OidcConfigurationException>(() => OidcKeyMaterial.Load(Options()));
Assert.Contains("Bifrost:Oidc:Clients:Mas:ClientSecretPath", failure.Message, StringComparison.Ordinal);
Assert.DoesNotContain("canary-", failure.Message, StringComparison.Ordinal);
}
[Fact]
public void A_P384_key_is_not_a_P256_key()
{
using var p384 = ECDsa.Create(ECCurve.NamedCurves.nistP384);
File.WriteAllText(_kit.SigningKeyPath, p384.ExportPkcs8PrivateKeyPem());
var failure = Assert.Throws<OidcConfigurationException>(() => OidcKeyMaterial.Load(Options()));
Assert.Contains("Bifrost:Oidc:SigningKeyPath", failure.Message, StringComparison.Ordinal);
}
private OidcIssuerOptions Options(string? blank = null, string? redirectUri = null) => new()
{
SigningKeyPath = blank == "SigningKeyPath" ? "" : _kit.SigningKeyPath,
EncryptionKeyPath = blank == "EncryptionKeyPath" ? "" : _kit.EncryptionKeyPath,
Clients = new OidcClientsOptions
{
Mas = new OidcClientOptions
{
ClientId = blank == "ClientId" ? "" : OidcTestKit.ClientId,
ClientSecretPath = blank == "ClientSecretPath" ? "" : _kit.ClientSecretPath,
RedirectUri = blank == "RedirectUri" ? "" : redirectUri ?? OidcTestKit.RedirectUri
}
}
};
}