Add Authority Advisory AI and API Lifecycle Configuration
- Introduced AuthorityAdvisoryAiOptions and related classes for managing advisory AI configurations, including remote inference options and tenant-specific settings. - Added AuthorityApiLifecycleOptions to control API lifecycle settings, including legacy OAuth endpoint configurations. - Implemented validation and normalization methods for both advisory AI and API lifecycle options to ensure proper configuration. - Created AuthorityNotificationsOptions and its related classes for managing notification settings, including ack tokens, webhooks, and escalation options. - Developed IssuerDirectoryClient and related models for interacting with the issuer directory service, including caching mechanisms and HTTP client configurations. - Added support for dependency injection through ServiceCollectionExtensions for the Issuer Directory Client. - Updated project file to include necessary package references for the new Issuer Directory Client library.
This commit is contained in:
		
							
								
								
									
										301
									
								
								src/Attestor/StellaOps.Attestor.Envelope/EnvelopeKey.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										301
									
								
								src/Attestor/StellaOps.Attestor.Envelope/EnvelopeKey.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,301 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Security.Cryptography;
 | 
			
		||||
using StellaOps.Cryptography;
 | 
			
		||||
 | 
			
		||||
namespace StellaOps.Attestor.Envelope;
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Describes the underlying key algorithm for DSSE envelope signing.
 | 
			
		||||
/// </summary>
 | 
			
		||||
