Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
3b4899f
Add V2 feature-train scaffolding (worktree + coverage-gated merge)
Jstatia May 8, 2026
f4e8885
train: D11 double-gate (per-project + full-solution) via -Project flag
Jstatia May 8, 2026
f800712
train: adopt release-train playbook (adapted from ai-cookbook)
Jstatia May 8, 2026
0097530
spec: add TrustPolicySpec discriminated union + predicates + parameters
May 8, 2026
8d78d6b
spec: tests — 132 passing across 6 fixtures
May 8, 2026
2703be6
spec: cover edge paths + suppress defensive arms
May 8, 2026
2658aa5
spec: address Hey Jeromy iter-1 findings (security, correctness, perf)
May 8, 2026
b152140
spec: add Phase-1 perf smoke tests + Phase-1 ship contract
May 8, 2026
8a1b879
train: add -NoRegress mode for D11 full-solution gate (amended)
Jstatia May 8, 2026
6fb7a5d
train: merge phase 'spec' (gate ≥ 95% line coverage)
Jstatia May 8, 2026
dbbdef1
fact-registry: add [TrustFactId] attribute
Jstatia May 8, 2026
6077af9
fact-registry: tag all *Fact types with stable ids matching Phase 1 b…
Jstatia May 8, 2026
dbc477f
fact-registry: add AttributeDrivenFactRegistry
Jstatia May 8, 2026
0d1a910
fact-registry: mark StaticFactRegistry [Obsolete]; wire DI extension
Jstatia May 8, 2026
2de8304
fact-registry: add baseline-equivalence conformance test
Jstatia May 8, 2026
a5a70bb
fact-registry: tests
Jstatia May 8, 2026
68243eb
fact-registry: tighten coverage on registry + DI extension
Jstatia May 8, 2026
2081aa3
train: merge phase 'fact-registry' (gate ≥ 95% line coverage)
Jstatia May 8, 2026
7bbd19e
frontend-json: add ICoseTrustPolicyFrontend abstraction + diagnostic …
May 8, 2026
9d3f078
frontend-json: add cose-tp-json/v1 JSON Schema (embedded resource)
May 8, 2026
d533f6e
frontend-json: add CoseTpJsonFrontend (parse + schema-validate + tran…
May 8, 2026
6a85fb5
frontend-json: add post-parse Bind(parameters) step
May 8, 2026
0db7832
frontend-json: add LRU translator cache
May 8, 2026
fc17a77
frontend-json: wire CLI --trust-policy override (per D8)
May 8, 2026
8480c0f
frontend-json: tests
May 8, 2026
50ed5ac
frontend-json: address jeromy_review findings
May 8, 2026
58219d4
train: merge phase 'frontend-json' (gate ≥ 95% line coverage)
May 8, 2026
418f33a
conformance: add FrontendConformanceTestBase with all 8 properties of…
May 8, 2026
fb355c6
conformance: README documenting the 8-property contract
May 8, 2026
b53fcf0
conformance: JSON frontend conformance fixtures (per fact + parametric)
May 8, 2026
0d3c770
conformance: per-helper coverage tests (PerfBudget, naming, internals)
May 8, 2026
df59e22
conformance: address jeromy_review findings (perf math, fixture-name …
May 8, 2026
b648795
train: merge phase 'conformance' (gate ≥ 95% line coverage)
May 8, 2026
e90f47c
frontend-rego: add CoseSign1.Validation.TrustFrontends.Rego project
May 8, 2026
574c50f
frontend-rego: constrained Rego subset tokenizer
May 8, 2026
aeeeff3
frontend-rego: recursive-descent parser with closed reject-list
May 8, 2026
edd0ae5
frontend-rego: CoseTpRegoFrontend forwarding to JSON walker
May 8, 2026
9937ff7
frontend-rego: wire CLI extension dispatch (D8)
May 8, 2026
343263c
frontend-rego: 16 fact + cross + 3 untranslatable fixtures
May 8, 2026
3e9f8bf
frontend-rego: RegoConformanceAdapter + JsonRegoCrossEquivalenceTests
May 8, 2026
b6e06bb
frontend-rego: README documenting accept-list and reject-list
May 8, 2026
b9355b8
frontend-rego: tests (parser + frontend + CLI dispatch)
May 8, 2026
f1b74f4
frontend-rego: coverage branch tests (per-project 98.4%)
May 8, 2026
84ab2f5
frontend-rego: address hey-jeromy findings (RT-MAJ-1 + minors)
May 8, 2026
e177571
frontend-rego: input-size cap + lone-surrogate / control-char rejecti…
May 8, 2026
a77b3f2
frontend-rego: split TPX306 (input-too-large) from TPX305 (max-depth)…
May 8, 2026
65fb648
train: merge phase 'frontend-rego' (gate ≥ 95% line coverage)
May 8, 2026
5fd9dff
docs: document the document-driven trust-policy surface in V2 docs/
May 8, 2026
4b9f167
integration-tests: add CoseSign1.Trust.Integration[.Tests] projects +…
Jstatia May 8, 2026
36cad8b
integration-tests: X509 happy-path matrix (json + rego x trust/identi…
Jstatia May 8, 2026
0c033ae
integration-tests: X509 deny matrix (untrusted chain, identity, EKU)
Jstatia May 8, 2026
de079d9
integration-tests: X509 parametrised allow-list matrix
Jstatia May 8, 2026
708b422
integration-tests: MST matrix (happy/deny/parametrised issuer-host)
Jstatia May 8, 2026
701d1dc
integration-tests: anti-patterns (malformed/schema/unknown-fact/forbi…
Jstatia May 8, 2026
aa14c5d
integration-tests: D8 override semantics (stricter and looser)
Jstatia May 8, 2026
287cdba
integration-tests: cross-format equivalence regression assertions
Jstatia May 8, 2026
a5e9e1a
integration-tests: targeted unit tests for the infrastructure helpers
Jstatia May 8, 2026
87e14b3
train: merge phase 'integration-tests' (gate ≥ 95% line coverage)
Jstatia May 8, 2026
7cfb8f3
docs: cross-port compatibility contract + shared fixture + demo script
Jstatia May 8, 2026
17f0e24
Rename indirect-signature content validation to ContentDigest verbiag…
Jstatia Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion V2/Cose.Abstractions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Generic COSE abstractions that are independent of any specific COSE message type
## Contents
- `CoseHeaderLocation` — Flags for searching protected/unprotected headers
- `IndirectSignatureHeaderLabels` — RFC 9054 header label constants
- `SignatureFormat` — Signature format enumeration
- `ContentDigestFormat` — Signature format enumeration

## Polyfills
- `Guard` — Cross-framework argument validation (ThrowIfNull, ThrowIfDisposed, etc.)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace CoseSign1.Abstractions.Tests.Extensions;

using System.Security.Cryptography.Cose;

/// <summary>
/// Tests for <see cref="ContentDigestFormat"/> enum.
/// </summary>
[TestFixture]
public class ContentDigestFormatTests
{
[Test]
public void Direct_HasValue0()
{
Assert.That((int)ContentDigestFormat.Direct, Is.EqualTo(0));
}

[Test]
public void IndirectHashLegacy_HasValue1()
{
Assert.That((int)ContentDigestFormat.IndirectHashLegacy, Is.EqualTo(1));
}

[Test]
public void IndirectCoseHashV_HasValue2()
{
Assert.That((int)ContentDigestFormat.IndirectCoseHashV, Is.EqualTo(2));
}

[Test]
public void IndirectCoseHashEnvelope_HasValue3()
{
Assert.That((int)ContentDigestFormat.IndirectCoseHashEnvelope, Is.EqualTo(3));
}

[Test]
public void AllValues_AreUnique()
{
var values = Enum.GetValues<ContentDigestFormat>();
Assert.That(values.Distinct().Count(), Is.EqualTo(values.Length));
}

[Test]
public void AllValues_AreDefined()
{
Assert.That(Enum.IsDefined(ContentDigestFormat.Direct), Is.True);
Assert.That(Enum.IsDefined(ContentDigestFormat.IndirectHashLegacy), Is.True);
Assert.That(Enum.IsDefined(ContentDigestFormat.IndirectCoseHashV), Is.True);
Assert.That(Enum.IsDefined(ContentDigestFormat.IndirectCoseHashEnvelope), Is.True);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,76 +91,76 @@ public void IsIndirectSignature_WithPayloadHashAlgHeader_ReturnsTrue()

#endregion

#region GetSignatureFormat Tests
#region GetContentDigestFormat Tests

[Test]
public void GetSignatureFormat_WithNoIndirectMarkers_ReturnsDirect()
public void GetContentDigestFormat_WithNoIndirectMarkers_ReturnsDirect()
{
var headers = new CoseHeaderMap
{
{ CoseHeaderLabel.ContentType, CoseHeaderValue.FromString("application/json") }
};
var message = CreateMessageWithHeaders(headers);

Assert.That(message.GetSignatureFormat(), Is.EqualTo(SignatureFormat.Direct));
Assert.That(message.GetContentDigestFormat(), Is.EqualTo(ContentDigestFormat.Direct));
}

[Test]
public void GetSignatureFormat_WithPayloadHashAlgHeader_ReturnsCoseHashEnvelope()
public void GetContentDigestFormat_WithPayloadHashAlgHeader_ReturnsCoseHashEnvelope()
{
var headers = new CoseHeaderMap
{
{ IndirectSignatureHeaderLabels.PayloadHashAlg, CoseHeaderValue.FromInt32(-16) }
};
var message = CreateMessageWithHeaders(headers);

Assert.That(message.GetSignatureFormat(), Is.EqualTo(SignatureFormat.IndirectCoseHashEnvelope));
Assert.That(message.GetContentDigestFormat(), Is.EqualTo(ContentDigestFormat.IndirectCoseHashEnvelope));
}

[Test]
public void GetSignatureFormat_WithCoseHashVContentType_ReturnsCoseHashV()
public void GetContentDigestFormat_WithCoseHashVContentType_ReturnsCoseHashV()
{
var headers = new CoseHeaderMap
{
{ CoseHeaderLabel.ContentType, CoseHeaderValue.FromString("application/json+cose-hash-v") }
};
var message = CreateMessageWithHeaders(headers);

Assert.That(message.GetSignatureFormat(), Is.EqualTo(SignatureFormat.IndirectCoseHashV));
Assert.That(message.GetContentDigestFormat(), Is.EqualTo(ContentDigestFormat.IndirectCoseHashV));
}

[Test]
public void GetSignatureFormat_WithHashLegacyContentType_ReturnsHashLegacy()
public void GetContentDigestFormat_WithHashLegacyContentType_ReturnsHashLegacy()
{
var headers = new CoseHeaderMap
{
{ CoseHeaderLabel.ContentType, CoseHeaderValue.FromString("application/json+hash-sha256") }
};
var message = CreateMessageWithHeaders(headers);

Assert.That(message.GetSignatureFormat(), Is.EqualTo(SignatureFormat.IndirectHashLegacy));
Assert.That(message.GetContentDigestFormat(), Is.EqualTo(ContentDigestFormat.IndirectHashLegacy));
}

[Test]
public void GetSignatureFormat_WithNullMessage_ReturnsDirect()
public void GetContentDigestFormat_WithNullMessage_ReturnsDirect()
{
CoseSign1Message? message = null;

Assert.That(message!.GetSignatureFormat(), Is.EqualTo(SignatureFormat.Direct));
Assert.That(message!.GetContentDigestFormat(), Is.EqualTo(ContentDigestFormat.Direct));
}

[TestCase("application/test+hash-sha384")]
[TestCase("text/plain+hash-SHA512")]
[TestCase("application/octet-stream+hash-sha_256")]
public void GetSignatureFormat_WithVariousHashLegacyFormats_ReturnsHashLegacy(string contentType)
public void GetContentDigestFormat_WithVariousHashLegacyFormats_ReturnsHashLegacy(string contentType)
{
var headers = new CoseHeaderMap
{
{ CoseHeaderLabel.ContentType, CoseHeaderValue.FromString(contentType) }
};
var message = CreateMessageWithHeaders(headers);

Assert.That(message.GetSignatureFormat(), Is.EqualTo(SignatureFormat.IndirectHashLegacy));
Assert.That(message.GetContentDigestFormat(), Is.EqualTo(ContentDigestFormat.IndirectHashLegacy));
}

#endregion
Expand Down
53 changes: 0 additions & 53 deletions V2/CoseSign1.Abstractions.Tests/Extensions/SignatureFormatTests.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
namespace System.Security.Cryptography.Cose;

/// <summary>
/// Specifies the signature format used by a COSE Sign1 message.
/// Specifies the content-digest format used by a COSE Sign1 message.
/// </summary>
public enum SignatureFormat
public enum ContentDigestFormat
{
/// <summary>
/// Standard embedded or detached signature where the payload is signed directly.
Expand Down
30 changes: 15 additions & 15 deletions V2/CoseSign1.Abstractions/Extensions/CoseSign1MessageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,24 @@ internal static class ClassStrings
/// <param name="message">The COSE Sign1 message to inspect.</param>
/// <returns><see langword="true"/> if the message uses any indirect signature format; otherwise, <see langword="false"/>.</returns>
public static bool IsIndirectSignature(this CoseSign1Message message)
=> message.GetSignatureFormat() != SignatureFormat.Direct;
=> message.GetContentDigestFormat() != ContentDigestFormat.Direct;

/// <summary>
/// Determines the signature format type.
/// </summary>
/// <param name="message">The COSE Sign1 message to inspect.</param>
/// <returns>The <see cref="SignatureFormat"/> for the provided message.</returns>
public static SignatureFormat GetSignatureFormat(this CoseSign1Message message)
/// <returns>The <see cref="ContentDigestFormat"/> for the provided message.</returns>
public static ContentDigestFormat GetContentDigestFormat(this CoseSign1Message message)
{
if (message == null)
{
return SignatureFormat.Direct;
return ContentDigestFormat.Direct;
}

// Check for CoseHashEnvelope (has header 258 - payload hash algorithm)
if (message.ProtectedHeaders.ContainsKey(IndirectSignatureHeaderLabels.PayloadHashAlg))
{
return SignatureFormat.IndirectCoseHashEnvelope;
return ContentDigestFormat.IndirectCoseHashEnvelope;
}

// Check content-type header for indirect signature markers
Expand All @@ -57,16 +57,16 @@ public static SignatureFormat GetSignatureFormat(this CoseSign1Message message)
{
if (CoseHashVRegex.IsMatch(contentType))
{
return SignatureFormat.IndirectCoseHashV;
return ContentDigestFormat.IndirectCoseHashV;
}

if (HashLegacyRegex.IsMatch(contentType))
{
return SignatureFormat.IndirectHashLegacy;
return ContentDigestFormat.IndirectHashLegacy;
}
}

return SignatureFormat.Direct;
return ContentDigestFormat.Direct;
}

#endregion
Expand All @@ -91,10 +91,10 @@ public static bool TryGetContentType(
return false;
}

var format = message.GetSignatureFormat();
var format = message.GetContentDigestFormat();

// For indirect signatures, get the pre-image content type
if (format != SignatureFormat.Direct)
if (format != ContentDigestFormat.Direct)
{
return message.TryGetIndirectContentType(format, out contentType);
}
Expand All @@ -111,16 +111,16 @@ public static bool TryGetContentType(
/// </summary>
private static bool TryGetIndirectContentType(
this CoseSign1Message message,
SignatureFormat format,
ContentDigestFormat format,
out string? contentType)
{
switch (format)
{
case SignatureFormat.IndirectCoseHashEnvelope:
case ContentDigestFormat.IndirectCoseHashEnvelope:
// For CoseHashEnvelope, content type is in header 259 (preimage content type)
return message.TryGetPreImageContentType(out contentType);

case SignatureFormat.IndirectCoseHashV:
case ContentDigestFormat.IndirectCoseHashV:
// For CoseHashV, strip the "+cose-hash-v" extension
if (message.TryGetHeader(CoseHeaderLabel.ContentType, out string? rawContentType))
{
Expand All @@ -130,7 +130,7 @@ private static bool TryGetIndirectContentType(
contentType = null;
return false;

case SignatureFormat.IndirectHashLegacy:
case ContentDigestFormat.IndirectHashLegacy:
// For legacy indirect, strip the "+hash-*" extension
if (message.TryGetHeader(CoseHeaderLabel.ContentType, out rawContentType))
{
Expand Down Expand Up @@ -194,7 +194,7 @@ public static bool TryGetPayloadLocation(
}

// Payload location is only meaningful for CoseHashEnvelope format
if (message.GetSignatureFormat() != SignatureFormat.IndirectCoseHashEnvelope)
if (message.GetContentDigestFormat() != ContentDigestFormat.IndirectCoseHashEnvelope)
{
payloadLocation = null;
return false;
Expand Down
26 changes: 26 additions & 0 deletions V2/CoseSign1.Certificates/Trust/Facts/AssemblyStrings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace CoseSign1.Certificates.Trust.Facts;

using System.Diagnostics.CodeAnalysis;

/// <summary>
/// String-literal pool for facts shipped from the certificate trust pack. The repo's
/// <c>StringLiteralAnalyzer</c> requires user-visible literals (including <c>[TrustFactId]</c>
/// values) to be sourced from a central <c>ClassStrings</c> static so id renames are diff-able
/// in one place.
/// </summary>
[ExcludeFromCodeCoverage]
internal static class AssemblyStrings
{
internal const string FactIdCertificateSigningKeyTrust = "certificate-signing-key-trust/v1";
internal const string FactIdX509ChainElementIdentity = "x509-chain-element-identity/v1";
internal const string FactIdX509ChainTrusted = "x509-chain-trusted/v1";
internal const string FactIdX509SigningCertificateBasicConstraints = "x509-cert-basic-constraints/v1";
internal const string FactIdX509SigningCertificateEku = "x509-cert-eku/v1";
internal const string FactIdX509SigningCertificateIdentityAllowed = "x509-cert-identity-allowed/v1";
internal const string FactIdX509SigningCertificateIdentity = "x509-cert-identity/v1";
internal const string FactIdX509SigningCertificateKeyUsage = "x509-cert-key-usage/v1";
internal const string FactIdX509X5ChainCertificateIdentity = "x509-x5chain-cert-identity/v1";
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace CoseSign1.Certificates.Trust.Facts;
/// <summary>
/// Fact summarizing certificate identity and trust evaluation for a message's signing key.
/// </summary>
[TrustFactId(AssemblyStrings.FactIdCertificateSigningKeyTrust)]
public sealed class CertificateSigningKeyTrustFact : ISigningKeyFact
{
/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace CoseSign1.Certificates.Trust.Facts;
/// <remarks>
/// Depth 0 is the leaf (signing) certificate. Depth increases toward the root.
/// </remarks>
[TrustFactId(AssemblyStrings.FactIdX509ChainElementIdentity)]
public sealed class X509ChainElementIdentityFact : ISigningKeyFact
{
/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace CoseSign1.Certificates.Trust.Facts;
/// <summary>
/// Fact summarizing X.509 chain trust evaluation for the primary signing key certificate.
/// </summary>
[TrustFactId(AssemblyStrings.FactIdX509ChainTrusted)]
public sealed class X509ChainTrustedFact : ISigningKeyFact
{
/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace CoseSign1.Certificates.Trust.Facts;
/// <summary>
/// Fact describing the basic constraints of the signing certificate.
/// </summary>
[TrustFactId(AssemblyStrings.FactIdX509SigningCertificateBasicConstraints)]
public sealed class X509SigningCertificateBasicConstraintsFact : ISigningKeyFact
{
/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace CoseSign1.Certificates.Trust.Facts;
/// <summary>
/// Fact representing an EKU OID on the signing certificate.
/// </summary>
[TrustFactId(AssemblyStrings.FactIdX509SigningCertificateEku)]
public sealed class X509SigningCertificateEkuFact : ISigningKeyFact
{
/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace CoseSign1.Certificates.Trust.Facts;
/// <summary>
/// Fact indicating whether the signing certificate identity satisfies the configured allow-list.
/// </summary>
[TrustFactId(AssemblyStrings.FactIdX509SigningCertificateIdentityAllowed)]
public sealed class X509SigningCertificateIdentityAllowedFact : ISigningKeyFact
{
/// <inheritdoc />
Expand Down
Loading
Loading