Skip to content

Latest commit

 

History

History
483 lines (363 loc) · 18.4 KB

File metadata and controls

483 lines (363 loc) · 18.4 KB

SCITT Compliance and CWT Claims Support

Overview

CoseSignTool provides comprehensive support for SCITT (Supply Chain Integrity, Transparency, and Trust) compliance through CWT (CBOR Web Token) Claims and DID:x509 identifiers. This enables organizations to create transparent, verifiable supply chain signatures that meet emerging industry standards.

What is SCITT?

SCITT is an IETF specification designed to provide transparency and verifiability for supply chain artifacts. It requires signatures to include standardized claims about the issuer and subject of the signature, enabling trust and auditability across supply chain systems.

For more information, see: SCITT Architecture Draft

What are CWT Claims?

CWT (CBOR Web Token) Claims, defined in RFC 8392, are standardized claim types encoded in CBOR format. In COSE signatures, CWT Claims are stored in the protected header under label 15 (as defined in RFC 9597), ensuring they are cryptographically protected.

Standard CWT Claims

Claim Label Type Description
iss (Issuer) 1 string The entity that issued/created the signature
sub (Subject) 2 string The subject or intent of the signature
aud (Audience) 3 string The intended audience/recipient
exp (Expiration Time) 4 integer/DateTimeOffset When the signature expires (Unix timestamp)
nbf (Not Before) 5 integer/DateTimeOffset When the signature becomes valid (auto-populated)
iat (Issued At) 6 integer/DateTimeOffset When the signature was created (auto-populated)
cti (CWT ID) 7 byte[] Unique identifier for the token

Note: The iat (Issued At) and nbf (Not Before) claims are automatically populated with the current timestamp when an issuer or subject is set, unless explicitly provided.

DID:x509 Identifiers

CoseSignTool automatically generates DID:x509 identifiers from your certificate chain, following the Microsoft DID:x509 specification.

DID:x509 identifiers generated by CoseSignTool have the following format:

did:x509:0:{algorithm}:{base64url-hash}::subject:{key}:{value}:{key}:{value}...

Where:

  • {base64url-hash} is the base64url-encoded certificate fingerprint (43 characters for SHA256)
  • Subject uses key:value pairs separated by colons (e.g., C:US:O:GitHub:CN:User)
  • Values are percent-encoded with only ALPHA, DIGIT, '-', '.', '_' allowed unencoded

Example:

did:x509:0:sha256:WE4P5dd8DnLHSkyHaIjhp4udlkF9LqoKwCvu9gl38jk::subject:C:US:O:Example%20Corp:CN:MyOrg

Features:

  • Automatic generation from certificate chains via CertificateCoseSigningKeyProvider.Issuer property
  • Spec-compliant: Follows DID:x509 specification exactly (base64url encoding, key:value subject format)
  • Extensible: The DidX509Generator class can be inherited to implement custom DID generation behaviors
  • Multiple hash algorithms supported (SHA-256, SHA-384, SHA-512)
  • Self-signed certificate support for testing and development purposes only (not for production SCITT ledgers)

Customizing the Issuer

The CertificateCoseSigningKeyProvider base class provides a virtual Issuer property that derived classes can override:

// Default behavior: DID:x509 from certificate chain
var provider = new X509Certificate2CoseSigningKeyProvider(cert);
string? issuer = provider.Issuer;  // "did:x509:0:sha256:..."

// Custom provider with overridden issuer
public class CustomCertificateProvider : X509Certificate2CoseSigningKeyProvider
{
    public override string? Issuer => GetIssuerFromConfiguration();
    
    // ... constructor and other implementations
}

var customProvider = new CustomCertificateProvider(cert);
// Now all SCITT operations use the custom issuer
var headerExtender = customProvider.CreateHeaderExtenderWithCWTClaims();

Azure Artifact Signing: Enhanced DID:X509:0 Format

Azure Artifact Signing certificates include Microsoft-specific Enhanced Key Usage (EKU) extensions that identify certificate purposes. When Microsoft EKUs are detected, a specialized EKU-based DID format is generated per the DID:X509 EKU Policy specification:

did:x509:0:sha256:{base64url-hash}::eku:{oid}

