search and ai stabilization work, localization stablized.

This commit is contained in:
master
2026-02-24 23:29:36 +02:00
parent 4f947a8b61
commit b07d27772e
766 changed files with 55299 additions and 3221 deletions

View File

@@ -1,6 +1,7 @@
using Microsoft.IdentityModel.Tokens;
using System;
using System.Security.Cryptography;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -11,7 +12,7 @@ public sealed partial class AwsKmsClient
var digest = new byte[32];
if (!SHA256.TryHashData(data.Span, digest, out _))
{
throw new InvalidOperationException("Failed to hash payload with SHA-256.");
throw new InvalidOperationException(_t("crypto.kms.hash_failed"));
}
return digest;

View File

@@ -3,6 +3,7 @@ using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -56,8 +57,8 @@ public sealed partial class AwsKmsClient
KmsAlgorithms.Es256,
ResolveCurveName(publicKey.Curve),
Array.Empty<byte>(),
parameters.Q.X ?? throw new InvalidOperationException("Public key missing X coordinate."),
parameters.Q.Y ?? throw new InvalidOperationException("Public key missing Y coordinate."),
parameters.Q.X ?? throw new InvalidOperationException(_t("crypto.kms.public_key_missing_x")),
parameters.Q.Y ?? throw new InvalidOperationException(_t("crypto.kms.public_key_missing_y")),
metadata.CreatedAt);
}
}

View File

@@ -2,6 +2,7 @@ using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -34,10 +35,10 @@ public sealed partial class AwsKmsClient : IKmsClient, IDisposable
}
public Task<KmsKeyMetadata> RotateAsync(string keyId, CancellationToken cancellationToken = default)
=> throw new NotSupportedException("AWS KMS rotation must be orchestrated via AWS KMS policies or schedules.");
=> throw new NotSupportedException(_t("crypto.kms.rotation_via_policy", "AWS KMS", "AWS KMS"));
public Task RevokeAsync(string keyId, CancellationToken cancellationToken = default)
=> throw new NotSupportedException("AWS KMS key revocation must be managed through AWS KMS APIs or console.");
=> throw new NotSupportedException(_t("crypto.kms.revocation_via_policy", "AWS KMS key", "AWS KMS"));
public void Dispose()
{

View File

@@ -2,6 +2,7 @@ using Amazon.KeyManagementService.Model;
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -16,7 +17,7 @@ internal sealed partial class AwsKmsFacade
KeyId = keyId,
}, cancellationToken).ConfigureAwait(false);
var metadata = response.KeyMetadata ?? throw new InvalidOperationException($"Key '{keyId}' was not found.");
var metadata = response.KeyMetadata ?? throw new InvalidOperationException(_t("crypto.kms.key_not_found", keyId));
var createdAt = metadata.CreationDate?.ToUniversalTime() ?? _timeProvider.GetUtcNow();
return new AwsKeyMetadata(

View File

@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -18,8 +19,8 @@ public sealed partial class Fido2KmsClient
metadata.Algorithm,
_curveName,
Array.Empty<byte>(),
_publicParameters.Q.X ?? throw new InvalidOperationException("FIDO2 public key missing X coordinate."),
_publicParameters.Q.Y ?? throw new InvalidOperationException("FIDO2 public key missing Y coordinate."),
_publicParameters.Q.X ?? throw new InvalidOperationException(_t("crypto.fido2.missing_x")),
_publicParameters.Q.Y ?? throw new InvalidOperationException(_t("crypto.fido2.missing_y")),
_options.CreatedAt ?? _timeProvider.GetUtcNow());
}
}

View File

@@ -1,6 +1,7 @@
using Microsoft.IdentityModel.Tokens;
using System;
using System.Security.Cryptography;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -11,7 +12,7 @@ public sealed partial class Fido2KmsClient
var digest = new byte[32];
if (!SHA256.TryHashData(data.Span, digest, out _))
{
throw new InvalidOperationException("Failed to hash payload with SHA-256.");
throw new InvalidOperationException(_t("crypto.kms.hash_failed"));
}
return digest;
@@ -33,7 +34,7 @@ public sealed partial class Fido2KmsClient
"1.2.840.10045.3.1.7" => JsonWebKeyECTypes.P256,
"1.3.132.0.34" => JsonWebKeyECTypes.P384,
"1.3.132.0.35" => JsonWebKeyECTypes.P521,
_ => throw new InvalidOperationException($"Unsupported FIDO2 curve OID '{oid}'."),
_ => throw new InvalidOperationException(_t("crypto.fido2.curve_unsupported", oid ?? string.Empty)),
};
}
}

