Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled

- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management.
- Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management.
- Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support.
- Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
This commit is contained in:
master
2025-12-16 16:40:19 +02:00
parent 415eff1207
commit 2170a58734
206 changed files with 30547 additions and 534 deletions

View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;
namespace StellaOps.Signer.KeyManagement.Entities;
/// <summary>
/// Key history entry for tracking key lifecycle.
/// Maps to signer.key_history table.
/// </summary>
[Table("key_history", Schema = "signer")]
public class KeyHistoryEntity
{
/// <summary>
/// Primary key.
/// </summary>
[Key]
[Column("history_id")]
public Guid HistoryId { get; set; }
/// <summary>
/// Reference to the trust anchor.
/// </summary>
[Required]
[Column("anchor_id")]
public Guid AnchorId { get; set; }
/// <summary>
/// The key ID.
/// </summary>
[Required]
[Column("key_id")]
public string KeyId { get; set; } = null!;
/// <summary>
/// The public key in PEM format.
/// </summary>
[Required]
[Column("public_key")]
public string PublicKey { get; set; } = null!;
/// <summary>
/// The algorithm (Ed25519, RSA-4096, etc.).
/// </summary>
[Required]
[Column("algorithm")]
public string Algorithm { get; set; } = null!;
/// <summary>
/// When the key was added.
/// </summary>
[Column("added_at")]
public DateTimeOffset AddedAt { get; set; }
/// <summary>
/// When the key was revoked (null if still active).
/// </summary>
[Column("revoked_at")]
public DateTimeOffset? RevokedAt { get; set; }
/// <summary>
/// Reason for revocation.
/// </summary>
[Column("revoke_reason")]
public string? RevokeReason { get; set; }
/// <summary>
/// Optional expiry date.
/// </summary>
[Column("expires_at")]
public DateTimeOffset? ExpiresAt { get; set; }
/// <summary>
/// Optional metadata.
/// </summary>
[Column("metadata", TypeName = "jsonb")]
public JsonDocument? Metadata { get; set; }
/// <summary>
/// When this record was created.
/// </summary>
[Column("created_at")]
public DateTimeOffset CreatedAt { get; set; }
}
/// <summary>
/// Key audit log entry for tracking all key operations.
/// Maps to signer.key_audit_log table.
/// </summary>
[Table("key_audit_log", Schema = "signer")]
public class KeyAuditLogEntity
{
/// <summary>
/// Primary key.
/// </summary>
[Key]
[Column("log_id")]
public Guid LogId { get; set; }
/// <summary>
/// Reference to the trust anchor.
/// </summary>
[Required]
[Column("anchor_id")]
public Guid AnchorId { get; set; }
/// <summary>
/// The key ID affected (if applicable).
/// </summary>
[Column("key_id")]
public string? KeyId { get; set; }
/// <summary>
/// The operation performed.
/// </summary>
[Required]
[Column("operation")]
public string Operation { get; set; } = null!;
/// <summary>
/// The actor who performed the operation.
/// </summary>
[Column("actor")]
public string? Actor { get; set; }
/// <summary>
/// The old state before the operation.
/// </summary>
[Column("old_state", TypeName = "jsonb")]
public JsonDocument? OldState { get; set; }
/// <summary>
/// The new state after the operation.
/// </summary>
[Column("new_state", TypeName = "jsonb")]
public JsonDocument? NewState { get; set; }
/// <summary>
/// Additional details about the operation.
/// </summary>
[Column("details", TypeName = "jsonb")]
public JsonDocument? Details { get; set; }
/// <summary>
/// IP address of the requestor.
/// </summary>
[Column("ip_address")]
public string? IpAddress { get; set; }
/// <summary>
/// User agent of the requestor.
/// </summary>
[Column("user_agent")]
public string? UserAgent { get; set; }
/// <summary>
/// When this audit entry was created.
/// </summary>
[Column("created_at")]
public DateTimeOffset CreatedAt { get; set; }
}

View File

