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.
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
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.
| 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.
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
- Automatic generation from certificate chains via
CertificateCoseSigningKeyProvider.Issuerproperty - Spec-compliant: Follows DID:x509 specification exactly (base64url encoding, key:value subject format)
- Extensible: The
DidX509Generatorclass 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)
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 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.
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.coseThis 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
# 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"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"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
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 falseWhen 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"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 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
);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
);// 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);var certWithCwt = new X509CertificateWithCWTClaimsHeaderExtender(
signingKeyProvider,
customClaims: null
);
// Modify the active CWT claims extender
certWithCwt.ActiveCWTClaimsExtender
.SetAudience("specific-audience")
.SetCustomClaim(200, "additional-metadata");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 (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"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)
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.
-
Use DID:x509 for Issuer: Let CoseSignTool automatically generate the issuer claim from your certificate for maximum verifiability.
-
Meaningful Subjects: Use descriptive subject claims that clearly indicate the purpose or intent of the signature:
software.release.v1.2.3container.image.productiondocument.approval.final
-
Set Expiration Times: Always include expiration times for signatures to enable time-bound validity:
--cwt-claims "exp:2025-12-31T23:59:59Z" -
Use ISO 8601 Format: For timestamp claims, prefer ISO 8601 date/time strings for better readability and timezone support.
-
Document Custom Claims: If using custom integer labels (100+), document their meaning for your organization.
-
Audience Specification: Use the audience claim to specify which systems should accept the signature.
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.