Implement VEX document verification system with issuer management and signature verification

- Added IIssuerDirectory interface for managing VEX document issuers, including methods for registration, revocation, and trust validation.
- Created InMemoryIssuerDirectory class as an in-memory implementation of IIssuerDirectory for testing and single-instance deployments.
- Introduced ISignatureVerifier interface for verifying signatures on VEX documents, with support for multiple signature formats.
- Developed SignatureVerifier class as the default implementation of ISignatureVerifier, allowing extensibility for different signature formats.
- Implemented handlers for DSSE and JWS signature formats, including methods for verification and signature extraction.
- Defined various records and enums for issuer and signature metadata, enhancing the structure and clarity of the verification process.
This commit is contained in:
StellaOps Bot
2025-12-06 13:41:22 +02:00
parent 2141196496
commit 5e514532df
112 changed files with 24861 additions and 211 deletions

View File

@@ -0,0 +1,446 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace StellaOps.Policy.RiskProfile.Scope;
/// <summary>
/// Service for managing effective policies with subject pattern matching and priority resolution.
/// Implements CONTRACT-AUTHORITY-EFFECTIVE-WRITE-008.
/// </summary>
public sealed class EffectivePolicyService
{
private readonly TimeProvider _timeProvider;
private readonly ConcurrentDictionary<string, EffectivePolicy> _policies;
private readonly ConcurrentDictionary<string, AuthorityScopeAttachment> _scopeAttachments;
private readonly ConcurrentDictionary<string, List<string>> _policyAttachmentIndex;
public EffectivePolicyService(TimeProvider? timeProvider = null)
{
_timeProvider = timeProvider ?? TimeProvider.System;
_policies = new ConcurrentDictionary<string, EffectivePolicy>(StringComparer.OrdinalIgnoreCase);
_scopeAttachments = new ConcurrentDictionary<string, AuthorityScopeAttachment>(StringComparer.OrdinalIgnoreCase);
_policyAttachmentIndex = new ConcurrentDictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
}
/// <summary>
/// Creates a new effective policy.
/// </summary>
public EffectivePolicy Create(CreateEffectivePolicyRequest request, string? createdBy = null)
{
ArgumentNullException.ThrowIfNull(request);
if (string.IsNullOrWhiteSpace(request.TenantId))
{
throw new ArgumentException("TenantId is required.");
}
if (string.IsNullOrWhiteSpace(request.PolicyId))
{
throw new ArgumentException("PolicyId is required.");
}
if (string.IsNullOrWhiteSpace(request.SubjectPattern))
{
throw new ArgumentException("SubjectPattern is required.");
}
if (!IsValidSubjectPattern(request.SubjectPattern))
{
throw new ArgumentException($"Invalid subject pattern: {request.SubjectPattern}");
}
var now = _timeProvider.GetUtcNow();
var id = GeneratePolicyId(request.TenantId, request.PolicyId, request.SubjectPattern, now);
var policy = new EffectivePolicy(
EffectivePolicyId: id,
TenantId: request.TenantId,
PolicyId: request.PolicyId,
PolicyVersion: request.PolicyVersion,
SubjectPattern: request.SubjectPattern,
Priority: request.Priority,
Enabled: request.Enabled,
ExpiresAt: request.ExpiresAt,
Scopes: request.Scopes?.ToList().AsReadOnly(),
CreatedAt: now,
CreatedBy: createdBy,
UpdatedAt: now);
_policies[id] = policy;
return policy;
}
/// <summary>
/// Gets an effective policy by ID.
/// </summary>
public EffectivePolicy? Get(string effectivePolicyId)
{
return _policies.TryGetValue(effectivePolicyId, out var policy) ? policy : null;
}
/// <summary>
/// Updates an effective policy.
/// </summary>
public EffectivePolicy? Update(string effectivePolicyId, UpdateEffectivePolicyRequest request, string? updatedBy = null)
{
ArgumentNullException.ThrowIfNull(request);
if (!_policies.TryGetValue(effectivePolicyId, out var existing))
{
return null;
}
var now = _timeProvider.GetUtcNow();
var updated = existing with
{
Priority = request.Priority ?? existing.Priority,
Enabled = request.Enabled ?? existing.Enabled,
ExpiresAt = request.ExpiresAt ?? existing.ExpiresAt,
Scopes = request.Scopes?.ToList().AsReadOnly() ?? existing.Scopes,
UpdatedAt = now
};
_policies[effectivePolicyId] = updated;
return updated;
}
/// <summary>
/// Deletes an effective policy.
/// </summary>
public bool Delete(string effectivePolicyId)
{
if (_policies.TryRemove(effectivePolicyId, out _))
{
// Remove associated scope attachments
if (_policyAttachmentIndex.TryRemove(effectivePolicyId, out var attachmentIds))
{
foreach (var attachmentId in attachmentIds)
{
_scopeAttachments.TryRemove(attachmentId, out _);
}
}
return true;
}
return false;
}
/// <summary>
/// Lists effective policies matching query criteria.
/// </summary>
public IReadOnlyList<EffectivePolicy> Query(EffectivePolicyQuery query)
{
ArgumentNullException.ThrowIfNull(query);
var now = _timeProvider.GetUtcNow();
IEnumerable<EffectivePolicy> results = _policies.Values;
if (!string.IsNullOrWhiteSpace(query.TenantId))
{
results = results.Where(p => p.TenantId.Equals(query.TenantId, StringComparison.OrdinalIgnoreCase));
}
if (!string.IsNullOrWhiteSpace(query.PolicyId))
{
results = results.Where(p => p.PolicyId.Equals(query.PolicyId, StringComparison.OrdinalIgnoreCase));
}
if (query.EnabledOnly)
{
results = results.Where(p => p.Enabled);
}
if (!query.IncludeExpired)
{
results = results.Where(p => !p.ExpiresAt.HasValue || p.ExpiresAt.Value > now);
}
return results
.OrderByDescending(p => p.Priority)
.ThenByDescending(p => p.UpdatedAt)
.Take(query.Limit)
.ToList()
.AsReadOnly();
}
/// <summary>
/// Attaches an authority scope to an effective policy.
/// </summary>
public AuthorityScopeAttachment AttachScope(AttachAuthorityScopeRequest request)
{
ArgumentNullException.ThrowIfNull(request);
if (string.IsNullOrWhiteSpace(request.EffectivePolicyId))
{
throw new ArgumentException("EffectivePolicyId is required.");
}
if (string.IsNullOrWhiteSpace(request.Scope))
{
throw new ArgumentException("Scope is required.");
}
if (!_policies.ContainsKey(request.EffectivePolicyId))
{
throw new ArgumentException($"Effective policy '{request.EffectivePolicyId}' not found.");
}
var now = _timeProvider.GetUtcNow();
var id = GenerateAttachmentId(request.EffectivePolicyId, request.Scope, now);
var attachment = new AuthorityScopeAttachment(
AttachmentId: id,
EffectivePolicyId: request.EffectivePolicyId,
Scope: request.Scope,
Conditions: request.Conditions,
CreatedAt: now);
_scopeAttachments[id] = attachment;
IndexAttachment(attachment);
return attachment;
}
/// <summary>
/// Detaches an authority scope.
/// </summary>
public bool DetachScope(string attachmentId)
{
if (_scopeAttachments.TryRemove(attachmentId, out var attachment))
{
RemoveFromIndex(attachment);
return true;
}
return false;
}
/// <summary>
/// Gets all scope attachments for an effective policy.
/// </summary>
public IReadOnlyList<AuthorityScopeAttachment> GetScopeAttachments(string effectivePolicyId)
{
if (_policyAttachmentIndex.TryGetValue(effectivePolicyId, out var attachmentIds))
{
lock (attachmentIds)
{
return attachmentIds
.Select(id => _scopeAttachments.TryGetValue(id, out var a) ? a : null)
.Where(a => a != null)
.Cast<AuthorityScopeAttachment>()
.ToList()
.AsReadOnly();
}
}
return Array.Empty<AuthorityScopeAttachment>();
}
/// <summary>
/// Resolves the effective policy for a subject using priority and specificity rules.
/// Priority resolution order:
/// 1. Higher priority value wins
/// 2. If equal priority, more specific pattern wins
/// 3. If equal specificity, most recently updated wins
/// </summary>
public EffectivePolicyResolutionResult Resolve(string subject, string? tenantId = null)
{
ArgumentNullException.ThrowIfNull(subject);
var sw = Stopwatch.StartNew();
var now = _timeProvider.GetUtcNow();
// Find all matching policies
var matchingPolicies = _policies.Values
.Where(p => p.Enabled)
.Where(p => string.IsNullOrWhiteSpace(tenantId) || p.TenantId.Equals(tenantId, StringComparison.OrdinalIgnoreCase))
.Where(p => !p.ExpiresAt.HasValue || p.ExpiresAt.Value > now)
.Where(p => MatchesPattern(subject, p.SubjectPattern))
.ToList();
if (matchingPolicies.Count == 0)
{
sw.Stop();
return new EffectivePolicyResolutionResult(
Subject: subject,
EffectivePolicy: null,
GrantedScopes: Array.Empty<string>(),
MatchedPattern: null,
ResolutionTimeMs: sw.Elapsed.TotalMilliseconds);
}
// Apply priority resolution rules
var winner = matchingPolicies
.OrderByDescending(p => p.Priority)
.ThenByDescending(p => GetPatternSpecificity(p.SubjectPattern))
.ThenByDescending(p => p.UpdatedAt)
.First();
// Collect granted scopes from the winning policy and its attachments
var grantedScopes = new List<string>();
if (winner.Scopes != null)
{
grantedScopes.AddRange(winner.Scopes);
}
// Add scopes from attachments
var attachments = GetScopeAttachments(winner.EffectivePolicyId);
foreach (var attachment in attachments)
{
if (!grantedScopes.Contains(attachment.Scope, StringComparer.OrdinalIgnoreCase))
{
grantedScopes.Add(attachment.Scope);
}
}
sw.Stop();
return new EffectivePolicyResolutionResult(
Subject: subject,
EffectivePolicy: winner,
GrantedScopes: grantedScopes.AsReadOnly(),
MatchedPattern: winner.SubjectPattern,
ResolutionTimeMs: sw.Elapsed.TotalMilliseconds);
}
/// <summary>
/// Validates a subject pattern.
/// Valid patterns: *, pkg:*, pkg:npm/*, pkg:npm/@org/*, oci://registry/*
/// </summary>
public static bool IsValidSubjectPattern(string pattern)
{
if (string.IsNullOrWhiteSpace(pattern))
{
return false;
}
// Universal wildcard
if (pattern == "*")
{
return true;
}
// Must be a valid PURL or OCI pattern with optional wildcards
if (pattern.StartsWith("pkg:", StringComparison.OrdinalIgnoreCase) ||
pattern.StartsWith("oci://", StringComparison.OrdinalIgnoreCase))
{
// Pattern should not have consecutive wildcards or invalid chars
if (pattern.Contains("**", StringComparison.Ordinal))
{
return false;
}
return true;
}
return false;
}
/// <summary>
/// Checks if a subject matches a glob-style pattern.
/// </summary>
public static bool MatchesPattern(string subject, string pattern)
{
if (string.IsNullOrWhiteSpace(subject) || string.IsNullOrWhiteSpace(pattern))
{
return false;
}
// Universal wildcard matches everything
if (pattern == "*")
{
return true;
}
// Convert glob pattern to regex
var regexPattern = GlobToRegex(pattern);
return Regex.IsMatch(subject, regexPattern, RegexOptions.IgnoreCase);
}
/// <summary>
/// Gets the specificity score of a pattern (higher = more specific).
/// Scoring: length of non-wildcard characters * 10, bonus for segment depth
/// </summary>
public static int GetPatternSpecificity(string pattern)
{
if (string.IsNullOrWhiteSpace(pattern))
{
return 0;
}
// Universal wildcard is least specific
if (pattern == "*")
{
return 0;
}
// Count literal (non-wildcard) characters
var literalChars = pattern.Count(c => c != '*');
// Count path segments (depth bonus)
var segmentCount = pattern.Count(c => c == '/') + 1;
// Base score: literal characters weighted heavily
// Segment bonus: more segments = more specific
return (literalChars * 10) + (segmentCount * 5);
}
private static string GlobToRegex(string pattern)
{
// Escape regex special characters except *
var escaped = Regex.Escape(pattern);
// Replace escaped wildcards with regex equivalents
// For trailing wildcards, match everything (including /)
// For middle wildcards, match single path segment only
if (escaped.EndsWith(@"\*", StringComparison.Ordinal))
{
// Trailing wildcard: match everything remaining
escaped = escaped[..^2] + ".*";
}
else
{
// Non-trailing wildcards: match single path segment
escaped = escaped.Replace(@"\*", @"[^/]*");
}
return $"^{escaped}$";
}
private void IndexAttachment(AuthorityScopeAttachment attachment)
{
var list = _policyAttachmentIndex.GetOrAdd(attachment.EffectivePolicyId, _ => new List<string>());
lock (list)
{
if (!list.Contains(attachment.AttachmentId))
{
list.Add(attachment.AttachmentId);
}
}
}
private void RemoveFromIndex(AuthorityScopeAttachment attachment)
{
if (_policyAttachmentIndex.TryGetValue(attachment.EffectivePolicyId, out var list))
{
lock (list)
{
list.Remove(attachment.AttachmentId);
}
}
}
private static string GeneratePolicyId(string tenantId, string policyId, string pattern, DateTimeOffset timestamp)
{
var seed = $"{tenantId}|{policyId}|{pattern}|{timestamp:O}";
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(seed));
return $"eff-{Convert.ToHexStringLower(hash)[..16]}";
}
private static string GenerateAttachmentId(string policyId, string scope, DateTimeOffset timestamp)
{
var seed = $"{policyId}|{scope}|{timestamp:O}";
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(seed));
return $"att-{Convert.ToHexStringLower(hash)[..16]}";
}
}