This Azure Artifact Signing specific format:

  • Uses base64url encoding: Certificate hash is base64url-encoded (43 characters for SHA256, not 64-character hex)
  • Detects Microsoft EKUs: Checks for any EKU starting with 1.3.6.1.4.1.311
  • Generates EKU-based DID: When Microsoft EKUs are present, includes the deepest greatest EKU in the DID
  • Falls back to subject-based format: When no Microsoft EKUs are found
  • Selects the "deepest greatest" Microsoft EKU when multiple are present
    • Prioritizes EKUs with more OID segments
    • Uses last segment value as tiebreaker
  • No percent-encoding of OID: The OID is in plain dotted decimal notation

Example:

did:x509:0:sha256:WE4P5dd8DnLHSkyHaIjhp4udlkF9LqoKwCvu9gl38jk::eku:1.3.6.1.4.1.311.10.3.13

For more details, see CoseSign1.Certificates.AzureArtifactSigning.md.

Using SCITT Compliance in CoseSignTool

Basic Usage

SCITT compliance is enabled by default when signing with certificates:

# Basic signing with automatic SCITT compliance
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose

This automatically adds:

  • Issuer claim (iss): DID:x509 derived from your certificate chain
  • Subject claim (sub): Defaults to "unknown.intent"
  • Issued At claim (iat): Auto-populated with current Unix timestamp
  • Not Before claim (nbf): Auto-populated with current Unix timestamp

Customizing CWT Claims

Setting Standard Claims

# Set custom issuer and subject
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt-iss "did:example:123" \
  --cwt-sub "software.release.v1.0"

# Add audience claim
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt-aud "production.systems"

Setting Timestamp Claims

Timestamp claims accept date/time strings or Unix timestamps:

# Using ISO 8601 date/time format (recommended)
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt "exp:2024-12-31T23:59:59Z" \
  --cwt "nbf:2024-01-01T00:00:00Z" \
  --cwt "iat:2024-11-19T10:30:00-05:00"

# Using Unix timestamps
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt "exp:1735689600"

Custom Claims

Add custom claims using integer labels (including negative labels), string labels, or standard claim names:

# Using positive integer labels (custom claims)
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt "100:custom-value" \
  --cwt "101:another-value"

# Using string labels (text string keys per CBOR spec)
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt "svn:2" \
  --cwt "build-id:abc123" \
  --cwt "custom-metadata:value"

# Using negative integer labels (private use per IANA registry)
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt "-65537:private-claim" \
  --cwt "-100:organization-specific"

# Using standard claim names
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt "cti:abc123" \
  --cwt "aud:production"

# Combining multiple claim types
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --cwt-iss "did:example:issuer" \
  --cwt-sub "release.v2.0" \
  --cwt "exp:2025-12-31T23:59:59Z" \
  --cwt "svn:42" \
  --cwt "200:custom-metadata"

Label Types Supported:

  • Integer labels: Both positive (e.g., 100, 200) and negative (e.g., -260, -65537)
  • String labels: Text strings for custom claim keys (e.g., "svn", "build-id", "custom-metadata")

Note: Per the IANA CWT Claims Registry, negative labels are supported:

  • Labels -256 to -1: Unassigned (available for use)
  • Labels -65536 to -257: Specification Required
  • Labels < -65536: Reserved for Private Use

Disabling SCITT Compliance

If your use case doesn't require SCITT compliance, you can disable automatic CWT claims:

# Disable SCITT compliance - no automatic CWT claims will be added
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --scitt false

When SCITT compliance is disabled:

  • No default CWT claims are automatically added (no issuer, subject, timestamps)
  • Custom CWT claims are still honored if explicitly specified
  • Useful for scenarios where CWT claims are not needed or managed separately
# SCITT disabled but custom claims still work
CoseSignTool sign --p payload.txt --pfx mycert.pfx --sf signature.cose \
  --scitt false \
  --cwt-iss "custom-issuer" \
  --cwt-sub "custom-subject"

Indirect Signatures

The indirect-sign command also supports SCITT compliance control:

# Enable SCITT compliance (default)
indirect-sign --payload data.bin --signature sig.cose --pfx cert.pfx