public enum EnvelopeKeyKind
 | 
			
		||||
{
 | 
			
		||||
    Ed25519,
 | 
			
		||||
    Ecdsa
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// <summary>
 | 
			
		||||
/// Represents signing or verification key material for DSSE envelope operations.
 | 
			
		||||
/// </summary>
 | 
			
		||||
public sealed class EnvelopeKey
 | 
			
		||||
{
 | 
			
		||||
    private const int Ed25519PublicKeyLength = 32;
 | 
			
		||||
    private const int Ed25519PrivateKeySeedLength = 32;
 | 
			
		||||
    private const int Ed25519PrivateKeyExpandedLength = 64;
 | 
			
		||||
 | 
			
		||||
    private readonly byte[]? ed25519PublicKey;
 | 
			
		||||
    private readonly byte[]? ed25519PrivateKey;
 | 
			
		||||
    private readonly ECParameters? ecdsaPublicParameters;
 | 
			
		||||
    private readonly ECParameters? ecdsaPrivateParameters;
 | 
			
		||||
 | 
			
		||||
    private EnvelopeKey(
 | 
			
		||||
        EnvelopeKeyKind kind,
 | 
			
		||||
        string algorithmId,
 | 
			
		||||
        string keyId,
 | 
			
		||||
        byte[]? ed25519PublicKey,
 | 
			
		||||
        byte[]? ed25519PrivateKey,
 | 
			
		||||
        ECParameters? ecdsaPublicParameters,
 | 
			
		||||
        ECParameters? ecdsaPrivateParameters)
 | 
			
		||||
    {
 | 
			
		||||
        Kind = kind;
 | 
			
		||||
        AlgorithmId = algorithmId;
 | 
			
		||||
        KeyId = keyId;
 | 
			
		||||
        this.ed25519PublicKey = ed25519PublicKey;
 | 
			
		||||
        this.ed25519PrivateKey = ed25519PrivateKey;
 | 
			
		||||
        this.ecdsaPublicParameters = ecdsaPublicParameters;
 | 
			
		||||
        this.ecdsaPrivateParameters = ecdsaPrivateParameters;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Gets the key classification.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public EnvelopeKeyKind Kind { get; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Gets the signing algorithm identifier (e.g., ED25519, ES256).
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string AlgorithmId { get; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Gets the deterministic key identifier (RFC7638 JWK thumbprint based).
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public string KeyId { get; }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Indicates whether the key has private material available.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public bool HasPrivateMaterial => Kind switch
 | 
			
		||||
    {
 | 
			
		||||
        EnvelopeKeyKind.Ed25519 => ed25519PrivateKey is not null,
 | 
			
		||||
        EnvelopeKeyKind.Ecdsa => ecdsaPrivateParameters.HasValue,
 | 
			
		||||
        _ => false
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Indicates whether the key has public material available.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    public bool HasPublicMaterial => Kind switch
 | 
			
		||||
    {
 | 
			
		||||
        EnvelopeKeyKind.Ed25519 => ed25519PublicKey is not null,
 | 
			
		||||
        EnvelopeKeyKind.Ecdsa => ecdsaPublicParameters.HasValue,
 | 
			
		||||
        _ => false
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    internal ReadOnlySpan<byte> GetEd25519PublicKey()
 | 
			
		||||
    {
 | 
			
		||||
        if (Kind != EnvelopeKeyKind.Ed25519 || ed25519PublicKey is null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidOperationException("Key does not provide Ed25519 public material.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ed25519PublicKey;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal ReadOnlySpan<byte> GetEd25519PrivateKey()
 | 
			
		||||
    {
 | 
			
		||||
        if (Kind != EnvelopeKeyKind.Ed25519 || ed25519PrivateKey is null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidOperationException("Key does not provide Ed25519 private material.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return ed25519PrivateKey;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal ECParameters GetEcdsaPublicParameters()
 | 
			
		||||
    {
 | 
			
		||||
        if (Kind != EnvelopeKeyKind.Ecdsa || !ecdsaPublicParameters.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidOperationException("Key does not provide ECDSA public parameters.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return CloneParameters(ecdsaPublicParameters.Value, includePrivate: false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal ECParameters GetEcdsaPrivateParameters()
 | 
			
		||||
    {
 | 
			
		||||
        if (Kind != EnvelopeKeyKind.Ecdsa || !ecdsaPrivateParameters.HasValue)
 | 
			
		||||
        {
 | 
			
		||||
            throw new InvalidOperationException("Key does not provide ECDSA private parameters.");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return CloneParameters(ecdsaPrivateParameters.Value, includePrivate: true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Creates an Ed25519 signing key (requires private + public material).
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="privateKey">64-byte Ed25519 private key (seed || public key).</param>
 | 
			
		||||
    /// <param name="publicKey">32-byte Ed25519 public key.</param>
 | 
			
		||||
    /// <param name="keyId">Optional external key identifier override.</param>
 | 
			
		||||
    /// <returns>Envelope key instance.</returns>
 | 
			
		||||
    public static EnvelopeKey CreateEd25519Signer(ReadOnlySpan<byte> privateKey, ReadOnlySpan<byte> publicKey, string? keyId = null)
 | 
			
		||||
    {
 | 
			
		||||
        var normalizedPrivate = NormalizeEd25519PrivateKey(privateKey);
 | 
			
		||||
        ValidateEd25519PublicLength(publicKey);
 | 
			
		||||
        var publicCopy = publicKey.ToArray();
 | 
			
		||||
        var resolvedKeyId = string.IsNullOrWhiteSpace(keyId)
 | 
			
		||||
            ? EnvelopeKeyIdCalculator.FromEd25519(publicCopy)
 | 
			
		||||
            : keyId;
 | 
			
		||||
 | 
			
		||||
        return new EnvelopeKey(
 | 
			
		||||
            EnvelopeKeyKind.Ed25519,
 | 
			
		||||
            SignatureAlgorithms.Ed25519,
 | 
			
		||||
            resolvedKeyId,
 | 
			
		||||
            publicCopy,
 | 
			
		||||
            normalizedPrivate,
 | 
			
		||||
            ecdsaPublicParameters: null,
 | 
			
		||||
            ecdsaPrivateParameters: null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Creates an Ed25519 verification key (public material only).
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="publicKey">32-byte Ed25519 public key.</param>
 | 
			
		||||
    /// <param name="keyId">Optional external key identifier override.</param>
 | 
			
		||||
    /// <returns>Envelope key instance.</returns>
 | 
			
		||||
    public static EnvelopeKey CreateEd25519Verifier(ReadOnlySpan<byte> publicKey, string? keyId = null)
 | 
			
		||||
    {
 | 
			
		||||
        ValidateEd25519PublicLength(publicKey);
 | 
			
		||||
 | 
			
		||||
        var publicCopy = publicKey.ToArray();
 | 
			
		||||
        var resolvedKeyId = string.IsNullOrWhiteSpace(keyId)
 | 
			
		||||
            ? EnvelopeKeyIdCalculator.FromEd25519(publicCopy)
 | 
			
		||||
            : keyId;
 | 
			
		||||
 | 
			
		||||
        return new EnvelopeKey(
 | 
			
		||||
            EnvelopeKeyKind.Ed25519,
 | 
			
		||||
            SignatureAlgorithms.Ed25519,
 | 
			
		||||
            resolvedKeyId,
 | 
			
		||||
            publicCopy,
 | 
			
		||||
            ed25519PrivateKey: null,
 | 
			
		||||
            ecdsaPublicParameters: null,
 | 
			
		||||
            ecdsaPrivateParameters: null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Creates an ECDSA signing key (private + public EC parameters).
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="algorithmId">ECDSA algorithm identifier (ES256, ES384, ES512).</param>
 | 
			
		||||
    /// <param name="privateParameters">EC parameters including private scalar.</param>
 | 
			
		||||
    /// <param name="keyId">Optional external key identifier override.</param>
 | 
			
		||||
    /// <returns>Envelope key instance.</returns>
 | 
			
		||||
    public static EnvelopeKey CreateEcdsaSigner(string algorithmId, in ECParameters privateParameters, string? keyId = null)
 | 
			
		||||
    {
 | 
			
		||||
        ValidateEcdsaAlgorithm(algorithmId);
 | 
			
		||||
 | 
			
		||||
        if (privateParameters.D is null || privateParameters.D.Length == 0)
 | 
			
		||||
        {
 | 
			
		||||
            throw new ArgumentException("ECDSA private parameters must include the scalar component (D).", nameof(privateParameters));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (privateParameters.Q.X is null || privateParameters.Q.Y is null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new ArgumentException("ECDSA private parameters must include public coordinates.", nameof(privateParameters));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var publicClone = CloneParameters(privateParameters, includePrivate: false);
 | 
			
		||||
        var privateClone = CloneParameters(privateParameters, includePrivate: true);
 | 
			
		||||
        var resolvedKeyId = string.IsNullOrWhiteSpace(keyId)
 | 
			
		||||
            ? EnvelopeKeyIdCalculator.FromEcdsa(algorithmId, publicClone)
 | 
			
		||||
            : keyId;
 | 
			
		||||
 | 
			
		||||
        return new EnvelopeKey(
 | 
			
		||||
            EnvelopeKeyKind.Ecdsa,
 | 
			
		||||
            algorithmId,
 | 
			
		||||
            resolvedKeyId,
 | 
			
		||||
            ed25519PublicKey: null,
 | 
			
		||||
            ed25519PrivateKey: null,
 | 
			
		||||
            ecdsaPublicParameters: publicClone,
 | 
			
		||||
            ecdsaPrivateParameters: privateClone);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Creates an ECDSA verification key (public EC parameters).
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="algorithmId">ECDSA algorithm identifier (ES256, ES384, ES512).</param>
 | 
			
		||||
    /// <param name="publicParameters">EC parameters containing only public coordinates.</param>
 | 
			
		||||
    /// <param name="keyId">Optional external key identifier override.</param>
 | 
			
		||||
    /// <returns>Envelope key instance.</returns>
 | 
			
		||||
    public static EnvelopeKey CreateEcdsaVerifier(string algorithmId, in ECParameters publicParameters, string? keyId = null)
 | 
			
		||||
    {
 | 
			
		||||
        ValidateEcdsaAlgorithm(algorithmId);
 | 
			
		||||
 | 
			
		||||
        if (publicParameters.Q.X is null || publicParameters.Q.Y is null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new ArgumentException("ECDSA public parameters must include X and Y coordinates.", nameof(publicParameters));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (publicParameters.D is not null)
 | 
			
		||||
        {
 | 
			
		||||
            throw new ArgumentException("ECDSA verification parameters must not include private scalar data.", nameof(publicParameters));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var publicClone = CloneParameters(publicParameters, includePrivate: false);
 | 
			
		||||
        var resolvedKeyId = string.IsNullOrWhiteSpace(keyId)
 | 
			
		||||
            ? EnvelopeKeyIdCalculator.FromEcdsa(algorithmId, publicClone)
 | 
			
		||||
            : keyId;
 | 
			
		||||
 | 
			
		||||
        return new EnvelopeKey(
 | 
			
		||||
            EnvelopeKeyKind.Ecdsa,
 | 
			
		||||
            algorithmId,
 | 
			
		||||
            resolvedKeyId,
 | 
			
		||||
            ed25519PublicKey: null,
 | 
			
		||||
            ed25519PrivateKey: null,
 | 
			
		||||
            ecdsaPublicParameters: publicClone,
 | 
			
		||||
            ecdsaPrivateParameters: null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static byte[] NormalizeEd25519PrivateKey(ReadOnlySpan<byte> privateKey)
 | 
			
		||||
    {
 | 
			
		||||
        return privateKey.Length switch
 | 
			
		||||
        {
 | 
			
		||||
            Ed25519PrivateKeySeedLength => privateKey.ToArray(),
 | 
			
		||||
            Ed25519PrivateKeyExpandedLength => privateKey[..Ed25519PrivateKeySeedLength].ToArray(),
 | 
			
		||||
            _ => throw new ArgumentException($"Ed25519 private key must be {Ed25519PrivateKeySeedLength} or {Ed25519PrivateKeyExpandedLength} bytes.", nameof(privateKey))
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void ValidateEd25519PublicLength(ReadOnlySpan<byte> publicKey)
 | 
			
		||||
    {
 | 
			
		||||
        if (publicKey.Length != Ed25519PublicKeyLength)
 | 
			
		||||
        {
 | 
			
		||||
            throw new ArgumentException($"Ed25519 public key must be {Ed25519PublicKeyLength} bytes.", nameof(publicKey));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void ValidateEcdsaAlgorithm(string algorithmId)
 | 
			
		||||
    {
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(algorithmId))
 | 
			
		||||
        {
 | 
			
		||||
            throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var supported = string.Equals(algorithmId, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase)
 | 
			
		||||
            || string.Equals(algorithmId, SignatureAlgorithms.Es384, StringComparison.OrdinalIgnoreCase)
 | 
			
		||||
            || string.Equals(algorithmId, SignatureAlgorithms.Es512, StringComparison.OrdinalIgnoreCase);
 | 
			
		||||
 | 
			
		||||
        if (!supported)
 | 
			
		||||
        {
 | 
			
		||||
            throw new ArgumentException($"Unsupported ECDSA algorithm '{algorithmId}'.", nameof(algorithmId));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static ECParameters CloneParameters(ECParameters source, bool includePrivate)
 | 
			
		||||
    {
 | 
			
		||||
        var clone = new ECParameters
 | 
			
		||||
        {
 | 
			
		||||
            Curve = source.Curve,
 | 
			
		||||
            Q = new ECPoint
 | 
			
		||||
            {
 | 
			
		||||
                X = source.Q.X is null ? null : (byte[])source.Q.X.Clone(),
 | 
			
		||||
                Y = source.Q.Y is null ? null : (byte[])source.Q.Y.Clone()
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (includePrivate && source.D is not null)
 | 
			
		||||
        {
 | 
			
		||||
            clone.D = (byte[])source.D.Clone();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return clone;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user