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
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:
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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 $$;
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user