# Disable SCITT compliance
indirect-sign --payload data.bin --signature sig.cose --pfx cert.pfx \
  --enable-scitt false

# Custom CWT claims with indirect signatures
indirect-sign --payload data.bin --signature sig.cose --pfx cert.pfx \
  --cwt-issuer "custom-issuer" \
  --cwt-subject "software.v2.0"

Using SCITT Compliance in CoseHandler Library

Basic Usage with Defaults

using CoseSign1.Certificates.Local;
using CoseSign1.Certificates.Extensions;

// Create signing key provider with certificate
// SCITT compliance is enabled by default (enableScittCompliance: true)
var cert = new X509Certificate2("mycert.pfx", "password");
var signingKeyProvider = new X509Certificate2CoseSigningKeyProvider(cert);

// Or explicitly disable SCITT compliance
// var signingKeyProvider = new X509Certificate2CoseSigningKeyProvider(
//     signingCertificate: cert,
//     enableScittCompliance: false
// );

// Create header extender with SCITT compliance (automatic DID:x509 issuer)
var headerExtender = signingKeyProvider.CreateHeaderExtenderWithCWTClaims(
    issuer: null,  // Uses DID:x509 from certificate
    subject: null  // Uses "unknown.intent"
);

// Sign with SCITT compliance
byte[] payload = File.ReadAllBytes("payload.txt");
var signature = CoseHandler.Sign(
    payload, 
    signingKeyProvider, 
    embedPayload: false,
    headerExtender: headerExtender
);

Custom CWT Claims

using CoseSign1.Headers;
using CoseSign1.Certificates.Extensions;

// Create custom CWT claims
var cwtClaims = new CWTClaimsHeaderExtender()
    .SetIssuer("did:example:custom-issuer")
    .SetSubject("software.build.v1.2.3")
    .SetAudience("production-environment")
    .SetExpirationTime(DateTimeOffset.UtcNow.AddYears(1))
    .SetIssuedAt(DateTimeOffset.UtcNow)
    .SetCustomClaim(100, "custom-value");

// Create combined header extender
var headerExtender = new X509CertificateWithCWTClaimsHeaderExtender(
    signingKeyProvider, 
    cwtClaims
);

// Sign with custom claims
var signature = CoseHandler.Sign(
    payload,
    signingKeyProvider,
    embedPayload: false,
    headerExtender: headerExtender
);

Working with DateTimeOffset

// Set expiration time using DateTimeOffset
var extender = new CWTClaimsHeaderExtender()
    .SetExpirationTime(DateTimeOffset.UtcNow.AddMonths(6))
    .SetNotBefore(DateTimeOffset.UtcNow.AddDays(-1))
    .SetIssuedAt(DateTimeOffset.UtcNow);

// Or use Unix timestamps directly if needed
extender.SetExpirationTime(1735689600L);

Accessing Active CWT Claims

var certWithCwt = new X509CertificateWithCWTClaimsHeaderExtender(
    signingKeyProvider, 
    customClaims: null
);

// Modify the active CWT claims extender
certWithCwt.ActiveCWTClaimsExtender
    .SetAudience("specific-audience")
    .SetCustomClaim(200, "additional-metadata");

Using with CoseSign1MessageBuilder

using CoseSign1;
using CoseSign1.Certificates.Local;
using CoseSign1.Certificates.Extensions;

// Create builder
var builder = new CoseSign1MessageBuilder()
    .SetPayloadBytes(payloadBytes);

// Add certificate-based signing with SCITT compliance
var cert = new X509Certificate2("mycert.pfx", "password");
var signingKeyProvider = new X509Certificate2CoseSigningKeyProvider(cert);

// Option 1: Use default SCITT compliance
var headerExtender = signingKeyProvider.CreateHeaderExtenderWithCWTClaims();
builder.UseHeaderExtender(headerExtender);

// Option 2: Customize claims
var cwtClaims = new CWTClaimsHeaderExtender()
    .SetSubject("my-custom-subject")
    .SetExpirationTime(DateTimeOffset.UtcNow.AddDays(30));

var customExtender = new X509CertificateWithCWTClaimsHeaderExtender(
    signingKeyProvider,
    cwtClaims
);
builder.UseHeaderExtender(customExtender);

