up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-27 07:46:56 +02:00
parent d63af51f84
commit ea970ead2a
302 changed files with 43161 additions and 1534 deletions

View File

@@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.Signer.Core;
namespace StellaOps.Signer.Infrastructure.Signing;
/// <summary>
/// DSSE signer implementation that uses StellaOps.Cryptography providers
/// for keyless (ephemeral) or KMS-backed signing operations.
/// Produces cosign-compatible DSSE envelopes.
/// </summary>
public sealed class CryptoDsseSigner : IDsseSigner
{
private const string DssePayloadType = "application/vnd.in-toto+json";
private const string PreAuthenticationEncodingPrefix = "DSSEv1";
private readonly ICryptoProviderRegistry _cryptoRegistry;
private readonly ISigningKeyResolver _keyResolver;
private readonly ILogger<CryptoDsseSigner> _logger;
private readonly DsseSignerOptions _options;
public CryptoDsseSigner(
ICryptoProviderRegistry cryptoRegistry,
ISigningKeyResolver keyResolver,
IOptions<DsseSignerOptions> options,
ILogger<CryptoDsseSigner> logger)
{
_cryptoRegistry = cryptoRegistry ?? throw new ArgumentNullException(nameof(cryptoRegistry));
_keyResolver = keyResolver ?? throw new ArgumentNullException(nameof(keyResolver));
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async ValueTask<SigningBundle> SignAsync(
SigningRequest request,
ProofOfEntitlementResult entitlement,
CallerContext caller,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(entitlement);
ArgumentNullException.ThrowIfNull(caller);
var signingMode = request.Options.Mode;
var algorithmId = ResolveAlgorithm(signingMode);
_logger.LogDebug(
"Starting DSSE signing for tenant {Tenant} with mode {Mode} and algorithm {Algorithm}",
caller.Tenant,
signingMode,
algorithmId);
// Build the in-toto statement payload
var statementPayload = SignerStatementBuilder.BuildStatementPayload(request);
// Encode payload as base64url for DSSE
var payloadBase64 = Convert.ToBase64String(statementPayload)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
// Build PAE (Pre-Authentication Encoding) for signing
var paeBytes = BuildPae(DssePayloadType, statementPayload);
// Resolve signing key and provider
var keyResolution = await _keyResolver
.ResolveKeyAsync(signingMode, caller.Tenant, cancellationToken)
.ConfigureAwait(false);
var keyReference = new CryptoKeyReference(keyResolution.KeyId, keyResolution.ProviderHint);
// Get signer from crypto registry
var signerResolution = _cryptoRegistry.ResolveSigner(
CryptoCapability.Signing,
algorithmId,
keyReference,
keyResolution.ProviderHint);
var signer = signerResolution.Signer;
// Sign the PAE
var signatureBytes = await signer
.SignAsync(paeBytes, cancellationToken)
.ConfigureAwait(false);
// Encode signature as base64url (cosign-compatible)
var signatureBase64 = Convert.ToBase64String(signatureBytes)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
_logger.LogInformation(
"DSSE signing completed for tenant {Tenant} using provider {Provider} with key {KeyId}",
caller.Tenant,
signerResolution.ProviderName,
signer.KeyId);
// Build certificate chain if available
var certChain = BuildCertificateChain(signer, keyResolution);
// Build DSSE envelope
var envelope = new DsseEnvelope(
Payload: payloadBase64,
PayloadType: DssePayloadType,
Signatures:
[
new DsseSignature(
Signature: signatureBase64,
KeyId: signer.KeyId)
]);
// Build signing metadata
var identity = new SigningIdentity(
Mode: signingMode.ToString().ToLowerInvariant(),
Issuer: keyResolution.Issuer ?? _options.DefaultIssuer,
Subject: keyResolution.Subject ?? caller.Subject,
ExpiresAtUtc: keyResolution.ExpiresAtUtc);
var metadata = new SigningMetadata(
Identity: identity,
CertificateChain: certChain,
ProviderName: signerResolution.ProviderName,
AlgorithmId: algorithmId);
return new SigningBundle(envelope, metadata);
}
/// <summary>
/// Builds the PAE (Pre-Authentication Encoding) as per DSSE specification.
/// PAE = "DSSEv1" || SP || LEN(type) || SP || type || SP || LEN(payload) || SP || payload
/// where SP is space (0x20) and LEN is decimal ASCII length.
/// </summary>
private static byte[] BuildPae(string payloadType, byte[] payload)
{
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
// Calculate total length
var prefixBytes = Encoding.UTF8.GetBytes(PreAuthenticationEncodingPrefix);
var typeLenStr = typeBytes.Length.ToString();
var payloadLenStr = payload.Length.ToString();
var totalLen = prefixBytes.Length + 1 +
typeLenStr.Length + 1 +
typeBytes.Length + 1 +
payloadLenStr.Length + 1 +
payload.Length;
var pae = new byte[totalLen];
var offset = 0;
// DSSEv1
Buffer.BlockCopy(prefixBytes, 0, pae, offset, prefixBytes.Length);
offset += prefixBytes.Length;
pae[offset++] = 0x20; // space
// LEN(type)
var typeLenBytes = Encoding.UTF8.GetBytes(typeLenStr);
Buffer.BlockCopy(typeLenBytes, 0, pae, offset, typeLenBytes.Length);
offset += typeLenBytes.Length;
pae[offset++] = 0x20; // space
// type
Buffer.BlockCopy(typeBytes, 0, pae, offset, typeBytes.Length);
offset += typeBytes.Length;
pae[offset++] = 0x20; // space
// LEN(payload)
var payloadLenBytes = Encoding.UTF8.GetBytes(payloadLenStr);
Buffer.BlockCopy(payloadLenBytes, 0, pae, offset, payloadLenBytes.Length);
offset += payloadLenBytes.Length;
pae[offset++] = 0x20; // space
// payload
Buffer.BlockCopy(payload, 0, pae, offset, payload.Length);
return pae;
}
private string ResolveAlgorithm(SigningMode mode)
{
return mode switch
{
SigningMode.Keyless => _options.KeylessAlgorithm ?? SignatureAlgorithms.Es256,
SigningMode.Kms => _options.KmsAlgorithm ?? SignatureAlgorithms.Es256,
_ => SignatureAlgorithms.Es256
};
}
private static IReadOnlyList<string> BuildCertificateChain(
ICryptoSigner signer,
SigningKeyResolution keyResolution)
{
var chain = new List<string>();
// Export public key as JWK for verification
try
{
var jwk = signer.ExportPublicJsonWebKey();
if (jwk is not null)
{
// Convert JWK to PEM-like representation for certificate chain
// In keyless mode, this represents the ephemeral signing certificate
var jwkJson = System.Text.Json.JsonSerializer.Serialize(jwk);
chain.Add(Convert.ToBase64String(Encoding.UTF8.GetBytes(jwkJson)));
}
}
catch
{
// Some signers may not support JWK export
}
// Add any additional certificates from key resolution
if (keyResolution.CertificateChain is { Count: > 0 })
{
chain.AddRange(keyResolution.CertificateChain);
}
return chain;
}
}