View File

@@ -107,3 +107,81 @@ public sealed record ScopeResolutionResult(
[property: JsonPropertyName("resolved_profile")] ResolvedScopeProfile? ResolvedProfile,
[property: JsonPropertyName("applicable_attachments")] IReadOnlyList<ScopeAttachment> ApplicableAttachments,
[property: JsonPropertyName("resolution_time_ms")] double ResolutionTimeMs);
/// <summary>
/// Effective policy attachment with subject pattern matching and priority rules.
/// Per CONTRACT-AUTHORITY-EFFECTIVE-WRITE-008.
/// </summary>
public sealed record EffectivePolicy(
[property: JsonPropertyName("effective_policy_id")] string EffectivePolicyId,
[property: JsonPropertyName("tenant_id")] string TenantId,
[property: JsonPropertyName("policy_id")] string PolicyId,
[property: JsonPropertyName("policy_version")] string? PolicyVersion,
[property: JsonPropertyName("subject_pattern")] string SubjectPattern,
[property: JsonPropertyName("priority")] int Priority,
[property: JsonPropertyName("enabled")] bool Enabled,
[property: JsonPropertyName("expires_at")] DateTimeOffset? ExpiresAt,
[property: JsonPropertyName("scopes")] IReadOnlyList<string>? Scopes,
[property: JsonPropertyName("created_at")] DateTimeOffset CreatedAt,
[property: JsonPropertyName("created_by")] string? CreatedBy,
[property: JsonPropertyName("updated_at")] DateTimeOffset UpdatedAt);
/// <summary>
/// Request to create an effective policy.
/// </summary>
public sealed record CreateEffectivePolicyRequest(
[property: JsonPropertyName("tenant_id")] string TenantId,
[property: JsonPropertyName("policy_id")] string PolicyId,
[property: JsonPropertyName("policy_version")] string? PolicyVersion,
[property: JsonPropertyName("subject_pattern")] string SubjectPattern,
[property: JsonPropertyName("priority")] int Priority,
[property: JsonPropertyName("enabled")] bool Enabled = true,
[property: JsonPropertyName("expires_at")] DateTimeOffset? ExpiresAt = null,
[property: JsonPropertyName("scopes")] IReadOnlyList<string>? Scopes = null);
/// <summary>
/// Request to update an effective policy.
/// </summary>
public sealed record UpdateEffectivePolicyRequest(
[property: JsonPropertyName("priority")] int? Priority = null,
[property: JsonPropertyName("enabled")] bool? Enabled = null,
[property: JsonPropertyName("expires_at")] DateTimeOffset? ExpiresAt = null,
[property: JsonPropertyName("scopes")] IReadOnlyList<string>? Scopes = null);
/// <summary>
/// Authority scope attachment with conditions.
/// </summary>
public sealed record AuthorityScopeAttachment(
[property: JsonPropertyName("attachment_id")] string AttachmentId,
[property: JsonPropertyName("effective_policy_id")] string EffectivePolicyId,
[property: JsonPropertyName("scope")] string Scope,
[property: JsonPropertyName("conditions")] Dictionary<string, string>? Conditions,
[property: JsonPropertyName("created_at")] DateTimeOffset CreatedAt);
/// <summary>
/// Request to attach an authority scope.
/// </summary>
public sealed record AttachAuthorityScopeRequest(
[property: JsonPropertyName("effective_policy_id")] string EffectivePolicyId,
[property: JsonPropertyName("scope")] string Scope,
[property: JsonPropertyName("conditions")] Dictionary<string, string>? Conditions = null);
/// <summary>
/// Result of resolving the effective policy for a subject.
/// </summary>
public sealed record EffectivePolicyResolutionResult(
[property: JsonPropertyName("subject")] string Subject,
[property: JsonPropertyName("effective_policy")] EffectivePolicy? EffectivePolicy,
[property: JsonPropertyName("granted_scopes")] IReadOnlyList<string> GrantedScopes,
[property: JsonPropertyName("matched_pattern")] string? MatchedPattern,
[property: JsonPropertyName("resolution_time_ms")] double ResolutionTimeMs);
/// <summary>
/// Query for listing effective policies.
/// </summary>
public sealed record EffectivePolicyQuery(
[property: JsonPropertyName("tenant_id")] string? TenantId = null,
[property: JsonPropertyName("policy_id")] string? PolicyId = null,
[property: JsonPropertyName("enabled_only")] bool EnabledOnly = true,
[property: JsonPropertyName("include_expired")] bool IncludeExpired = false,
[property: JsonPropertyName("limit")] int Limit = 100);