// Build and sign
byte[] coseSigned = builder.Sign(signingKeyProvider);

Indirect Signatures with SCITT

Indirect signatures (using CoseIndirectSignature or the IndirectSign plugin) create embedded COSE signatures with a hash envelope instead of the full payload. These also support SCITT compliance:

# Using the indirect-sign plugin with SCITT
CoseSignTool indirect-sign \
  --payload payload.txt \
  --signature signature.cose \
  --pfx mycert.pfx \
  --cwt-subject "indirect.signature.v1" \
  --cwt-claims "exp:2025-12-31T23:59:59Z"

Payload Location (RFC 9054)

When using indirect signatures with the CoseHashEnvelope format, you can optionally include a payload location URI. This header (label 260, per RFC 9054) indicates where the original payload can be retrieved from, enabling verification scenarios where the payload is stored separately from the signature.

# Include payload location for remote retrieval
CoseSignTool indirect-sign \
  --payload artifact.bin \
  --signature artifact.cose \
  --pfx mycert.pfx \
  --cwt-subject "artifact.release.v2.1" \
  --payload-location "https://artifacts.example.com/releases/v2.1/artifact.bin"

The payload location is stored in the COSE protected headers alongside other Hash Envelope headers:

  • Label 258: Payload Hash Algorithm (e.g., SHA-256)
  • Label 259: Preimage Content Type (e.g., application/octet-stream)
  • Label 260: Payload Location (optional URI)

Validation and Reading CWT Claims

When validating signatures, CoseHandler automatically validates the CWT Claims structure. To read the claims:

using System.Security.Cryptography.Cose;
using System.Formats.Cbor;

// Read the signature
byte[] coseBytes = File.ReadAllBytes("signature.cose");
CoseSign1Message message = CoseSign1Message.DecodeSign1(coseBytes);

// Use the TryGetCwtClaims extension method
if (message.TryGetCwtClaims(out CwtClaims? claims))
{
    Console.WriteLine($"Issuer: {claims.Issuer}");
    Console.WriteLine($"Subject: {claims.Subject}");
    
    if (claims.ExpirationTime.HasValue)
    {
        Console.WriteLine($"Expires: {claims.ExpirationTime.Value}");
    }
    
    // Print all claims
    Console.WriteLine("\nAll Claims:");
    Console.WriteLine(claims.ToString());
}
else
{
    Console.WriteLine("No CWT Claims found");
}

For advanced scenarios requiring direct CBOR access, see the CoseSign1.Headers documentation.

Best Practices

  1. Use DID:x509 for Issuer: Let CoseSignTool automatically generate the issuer claim from your certificate for maximum verifiability.

  2. Meaningful Subjects: Use descriptive subject claims that clearly indicate the purpose or intent of the signature:

    • software.release.v1.2.3
    • container.image.production
    • document.approval.final
  3. Set Expiration Times: Always include expiration times for signatures to enable time-bound validity:

    --cwt-claims "exp:2025-12-31T23:59:59Z"
  4. Use ISO 8601 Format: For timestamp claims, prefer ISO 8601 date/time strings for better readability and timezone support.

  5. Document Custom Claims: If using custom integer labels (100+), document their meaning for your organization.

  6. Audience Specification: Use the audience claim to specify which systems should accept the signature.

Troubleshooting

Common Issues

Q: Why is my custom issuer not a DID:x509?
A: You can specify any issuer string. DID:x509 is only auto-generated when you don't provide a custom issuer.

Q: Can I use SCITT compliance with self-signed certificates?
A: Self-signed certificates are supported for testing and development purposes only. Production SCITT ledgers will reject signatures from self-signed certificates as they cannot establish a trusted certificate chain. For production use, always use certificates issued by a trusted Certificate Authority (CA).

Q: How do I verify CWT Claims are included in my signature?
A: Use a CBOR decoder or the validation code example above to inspect the protected headers.

Q: What happens if I set both --cwt-issuer and let it auto-generate?
A: The --cwt-issuer value takes precedence over auto-generation.

Q: Are CWT Claims validated during signature validation?
A: The structure is validated, but claim semantics (like expiration) require application-level validation.

Related Documentation

References