feat(api): Implement Console Export Client and Models
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (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
mock-dev-release / package-mock-release (push) Has been cancelled

- Added ConsoleExportClient for managing export requests and responses.
- Introduced ConsoleExportRequest and ConsoleExportResponse models.
- Implemented methods for creating and retrieving exports with appropriate headers.

feat(crypto): Add Software SM2/SM3 Cryptography Provider

- Implemented SmSoftCryptoProvider for software-only SM2/SM3 cryptography.
- Added support for signing and verification using SM2 algorithm.
- Included hashing functionality with SM3 algorithm.
- Configured options for loading keys from files and environment gate checks.

test(crypto): Add unit tests for SmSoftCryptoProvider

- Created comprehensive tests for signing, verifying, and hashing functionalities.
- Ensured correct behavior for key management and error handling.

feat(api): Enhance Console Export Models

- Expanded ConsoleExport models to include detailed status and event types.
- Added support for various export formats and notification options.

test(time): Implement TimeAnchorPolicyService tests

- Developed tests for TimeAnchorPolicyService to validate time anchors.
- Covered scenarios for anchor validation, drift calculation, and policy enforcement.
This commit is contained in:
StellaOps Bot
2025-12-07 00:27:33 +02:00
parent 9bd6a73926
commit 0de92144d2
229 changed files with 32351 additions and 1481 deletions

View File

@@ -1,32 +1,218 @@
using System.Formats.Asn1;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using StellaOps.AirGap.Time.Models;
using StellaOps.AirGap.Time.Parsing;
namespace StellaOps.AirGap.Time.Services;
/// <summary>
/// Verifies RFC 3161 timestamp tokens using SignedCms and X509 certificate chain validation.
/// Per AIRGAP-TIME-57-001: Provides trusted time-anchor service with real crypto verification.
/// </summary>
public sealed class Rfc3161Verifier : ITimeTokenVerifier
{
// RFC 3161 OIDs
private static readonly Oid TstInfoOid = new("1.2.840.113549.1.9.16.1.4"); // id-ct-TSTInfo
private static readonly Oid SigningTimeOid = new("1.2.840.113549.1.9.5");
public TimeTokenFormat Format => TimeTokenFormat.Rfc3161;
public TimeAnchorValidationResult Verify(ReadOnlySpan<byte> tokenBytes, IReadOnlyList<TimeTrustRoot> trustRoots, out TimeAnchor anchor)
{
anchor = TimeAnchor.Unknown;
if (trustRoots.Count == 0)
{
return TimeAnchorValidationResult.Failure("trust-roots-required");
return TimeAnchorValidationResult.Failure("rfc3161-trust-roots-required");
}
if (tokenBytes.IsEmpty)
{
return TimeAnchorValidationResult.Failure("token-empty");
return TimeAnchorValidationResult.Failure("rfc3161-token-empty");
}
// Stub verification: derive anchor deterministically; rely on presence of trust roots for gating.
var digest = Convert.ToHexString(SHA256.HashData(tokenBytes)).ToLowerInvariant();
var seconds = BitConverter.ToUInt64(SHA256.HashData(tokenBytes).AsSpan(0, 8));
var anchorTime = DateTimeOffset.UnixEpoch.AddSeconds(seconds % (3600 * 24 * 365));
var signerKeyId = trustRoots.FirstOrDefault()?.KeyId ?? "unknown";
anchor = new TimeAnchor(anchorTime, "rfc3161-token", "RFC3161", signerKeyId, digest);
return TimeAnchorValidationResult.Success("rfc3161-stub-verified");
// Compute token digest for reference
var tokenDigest = Convert.ToHexString(SHA256.HashData(tokenBytes)).ToLowerInvariant();
try
{
// Parse the SignedCms structure
var signedCms = new SignedCms();
signedCms.Decode(tokenBytes.ToArray());
// Verify signature (basic check without chain building)
try
{
signedCms.CheckSignature(verifySignatureOnly: true);
}
catch (CryptographicException ex)
{
return TimeAnchorValidationResult.Failure($"rfc3161-signature-invalid:{ex.Message}");
}
// Extract the signing certificate
if (signedCms.SignerInfos.Count == 0)
{
return TimeAnchorValidationResult.Failure("rfc3161-no-signer");
}
var signerInfo = signedCms.SignerInfos[0];
var signerCert = signerInfo.Certificate;
if (signerCert is null)
{
return TimeAnchorValidationResult.Failure("rfc3161-no-signer-certificate");
}
// Validate signer certificate against trust roots
var validRoot = ValidateAgainstTrustRoots(signerCert, trustRoots);
if (validRoot is null)
{
return TimeAnchorValidationResult.Failure("rfc3161-certificate-not-trusted");
}
// Extract signing time from the TSTInfo or signed attributes
var signingTime = ExtractSigningTime(signedCms, signerInfo);
if (signingTime is null)
{
return TimeAnchorValidationResult.Failure("rfc3161-no-signing-time");
}
// Compute certificate fingerprint
var certFingerprint = Convert.ToHexString(SHA256.HashData(signerCert.RawData)).ToLowerInvariant()[..16];
anchor = new TimeAnchor(
signingTime.Value,
$"rfc3161:{validRoot.KeyId}",
"RFC3161",
certFingerprint,
tokenDigest);
return TimeAnchorValidationResult.Success("rfc3161-verified");
}
catch (CryptographicException ex)
{
return TimeAnchorValidationResult.Failure($"rfc3161-decode-error:{ex.Message}");
}
catch (Exception ex)
{
return TimeAnchorValidationResult.Failure($"rfc3161-error:{ex.Message}");
}
}
private static TimeTrustRoot? ValidateAgainstTrustRoots(X509Certificate2 signerCert, IReadOnlyList<TimeTrustRoot> trustRoots)
{
foreach (var root in trustRoots)
{
// Match by certificate thumbprint or subject key identifier
try
{
// Try direct certificate match
var rootCert = X509CertificateLoader.LoadCertificate(root.PublicKey);
if (signerCert.Thumbprint.Equals(rootCert.Thumbprint, StringComparison.OrdinalIgnoreCase))
{
return root;
}
// Try chain validation against root
using var chain = new X509Chain();
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(rootCert);
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; // Offline mode
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
if (chain.Build(signerCert))
{
return root;
}
}
catch
{
// Invalid root certificate format, try next
continue;
}
}
return null;
}
private static DateTimeOffset? ExtractSigningTime(SignedCms signedCms, SignerInfo signerInfo)
{
// Try to get signing time from signed attributes
foreach (var attr in signerInfo.SignedAttributes)
{
if (attr.Oid.Value == SigningTimeOid.Value)
{
try
{
var reader = new AsnReader(attr.Values[0].RawData, AsnEncodingRules.DER);
var time = reader.ReadUtcTime();
return time;
}
catch
{
continue;
}
}
}
// Try to extract from TSTInfo content
try
{
var content = signedCms.ContentInfo;
if (content.ContentType.Value == TstInfoOid.Value)
{
var tstInfo = ParseTstInfo(content.Content);
if (tstInfo.HasValue)
{
return tstInfo.Value;
}
}
}
catch
{
// Fall through
}
return null;
}
private static DateTimeOffset? ParseTstInfo(ReadOnlyMemory<byte> tstInfoBytes)
{
// TSTInfo ::= SEQUENCE {
// version INTEGER,
// policy OBJECT IDENTIFIER,
// messageImprint MessageImprint,
// serialNumber INTEGER,
// genTime GeneralizedTime,
// ...
// }
try
{
var reader = new AsnReader(tstInfoBytes, AsnEncodingRules.DER);
var sequenceReader = reader.ReadSequence();
// Skip version
sequenceReader.ReadInteger();
// Skip policy OID
sequenceReader.ReadObjectIdentifier();
// Skip messageImprint (SEQUENCE)
sequenceReader.ReadSequence();
// Skip serialNumber
sequenceReader.ReadInteger();
// Read genTime (GeneralizedTime)
var genTime = sequenceReader.ReadGeneralizedTime();
return genTime;
}
catch
{
return null;
}
}
}