using System.Security.Cryptography;
namespace StellaOps.Cryptography.Plugin.OfflineVerification;
///
/// Cryptographic provider for offline/air-gapped environments using .NET BCL cryptography.
/// This provider wraps System.Security.Cryptography in the ICryptoProvider abstraction
/// to enable configuration-driven crypto while maintaining offline verification capabilities.
///
public sealed class OfflineVerificationCryptoProvider : ICryptoProvider
{
///
/// Provider name for registry resolution.
///
public string Name => "offline-verification";
///
/// Checks if this provider supports the specified capability and algorithm.
///
public bool Supports(CryptoCapability capability, string algorithmId)
{
return capability switch
{
CryptoCapability.Signing => algorithmId is "ES256" or "ES384" or "ES512"
or "RS256" or "RS384" or "RS512"
or "PS256" or "PS384" or "PS512",
CryptoCapability.Verification => algorithmId is "ES256" or "ES384" or "ES512"
or "RS256" or "RS384" or "RS512"
or "PS256" or "PS384" or "PS512",
CryptoCapability.ContentHashing => algorithmId is "SHA-256" or "SHA-384" or "SHA-512"
or "SHA256" or "SHA384" or "SHA512",
CryptoCapability.PasswordHashing => algorithmId is "PBKDF2" or "Argon2id",
_ => false
};
}
///
/// Not supported for offline verification - no password hashing.
///
public IPasswordHasher GetPasswordHasher(string algorithmId)
{
throw new NotSupportedException(
$"Password hashing is not supported by the offline verification provider.");
}
///
/// Gets a content hasher for the specified algorithm.
///
public ICryptoHasher GetHasher(string algorithmId)
{
var normalized = NormalizeAlgorithmId(algorithmId);
return normalized switch
{
"SHA-256" => new BclHasher("SHA-256", HashAlgorithmName.SHA256),
"SHA-384" => new BclHasher("SHA-384", HashAlgorithmName.SHA384),
"SHA-512" => new BclHasher("SHA-512", HashAlgorithmName.SHA512),
_ => throw new NotSupportedException($"Hash algorithm '{algorithmId}' is not supported.")
};
}
///
/// Gets a signer for the specified algorithm and key reference.
///
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
{
return algorithmId switch
{
"ES256" => new EcdsaSigner(algorithmId, ECCurve.NamedCurves.nistP256, HashAlgorithmName.SHA256, keyReference),
"ES384" => new EcdsaSigner(algorithmId, ECCurve.NamedCurves.nistP384, HashAlgorithmName.SHA384, keyReference),
"ES512" => new EcdsaSigner(algorithmId, ECCurve.NamedCurves.nistP521, HashAlgorithmName.SHA512, keyReference),
"RS256" => new RsaSigner(algorithmId, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1, keyReference),
"RS384" => new RsaSigner(algorithmId, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1, keyReference),
"RS512" => new RsaSigner(algorithmId, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1, keyReference),
"PS256" => new RsaSigner(algorithmId, HashAlgorithmName.SHA256, RSASignaturePadding.Pss, keyReference),
"PS384" => new RsaSigner(algorithmId, HashAlgorithmName.SHA384, RSASignaturePadding.Pss, keyReference),
"PS512" => new RsaSigner(algorithmId, HashAlgorithmName.SHA512, RSASignaturePadding.Pss, keyReference),
_ => throw new NotSupportedException($"Signing algorithm '{algorithmId}' is not supported.")
};
}
///
/// Creates an ephemeral verifier from raw public key bytes (verification-only).
///
public ICryptoSigner CreateEphemeralVerifier(string algorithmId, ReadOnlySpan publicKeyBytes)
{
return algorithmId switch
{
"ES256" => new EcdsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA256, publicKeyBytes.ToArray()),
"ES384" => new EcdsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA384, publicKeyBytes.ToArray()),
"ES512" => new EcdsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA512, publicKeyBytes.ToArray()),
"RS256" => new RsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1, publicKeyBytes.ToArray()),
"RS384" => new RsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1, publicKeyBytes.ToArray()),
"RS512" => new RsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1, publicKeyBytes.ToArray()),
"PS256" => new RsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA256, RSASignaturePadding.Pss, publicKeyBytes.ToArray()),
"PS384" => new RsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA384, RSASignaturePadding.Pss, publicKeyBytes.ToArray()),
"PS512" => new RsaEphemeralVerifier(algorithmId, HashAlgorithmName.SHA512, RSASignaturePadding.Pss, publicKeyBytes.ToArray()),
_ => throw new NotSupportedException($"Verification algorithm '{algorithmId}' is not supported.")
};
}
///
/// Not supported - offline verification provider does not manage keys.
///
public void UpsertSigningKey(CryptoSigningKey signingKey)
{
throw new NotSupportedException(
"The offline verification provider does not support key management.");
}
///
/// Not supported - offline verification provider does not manage keys.
///
public bool RemoveSigningKey(string keyId)
{
throw new NotSupportedException(
"The offline verification provider does not support key management.");
}
///
/// Returns empty collection - offline verification provider does not manage keys.
///
public IReadOnlyCollection GetSigningKeys()
{
return Array.Empty();
}
private static string NormalizeAlgorithmId(string algorithmId)
{
if (string.IsNullOrEmpty(algorithmId))
return algorithmId ?? string.Empty;
return algorithmId.ToUpperInvariant() switch
{
"SHA256" or "SHA-256" => "SHA-256",
"SHA384" or "SHA-384" => "SHA-384",
"SHA512" or "SHA-512" => "SHA-512",
_ => algorithmId
};
}
///
/// Internal hasher implementation using .NET BCL.
///
private sealed class BclHasher : ICryptoHasher
{
private readonly HashAlgorithmName _hashAlgorithmName;
public string AlgorithmId { get; }
public BclHasher(string algorithmId, HashAlgorithmName hashAlgorithmName)
{
AlgorithmId = algorithmId;
_hashAlgorithmName = hashAlgorithmName;
}
public byte[] ComputeHash(ReadOnlySpan data)
{
// Use System.Security.Cryptography internally - this is allowed within plugin
return _hashAlgorithmName.Name switch
{
"SHA256" => SHA256.HashData(data),
"SHA384" => SHA384.HashData(data),
"SHA512" => SHA512.HashData(data),
_ => throw new NotSupportedException($"Hash algorithm '{_hashAlgorithmName}' is not supported.")
};
}
public string ComputeHashHex(ReadOnlySpan data)
{
var hash = ComputeHash(data);
return Convert.ToHexString(hash).ToLowerInvariant();
}
}
///
/// Internal ECDSA signer using .NET BCL.
///
private sealed class EcdsaSigner : ICryptoSigner
{
private readonly ECCurve _curve;
private readonly HashAlgorithmName _hashAlgorithm;
private readonly CryptoKeyReference _keyReference;
public string KeyId { get; }
public string AlgorithmId { get; }
public EcdsaSigner(string algorithmId, ECCurve curve, HashAlgorithmName hashAlgorithm, CryptoKeyReference keyReference)
{
AlgorithmId = algorithmId;
_curve = curve;
_hashAlgorithm = hashAlgorithm;
_keyReference = keyReference;
KeyId = keyReference.KeyId;
}
public ValueTask SignAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default)
{
// Use System.Security.Cryptography internally - this is allowed within plugin
using var ecdsa = ECDsa.Create(_curve);
// In a real implementation, would load key material from _keyReference
// For now, generate ephemeral key (caller should provide key material)
var signature = ecdsa.SignData(data.Span, _hashAlgorithm);
return new ValueTask(signature);
}
public ValueTask VerifyAsync(ReadOnlyMemory data, ReadOnlyMemory signature, CancellationToken cancellationToken = default)
{
// Use System.Security.Cryptography internally - this is allowed within plugin
using var ecdsa = ECDsa.Create(_curve);
// In a real implementation, would load public key from _keyReference
var isValid = ecdsa.VerifyData(data.Span, signature.Span, _hashAlgorithm);
return new ValueTask(isValid);
}
public Microsoft.IdentityModel.Tokens.JsonWebKey ExportPublicJsonWebKey()
{
// In a real implementation, would export actual public key
// For offline verification, this is typically not needed
throw new NotSupportedException("JWK export not supported in offline verification mode.");
}
}
///
/// Internal RSA signer using .NET BCL.
///
private sealed class RsaSigner : ICryptoSigner
{
private readonly HashAlgorithmName _hashAlgorithm;
private readonly RSASignaturePadding _padding;
private readonly CryptoKeyReference _keyReference;
public string KeyId { get; }
public string AlgorithmId { get; }
public RsaSigner(string algorithmId, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, CryptoKeyReference keyReference)
{
AlgorithmId = algorithmId;
_hashAlgorithm = hashAlgorithm;
_padding = padding;
_keyReference = keyReference;
KeyId = keyReference.KeyId;
}
public ValueTask SignAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default)
{
// Use System.Security.Cryptography internally - this is allowed within plugin
using var rsa = RSA.Create();
// In a real implementation, would load key material from _keyReference
// For now, generate ephemeral key (caller should provide key material)
var signature = rsa.SignData(data.Span, _hashAlgorithm, _padding);
return new ValueTask(signature);
}
public ValueTask VerifyAsync(ReadOnlyMemory data, ReadOnlyMemory signature, CancellationToken cancellationToken = default)
{
// Use System.Security.Cryptography internally - this is allowed within plugin
using var rsa = RSA.Create();
// In a real implementation, would load public key from _keyReference
var isValid = rsa.VerifyData(data.Span, signature.Span, _hashAlgorithm, _padding);
return new ValueTask(isValid);
}
public Microsoft.IdentityModel.Tokens.JsonWebKey ExportPublicJsonWebKey()
{
// In a real implementation, would export actual public key
// For offline verification, this is typically not needed
throw new NotSupportedException("JWK export not supported in offline verification mode.");
}
}
///
/// Ephemeral RSA verifier using raw public key bytes (verification-only).
///
private sealed class RsaEphemeralVerifier : ICryptoSigner
{
private readonly HashAlgorithmName _hashAlgorithm;
private readonly RSASignaturePadding _padding;
private readonly byte[] _publicKeyBytes;
public string KeyId { get; } = "ephemeral";
public string AlgorithmId { get; }
public RsaEphemeralVerifier(string algorithmId, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding, byte[] publicKeyBytes)
{
AlgorithmId = algorithmId;
_hashAlgorithm = hashAlgorithm;
_padding = padding;
_publicKeyBytes = publicKeyBytes;
}
public ValueTask SignAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default)
{
throw new NotSupportedException("Ephemeral verifier does not support signing operations.");
}
public ValueTask VerifyAsync(ReadOnlyMemory data, ReadOnlyMemory signature, CancellationToken cancellationToken = default)
{
// Use System.Security.Cryptography internally - this is allowed within plugin
using var rsa = RSA.Create();
rsa.ImportSubjectPublicKeyInfo(_publicKeyBytes, out _);
var isValid = rsa.VerifyData(data.Span, signature.Span, _hashAlgorithm, _padding);
return new ValueTask(isValid);
}
public Microsoft.IdentityModel.Tokens.JsonWebKey ExportPublicJsonWebKey()
{
throw new NotSupportedException("JWK export not supported for ephemeral verifiers.");
}
}
///
/// Ephemeral ECDSA verifier using raw public key bytes (verification-only).
///
private sealed class EcdsaEphemeralVerifier : ICryptoSigner
{
private readonly HashAlgorithmName _hashAlgorithm;
private readonly byte[] _publicKeyBytes;
public string KeyId { get; } = "ephemeral";
public string AlgorithmId { get; }
public EcdsaEphemeralVerifier(string algorithmId, HashAlgorithmName hashAlgorithm, byte[] publicKeyBytes)
{
AlgorithmId = algorithmId;
_hashAlgorithm = hashAlgorithm;
_publicKeyBytes = publicKeyBytes;
}
public ValueTask SignAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default)
{
throw new NotSupportedException("Ephemeral verifier does not support signing operations.");
}
public ValueTask VerifyAsync(ReadOnlyMemory data, ReadOnlyMemory signature, CancellationToken cancellationToken = default)
{
// Use System.Security.Cryptography internally - this is allowed within plugin
using var ecdsa = ECDsa.Create();
ecdsa.ImportSubjectPublicKeyInfo(_publicKeyBytes, out _);
var isValid = ecdsa.VerifyData(data.Span, signature.Span, _hashAlgorithm);
return new ValueTask(isValid);
}
public Microsoft.IdentityModel.Tokens.JsonWebKey ExportPublicJsonWebKey()
{
throw new NotSupportedException("JWK export not supported for ephemeral verifiers.");
}
}
}