@@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Signer.KeyManagement;
/// <summary>
/// Service for managing key rotation operations.
/// Implements advisory §8.2 key rotation workflow.
/// </summary>
public interface IKeyRotationService
{
/// <summary>
/// Add a new signing key to a trust anchor.
/// </summary>
/// <param name="anchorId">The trust anchor ID.</param>
/// <param name="request">The add key request.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The result of the operation.</returns>
Task<KeyRotationResult> AddKeyAsync(
Guid anchorId,
AddKeyRequest request,
CancellationToken ct = default);
/// <summary>
/// Revoke a signing key from a trust anchor.
/// The key is moved to revokedKeys and remains valid for proofs signed before revocation.
/// </summary>
/// <param name="anchorId">The trust anchor ID.</param>
/// <param name="keyId">The key ID to revoke.</param>
/// <param name="request">The revoke request with reason.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The result of the operation.</returns>
Task<KeyRotationResult> RevokeKeyAsync(
Guid anchorId,
string keyId,
RevokeKeyRequest request,
CancellationToken ct = default);
/// <summary>
/// Check if a key was valid at a specific point in time.
/// This is used for verifying historical proofs.
/// </summary>
/// <param name="anchorId">The trust anchor ID.</param>
/// <param name="keyId">The key ID to check.</param>
/// <param name="signedAt">The time the signature was created.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The key validity result.</returns>
Task<KeyValidityResult> CheckKeyValidityAsync(
Guid anchorId,
string keyId,
DateTimeOffset signedAt,
CancellationToken ct = default);
/// <summary>
/// Get rotation warnings for a trust anchor (e.g., keys approaching expiry).
/// </summary>
/// <param name="anchorId">The trust anchor ID.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>List of rotation warnings.</returns>
Task<IReadOnlyList<KeyRotationWarning>> GetRotationWarningsAsync(
Guid anchorId,
CancellationToken ct = default);
/// <summary>
/// Get the full key history for a trust anchor.
/// </summary>
/// <param name="anchorId">The trust anchor ID.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The key history entries.</returns>
Task<IReadOnlyList<KeyHistoryEntry>> GetKeyHistoryAsync(
Guid anchorId,
CancellationToken ct = default);
}
/// <summary>
/// Request to add a new key.
/// </summary>
public sealed record AddKeyRequest
{
/// <summary>
/// The key ID (unique identifier).
/// </summary>
public required string KeyId { get; init; }
/// <summary>
/// The public key in PEM format.
/// </summary>
public required string PublicKey { get; init; }
/// <summary>
/// The algorithm (Ed25519, RSA-4096, etc.).
/// </summary>
public required string Algorithm { get; init; }
/// <summary>
/// Optional expiry date for the key.
/// </summary>
public DateTimeOffset? ExpiresAt { get; init; }
/// <summary>
/// Optional metadata about the key.
/// </summary>
public IReadOnlyDictionary<string, string>? Metadata { get; init; }
}
/// <summary>
/// Request to revoke a key.
/// </summary>
public sealed record RevokeKeyRequest
{
/// <summary>
/// Reason for revocation.
/// </summary>
public required string Reason { get; init; }
/// <summary>
/// When the revocation takes effect. Defaults to now.
/// </summary>
public DateTimeOffset? EffectiveAt { get; init; }
}
/// <summary>
/// Result of a key rotation operation.
/// </summary>
public sealed record KeyRotationResult
{
/// <summary>
/// Whether the operation succeeded.
/// </summary>
public required bool Success { get; init; }
/// <summary>
/// The updated allowed key IDs.
/// </summary>
public required IReadOnlyList<string> AllowedKeyIds { get; init; }
/// <summary>
/// The updated revoked key IDs.
/// </summary>
public required IReadOnlyList<string> RevokedKeyIds { get; init; }
/// <summary>
/// Error message if operation failed.
/// </summary>
public string? ErrorMessage { get; init; }
/// <summary>
/// Audit log entry ID for this operation.
/// </summary>
public Guid? AuditLogId { get; init; }
}
/// <summary>
/// Result of key validity check.
/// </summary>
public sealed record KeyValidityResult
{
/// <summary>
/// Whether the key was valid at the specified time.
/// </summary>
public required bool IsValid { get; init; }
/// <summary>
/// The status of the key.
/// </summary>
public required KeyStatus Status { get; init; }
/// <summary>
/// When the key was added.
/// </summary>
public required DateTimeOffset AddedAt { get; init; }
/// <summary>
/// When the key was revoked (if applicable).
/// </summary>
public DateTimeOffset? RevokedAt { get; init; }
/// <summary>
/// Reason why the key is invalid (if applicable).
/// </summary>
public string? InvalidReason { get; init; }
}
/// <summary>
/// Status of a key.
/// </summary>
public enum KeyStatus
{
/// <summary>Key is active and can be used for signing.</summary>
Active,
/// <summary>Key was revoked but may be valid for historical proofs.</summary>
Revoked,
/// <summary>Key has expired.</summary>
Expired,
/// <summary>Key was not valid at the specified time (signed before key was added).</summary>
NotYetValid,
/// <summary>Key is unknown.</summary>
Unknown
}
/// <summary>
/// A warning about key rotation needs.
/// </summary>
public sealed record KeyRotationWarning
{
/// <summary>
/// The key ID this warning applies to.
/// </summary>
public required string KeyId { get; init; }
/// <summary>
/// The warning type.
/// </summary>
public required RotationWarningType WarningType { get; init; }
/// <summary>
/// Human-readable message.
/// </summary>
public required string Message { get; init; }
/// <summary>
/// When the warning becomes critical (e.g., expiry date).
/// </summary>
public DateTimeOffset? CriticalAt { get; init; }
}
/// <summary>
/// Types of rotation warnings.
/// </summary>
public enum RotationWarningType
{
/// <summary>Key is approaching expiry.</summary>
ExpiryApproaching,
/// <summary>Key has been active for a long time.</summary>
LongLived,
/// <summary>Algorithm is being deprecated.</summary>
AlgorithmDeprecating,
/// <summary>Key has high usage count.</summary>
HighUsage
}
/// <summary>
/// Entry in the key history.
/// </summary>
public sealed record KeyHistoryEntry
{
/// <summary>
/// The key ID.
/// </summary>
public required string KeyId { get; init; }
/// <summary>
/// When the key was added.
/// </summary>
public required DateTimeOffset AddedAt { get; init; }
/// <summary>
/// When the key was revoked (if applicable).
/// </summary>
public DateTimeOffset? RevokedAt { get; init; }
/// <summary>
/// Reason for revocation (if applicable).
/// </summary>
public string? RevokeReason { get; init; }
/// <summary>
/// The algorithm of the key.
/// </summary>
public required string Algorithm { get; init; }
/// <summary>
/// Optional expiry date.
/// </summary>
public DateTimeOffset? ExpiresAt { get; init; }
}

