Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
182 lines
6.4 KiB
C#
182 lines
6.4 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace StellaOps.Cryptography;
|
|
|
|
/// <summary>
|
|
/// Default in-process crypto provider exposing password hashing capabilities.
|
|
/// </summary>
|
|
public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiagnostics
|
|
{
|
|
private readonly ConcurrentDictionary<string, IPasswordHasher> passwordHashers;
|
|
private readonly ConcurrentDictionary<string, CryptoSigningKey> signingKeys;
|
|
private static readonly HashSet<string> SupportedSigningAlgorithms = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
SignatureAlgorithms.Es256
|
|
};
|
|
|
|
private static readonly HashSet<string> SupportedHashAlgorithms = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
HashAlgorithms.Sha256,
|
|
HashAlgorithms.Sha384,
|
|
HashAlgorithms.Sha512
|
|
};
|
|
|
|
public DefaultCryptoProvider()
|
|
{
|
|
passwordHashers = new ConcurrentDictionary<string, IPasswordHasher>(StringComparer.OrdinalIgnoreCase);
|
|
signingKeys = new ConcurrentDictionary<string, CryptoSigningKey>(StringComparer.Ordinal);
|
|
|
|
var argon = new Argon2idPasswordHasher();
|
|
var pbkdf2 = new Pbkdf2PasswordHasher();
|
|
|
|
passwordHashers.TryAdd(PasswordHashAlgorithm.Argon2id.ToString(), argon);
|
|
passwordHashers.TryAdd(PasswordHashAlgorithms.Argon2id, argon);
|
|
passwordHashers.TryAdd(PasswordHashAlgorithm.Pbkdf2.ToString(), pbkdf2);
|
|
passwordHashers.TryAdd(PasswordHashAlgorithms.Pbkdf2Sha256, pbkdf2);
|
|
}
|
|
|
|
public string Name => "default";
|
|
|
|
public bool Supports(CryptoCapability capability, string algorithmId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(algorithmId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return capability switch
|
|
{
|
|
CryptoCapability.PasswordHashing => passwordHashers.ContainsKey(algorithmId),
|
|
CryptoCapability.Signing or CryptoCapability.Verification => SupportedSigningAlgorithms.Contains(algorithmId),
|
|
CryptoCapability.ContentHashing => SupportedHashAlgorithms.Contains(algorithmId),
|
|
_ => false
|
|
};
|
|
}
|
|
|
|
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
|
{
|
|
if (!Supports(CryptoCapability.PasswordHashing, algorithmId))
|
|
{
|
|
throw new InvalidOperationException($"Password hashing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
|
}
|
|
|
|
return passwordHashers[algorithmId];
|
|
}
|
|
|
|
public ICryptoHasher GetHasher(string algorithmId)
|
|
{
|
|
if (!Supports(CryptoCapability.ContentHashing, algorithmId))
|
|
{
|
|
throw new InvalidOperationException($"Hash algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
|
}
|
|
|
|
return new DefaultCryptoHasher(algorithmId);
|
|
}
|
|
|
|
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(keyReference);
|
|
|
|
if (!Supports(CryptoCapability.Signing, algorithmId))
|
|
{
|
|
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
|
}
|
|
|
|
if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey))
|
|
{
|
|
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
|
|
}
|
|
|
|
if (!string.Equals(signingKey.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'.");
|
|
}
|
|
|
|
return EcdsaSigner.Create(signingKey);
|
|
}
|
|
|
|
public void UpsertSigningKey(CryptoSigningKey signingKey)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(signingKey);
|
|
EnsureSigningSupported(signingKey.AlgorithmId);
|
|
if (signingKey.Kind != CryptoSigningKeyKind.Ec)
|
|
{
|
|
throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys.");
|
|
}
|
|
ValidateSigningKey(signingKey);
|
|
|
|
signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey);
|
|
}
|
|
|
|
public bool RemoveSigningKey(string keyId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(keyId))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return signingKeys.TryRemove(keyId, out _);
|
|
}
|
|
|
|
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
|
|
=> signingKeys.Values.ToArray();
|
|
|
|
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys()
|
|
{
|
|
foreach (var key in signingKeys.Values)
|
|
{
|
|
var metadata = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
["kind"] = key.Kind.ToString(),
|
|
["createdAt"] = key.CreatedAt.UtcDateTime.ToString("O"),
|
|
["providerHint"] = key.Reference.ProviderHint,
|
|
["provider"] = Name
|
|
};
|
|
|
|
if (key.ExpiresAt.HasValue)
|
|
{
|
|
metadata["expiresAt"] = key.ExpiresAt.Value.UtcDateTime.ToString("O");
|
|
}
|
|
|
|
foreach (var pair in key.Metadata)
|
|
{
|
|
metadata[$"meta.{pair.Key}"] = pair.Value;
|
|
}
|
|
|
|
yield return new CryptoProviderKeyDescriptor(
|
|
Name,
|
|
key.Reference.KeyId,
|
|
key.AlgorithmId,
|
|
metadata);
|
|
}
|
|
}
|
|
|
|
private static void EnsureSigningSupported(string algorithmId)
|
|
{
|
|
if (!SupportedSigningAlgorithms.Contains(algorithmId))
|
|
{
|
|
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'default'.");
|
|
}
|
|
}
|
|
|
|
private static void ValidateSigningKey(CryptoSigningKey signingKey)
|
|
{
|
|
if (!string.Equals(signingKey.AlgorithmId, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new InvalidOperationException($"Only ES256 signing keys are currently supported by provider 'default'.");
|
|
}
|
|
|
|
var expected = ECCurve.NamedCurves.nistP256;
|
|
var curve = signingKey.PrivateParameters.Curve;
|
|
if (!curve.IsNamed || !string.Equals(curve.Oid.Value, expected.Oid.Value, StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException("ES256 signing keys must use the NIST P-256 curve.");
|
|
}
|
|
}
|
|
}
|