license switch agpl -> busl1, sprints work, new product advisories

This commit is contained in:
master
2026-01-20 15:32:20 +02:00
parent 4903395618
commit c32fff8f86
1835 changed files with 38630 additions and 4359 deletions

View File

@@ -39,6 +39,12 @@ public sealed record VerificationReportPredicate
[JsonPropertyName("generator")]
public required GeneratorInfo Generator { get; init; }
/// <summary>
/// Verifier metadata for the signed report.
/// </summary>
[JsonPropertyName("verifier")]
public VerifierInfo? Verifier { get; init; }
/// <summary>
/// Subject being verified.
/// </summary>
@@ -88,6 +94,25 @@ public sealed record GeneratorInfo
public HostInfo? HostInfo { get; init; }
}
/// <summary>
/// Verifier metadata captured when a report is signed.
/// </summary>
public sealed record VerifierInfo
{
/// <summary>Signature algorithm identifier.</summary>
[JsonPropertyName("algo")]
public string? Algo { get; init; }
/// <summary>Signing certificate or chain (PEM).</summary>
[JsonPropertyName("cert")]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public string? Cert { get; init; }
/// <summary>When the report was signed (UTC).</summary>
[JsonPropertyName("signed_at")]
public DateTimeOffset? SignedAt { get; init; }
}
/// <summary>
/// Host information.
/// </summary>

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using System.Security.Cryptography;

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using System.Text.Json.Serialization;

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using System.Text.Json.Serialization;

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using System.Globalization;

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using System.Text.Json;

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

View File

@@ -1,5 +1,5 @@
// <copyright file="GraphType.cs" company="StellaOps">
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-License-Identifier: BUSL-1.1
// </copyright>
namespace StellaOps.Attestor.GraphRoot;

View File

@@ -1,5 +1,5 @@
// <copyright file="BuildAttestationMapper.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;

View File

@@ -1,5 +1,5 @@
// <copyright file="BuildRelationshipBuilder.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;

View File

@@ -1,5 +1,5 @@
// <copyright file="CombinedDocumentBuilder.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;

View File

@@ -1,5 +1,5 @@
// <copyright file="DsseSpdx3Signer.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;

View File

@@ -1,5 +1,5 @@
// <copyright file="IBuildAttestationMapper.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using StellaOps.Spdx3.Model.Build;

View File

