Files
git.stella-ops.org/src/__Libraries/StellaOps.Cryptography.Kms/Pkcs11Facade.cs

112 lines
4.6 KiB
C#

using Microsoft.Extensions.Options;
using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
using Net.Pkcs11Interop.HighLevelAPI.Factories;
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using static StellaOps.Localization.T;
namespace StellaOps.Cryptography.Kms;
internal sealed partial class Pkcs11InteropFacade : IPkcs11Facade
{
private readonly Pkcs11Options _options;
private readonly Pkcs11InteropFactories _factories;
private readonly IPkcs11Library _library;
private readonly ISlot _slot;
private readonly ConcurrentDictionary<string, IObjectAttribute[]> _attributeCache = new(StringComparer.Ordinal);
private readonly TimeProvider _timeProvider;
public Pkcs11InteropFacade(Pkcs11Options options, TimeProvider? timeProvider = null)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_timeProvider = timeProvider ?? TimeProvider.System;
if (string.IsNullOrWhiteSpace(_options.LibraryPath))
{
throw new ArgumentException("PKCS#11 library path must be provided.", nameof(options));
}
_factories = new Pkcs11InteropFactories();
_library = _factories.Pkcs11LibraryFactory.LoadPkcs11Library(_factories, _options.LibraryPath, AppType.MultiThreaded);
_slot = ResolveSlot(_library, _options)
?? throw new InvalidOperationException(_t("crypto.pkcs11.slot_not_found"));
}
public Pkcs11InteropFacade(IOptions<Pkcs11Options> options, TimeProvider timeProvider)
: this(options?.Value ?? new Pkcs11Options(), timeProvider)
{
}
public async Task<Pkcs11KeyDescriptor> GetKeyAsync(CancellationToken cancellationToken)
{
using var context = await OpenSessionAsync(cancellationToken).ConfigureAwait(false);
var session = context.Session;
var privateHandle = FindKey(session, CKO.CKO_PRIVATE_KEY, _options.PrivateKeyLabel);
if (privateHandle is null)
{
throw new InvalidOperationException(_t("crypto.pkcs11.private_key_not_found"));
}
var labelAttr = GetAttribute(session, privateHandle, CKA.CKA_LABEL);
var label = labelAttr?.GetValueAsString();
return new Pkcs11KeyDescriptor(
KeyId: label ?? privateHandle.ObjectId.ToString(),
Label: label,
CreatedAt: _timeProvider.GetUtcNow());
}
public async Task<Pkcs11PublicKeyMaterial> GetPublicKeyAsync(CancellationToken cancellationToken)
{
using var context = await OpenSessionAsync(cancellationToken).ConfigureAwait(false);
var session = context.Session;
var publicHandle = FindKey(session, CKO.CKO_PUBLIC_KEY, _options.PublicKeyLabel ?? _options.PrivateKeyLabel);
if (publicHandle is null)
{
throw new InvalidOperationException(_t("crypto.pkcs11.public_key_not_found"));
}
var pointAttr = GetAttribute(session, publicHandle, CKA.CKA_EC_POINT)
?? throw new InvalidOperationException(_t("crypto.pkcs11.missing_ec_point"));
var paramsAttr = GetAttribute(session, publicHandle, CKA.CKA_EC_PARAMS)
?? 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(_t("crypto.pkcs11.unsupported_point_format"));
}
var qx = ecPoint.AsSpan(1, coordinateSize).ToArray();
var qy = ecPoint.AsSpan(1 + coordinateSize, coordinateSize).ToArray();
var keyId = GetAttribute(session, publicHandle, CKA.CKA_LABEL)?.GetValueAsString()
?? publicHandle.ObjectId.ToString();
return new Pkcs11PublicKeyMaterial(
keyId,
curve,
qx,
qy);
}
public async Task<byte[]> SignDigestAsync(ReadOnlyMemory<byte> digest, CancellationToken cancellationToken)
{
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(_t("crypto.pkcs11.private_key_not_found"));
var mechanism = _factories.MechanismFactory.Create(_options.MechanismId);
return session.Sign(mechanism, privateHandle, digest.ToArray());
}
public void Dispose()
{
_library.Dispose();
}
}