View File

@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace StellaOps.Signer.KeyManagement;
/// <summary>
/// Manages trust anchors and their key bindings.
/// Implements advisory §8.3 trust anchor structure.
/// </summary>
public interface ITrustAnchorManager
{
/// <summary>
/// Get a trust anchor by ID.
/// </summary>
/// <param name="anchorId">The anchor ID.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The trust anchor or null.</returns>
Task<TrustAnchorInfo?> GetAnchorAsync(
Guid anchorId,
CancellationToken ct = default);
/// <summary>
/// Find a trust anchor matching a PURL.
/// Uses pattern matching (e.g., pkg:npm/* matches pkg:npm/lodash@4.17.21).
/// </summary>
/// <param name="purl">The PURL to match.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The matching trust anchor or null.</returns>
Task<TrustAnchorInfo?> FindAnchorForPurlAsync(
string purl,
CancellationToken ct = default);
/// <summary>
/// Create a new trust anchor.
/// </summary>
/// <param name="request">The creation request.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The created trust anchor.</returns>
Task<TrustAnchorInfo> CreateAnchorAsync(
CreateTrustAnchorRequest request,
CancellationToken ct = default);
/// <summary>
/// Update a trust anchor.
/// </summary>
/// <param name="anchorId">The anchor ID.</param>
/// <param name="request">The update request.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The updated trust anchor.</returns>
Task<TrustAnchorInfo> UpdateAnchorAsync(
Guid anchorId,
UpdateTrustAnchorRequest request,
CancellationToken ct = default);
/// <summary>
/// Deactivate a trust anchor (soft delete).
/// </summary>
/// <param name="anchorId">The anchor ID.</param>
/// <param name="ct">Cancellation token.</param>
Task DeactivateAnchorAsync(
Guid anchorId,
CancellationToken ct = default);
/// <summary>
/// Verify a signature against a trust anchor's allowed keys.
/// Supports temporal verification for historical proofs.
/// </summary>
/// <param name="anchorId">The anchor ID.</param>
/// <param name="keyId">The key ID that signed.</param>
/// <param name="signedAt">When the signature was created.</param>
/// <param name="predicateType">The predicate type (if restricted).</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>The verification result.</returns>
Task<TrustVerificationResult> VerifySignatureAuthorizationAsync(
Guid anchorId,
string keyId,
DateTimeOffset signedAt,
string? predicateType = null,
CancellationToken ct = default);
/// <summary>
/// Get all active trust anchors.
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>List of active anchors.</returns>
Task<IReadOnlyList<TrustAnchorInfo>> GetActiveAnchorsAsync(
CancellationToken ct = default);
}
/// <summary>
/// Full trust anchor information including key history.
/// </summary>
public sealed record TrustAnchorInfo
{
/// <summary>
/// The anchor ID.
/// </summary>
public required Guid AnchorId { get; init; }
/// <summary>
/// PURL glob pattern.
/// </summary>
public required string PurlPattern { get; init; }
/// <summary>
/// Currently allowed key IDs.
/// </summary>
public required IReadOnlyList<string> AllowedKeyIds { get; init; }
/// <summary>
/// Allowed predicate types (null = all).
/// </summary>
public IReadOnlyList<string>? AllowedPredicateTypes { get; init; }
/// <summary>
/// Policy reference.
/// </summary>
public string? PolicyRef { get; init; }
/// <summary>
/// Policy version.
/// </summary>
public string? PolicyVersion { get; init; }
/// <summary>
/// Revoked key IDs (still valid for historical proofs).
/// </summary>
public required IReadOnlyList<string> RevokedKeyIds { get; init; }
/// <summary>
/// Full key history.
/// </summary>
public required IReadOnlyList<KeyHistoryEntry> KeyHistory { get; init; }
/// <summary>
/// Whether the anchor is active.
/// </summary>
public bool IsActive { get; init; } = true;
/// <summary>
/// When the anchor was created.
/// </summary>
public required DateTimeOffset CreatedAt { get; init; }
/// <summary>
/// When the anchor was last updated.
/// </summary>
public required DateTimeOffset UpdatedAt { get; init; }
}
/// <summary>
/// Request to create a trust anchor.
/// </summary>
public sealed record CreateTrustAnchorRequest
{
/// <summary>
/// PURL glob pattern.
/// </summary>
public required string PurlPattern { get; init; }
/// <summary>
/// Initial allowed key IDs.
/// </summary>
public required IReadOnlyList<string> AllowedKeyIds { get; init; }
/// <summary>
/// Allowed predicate types (null = all).
/// </summary>
public IReadOnlyList<string>? AllowedPredicateTypes { get; init; }
/// <summary>
/// Policy reference.
/// </summary>
public string? PolicyRef { get; init; }
/// <summary>
/// Policy version.
/// </summary>
public string? PolicyVersion { get; init; }
}
/// <summary>
/// Request to update a trust anchor.
/// </summary>
public sealed record UpdateTrustAnchorRequest
{
/// <summary>
/// Updated predicate types.
/// </summary>
public IReadOnlyList<string>? AllowedPredicateTypes { get; init; }
/// <summary>
/// Updated policy reference.
/// </summary>
public string? PolicyRef { get; init; }
/// <summary>
/// Updated policy version.
/// </summary>
public string? PolicyVersion { get; init; }
}
/// <summary>
/// Result of trust verification.
/// </summary>
public sealed record TrustVerificationResult
{
/// <summary>
/// Whether the signature is authorized.
/// </summary>
public required bool IsAuthorized { get; init; }
/// <summary>
/// Reason for authorization failure (if applicable).
/// </summary>
public string? FailureReason { get; init; }
/// <summary>
/// The key status at the time of signing.
/// </summary>
public required KeyStatus KeyStatus { get; init; }
/// <summary>
/// Whether the predicate type was allowed.
/// </summary>
public bool? PredicateTypeAllowed { get; init; }
}

