part #2
This commit is contained in:
@@ -1,35 +1,15 @@
|
||||
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
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.Formats.Asn1;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Cryptography.Kms;
|
||||
|
||||
public interface IPkcs11Facade : IDisposable
|
||||
{
|
||||
Task<Pkcs11KeyDescriptor> GetKeyAsync(CancellationToken cancellationToken);
|
||||
|
||||
Task<Pkcs11PublicKeyMaterial> GetPublicKeyAsync(CancellationToken cancellationToken);
|
||||
|
||||
Task<byte[]> SignDigestAsync(ReadOnlyMemory<byte> digest, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public sealed record Pkcs11KeyDescriptor(
|
||||
string KeyId,
|
||||
string? Label,
|
||||
DateTimeOffset CreatedAt);
|
||||
|
||||
public sealed record Pkcs11PublicKeyMaterial(
|
||||
string KeyId,
|
||||
string Curve,
|
||||
byte[] Qx,
|
||||
byte[] Qy);
|
||||
|
||||
internal sealed class Pkcs11InteropFacade : IPkcs11Facade
|
||||
internal sealed partial class Pkcs11InteropFacade : IPkcs11Facade
|
||||
{
|
||||
private readonly Pkcs11Options _options;
|
||||
private readonly Pkcs11InteropFactories _factories;
|
||||
@@ -53,6 +33,11 @@ internal sealed class Pkcs11InteropFacade : IPkcs11Facade
|
||||
?? throw new InvalidOperationException("Could not resolve PKCS#11 slot.");
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -119,169 +104,8 @@ internal sealed class Pkcs11InteropFacade : IPkcs11Facade
|
||||
return session.Sign(mechanism, privateHandle, digest.ToArray());
|
||||
}
|
||||
|
||||
private async Task<SessionContext> OpenSessionAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var session = _slot.OpenSession(SessionType.ReadOnly);
|
||||
|
||||
var loggedIn = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_options.UserPin))
|
||||
{
|
||||
session.Login(CKU.CKU_USER, _options.UserPin);
|
||||
loggedIn = true;
|
||||
}
|
||||
|
||||
return new SessionContext(session, loggedIn);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (loggedIn)
|
||||
{
|
||||
try { session.Logout(); } catch { /* ignore */ }
|
||||
}
|
||||
|
||||
session.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private IObjectHandle? FindKey(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 IObjectAttribute? GetAttribute(ISession session, IObjectHandle handle, CKA type)
|
||||
{
|
||||
var cacheKey = $"{handle.ObjectId}:{(uint)type}";
|
||||
if (_attributeCache.TryGetValue(cacheKey, out var cached))
|
||||
{
|
||||
return cached.FirstOrDefault();
|
||||
}
|
||||
|
||||
var attributes = session.GetAttributeValue(handle, new List<CKA> { type })
|
||||
?.ToArray() ?? Array.Empty<IObjectAttribute>();
|
||||
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
_attributeCache[cacheKey] = attributes;
|
||||
return attributes[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ISlot? ResolveSlot(IPkcs11Library pkcs11, Pkcs11Options 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 info = slot.GetTokenInfo();
|
||||
return string.Equals(info.Label?.Trim(), options.TokenLabel.Trim(), StringComparison.Ordinal);
|
||||
});
|
||||
}
|
||||
|
||||
return slots[0];
|
||||
}
|
||||
|
||||
private static byte[] ExtractEcPoint(byte[] derEncoded)
|
||||
{
|
||||
var reader = new AsnReader(derEncoded, AsnEncodingRules.DER);
|
||||
var point = reader.ReadOctetString();
|
||||
reader.ThrowIfNotEmpty();
|
||||
return point;
|
||||
}
|
||||
|
||||
private static (string CurveName, int CoordinateSize) DecodeCurve(byte[] ecParamsDer)
|
||||
{
|
||||
var reader = new AsnReader(ecParamsDer, AsnEncodingRules.DER);
|
||||
var oid = reader.ReadObjectIdentifier();
|
||||
reader.ThrowIfNotEmpty();
|
||||
|
||||
var curve = oid switch
|
||||
{
|
||||
"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}'."),
|
||||
};
|
||||
|
||||
var coordinateSize = curve switch
|
||||
{
|
||||
JsonWebKeyECTypes.P256 => 32,
|
||||
JsonWebKeyECTypes.P384 => 48,
|
||||
JsonWebKeyECTypes.P521 => 66,
|
||||
_ => throw new InvalidOperationException($"Unsupported EC curve '{curve}'."),
|
||||
};
|
||||
|
||||
return (curve, coordinateSize);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_library.Dispose();
|
||||
}
|
||||
|
||||
private sealed class SessionContext : System.IDisposable
|
||||
{
|
||||
private readonly ISession _session;
|
||||
private readonly bool _logoutOnDispose;
|
||||
private bool _disposed;
|
||||
|
||||
public SessionContext(ISession session, bool logoutOnDispose)
|
||||
{
|
||||
_session = session ?? throw new System.ArgumentNullException(nameof(session));
|
||||
_logoutOnDispose = logoutOnDispose;
|
||||
}
|
||||
|
||||
public ISession Session => _session;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_logoutOnDispose)
|
||||
{
|
||||
try
|
||||
{
|
||||
_session.Logout();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore logout failures
|
||||
}
|
||||
}
|
||||
|
||||
_session.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user