View File

@@ -1,16 +1,17 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
public sealed partial class Fido2KmsClient
{
public Task<KmsKeyMetadata> RotateAsync(string keyId, CancellationToken cancellationToken = default)
=> throw new NotSupportedException("FIDO2 credential rotation requires new enrolment.");
=> throw new NotSupportedException(_t("crypto.fido2.rotation_required"));
public Task RevokeAsync(string keyId, CancellationToken cancellationToken = default)
=> throw new NotSupportedException("FIDO2 credential revocation must be managed in the relying party.");
=> throw new NotSupportedException(_t("crypto.fido2.revocation_relying_party"));
public void Dispose()
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Security.Cryptography;
using System.Text.Json;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -10,7 +11,7 @@ public sealed partial class FileKmsClient
{
if (!string.Equals(algorithm, KmsAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
{
throw new NotSupportedException($"Algorithm '{algorithm}' is not supported by the file KMS driver.");
throw new NotSupportedException(_t("crypto.kms.algorithm_unsupported", algorithm));
}
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);

View File

@@ -1,5 +1,6 @@
using System;
using System.Security.Cryptography;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -53,6 +54,6 @@ public sealed partial class FileKmsClient
private static ECCurve ResolveCurve(string curveName) => curveName switch
{
"nistP256" or "P-256" or "ES256" => ECCurve.NamedCurves.nistP256,
_ => throw new NotSupportedException($"Curve '{curveName}' is not supported."),
_ => throw new NotSupportedException(_t("crypto.kms.curve_unsupported", curveName)),
};
}

View File

@@ -5,6 +5,7 @@ using System.Security.Cryptography;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -32,11 +33,11 @@ public sealed partial class FileKmsClient
try
{
var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: true).ConfigureAwait(false)
?? throw new InvalidOperationException("Failed to create or load key metadata.");
?? throw new InvalidOperationException(_t("crypto.kms.metadata_failed"));
if (!string.Equals(record.Algorithm, material.Algorithm, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"Algorithm mismatch. Expected '{record.Algorithm}', received '{material.Algorithm}'.");
throw new InvalidOperationException(_t("crypto.kms.algorithm_mismatch", record.Algorithm, material.Algorithm));
}
var versionId = string.IsNullOrWhiteSpace(material.VersionId)
@@ -45,7 +46,7 @@ public sealed partial class FileKmsClient
if (record.Versions.Any(v => string.Equals(v.VersionId, versionId, StringComparison.Ordinal)))
{
throw new InvalidOperationException($"Key version '{versionId}' already exists for key '{record.KeyId}'.");
throw new InvalidOperationException(_t("crypto.kms.version_exists", versionId, record.KeyId));
}
var curveName = string.IsNullOrWhiteSpace(material.Curve) ? "nistP256" : material.Curve;

View File

@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -14,7 +15,7 @@ public sealed partial class FileKmsClient
try
{
var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: false).ConfigureAwait(false)
?? throw new InvalidOperationException($"Key '{keyId}' does not exist.");
?? throw new InvalidOperationException(_t("crypto.kms.key_not_found", keyId));
return ToMetadata(record);
}
finally
@@ -31,12 +32,12 @@ public sealed partial class FileKmsClient
try
{
var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: false).ConfigureAwait(false)
?? throw new InvalidOperationException($"Key '{keyId}' does not exist.");
?? throw new InvalidOperationException(_t("crypto.kms.key_not_found", keyId));
var version = ResolveVersion(record, keyVersion);
if (string.IsNullOrWhiteSpace(version.PublicKey))
{
throw new InvalidOperationException($"Key '{keyId}' version '{version.VersionId}' does not have public key material.");
throw new InvalidOperationException(_t("crypto.kms.key_no_public_material", keyId, version.VersionId));
}
var privateKey = await LoadPrivateKeyAsync(record, version, cancellationToken).ConfigureAwait(false);

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -24,7 +25,7 @@ public sealed partial class FileKmsClient
version = record.Versions.SingleOrDefault(v => string.Equals(v.VersionId, keyVersion, StringComparison.Ordinal));
if (version is null)
{
throw new InvalidOperationException($"Key version '{keyVersion}' does not exist for key '{record.KeyId}'.");
throw new InvalidOperationException(_t("crypto.kms.key_version_not_found", keyVersion, record.KeyId));
}
}
else if (!string.IsNullOrWhiteSpace(record.ActiveVersion))
@@ -39,7 +40,7 @@ public sealed partial class FileKmsClient
if (version is null)
{
throw new InvalidOperationException($"Key '{record.KeyId}' does not have an active version.");
throw new InvalidOperationException(_t("crypto.kms.key_no_active_version", record.KeyId));
}
return version;