View File

@@ -0,0 +1,74 @@
-- Migration: 20251214000001_AddKeyManagementSchema
-- Creates the key management schema for key rotation and trust anchor management.
-- Create schema
CREATE SCHEMA IF NOT EXISTS signer;
-- Key history table (tracks all keys ever added to trust anchors)
CREATE TABLE IF NOT EXISTS signer.key_history (
history_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
anchor_id UUID NOT NULL,
key_id TEXT NOT NULL,
public_key TEXT NOT NULL,
algorithm TEXT NOT NULL,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revoked_at TIMESTAMPTZ,
revoke_reason TEXT,
expires_at TIMESTAMPTZ,
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Unique constraint for key_id within an anchor
CONSTRAINT uq_key_history_anchor_key UNIQUE (anchor_id, key_id)
);
CREATE INDEX IF NOT EXISTS idx_key_history_anchor ON signer.key_history(anchor_id);
CREATE INDEX IF NOT EXISTS idx_key_history_key_id ON signer.key_history(key_id);
CREATE INDEX IF NOT EXISTS idx_key_history_added ON signer.key_history(added_at);
CREATE INDEX IF NOT EXISTS idx_key_history_revoked ON signer.key_history(revoked_at) WHERE revoked_at IS NOT NULL;
COMMENT ON TABLE signer.key_history IS 'Tracks all keys ever added to trust anchors for historical verification';
COMMENT ON COLUMN signer.key_history.revoke_reason IS 'Reason for revocation (e.g., rotation-complete, compromised)';
-- Key audit log table (tracks all key operations)
CREATE TABLE IF NOT EXISTS signer.key_audit_log (
log_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
anchor_id UUID NOT NULL,
key_id TEXT,
operation TEXT NOT NULL,
actor TEXT,
old_state JSONB,
new_state JSONB,
details JSONB,
ip_address TEXT,
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_key_audit_anchor ON signer.key_audit_log(anchor_id);
CREATE INDEX IF NOT EXISTS idx_key_audit_key ON signer.key_audit_log(key_id) WHERE key_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_key_audit_operation ON signer.key_audit_log(operation);
CREATE INDEX IF NOT EXISTS idx_key_audit_created ON signer.key_audit_log(created_at DESC);
COMMENT ON TABLE signer.key_audit_log IS 'Audit log for all key management operations';
COMMENT ON COLUMN signer.key_audit_log.operation IS 'Operation type: add_key, revoke_key, create_anchor, update_anchor, etc.';
-- Optional: Create foreign key to proofchain.trust_anchors if that schema exists
-- This is conditional to avoid errors if the other schema doesn't exist yet
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'proofchain' AND table_name = 'trust_anchors'
) THEN
ALTER TABLE signer.key_history
ADD CONSTRAINT fk_key_history_anchor
FOREIGN KEY (anchor_id) REFERENCES proofchain.trust_anchors(anchor_id)
ON DELETE CASCADE;
ALTER TABLE signer.key_audit_log
ADD CONSTRAINT fk_key_audit_anchor
FOREIGN KEY (anchor_id) REFERENCES proofchain.trust_anchors(anchor_id)
ON DELETE CASCADE;
END IF;
END $$;

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<RootNamespace>StellaOps.Signer.KeyManagement</RootNamespace>
<Description>Key rotation and trust anchor management for StellaOps signing infrastructure.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0-preview.*" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0-preview.*" />
</ItemGroup>
<ItemGroup>
<None Include="Migrations\*.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>