112 lines
4.6 KiB
C#
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();
|
|
}
|
|
} |