View File

@@ -4,6 +4,7 @@ using System.Security.Cryptography;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -70,18 +71,18 @@ public sealed partial class FileKmsClient
var keyPath = Path.Combine(GetKeyDirectory(record.KeyId), version.FileName);
if (!File.Exists(keyPath))
{
throw new InvalidOperationException($"Key material for version '{version.VersionId}' was not found.");
throw new InvalidOperationException(_t("crypto.kms.material_not_found", version.VersionId));
}
await using var stream = File.Open(keyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
var envelope = await JsonSerializer.DeserializeAsync<KeyEnvelope>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false)
?? throw new InvalidOperationException("Key envelope could not be deserialized.");
?? throw new InvalidOperationException(_t("crypto.kms.envelope_deserialize_failed"));
var payload = DecryptPrivateKey(envelope);
try
{
return JsonSerializer.Deserialize<EcdsaPrivateKeyRecord>(payload, _jsonOptions)
?? throw new InvalidOperationException("Key payload could not be deserialized.");
?? throw new InvalidOperationException(_t("crypto.kms.payload_deserialize_failed"));
}
finally
{

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -17,11 +18,11 @@ public sealed partial class FileKmsClient
try
{
var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: true).ConfigureAwait(false)
?? throw new InvalidOperationException("Failed to create or load key metadata.");
?? throw new InvalidOperationException(_t("crypto.kms.metadata_failed"));
if (record.State == KmsKeyState.Revoked)
{
throw new InvalidOperationException($"Key '{keyId}' has been revoked and cannot be rotated.");
throw new InvalidOperationException(_t("crypto.kms.key_revoked", keyId));
}
var timestamp = _timeProvider.GetUtcNow();
@@ -76,7 +77,7 @@ public sealed partial class FileKmsClient
try
{
var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: false).ConfigureAwait(false)
?? throw new InvalidOperationException($"Key '{keyId}' does not exist.");
?? throw new InvalidOperationException(_t("crypto.kms.key_not_found", keyId));
var timestamp = _timeProvider.GetUtcNow();
record.State = KmsKeyState.Revoked;

View File

@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -22,17 +23,17 @@ public sealed partial class FileKmsClient
try
{
var record = await LoadOrCreateMetadataAsync(keyId, cancellationToken, createIfMissing: false).ConfigureAwait(false)
?? throw new InvalidOperationException($"Key '{keyId}' does not exist.");
?? throw new InvalidOperationException(_t("crypto.kms.key_not_found", keyId));
if (record.State == KmsKeyState.Revoked)
{
throw new InvalidOperationException($"Key '{keyId}' is revoked and cannot be used for signing.");
throw new InvalidOperationException(_t("crypto.kms.key_revoked_signing", keyId));
}
var version = ResolveVersion(record, keyVersion);
if (version.State != KmsKeyState.Active)
{
throw new InvalidOperationException($"Key version '{version.VersionId}' is not active. Current state: {version.State}");
throw new InvalidOperationException(_t("crypto.kms.key_version_inactive", version.VersionId, version.State));
}
var privateKey = await LoadPrivateKeyAsync(record, version, cancellationToken).ConfigureAwait(false);

View File

@@ -3,6 +3,7 @@ using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -30,7 +31,7 @@ public sealed partial class GcpKmsClient
{
if (string.IsNullOrWhiteSpace(pem))
{
throw new InvalidOperationException("Public key PEM cannot be empty.");
throw new InvalidOperationException(_t("crypto.kms.pem_empty"));
}
var builder = new StringBuilder(pem.Length);
@@ -54,7 +55,7 @@ public sealed partial class GcpKmsClient
var digest = new byte[32];
if (!SHA256.TryHashData(data.Span, digest, out _))
{
throw new InvalidOperationException("Failed to hash payload with SHA-256.");
throw new InvalidOperationException(_t("crypto.kms.hash_failed"));
}
return digest;

View File

@@ -3,6 +3,7 @@ using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -64,8 +65,8 @@ public sealed partial class GcpKmsClient
KmsAlgorithms.Es256,
ResolveCurve(publicMaterial.Algorithm),
Array.Empty<byte>(),
parameters.Q.X ?? throw new InvalidOperationException("Public key missing X coordinate."),
parameters.Q.Y ?? throw new InvalidOperationException("Public key missing Y coordinate."),
parameters.Q.X ?? throw new InvalidOperationException(_t("crypto.kms.public_key_missing_x")),
parameters.Q.Y ?? throw new InvalidOperationException(_t("crypto.kms.public_key_missing_y")),
snapshot.Metadata.CreateTime);
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -25,6 +26,6 @@ public sealed partial class GcpKmsClient
return firstActive.VersionName;
}
throw new InvalidOperationException($"Crypto key '{keyId}' does not have an active primary version.");
throw new InvalidOperationException(_t("crypto.kms.no_primary_version", keyId));
}
}