@@ -72,6 +72,25 @@ public sealed class BinaryDiffDsseVerifier : IBinaryDiffDsseVerifier
return BinaryDiffVerificationResult.Failure("DSSE signature verification failed.");
}
JsonDocument document;
try
{
document = JsonDocument.Parse(envelope.Payload);
}
catch (JsonException ex)
{
return BinaryDiffVerificationResult.Failure($"Failed to parse predicate JSON: {ex.Message}");
}
using (document)
{
var schemaResult = BinaryDiffSchema.Validate(document.RootElement);
if (!schemaResult.IsValid)
{
return BinaryDiffVerificationResult.Failure("Schema validation failed.", schemaResult.Errors);
}
}
BinaryDiffPredicate predicate;
try
{
@@ -87,13 +106,6 @@ public sealed class BinaryDiffDsseVerifier : IBinaryDiffDsseVerifier
return BinaryDiffVerificationResult.Failure("Predicate type does not match BinaryDiffV1.");
}
using var document = JsonDocument.Parse(envelope.Payload);
var schemaResult = BinaryDiffSchema.Validate(document.RootElement);
if (!schemaResult.IsValid)
{
return BinaryDiffVerificationResult.Failure("Schema validation failed.", schemaResult.Errors);
}
if (!HasDeterministicOrdering(predicate))
{
return BinaryDiffVerificationResult.Failure("Predicate ordering is not deterministic.");

View File

@@ -0,0 +1,35 @@
namespace StellaOps.Attestor.StandardPredicates.Licensing;
public abstract record SpdxLicenseExpression;
public sealed record SpdxSimpleLicense(string LicenseId) : SpdxLicenseExpression;
public sealed record SpdxConjunctiveLicense(
SpdxLicenseExpression Left,
SpdxLicenseExpression Right) : SpdxLicenseExpression;
public sealed record SpdxDisjunctiveLicense(
SpdxLicenseExpression Left,
SpdxLicenseExpression Right) : SpdxLicenseExpression;
public sealed record SpdxWithException(
SpdxLicenseExpression License,
string Exception) : SpdxLicenseExpression;
public sealed record SpdxNoneLicense : SpdxLicenseExpression
{
public static SpdxNoneLicense Instance { get; } = new();
private SpdxNoneLicense()
{
}
}
public sealed record SpdxNoAssertionLicense : SpdxLicenseExpression
{
public static SpdxNoAssertionLicense Instance { get; } = new();
private SpdxNoAssertionLicense()
{
}
}

View File

@@ -0,0 +1,406 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json;
namespace StellaOps.Attestor.StandardPredicates.Licensing;
public enum SpdxLicenseListVersion
{
V3_21
}
public sealed record SpdxLicenseList
{
public required string Version { get; init; }
public required ImmutableHashSet<string> LicenseIds { get; init; }
public required ImmutableHashSet<string> ExceptionIds { get; init; }
}
public static class SpdxLicenseListProvider
{
private const string LicenseResource = "StellaOps.Attestor.StandardPredicates.Resources.spdx-license-list-3.21.json";
private const string ExceptionResource = "StellaOps.Attestor.StandardPredicates.Resources.spdx-license-exceptions-3.21.json";
private static readonly Lazy<SpdxLicenseList> LicenseListV321 = new(LoadV321);
public static SpdxLicenseList Get(SpdxLicenseListVersion version)
=> version switch
{
SpdxLicenseListVersion.V3_21 => LicenseListV321.Value,
_ => LicenseListV321.Value,
};
private static SpdxLicenseList LoadV321()
{
var assembly = Assembly.GetExecutingAssembly();
var licenseIds = LoadLicenseIds(assembly, LicenseResource, "licenses", "licenseId");
var exceptionIds = LoadLicenseIds(assembly, ExceptionResource, "exceptions", "licenseExceptionId");
return new SpdxLicenseList
{
Version = "3.21",
LicenseIds = licenseIds,
ExceptionIds = exceptionIds,
};
}
private static ImmutableHashSet<string> LoadLicenseIds(
Assembly assembly,
string resourceName,
string arrayProperty,
string idProperty)
{
using var stream = assembly.GetManifestResourceStream(resourceName)
?? throw new InvalidOperationException($"Missing embedded resource: {resourceName}");
using var document = JsonDocument.Parse(stream);
if (!document.RootElement.TryGetProperty(arrayProperty, out var array) ||
array.ValueKind != JsonValueKind.Array)
{
return ImmutableHashSet<string>.Empty;
}
var builder = ImmutableHashSet.CreateBuilder<string>(StringComparer.Ordinal);
foreach (var entry in array.EnumerateArray())
{
if (entry.TryGetProperty(idProperty, out var idElement) &&
idElement.ValueKind == JsonValueKind.String &&
idElement.GetString() is { Length: > 0 } id)
{
builder.Add(id);
}
}
return builder.ToImmutable();
}
}
public static class SpdxLicenseExpressionParser
{
public static bool TryParse(string expression, out SpdxLicenseExpression? result, SpdxLicenseList? licenseList = null)
{
result = null;
if (string.IsNullOrWhiteSpace(expression))
{
return false;
}
try
{
result = Parse(expression, licenseList);
return true;
}
catch (FormatException)
{
return false;
}
}
public static SpdxLicenseExpression Parse(string expression, SpdxLicenseList? licenseList = null)
{
if (string.IsNullOrWhiteSpace(expression))
{
throw new FormatException("License expression is empty.");
}
var tokens = Tokenize(expression);
var parser = new Parser(tokens);
var parsed = parser.ParseExpression();
if (parser.HasMoreTokens)
{
throw new FormatException("Unexpected trailing tokens in license expression.");
}
if (licenseList is not null)
{
Validate(parsed, licenseList);
}
return parsed;
}
private static void Validate(SpdxLicenseExpression expression, SpdxLicenseList list)
{
switch (expression)
{
case SpdxSimpleLicense simple:
if (IsSpecial(simple.LicenseId) || IsLicenseRef(simple.LicenseId))
{
return;
}
if (!list.LicenseIds.Contains(simple.LicenseId))
{
throw new FormatException($"Unknown SPDX license identifier: {simple.LicenseId}");
}
break;
case SpdxWithException withException:
Validate(withException.License, list);
if (!list.ExceptionIds.Contains(withException.Exception))
{
throw new FormatException($"Unknown SPDX license exception: {withException.Exception}");
}
break;
case SpdxConjunctiveLicense conjunctive:
Validate(conjunctive.Left, list);
Validate(conjunctive.Right, list);
break;
case SpdxDisjunctiveLicense disjunctive:
Validate(disjunctive.Left, list);
Validate(disjunctive.Right, list);
break;
case SpdxNoneLicense:
case SpdxNoAssertionLicense:
break;
default:
throw new FormatException("Unsupported SPDX license expression node.");
}
}
private static bool IsSpecial(string licenseId)
=> string.Equals(licenseId, "NONE", StringComparison.Ordinal)
|| string.Equals(licenseId, "NOASSERTION", StringComparison.Ordinal);
private static bool IsLicenseRef(string licenseId)
=> licenseId.StartsWith("LicenseRef-", StringComparison.Ordinal)
|| licenseId.StartsWith("DocumentRef-", StringComparison.Ordinal);
private static List<Token> Tokenize(string expression)
{
var tokens = new List<Token>();
var buffer = new StringBuilder();
void Flush()
{
if (buffer.Length == 0)
{
return;
}
var value = buffer.ToString();
buffer.Clear();
tokens.Add(Token.From(value));
}
foreach (var ch in expression)
{
switch (ch)
{
case '(':
Flush();
tokens.Add(new Token(TokenType.OpenParen, "("));
break;
case ')':
Flush();
tokens.Add(new Token(TokenType.CloseParen, ")"));
break;
default:
if (char.IsWhiteSpace(ch))
{
Flush();
}
else
{
buffer.Append(ch);
}
break;
}
}
Flush();
return tokens;
}
private sealed class Parser
{
private readonly IReadOnlyList<Token> _tokens;
private int _index;
public Parser(IReadOnlyList<Token> tokens)
{
_tokens = tokens;
}
public bool HasMoreTokens => _index < _tokens.Count;
public SpdxLicenseExpression ParseExpression()
{
var left = ParseWith();
while (TryMatch(TokenType.And, out var op) || TryMatch(TokenType.Or, out op))
{
var right = ParseWith();
left = op!.Type == TokenType.And
? new SpdxConjunctiveLicense(left, right)
: new SpdxDisjunctiveLicense(left, right);
}
return left;
}
private SpdxLicenseExpression ParseWith()
{
var left = ParsePrimary();
if (TryMatch(TokenType.With, out var withToken))
{
var exception = Expect(TokenType.Identifier);
left = new SpdxWithException(left, exception.Value);
}
return left;
}
private SpdxLicenseExpression ParsePrimary()
{
if (TryMatch(TokenType.OpenParen, out _))
{
var inner = ParseExpression();
Expect(TokenType.CloseParen);
return inner;
}
var token = Expect(TokenType.Identifier);
if (string.Equals(token.Value, "NONE", StringComparison.OrdinalIgnoreCase))
{
return SpdxNoneLicense.Instance;
}
if (string.Equals(token.Value, "NOASSERTION", StringComparison.OrdinalIgnoreCase))
{
return SpdxNoAssertionLicense.Instance;
}
return new SpdxSimpleLicense(token.Value);
}
private bool TryMatch(TokenType type, out Token? token)
{
token = null;
if (_index >= _tokens.Count)
{
return false;
}
var candidate = _tokens[_index];
if (candidate.Type != type)
{
return false;
}
_index++;
token = candidate;
return true;
}
private Token Expect(TokenType type)
{
if (_index >= _tokens.Count)
{
throw new FormatException($"Expected {type} but reached end of expression.");
}
var token = _tokens[_index++];
if (token.Type != type)
{
throw new FormatException($"Expected {type} but found {token.Type}.");
}
return token;
}
}
private sealed record Token(TokenType Type, string Value)
{
public static Token From(string value)
{
var normalized = value.Trim();
if (string.Equals(normalized, "AND", StringComparison.OrdinalIgnoreCase))
{
return new Token(TokenType.And, "AND");
}
if (string.Equals(normalized, "OR", StringComparison.OrdinalIgnoreCase))
{
return new Token(TokenType.Or, "OR");
}
if (string.Equals(normalized, "WITH", StringComparison.OrdinalIgnoreCase))
{
return new Token(TokenType.With, "WITH");
}
return new Token(TokenType.Identifier, normalized);
}
}
private enum TokenType
{
Identifier,
And,
Or,
With,
OpenParen,
CloseParen
}
}
public static class SpdxLicenseExpressionRenderer
{
public static string Render(SpdxLicenseExpression expression)
{
return RenderInternal(expression, parentOperator: null);
}
private static string RenderInternal(SpdxLicenseExpression expression, SpdxBinaryOperator? parentOperator)
{
switch (expression)
{
case SpdxSimpleLicense simple:
return simple.LicenseId;
case SpdxNoneLicense:
return "NONE";
case SpdxNoAssertionLicense:
return "NOASSERTION";
case SpdxWithException withException:
var licenseText = RenderInternal(withException.License, parentOperator: null);
return $"{licenseText} WITH {withException.Exception}";
case SpdxConjunctiveLicense conjunctive:
return RenderBinary(conjunctive.Left, conjunctive.Right, "AND", SpdxBinaryOperator.And, parentOperator);
case SpdxDisjunctiveLicense disjunctive:
return RenderBinary(disjunctive.Left, disjunctive.Right, "OR", SpdxBinaryOperator.Or, parentOperator);
default:
throw new InvalidOperationException("Unsupported SPDX license expression node.");
}
}
private static string RenderBinary(
SpdxLicenseExpression left,
SpdxLicenseExpression right,
string op,
SpdxBinaryOperator current,
SpdxBinaryOperator? parent)
{
var leftText = RenderInternal(left, current);
var rightText = RenderInternal(right, current);
var text = $"{leftText} {op} {rightText}";
if (parent.HasValue && parent.Value != current)
{
return $"({text})";
}
return text;
}
private enum SpdxBinaryOperator
{
And,
Or
}
}

View File

@@ -0,0 +1,643 @@
{
"licenseListVersion": "3.21",
"exceptions": [
{
"reference": "./389-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./389-exception.html",
"referenceNumber": 48,
"name": "389 Directory Server Exception",
"licenseExceptionId": "389-exception",
"seeAlso": [
"http://directory.fedoraproject.org/wiki/GPL_Exception_License_Text",
"https://web.archive.org/web/20080828121337/http://directory.fedoraproject.org/wiki/GPL_Exception_License_Text"
]
},
{
"reference": "./Asterisk-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Asterisk-exception.html",
"referenceNumber": 33,
"name": "Asterisk exception",
"licenseExceptionId": "Asterisk-exception",
"seeAlso": [
"https://github.com/asterisk/libpri/blob/7f91151e6bd10957c746c031c1f4a030e8146e9a/pri.c#L22",
"https://github.com/asterisk/libss7/blob/03e81bcd0d28ff25d4c77c78351ddadc82ff5c3f/ss7.c#L24"
]
},
{
"reference": "./Autoconf-exception-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Autoconf-exception-2.0.html",
"referenceNumber": 42,
"name": "Autoconf exception 2.0",
"licenseExceptionId": "Autoconf-exception-2.0",
"seeAlso": [
"http://ac-archive.sourceforge.net/doc/copyright.html",
"http://ftp.gnu.org/gnu/autoconf/autoconf-2.59.tar.gz"
]
},
{
"reference": "./Autoconf-exception-3.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Autoconf-exception-3.0.html",
"referenceNumber": 41,
"name": "Autoconf exception 3.0",
"licenseExceptionId": "Autoconf-exception-3.0",
"seeAlso": [
"http://www.gnu.org/licenses/autoconf-exception-3.0.html"
]
},
{
"reference": "./Autoconf-exception-generic.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Autoconf-exception-generic.html",
"referenceNumber": 4,
"name": "Autoconf generic exception",
"licenseExceptionId": "Autoconf-exception-generic",
"seeAlso": [
"https://launchpad.net/ubuntu/precise/+source/xmltooling/+copyright",
"https://tracker.debian.org/media/packages/s/sipwitch/copyright-1.9.15-3",
"https://opensource.apple.com/source/launchd/launchd-258.1/launchd/compile.auto.html"
]
},
{
"reference": "./Autoconf-exception-macro.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Autoconf-exception-macro.html",
"referenceNumber": 19,
"name": "Autoconf macro exception",
"licenseExceptionId": "Autoconf-exception-macro",
"seeAlso": [
"https://github.com/freedesktop/xorg-macros/blob/39f07f7db58ebbf3dcb64a2bf9098ed5cf3d1223/xorg-macros.m4.in",
"https://www.gnu.org/software/autoconf-archive/ax_pthread.html",
"https://launchpad.net/ubuntu/precise/+source/xmltooling/+copyright"
]
},
{
"reference": "./Bison-exception-2.2.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Bison-exception-2.2.html",
"referenceNumber": 11,
"name": "Bison exception 2.2",
"licenseExceptionId": "Bison-exception-2.2",
"seeAlso": [
"http://git.savannah.gnu.org/cgit/bison.git/tree/data/yacc.c?id\u003d193d7c7054ba7197b0789e14965b739162319b5e#n141"
]
},
{
"reference": "./Bootloader-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Bootloader-exception.html",
"referenceNumber": 50,
"name": "Bootloader Distribution Exception",
"licenseExceptionId": "Bootloader-exception",
"seeAlso": [
"https://github.com/pyinstaller/pyinstaller/blob/develop/COPYING.txt"
]
},
{
"reference": "./Classpath-exception-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Classpath-exception-2.0.html",
"referenceNumber": 36,
"name": "Classpath exception 2.0",
"licenseExceptionId": "Classpath-exception-2.0",
"seeAlso": [
"http://www.gnu.org/software/classpath/license.html",
"https://fedoraproject.org/wiki/Licensing/GPL_Classpath_Exception"
]
},
{
"reference": "./CLISP-exception-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./CLISP-exception-2.0.html",
"referenceNumber": 9,
"name": "CLISP exception 2.0",
"licenseExceptionId": "CLISP-exception-2.0",
"seeAlso": [
"http://sourceforge.net/p/clisp/clisp/ci/default/tree/COPYRIGHT"
]
},
{
"reference": "./cryptsetup-OpenSSL-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./cryptsetup-OpenSSL-exception.html",
"referenceNumber": 39,
"name": "cryptsetup OpenSSL exception",
"licenseExceptionId": "cryptsetup-OpenSSL-exception",
"seeAlso": [
"https://gitlab.com/cryptsetup/cryptsetup/-/blob/main/COPYING",
"https://gitlab.nic.cz/datovka/datovka/-/blob/develop/COPYING",
"https://github.com/nbs-system/naxsi/blob/951123ad456bdf5ac94e8d8819342fe3d49bc002/naxsi_src/naxsi_raw.c",
"http://web.mit.edu/jgross/arch/amd64_deb60/bin/mosh"
]
},
{
"reference": "./DigiRule-FOSS-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./DigiRule-FOSS-exception.html",
"referenceNumber": 20,
"name": "DigiRule FOSS License Exception",
"licenseExceptionId": "DigiRule-FOSS-exception",
"seeAlso": [
"http://www.digirulesolutions.com/drupal/foss"
]
},
{
"reference": "./eCos-exception-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./eCos-exception-2.0.html",
"referenceNumber": 38,
"name": "eCos exception 2.0",
"licenseExceptionId": "eCos-exception-2.0",
"seeAlso": [
"http://ecos.sourceware.org/license-overview.html"
]
},
{
"reference": "./Fawkes-Runtime-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Fawkes-Runtime-exception.html",
"referenceNumber": 8,
"name": "Fawkes Runtime Exception",
"licenseExceptionId": "Fawkes-Runtime-exception",
"seeAlso": [
"http://www.fawkesrobotics.org/about/license/"
]
},
{
"reference": "./FLTK-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./FLTK-exception.html",
"referenceNumber": 18,
"name": "FLTK exception",
"licenseExceptionId": "FLTK-exception",
"seeAlso": [
"http://www.fltk.org/COPYING.php"
]
},
{
"reference": "./Font-exception-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Font-exception-2.0.html",
"referenceNumber": 7,
"name": "Font exception 2.0",
"licenseExceptionId": "Font-exception-2.0",
"seeAlso": [
"http://www.gnu.org/licenses/gpl-faq.html#FontException"
]
},
{
"reference": "./freertos-exception-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./freertos-exception-2.0.html",
"referenceNumber": 47,
"name": "FreeRTOS Exception 2.0",
"licenseExceptionId": "freertos-exception-2.0",
"seeAlso": [
"https://web.archive.org/web/20060809182744/http://www.freertos.org/a00114.html"
]
},
{
"reference": "./GCC-exception-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GCC-exception-2.0.html",
"referenceNumber": 54,
"name": "GCC Runtime Library exception 2.0",
"licenseExceptionId": "GCC-exception-2.0",
"seeAlso": [
"https://gcc.gnu.org/git/?p\u003dgcc.git;a\u003dblob;f\u003dgcc/libgcc1.c;h\u003d762f5143fc6eed57b6797c82710f3538aa52b40b;hb\u003dcb143a3ce4fb417c68f5fa2691a1b1b1053dfba9#l10"
]
},
{
"reference": "./GCC-exception-3.1.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GCC-exception-3.1.html",
"referenceNumber": 27,
"name": "GCC Runtime Library exception 3.1",
"licenseExceptionId": "GCC-exception-3.1",
"seeAlso": [
"http://www.gnu.org/licenses/gcc-exception-3.1.html"
]
},
{
"reference": "./GNAT-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GNAT-exception.html",
"referenceNumber": 13,
"name": "GNAT exception",
"licenseExceptionId": "GNAT-exception",
"seeAlso": [
"https://github.com/AdaCore/florist/blob/master/libsrc/posix-configurable_file_limits.adb"
]
},
{
"reference": "./gnu-javamail-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./gnu-javamail-exception.html",
"referenceNumber": 34,
"name": "GNU JavaMail exception",
"licenseExceptionId": "gnu-javamail-exception",
"seeAlso": [
"http://www.gnu.org/software/classpathx/javamail/javamail.html"
]
},
{
"reference": "./GPL-3.0-interface-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GPL-3.0-interface-exception.html",
"referenceNumber": 21,
"name": "GPL-3.0 Interface Exception",
"licenseExceptionId": "GPL-3.0-interface-exception",
"seeAlso": [
"https://www.gnu.org/licenses/gpl-faq.en.html#LinkingOverControlledInterface"
]
},
{
"reference": "./GPL-3.0-linking-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GPL-3.0-linking-exception.html",
"referenceNumber": 1,
"name": "GPL-3.0 Linking Exception",
"licenseExceptionId": "GPL-3.0-linking-exception",
"seeAlso": [
"https://www.gnu.org/licenses/gpl-faq.en.html#GPLIncompatibleLibs"
]
},
{
"reference": "./GPL-3.0-linking-source-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GPL-3.0-linking-source-exception.html",
"referenceNumber": 37,
"name": "GPL-3.0 Linking Exception (with Corresponding Source)",
"licenseExceptionId": "GPL-3.0-linking-source-exception",
"seeAlso": [
"https://www.gnu.org/licenses/gpl-faq.en.html#GPLIncompatibleLibs",
"https://github.com/mirror/wget/blob/master/src/http.c#L20"
]
},
{
"reference": "./GPL-CC-1.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GPL-CC-1.0.html",
"referenceNumber": 52,
"name": "GPL Cooperation Commitment 1.0",
"licenseExceptionId": "GPL-CC-1.0",
"seeAlso": [
"https://github.com/gplcc/gplcc/blob/master/Project/COMMITMENT",
"https://gplcc.github.io/gplcc/Project/README-PROJECT.html"
]
},
{
"reference": "./GStreamer-exception-2005.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GStreamer-exception-2005.html",
"referenceNumber": 35,
"name": "GStreamer Exception (2005)",
"licenseExceptionId": "GStreamer-exception-2005",
"seeAlso": [
"https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/licensing.html?gi-language\u003dc#licensing-of-applications-using-gstreamer"
]
},
{
"reference": "./GStreamer-exception-2008.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./GStreamer-exception-2008.html",
"referenceNumber": 30,
"name": "GStreamer Exception (2008)",
"licenseExceptionId": "GStreamer-exception-2008",
"seeAlso": [
"https://gstreamer.freedesktop.org/documentation/frequently-asked-questions/licensing.html?gi-language\u003dc#licensing-of-applications-using-gstreamer"
]
},
{
"reference": "./i2p-gpl-java-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./i2p-gpl-java-exception.html",
"referenceNumber": 40,
"name": "i2p GPL+Java Exception",
"licenseExceptionId": "i2p-gpl-java-exception",
"seeAlso": [
"http://geti2p.net/en/get-involved/develop/licenses#java_exception"
]
},
{
"reference": "./KiCad-libraries-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./KiCad-libraries-exception.html",
"referenceNumber": 28,
"name": "KiCad Libraries Exception",
"licenseExceptionId": "KiCad-libraries-exception",
"seeAlso": [
"https://www.kicad.org/libraries/license/"
]
},
{
"reference": "./LGPL-3.0-linking-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./LGPL-3.0-linking-exception.html",
"referenceNumber": 2,
"name": "LGPL-3.0 Linking Exception",
"licenseExceptionId": "LGPL-3.0-linking-exception",
"seeAlso": [
"https://raw.githubusercontent.com/go-xmlpath/xmlpath/v2/LICENSE",
"https://github.com/goamz/goamz/blob/master/LICENSE",
"https://github.com/juju/errors/blob/master/LICENSE"
]
},
{
"reference": "./libpri-OpenH323-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./libpri-OpenH323-exception.html",
"referenceNumber": 32,
"name": "libpri OpenH323 exception",
"licenseExceptionId": "libpri-OpenH323-exception",
"seeAlso": [
"https://github.com/asterisk/libpri/blob/1.6.0/README#L19-L22"
]
},
{
"reference": "./Libtool-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Libtool-exception.html",
"referenceNumber": 17,
"name": "Libtool Exception",
"licenseExceptionId": "Libtool-exception",
"seeAlso": [
"http://git.savannah.gnu.org/cgit/libtool.git/tree/m4/libtool.m4"
]
},
{
"reference": "./Linux-syscall-note.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Linux-syscall-note.html",
"referenceNumber": 49,
"name": "Linux Syscall Note",
"licenseExceptionId": "Linux-syscall-note",
"seeAlso": [
"https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/COPYING"
]
},
{
"reference": "./LLGPL.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./LLGPL.html",
"referenceNumber": 3,
"name": "LLGPL Preamble",
"licenseExceptionId": "LLGPL",
"seeAlso": [
"http://opensource.franz.com/preamble.html"
]
},
{
"reference": "./LLVM-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./LLVM-exception.html",
"referenceNumber": 14,
"name": "LLVM Exception",
"licenseExceptionId": "LLVM-exception",
"seeAlso": [
"http://llvm.org/foundation/relicensing/LICENSE.txt"
]
},
{
"reference": "./LZMA-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./LZMA-exception.html",
"referenceNumber": 55,
"name": "LZMA exception",
"licenseExceptionId": "LZMA-exception",
"seeAlso": [
"http://nsis.sourceforge.net/Docs/AppendixI.html#I.6"
]
},
{
"reference": "./mif-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./mif-exception.html",
"referenceNumber": 53,
"name": "Macros and Inline Functions Exception",
"licenseExceptionId": "mif-exception",
"seeAlso": [
"http://www.scs.stanford.edu/histar/src/lib/cppsup/exception",
"http://dev.bertos.org/doxygen/",
"https://www.threadingbuildingblocks.org/licensing"
]
},
{
"reference": "./Nokia-Qt-exception-1.1.json",
"isDeprecatedLicenseId": true,
"detailsUrl": "./Nokia-Qt-exception-1.1.html",
"referenceNumber": 31,
"name": "Nokia Qt LGPL exception 1.1",
"licenseExceptionId": "Nokia-Qt-exception-1.1",
"seeAlso": [
"https://www.keepassx.org/dev/projects/keepassx/repository/revisions/b8dfb9cc4d5133e0f09cd7533d15a4f1c19a40f2/entry/LICENSE.NOKIA-LGPL-EXCEPTION"
]
},
{
"reference": "./OCaml-LGPL-linking-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./OCaml-LGPL-linking-exception.html",
"referenceNumber": 29,
"name": "OCaml LGPL Linking Exception",
"licenseExceptionId": "OCaml-LGPL-linking-exception",
"seeAlso": [
"https://caml.inria.fr/ocaml/license.en.html"
]
},
{
"reference": "./OCCT-exception-1.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./OCCT-exception-1.0.html",
"referenceNumber": 15,
"name": "Open CASCADE Exception 1.0",
"licenseExceptionId": "OCCT-exception-1.0",
"seeAlso": [
"http://www.opencascade.com/content/licensing"
]
},
{
"reference": "./OpenJDK-assembly-exception-1.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./OpenJDK-assembly-exception-1.0.html",
"referenceNumber": 24,
"name": "OpenJDK Assembly exception 1.0",
"licenseExceptionId": "OpenJDK-assembly-exception-1.0",
"seeAlso": [
"http://openjdk.java.net/legal/assembly-exception.html"
]
},
{
"reference": "./openvpn-openssl-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./openvpn-openssl-exception.html",
"referenceNumber": 43,
"name": "OpenVPN OpenSSL Exception",
"licenseExceptionId": "openvpn-openssl-exception",
"seeAlso": [
"http://openvpn.net/index.php/license.html"
]
},
{
"reference": "./PS-or-PDF-font-exception-20170817.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./PS-or-PDF-font-exception-20170817.html",
"referenceNumber": 45,
"name": "PS/PDF font exception (2017-08-17)",
"licenseExceptionId": "PS-or-PDF-font-exception-20170817",
"seeAlso": [
"https://github.com/ArtifexSoftware/urw-base35-fonts/blob/65962e27febc3883a17e651cdb23e783668c996f/LICENSE"
]
},
{
"reference": "./QPL-1.0-INRIA-2004-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./QPL-1.0-INRIA-2004-exception.html",
"referenceNumber": 44,
"name": "INRIA QPL 1.0 2004 variant exception",
"licenseExceptionId": "QPL-1.0-INRIA-2004-exception",
"seeAlso": [
"https://git.frama-c.com/pub/frama-c/-/blob/master/licenses/Q_MODIFIED_LICENSE",
"https://github.com/maranget/hevea/blob/master/LICENSE"
]
},
{
"reference": "./Qt-GPL-exception-1.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Qt-GPL-exception-1.0.html",
"referenceNumber": 10,
"name": "Qt GPL exception 1.0",
"licenseExceptionId": "Qt-GPL-exception-1.0",
"seeAlso": [
"http://code.qt.io/cgit/qt/qtbase.git/tree/LICENSE.GPL3-EXCEPT"
]
},
{
"reference": "./Qt-LGPL-exception-1.1.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Qt-LGPL-exception-1.1.html",
"referenceNumber": 16,
"name": "Qt LGPL exception 1.1",
"licenseExceptionId": "Qt-LGPL-exception-1.1",
"seeAlso": [
"http://code.qt.io/cgit/qt/qtbase.git/tree/LGPL_EXCEPTION.txt"
]
},
{
"reference": "./Qwt-exception-1.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Qwt-exception-1.0.html",
"referenceNumber": 51,
"name": "Qwt exception 1.0",
"licenseExceptionId": "Qwt-exception-1.0",
"seeAlso": [
"http://qwt.sourceforge.net/qwtlicense.html"
]
},
{
"reference": "./SHL-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./SHL-2.0.html",
"referenceNumber": 26,
"name": "Solderpad Hardware License v2.0",
"licenseExceptionId": "SHL-2.0",
"seeAlso": [
"https://solderpad.org/licenses/SHL-2.0/"
]
},
{
"reference": "./SHL-2.1.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./SHL-2.1.html",
"referenceNumber": 23,
"name": "Solderpad Hardware License v2.1",
"licenseExceptionId": "SHL-2.1",
"seeAlso": [
"https://solderpad.org/licenses/SHL-2.1/"
]
},
{
"reference": "./SWI-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./SWI-exception.html",
"referenceNumber": 22,
"name": "SWI exception",
"licenseExceptionId": "SWI-exception",
"seeAlso": [
"https://github.com/SWI-Prolog/packages-clpqr/blob/bfa80b9270274f0800120d5b8e6fef42ac2dc6a5/clpqr/class.pl"
]
},
{
"reference": "./Swift-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Swift-exception.html",
"referenceNumber": 46,
"name": "Swift Exception",
"licenseExceptionId": "Swift-exception",
"seeAlso": [
"https://swift.org/LICENSE.txt",
"https://github.com/apple/swift-package-manager/blob/7ab2275f447a5eb37497ed63a9340f8a6d1e488b/LICENSE.txt#L205"
]
},
{
"reference": "./u-boot-exception-2.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./u-boot-exception-2.0.html",
"referenceNumber": 5,
"name": "U-Boot exception 2.0",
"licenseExceptionId": "u-boot-exception-2.0",
"seeAlso": [
"http://git.denx.de/?p\u003du-boot.git;a\u003dblob;f\u003dLicenses/Exceptions"
]
},
{
"reference": "./Universal-FOSS-exception-1.0.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./Universal-FOSS-exception-1.0.html",
"referenceNumber": 12,
"name": "Universal FOSS Exception, Version 1.0",
"licenseExceptionId": "Universal-FOSS-exception-1.0",
"seeAlso": [
"https://oss.oracle.com/licenses/universal-foss-exception/"
]
},
{
"reference": "./vsftpd-openssl-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./vsftpd-openssl-exception.html",
"referenceNumber": 56,
"name": "vsftpd OpenSSL exception",
"licenseExceptionId": "vsftpd-openssl-exception",
"seeAlso": [
"https://git.stg.centos.org/source-git/vsftpd/blob/f727873674d9c9cd7afcae6677aa782eb54c8362/f/LICENSE",
"https://launchpad.net/debian/squeeze/+source/vsftpd/+copyright",
"https://github.com/richardcochran/vsftpd/blob/master/COPYING"
]
},
{
"reference": "./WxWindows-exception-3.1.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./WxWindows-exception-3.1.html",
"referenceNumber": 25,
"name": "WxWindows Library Exception 3.1",
"licenseExceptionId": "WxWindows-exception-3.1",
"seeAlso": [
"http://www.opensource.org/licenses/WXwindows"
]
},
{
"reference": "./x11vnc-openssl-exception.json",
"isDeprecatedLicenseId": false,
"detailsUrl": "./x11vnc-openssl-exception.html",
"referenceNumber": 6,
"name": "x11vnc OpenSSL Exception",
"licenseExceptionId": "x11vnc-openssl-exception",
"seeAlso": [
"https://github.com/LibVNC/x11vnc/blob/master/src/8to24.c#L22"
]
}
],
"releaseDate": "2023-06-18"
}

View File

@@ -15,6 +15,10 @@
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="JsonSchema.Net" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\\spdx-license-list-3.21.json" />
<EmbeddedResource Include="Resources\\spdx-license-exceptions-3.21.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Attestor.Envelope\StellaOps.Attestor.Envelope.csproj" />

View File

@@ -1,7 +1,8 @@
# Attestor StandardPredicates Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20260113_001_002_ATTESTOR_binary_diff_predicate.md`.
Source of truth: `docs/implplan/SPRINT_20260119_013_Attestor_cyclonedx_1.7_generation.md`,
`docs/implplan/SPRINT_20260119_014_Attestor_spdx_3.0.1_generation.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
@@ -15,3 +16,14 @@ Source of truth: `docs/implplan/SPRINT_20260113_001_002_ATTESTOR_binary_diff_pre
| BINARYDIFF-SIGNER-0001 | DONE | Implement DSSE signer for binary diff predicates. |
| BINARYDIFF-VERIFIER-0001 | DONE | Implement DSSE verifier for binary diff predicates. |
| BINARYDIFF-DI-0001 | DONE | Register BinaryDiff services and options in DI. |
| TASK-013-001 | DONE | Extended SbomDocument with CycloneDX 1.7 concepts. |
| TASK-013-002 | DONE | CycloneDxWriter upgraded to spec 1.7 and new sections. |
| TASK-013-003 | DONE | Component-level 1.7 fields and evidence support. |
| TASK-013-004 | DONE | Services and formulation serialization implemented. |
| TASK-013-005 | DONE | ModelCard generation for ML components. |
| TASK-013-006 | DONE | CryptoProperties and CBOM fields supported. |
| TASK-013-007 | DONE | Annotations, compositions, declarations, definitions serialized. |
| TASK-013-008 | DONE | Signature mapping and JWK validation added. |
| TASK-014-001 | DOING | SPDX 3.0.1 context/spec version and writer baseline. |
| TASK-014-002 | DOING | Core profile elements in progress. |
| TASK-014-011 | DOING | Integrity methods and external references/identifiers in progress. |

View File

@@ -5,6 +5,8 @@
// Description: Interface for deterministic SBOM writing
// -----------------------------------------------------------------------------
using StellaOps.Attestor.StandardPredicates.Models;
namespace StellaOps.Attestor.StandardPredicates.Writers;
/// <summary>
@@ -58,167 +60,3 @@ public interface ISbomWriter
/// <returns>Write result containing canonical bytes and hash.</returns>
Task<SbomWriteResult> WriteAsync(SbomDocument document, CancellationToken ct = default);
}
/// <summary>
/// Unified SBOM document model for Attestor operations.
/// </summary>
public sealed record SbomDocument
{
/// <summary>
/// Document name/identifier.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Document version.
/// </summary>
public string? Version { get; init; }
/// <summary>
/// Creation timestamp (UTC).
/// </summary>
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
/// <summary>
/// SHA-256 digest of the artifact this SBOM describes (e.g., container image digest).
/// Used to derive deterministic serialNumber: urn:sha256:&lt;artifact-digest&gt;
/// </summary>
/// <remarks>
/// Sprint: SPRINT_20260118_025_ReleaseOrchestrator_sbom_release_association (TASK-025-004)
/// If provided, CycloneDxWriter will generate serialNumber as urn:sha256:&lt;artifact-digest&gt;
/// instead of using a deterministic UUID. This enables reproducible SBOMs where the
/// serialNumber directly references the artifact being described.
/// Format: lowercase hex string, 64 characters (no prefix).
/// </remarks>
public string? ArtifactDigest { get; init; }
/// <summary>
/// Components in the SBOM.
/// </summary>
public IReadOnlyList<SbomComponent> Components { get; init; } = [];
/// <summary>
/// Dependencies between components.
/// </summary>
public IReadOnlyList<SbomDependency> Dependencies { get; init; } = [];
/// <summary>
/// Tool information.
/// </summary>
public SbomTool? Tool { get; init; }
/// <summary>
/// External references.
/// </summary>
public IReadOnlyList<SbomExternalReference> ExternalReferences { get; init; } = [];
}
/// <summary>
/// A component in the SBOM.
/// </summary>
public sealed record SbomComponent
{
/// <summary>
/// Unique reference ID.
/// </summary>
public required string BomRef { get; init; }
/// <summary>
/// Component name.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Component version.
/// </summary>
public string? Version { get; init; }
/// <summary>
/// Package URL (purl).
/// </summary>
public string? Purl { get; init; }
/// <summary>
/// Component type.
/// </summary>
public string Type { get; init; } = "library";
/// <summary>
/// Hashes for the component.
/// </summary>
public IReadOnlyList<SbomHash> Hashes { get; init; } = [];
/// <summary>
/// License identifiers.
/// </summary>
public IReadOnlyList<string> Licenses { get; init; } = [];
}
/// <summary>
/// A hash in the SBOM.
/// </summary>
public sealed record SbomHash
{
/// <summary>
/// Hash algorithm (e.g., SHA-256, SHA-512).
/// </summary>
public required string Algorithm { get; init; }
/// <summary>
/// Hash value in hex format.
/// </summary>
public required string Value { get; init; }
}
/// <summary>
/// A dependency relationship.
/// </summary>
public sealed record SbomDependency
{
/// <summary>
/// The component that has the dependency.
/// </summary>
public required string Ref { get; init; }
/// <summary>
/// Components this component depends on.
/// </summary>
public IReadOnlyList<string> DependsOn { get; init; } = [];
}
/// <summary>
/// Tool information.
/// </summary>
public sealed record SbomTool
{
/// <summary>
/// Tool vendor.
/// </summary>
public string? Vendor { get; init; }
/// <summary>
/// Tool name.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Tool version.
/// </summary>
public string? Version { get; init; }
}
/// <summary>
/// An external reference.
/// </summary>
public sealed record SbomExternalReference
{
/// <summary>
/// Reference type.
/// </summary>
public required string Type { get; init; }
/// <summary>
/// Reference URL.
/// </summary>
public required string Url { get; init; }
}

