Files
git.stella-ops.org/src/__Libraries/StellaOps.Cryptography.Plugin.Pkcs11Gost/Pkcs11SignerUtilities.cs
2026-02-01 21:37:40 +02:00

111 lines
3.5 KiB
C#

using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
using Net.Pkcs11Interop.HighLevelAPI.Factories;
using StellaOps.Cryptography;
using System;
using System.Collections.Generic;
using System.Linq;
namespace StellaOps.Cryptography.Plugin.Pkcs11Gost;
internal static class Pkcs11SignerUtilities
{
private static readonly Pkcs11InteropFactories Factories = new();
public static byte[] SignDigest(Pkcs11GostKeyEntry entry, ReadOnlySpan<byte> digest)
{
using var pkcs11 = Factories.Pkcs11LibraryFactory.LoadPkcs11Library(Factories, entry.Session.LibraryPath, AppType.MultiThreaded);
var slot = ResolveSlot(pkcs11, entry.Session);
if (slot is null)
{
throw new InvalidOperationException("No PKCS#11 slot/token matched the provided configuration.");
}
using var session = slot.OpenSession(SessionType.ReadWrite);
var loggedIn = false;
try
{
var pin = ResolvePin(entry.Session);
if (!string.IsNullOrWhiteSpace(pin))
{
session.Login(CKU.CKU_USER, pin);
loggedIn = true;
}
var privateHandle = FindObject(session, CKO.CKO_PRIVATE_KEY, entry.Session.PrivateKeyLabel);
if (privateHandle is null)
{
throw new InvalidOperationException($"Private key with label '{entry.Session.PrivateKeyLabel}' was not found.");
}
using var mechanism = Factories.MechanismFactory.Create(entry.SignMechanismId);
return session.Sign(mechanism, privateHandle, digest.ToArray());
}
finally
{
if (loggedIn)
{
try { session.Logout(); } catch { /* ignored */ }
}
}
}
private static ISlot? ResolveSlot(IPkcs11Library pkcs11, Pkcs11SessionOptions options)
{
var slots = pkcs11.GetSlotList(SlotsType.WithTokenPresent);
if (slots.Count == 0)
{
return null;
}
if (!string.IsNullOrWhiteSpace(options.SlotId))
{
return slots.FirstOrDefault(slot =>
string.Equals(slot.SlotId.ToString(), options.SlotId, StringComparison.OrdinalIgnoreCase));
}
if (!string.IsNullOrWhiteSpace(options.TokenLabel))
{
return slots.FirstOrDefault(slot =>
{
var tokenInfo = slot.GetTokenInfo();
return string.Equals(tokenInfo.Label?.Trim(), options.TokenLabel?.Trim(), StringComparison.OrdinalIgnoreCase);
});
}
return slots[0];
}
private static IObjectHandle? FindObject(ISession session, CKO objectClass, string? label)
{
var template = new List<IObjectAttribute>
{
Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, (uint)objectClass)
};
if (!string.IsNullOrWhiteSpace(label))
{
template.Add(Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, label));
}
var handles = session.FindAllObjects(template);
return handles.FirstOrDefault();
}
private static string? ResolvePin(Pkcs11SessionOptions options)
{
if (!string.IsNullOrWhiteSpace(options.UserPin))
{
return options.UserPin;
}
if (!string.IsNullOrWhiteSpace(options.UserPinEnvironmentVariable))
{
return Environment.GetEnvironmentVariable(options.UserPinEnvironmentVariable);
}
return null;
}
}