View File

@@ -2,6 +2,7 @@ using Microsoft.Extensions.Options;
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -35,10 +36,10 @@ public sealed partial class GcpKmsClient : IKmsClient, IDisposable
}
public Task<KmsKeyMetadata> RotateAsync(string keyId, CancellationToken cancellationToken = default)
=> throw new NotSupportedException("Google Cloud KMS rotation must be managed via Cloud KMS rotation schedules.");
=> throw new NotSupportedException(_t("crypto.kms.rotation_via_policy", "Google Cloud KMS", "Cloud KMS rotation schedules"));
public Task RevokeAsync(string keyId, CancellationToken cancellationToken = default)
=> throw new NotSupportedException("Google Cloud KMS key revocation must be managed via Cloud KMS destroy/disable operations.");
=> throw new NotSupportedException(_t("crypto.kms.revocation_via_policy", "Google Cloud KMS key", "Cloud KMS destroy/disable"));
public void Dispose()
{

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using StellaOps.Cryptography;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -29,10 +30,10 @@ public sealed partial class KmsCryptoProvider : ICryptoProvider
}
public IPasswordHasher GetPasswordHasher(string algorithmId)
=> throw new InvalidOperationException($"Provider '{Name}' does not support password hashing.");
=> throw new InvalidOperationException(_t("crypto.provider.no_password_hashing", Name));
public ICryptoHasher GetHasher(string algorithmId)
=> throw new InvalidOperationException($"Provider '{Name}' does not support content hashing.");
=> throw new InvalidOperationException(_t("crypto.provider.no_content_hashing", Name));
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
{
@@ -40,12 +41,12 @@ public sealed partial class KmsCryptoProvider : ICryptoProvider
if (!Supports(CryptoCapability.Signing, algorithmId))
{
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
throw new InvalidOperationException(_t("crypto.provider.algorithm_not_supported", algorithmId, Name));
}
if (!_registrations.TryGetValue(keyReference.KeyId, out var registration))
{
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
throw new KeyNotFoundException(_t("crypto.provider.key_not_registered", keyReference.KeyId, Name));
}
return new KmsSigner(_kmsClient, registration);
@@ -57,14 +58,14 @@ public sealed partial class KmsCryptoProvider : ICryptoProvider
if (!string.Equals(signingKey.AlgorithmId, KmsAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException($"Provider '{Name}' only supports {KmsAlgorithms.Es256} signing keys.");
throw new InvalidOperationException(_t("crypto.kms.es256_only", Name));
}
if (signingKey.Metadata is null ||
!signingKey.Metadata.TryGetValue(KmsMetadataKeys.Version, out var versionId) ||
string.IsNullOrWhiteSpace(versionId))
{
throw new InvalidOperationException("KMS signing keys must include metadata entry 'kms.version'.");
throw new InvalidOperationException(_t("crypto.kms.version_metadata_required"));
}
KmsPublicKey? publicKey = null;

View File

@@ -3,6 +3,7 @@ using StellaOps.Cryptography;
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -42,7 +43,7 @@ internal sealed class KmsSigner : ICryptoSigner
public JsonWebKey ExportPublicJsonWebKey()
{
var publicKey = _registration.PublicKey
?? throw new InvalidOperationException("KMS signing key is missing public key material.");
?? throw new InvalidOperationException(_t("crypto.kms.missing_public_key"));
var jwk = new JsonWebKey
{
Kid = _registration.Reference.KeyId,

View File

@@ -1,11 +1,12 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
internal sealed class MissingFido2Authenticator : IFido2Authenticator
{
public Task<byte[]> SignAsync(string credentialId, ReadOnlyMemory<byte> digest, CancellationToken cancellationToken = default)
=> throw new InvalidOperationException("IFido2Authenticator must be registered to use FIDO2 KMS.");
=> throw new InvalidOperationException(_t("crypto.fido2.authenticator_required"));
}

View File

@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Formats.Asn1;
using System.Linq;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -90,7 +91,7 @@ internal sealed partial class Pkcs11InteropFacade
"1.2.840.10045.3.1.7" => JsonWebKeyECTypes.P256,
"1.3.132.0.34" => JsonWebKeyECTypes.P384,
"1.3.132.0.35" => JsonWebKeyECTypes.P521,
_ => throw new InvalidOperationException($"Unsupported EC curve OID '{oid}'."),
_ => throw new InvalidOperationException(_t("crypto.pkcs11.curve_oid_unsupported", oid)),
};
var coordinateSize = curve switch
@@ -98,7 +99,7 @@ internal sealed partial class Pkcs11InteropFacade
JsonWebKeyECTypes.P256 => 32,
JsonWebKeyECTypes.P384 => 48,
JsonWebKeyECTypes.P521 => 66,
_ => throw new InvalidOperationException($"Unsupported EC curve '{curve}'."),
_ => throw new InvalidOperationException(_t("crypto.pkcs11.curve_unsupported", curve)),
};
return (curve, coordinateSize);

View File

@@ -8,6 +8,7 @@ namespace StellaOps.Cryptography.Kms;
internal sealed partial class Pkcs11InteropFacade
{
#pragma warning disable CS1998
private async Task<SessionContext> OpenSessionAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

View File

@@ -6,6 +6,7 @@ using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -30,7 +31,7 @@ internal sealed partial class Pkcs11InteropFacade : IPkcs11Facade
_factories = new Pkcs11InteropFactories();
_library = _factories.Pkcs11LibraryFactory.LoadPkcs11Library(_factories, _options.LibraryPath, AppType.MultiThreaded);
_slot = ResolveSlot(_library, _options)
?? throw new InvalidOperationException("Could not resolve PKCS#11 slot.");
?? throw new InvalidOperationException(_t("crypto.pkcs11.slot_not_found"));
}
public Pkcs11InteropFacade(IOptions<Pkcs11Options> options, TimeProvider timeProvider)
@@ -45,7 +46,7 @@ internal sealed partial class Pkcs11InteropFacade : IPkcs11Facade
var privateHandle = FindKey(session, CKO.CKO_PRIVATE_KEY, _options.PrivateKeyLabel);
if (privateHandle is null)
{
throw new InvalidOperationException("PKCS#11 private key not found.");
throw new InvalidOperationException(_t("crypto.pkcs11.private_key_not_found"));
}
var labelAttr = GetAttribute(session, privateHandle, CKA.CKA_LABEL);
@@ -64,20 +65,20 @@ internal sealed partial class Pkcs11InteropFacade : IPkcs11Facade
var publicHandle = FindKey(session, CKO.CKO_PUBLIC_KEY, _options.PublicKeyLabel ?? _options.PrivateKeyLabel);
if (publicHandle is null)
{
throw new InvalidOperationException("PKCS#11 public key not found.");
throw new InvalidOperationException(_t("crypto.pkcs11.public_key_not_found"));
}
var pointAttr = GetAttribute(session, publicHandle, CKA.CKA_EC_POINT)
?? throw new InvalidOperationException("Public key missing EC point.");
?? throw new InvalidOperationException(_t("crypto.pkcs11.missing_ec_point"));
var paramsAttr = GetAttribute(session, publicHandle, CKA.CKA_EC_PARAMS)
?? throw new InvalidOperationException("Public key missing EC parameters.");
?? throw new InvalidOperationException(_t("crypto.pkcs11.missing_ec_params"));
var ecPoint = ExtractEcPoint(pointAttr.GetValueAsByteArray());
var (curve, coordinateSize) = DecodeCurve(paramsAttr.GetValueAsByteArray());
if (ecPoint.Length != 1 + (coordinateSize * 2) || ecPoint[0] != 0x04)
{
throw new InvalidOperationException("Unsupported EC point format.");
throw new InvalidOperationException(_t("crypto.pkcs11.unsupported_point_format"));
}
var qx = ecPoint.AsSpan(1, coordinateSize).ToArray();
@@ -98,7 +99,7 @@ internal sealed partial class Pkcs11InteropFacade : IPkcs11Facade
using var context = await OpenSessionAsync(cancellationToken).ConfigureAwait(false);
var session = context.Session;
var privateHandle = FindKey(session, CKO.CKO_PRIVATE_KEY, _options.PrivateKeyLabel)
?? throw new InvalidOperationException("PKCS#11 private key not found.");
?? throw new InvalidOperationException(_t("crypto.pkcs11.private_key_not_found"));
var mechanism = _factories.MechanismFactory.Create(_options.MechanismId);
return session.Sign(mechanism, privateHandle, digest.ToArray());

View File

@@ -1,6 +1,7 @@
using Microsoft.IdentityModel.Tokens;
using System;
using System.Security.Cryptography;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
@@ -11,7 +12,7 @@ public sealed partial class Pkcs11KmsClient
var digest = new byte[32];
if (!SHA256.TryHashData(data.Span, digest, out _))
{
throw new InvalidOperationException("Failed to hash payload with SHA-256.");
throw new InvalidOperationException(_t("crypto.kms.hash_failed"));
}
return digest;
@@ -23,7 +24,7 @@ public sealed partial class Pkcs11KmsClient
JsonWebKeyECTypes.P256 => ECCurve.NamedCurves.nistP256,
JsonWebKeyECTypes.P384 => ECCurve.NamedCurves.nistP384,
JsonWebKeyECTypes.P521 => ECCurve.NamedCurves.nistP521,
_ => throw new InvalidOperationException($"Unsupported EC curve '{curve}'."),
_ => throw new InvalidOperationException(_t("crypto.pkcs11.curve_unsupported", curve)),
};
private void ThrowIfDisposed()

View File

@@ -1,16 +1,17 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
public sealed partial class Pkcs11KmsClient
{
public Task<KmsKeyMetadata> RotateAsync(string keyId, CancellationToken cancellationToken = default)
=> throw new NotSupportedException("PKCS#11 rotation requires HSM administrative tooling.");
=> throw new NotSupportedException(_t("crypto.pkcs11.rotation_hsm"));
public Task RevokeAsync(string keyId, CancellationToken cancellationToken = default)
=> throw new NotSupportedException("PKCS#11 revocation must be handled by HSM policies.");
=> throw new NotSupportedException(_t("crypto.pkcs11.revocation_hsm"));
public void Dispose()
{

View File

@@ -15,5 +15,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<ProjectReference Include="../StellaOps.Localization/StellaOps.Localization.csproj" />
</ItemGroup>
</Project>