View File

@@ -5,6 +5,8 @@
// Description: Service implementation for timestamping attestations.
// -----------------------------------------------------------------------------
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Security.Cryptography;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -18,16 +20,31 @@ public sealed class AttestationTimestampService : IAttestationTimestampService
{
private readonly AttestationTimestampServiceOptions _options;
private readonly ILogger<AttestationTimestampService> _logger;
private readonly Histogram<double>? _timestampDurationSeconds;
private readonly Counter<long>? _timestampAttempts;
/// <summary>
/// Initializes a new instance of the <see cref="AttestationTimestampService"/> class.
/// </summary>
public AttestationTimestampService(
IOptions<AttestationTimestampServiceOptions> options,
ILogger<AttestationTimestampService> logger)
ILogger<AttestationTimestampService> logger,
IMeterFactory? meterFactory = null)
{
_options = options.Value;
_logger = logger;
if (meterFactory is not null)
{
var meter = meterFactory.Create("StellaOps.Attestor.Timestamping");
_timestampDurationSeconds = meter.CreateHistogram<double>(
"attestation_timestamp_duration_seconds",
unit: "s",
description: "Duration of RFC-3161 timestamp requests.");
_timestampAttempts = meter.CreateCounter<long>(
"attestation_timestamp_attempts_total",
description: "Total RFC-3161 timestamp attempts grouped by result.");
}
}
/// <inheritdoc />
@@ -37,42 +54,57 @@ public sealed class AttestationTimestampService : IAttestationTimestampService
CancellationToken cancellationToken = default)
{
options ??= AttestationTimestampOptions.Default;
var startTimestamp = Stopwatch.GetTimestamp();
var success = false;
// Hash the envelope
var algorithm = options.HashAlgorithm switch
try
{
"SHA256" => HashAlgorithmName.SHA256,
"SHA384" => HashAlgorithmName.SHA384,
"SHA512" => HashAlgorithmName.SHA512,
_ => HashAlgorithmName.SHA256
};
// Hash the envelope
var algorithm = options.HashAlgorithm switch
{
"SHA256" => HashAlgorithmName.SHA256,
"SHA384" => HashAlgorithmName.SHA384,
"SHA512" => HashAlgorithmName.SHA512,
_ => HashAlgorithmName.SHA256
};
var hash = ComputeHash(envelope.Span, algorithm);
var digestHex = Convert.ToHexString(hash).ToLowerInvariant();
var hash = ComputeHash(envelope.Span, algorithm);
var digestHex = Convert.ToHexString(hash).ToLowerInvariant();
_logger.LogDebug(
"Timestamping attestation envelope with {Algorithm} digest: {Digest}",
options.HashAlgorithm,
digestHex);
_logger.LogDebug(
"Timestamping attestation envelope with {Algorithm} digest: {Digest}",
options.HashAlgorithm,
digestHex);
// Call TSA client (placeholder - would integrate with ITimeStampAuthorityClient)
var tstBytes = await RequestTimestampAsync(hash, options, cancellationToken);
var (genTime, tsaName, policyOid) = ParseTstInfo(tstBytes);
// Call TSA client (placeholder - would integrate with ITimeStampAuthorityClient)
var tstBytes = await RequestTimestampAsync(hash, options, cancellationToken);
var (genTime, tsaName, policyOid) = ParseTstInfo(tstBytes);
_logger.LogInformation(
"Attestation timestamped at {Time} by {TSA}",
genTime,
tsaName);
_logger.LogInformation(
"Attestation timestamped at {Time} by {TSA}",
genTime,
tsaName);
return new TimestampedAttestation
var result = new TimestampedAttestation
{
Envelope = envelope.ToArray(),
EnvelopeDigest = $"{options.HashAlgorithm.ToLowerInvariant()}:{digestHex}",
TimeStampToken = tstBytes,
TimestampTime = genTime,
TsaName = tsaName,
TsaPolicyOid = policyOid
};
success = true;
return result;
}
finally
{
Envelope = envelope.ToArray(),
EnvelopeDigest = $"{options.HashAlgorithm.ToLowerInvariant()}:{digestHex}",
TimeStampToken = tstBytes,
TimestampTime = genTime,
TsaName = tsaName,
TsaPolicyOid = policyOid
};
var elapsed = Stopwatch.GetElapsedTime(startTimestamp).TotalSeconds;
var tags = new TagList { { "result", success ? "success" : "failure" } };
_timestampDurationSeconds?.Record(elapsed, tags);
_timestampAttempts?.Add(1, tags);
}
}
/// <inheritdoc />

View File

@@ -0,0 +1,11 @@
# Attestor Timestamping Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20260119_010_Attestor_tst_integration.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| ATT-001 | DONE | Added timestamping metrics and unit coverage for envelope hashing. |
| ATT-002 | DONE | Covered timestamp verification scenarios and Rekor consistency checks. |
| ATT-003 | DONE | Added policy context docs and evaluator tests for timestamp assertions. |
| ATT-006 | DONE | Added time correlation validator unit tests. |
| TASK-029-002 | DONE | Bundle TSA chain + revocation data for offline verification. |

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using FluentAssertions;

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using FluentAssertions;

View File

@@ -1,4 +1,4 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using System.Text.Json;

View File

@@ -1,5 +1,5 @@
// <copyright file="BuildAttestationMapperTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;

View File

@@ -1,5 +1,5 @@
// <copyright file="BuildProfileValidatorTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;

View File

@@ -1,5 +1,5 @@
// <copyright file="CombinedDocumentBuilderTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;

View File

@@ -1,5 +1,5 @@
// <copyright file="DsseSpdx3SignerTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under the AGPL-3.0-or-later.
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
// </copyright>
using System.Collections.Immutable;