Vanara/Net/DistributedRoutingTable.cs

1598 lines
63 KiB
C#

#nullable enable
using System.Linq;
using System.Net;
using Vanara.PInvoke;
using static Vanara.Net.DrtUtil;
using static Vanara.PInvoke.Crypt32;
using static Vanara.PInvoke.Drt;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.Ws2_32;
namespace Vanara.Net;
/// <summary>DNS Bootstrapper</summary>
/// <seealso cref="Vanara.Net.DrtCustomBootstrapProvider"/>
public class CustomDnsBootstapper : DrtCustomBootstrapProvider
{
private readonly string hostname;
private readonly object m_lock = new();
private readonly string port;
private uint m_CallbackThreadId;
private uint m_dwMaxResults;
private bool m_fEndResolve;
private bool m_fResolveInProgress;
private SafeEventHandle? m_hCallbackComplete;
/// <summary>Initializes a new instance of the <see cref="CustomDnsBootstapper"/> class.</summary>
/// <param name="pNodeName">
/// A string that contains a host (node) name or a numeric host address string. For the Internet protocol, the numeric host address
/// string is a dotted-decimal IPv4 address or an IPv6 hex address.
/// </param>
/// <param name="pServiceName">
/// <para>A string that contains either a service name or port number represented as a string.</para>
/// <para>
/// A service name is a string alias for a port number. For example, “http” is an alias for port 80 defined by the Internet Engineering
/// Task Force (IETF) as the default port used by web servers for the HTTP protocol. Possible values for the pServiceName parameter when
/// a port number is not specified are listed in the following file:
/// </para>
/// </param>
public CustomDnsBootstapper(string pNodeName, string pServiceName)
{
hostname = pNodeName;
port = pServiceName;
}
/// <inheritdoc/>
protected override void EndResolve([In] DRT_BOOTSTRAP_RESOLVE_CONTEXT ResolveContext)
{
var fWaitForCallback = false;
var CallbackComplete = CreateEvent(default, true, false, default);
lock (m_lock)
{
if (m_fResolveInProgress && (GetCurrentThreadId() != m_CallbackThreadId))
{
if (!m_fEndResolve)
{
// This is the first thread to call EndResolve and we need to wait for a callback to complete so initialize the class
// member event
m_fEndResolve = true;
m_hCallbackComplete = CallbackComplete;
}
fWaitForCallback = true;
}
}
if (!CallbackComplete.IsInvalid && !Equals(CallbackComplete, m_hCallbackComplete))
{
// This thread was not the first to call EndResolve, so its event is not in use, release it (m_hCallbackComplete is released in
// the destructor)
CallbackComplete.Dispose();
}
if (fWaitForCallback && m_hCallbackComplete is not null)
{
WaitForSingleObject(m_hCallbackComplete, INFINITE);
}
Release();
}
/// <inheritdoc/>
protected override HRESULT InitResolve(bool fSplitDetect, TimeSpan timeout, uint cMaxResults, out DRT_BOOTSTRAP_RESOLVE_CONTEXT pResolveContext, out bool fFatalError)
{
fFatalError = false;
pResolveContext = default;
var hr = HRESULT.DRT_E_BOOTSTRAPPROVIDER_NOT_ATTACHED;
if (IsAttached)
{
// The cache is not scope aware so we ask for a larger number of addresses than the cache wants. In the expectation that one of
// them may be good for us
m_dwMaxResults = cMaxResults;
AddRef();
hr = HRESULT.S_OK;
}
if (hr.Failed)
{
// CustomDNSResolver has no retry cases, so any failed HRESULT is fatal
fFatalError = true;
}
return hr;
}
/// <inheritdoc/>
protected override HRESULT IssueResolve(DRT_BOOTSTRAP_RESOLVE_CALLBACK callback, [In] IntPtr pvCallbackContext, [In] DRT_BOOTSTRAP_RESOLVE_CONTEXT ResolveContext, out bool fFatalError)
{
fFatalError = false;
if (callback is null)
{
return HRESULT.E_INVALIDARG;
}
var hr = HRESULT.DRT_E_BOOTSTRAPPROVIDER_NOT_ATTACHED;
if (IsAttached)
{
lock (m_lock)
{
m_fResolveInProgress = true;
m_CallbackThreadId = GetCurrentThreadId();
}
if (m_dwMaxResults > 0)
{
var addresses = hostname.Split(new[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray();
foreach (var CurrentAddress in addresses)
{
if (m_fEndResolve)
break;
// Retrieve bootstrap possibilities
var addrInf = new ADDRINFOW
{
ai_flags = ADDRINFO_FLAGS.AI_CANONNAME,
ai_family = ADDRESS_FAMILY.AF_UNSPEC,
ai_socktype = SOCK.SOCK_STREAM
};
var nStat = GetAddrInfoW(CurrentAddress, port, addrInf, out var results);
if (nStat.Succeeded)
{
using (results)
{
var cbSA6 = Marshal.SizeOf(typeof(SOCKADDR_IN6));
using var psockAddrs = new SafeNativeArray<SOCKADDR_IN6>(results.Select(a => { using var ar = a.addr; return (SOCKADDR_IN6)ar; }).ToArray());
var Addresses = new SOCKET_ADDRESS_LIST
{
iAddressCount = psockAddrs.Count,
Address = psockAddrs.Select((a, i) => new SOCKET_ADDRESS { iSockaddrLength = cbSA6, lpSockaddr = ((IntPtr)psockAddrs).Offset(cbSA6) }).ToArray()
};
// Call the callback to signal completion
using var pAddresses = Addresses.Pack();
callback?.Invoke(hr, pvCallbackContext, pAddresses, false);
}
}
else
{
// GetAddrInfoW Failed but there may be more addresses in the string so keep going otherwise we return
// HRESULT.E_NO_MORE and retry next cycle
}
}
}
// Tell the drt there will be no more results
if (!m_fEndResolve)
callback?.Invoke(HRESULT.DRT_E_NO_MORE, pvCallbackContext, default, false);
lock (m_lock)
{
if (m_hCallbackComplete is not null && !m_hCallbackComplete.IsInvalid)
{
// Notify EndResolve that callbacks have completed
m_hCallbackComplete.Set();
}
m_fResolveInProgress = false;
}
hr = HRESULT.S_OK;
}
if (hr.Failed)
{
// DNSResolver has no retry cases, so any failed HRESULT is fatal
fFatalError = true;
}
return hr;
}
}
/// <summary>Represents a distributed routing table from Win32.</summary>
public class DistributedRoutingTable : IDisposable
{
private static readonly SafeWSA ws = SafeWSA.Initialize();
private readonly SafeRegisteredWaitHandle? drtWaitEvent;
private readonly SafeEventHandle? evt;
private readonly SafeHDRT? hDrt;
private readonly SafeHDRT_TRANSPORT hTransport;
private readonly IntPtr selfPin;
private bool disposedValue;
private DRT_SETTINGS pSettings;
/// <summary>Initializes a new instance of the <see cref="DistributedRoutingTable"/> class.</summary>
/// <param name="securityProvider">The security provider.</param>
/// <param name="bootstrapper">The bootstrapper.</param>
public DistributedRoutingTable(DrtSecurityProvider? securityProvider, DrtBootstrapProvider bootstrapper) :
this((IntPtr)(securityProvider ?? DrtSecurityProvider.CreateNullSecurityProvider()), (IntPtr)bootstrapper)
{ }
private DistributedRoutingTable(IntPtr pSecProv, IntPtr pBootProv)
{
ushort port = 0;
pSettings = new()
{
dwSize = (uint)Marshal.SizeOf(typeof(DRT_SETTINGS)),
cbKey = 32,
ulMaxRoutingAddresses = 4,
bProtocolMajorVersion = 0x6,
bProtocolMinorVersion = 0x65,
eSecurityMode = DRT_SECURITY_MODE.DRT_SECURE_CONFIDENTIALPAYLOAD,
pwzDrtInstancePrefix = "__VanaraDRT" + Guid.NewGuid().ToString("N"),
pSecurityProvider = pSecProv,
pBootstrapProvider = pBootProv,
};
DrtCreateIpv6UdpTransport(DRT_SCOPE.DRT_GLOBAL_SCOPE, 0, 300, ref port, out hTransport).ThrowIfFailed();
pSettings.hTransport = hTransport;
evt = CreateEvent(null, false, false);
DrtOpen(pSettings, evt, default, out hDrt).ThrowIfFailed();
selfPin = (IntPtr)GCHandle.Alloc(this, GCHandleType.Normal);
Win32Error.ThrowLastErrorIfFalse(RegisterWaitForSingleObject(out drtWaitEvent, evt, DrtEventCallback, selfPin, INFINITE, WT.WT_EXECUTEDEFAULT));
}
/// <summary>Finalizes an instance of the <see cref="DistributedRoutingTable"/> class.</summary>
~DistributedRoutingTable()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}
/// <summary>Occurs when the leaf set key changes.</summary>
public event EventHandler<DrtLeafSetKeyChangeEventArgs>? LeafSetKeyChange;
/// <summary>Occurs when the registration state changes.</summary>
public event EventHandler<DrtRegistrationStateChangeEventArgs>? RegistrationStateChange;
/// <summary>Occurs when the status changes.</summary>
public event EventHandler<DrtStatusChangeEventArgs>? StatusChange;
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>Releases unmanaged and - optionally - managed resources.</summary>
/// <param name="disposing">
/// <see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
if (selfPin != IntPtr.Zero)
GCHandle.FromIntPtr(selfPin).Free();
}
drtWaitEvent?.Dispose();
evt?.Dispose();
hDrt?.Dispose();
hTransport?.Dispose();
disposedValue = true;
}
}
private static void DrtEventCallback(IntPtr Param, bool TimedOut)
{
var Drt = (DistributedRoutingTable?)GCHandle.FromIntPtr(Param).Target;
HRESULT hr = DrtGetEventDataSize(Drt?.hDrt ?? HDRT.NULL, out var ulDrtEventDataLen);
if (hr.Failed)
{
if (hr != HRESULT.DRT_E_NO_MORE)
throw hr.GetException()!;
goto Cleanup;
}
using (SafeCoTaskMemStruct<DRT_EVENT_DATA> pEventData = new(ulDrtEventDataLen))
{
hr = DrtGetEventData(Drt?.hDrt ?? HDRT.NULL, ulDrtEventDataLen, pEventData);
if (hr.Failed)
{
if (hr != HRESULT.DRT_E_NO_MORE)
throw hr.GetException()!;
goto Cleanup;
}
switch (pEventData.Value.type)
{
case DRT_EVENT_TYPE.DRT_EVENT_STATUS_CHANGED:
Drt?.StatusChange?.Invoke(Drt, new(pEventData.Value));
break;
case DRT_EVENT_TYPE.DRT_EVENT_LEAFSET_KEY_CHANGED:
Drt?.LeafSetKeyChange?.Invoke(Drt, new(pEventData.Value));
break;
case DRT_EVENT_TYPE.DRT_EVENT_REGISTRATION_STATE_CHANGED:
Drt?.RegistrationStateChange?.Invoke(Drt, new(pEventData.Value));
break;
}
}
Cleanup:
return;
}
}
/// <summary>Abstract base class for a custom DRT bootstrap provider.</summary>
public class DrtBootstrapProvider : IDisposable
{
/// <summary>The bootstrap provider structure.</summary>
protected DRT_BOOTSTRAP_PROVIDER prov;
private readonly IntPtr pProv;
private readonly char pProvType;
/// <summary>Initializes a new instance of the <see cref="DrtBootstrapProvider"/> class.</summary>
/// <param name="prov">The prov.</param>
protected DrtBootstrapProvider(in DRT_BOOTSTRAP_PROVIDER prov)
{
this.prov = prov;
pProv = GCHandle.Alloc(this.prov, GCHandleType.Pinned).AddrOfPinnedObject();
pProvType = 'h';
}
/// <summary>Initializes a new instance of the <see cref="DrtBootstrapProvider"/> class.</summary>
protected DrtBootstrapProvider() { }
/// <summary>
/// Creates a bootstrap resolver that will use the GetAddrInfo system function to resolve the hostname of a will known node already
/// present in the DRT mesh.
/// </summary>
/// <param name="hostname">Specifies the hostname of the well known node.</param>
/// <param name="port">Specifies the port to which the DRT protocol is bound on the well known node.</param>
/// <returns>A DNS <see cref="DrtBootstrapProvider"/> instance.</returns>
public DrtBootstrapProvider(string? hostname, ushort port)
{
hostname ??= LocalDnsHost;
DrtCreateDnsBootstrapResolver(port, hostname, out IntPtr pbp).ThrowIfFailed();
pProv = pbp;
pProvType = 'd';
}
/// <summary>Initializes a new instance of the <see cref="DrtBootstrapProvider"/> class.</summary>
/// <param name="ptr">The PTR.</param>
/// <param name="provType">Type of the provider pointer.</param>
private DrtBootstrapProvider(IntPtr ptr, char provType)
{
pProv = ptr;
pProvType = provType;
}
/// <summary>Gets the local DNS host.</summary>
protected static string LocalDnsHost
{
get
{
Win32Error.ThrowLastErrorIfFalse(GetComputerNameEx(COMPUTER_NAME_FORMAT.ComputerNameDnsFullyQualified, out string? name));
return name!;
}
}
/// <summary>Creates a bootstrap resolver based on the Peer Name Resolution Protocol (PNRP).</summary>
/// <param name="peerName">The name of the peer to search for in the PNRP cloud. This string has a maximum limit of 137 unicode characters</param>
/// <param name="cloudName">
/// <para>The name of the cloud to search for in for the DRT corresponding to the MeshName.</para>
/// <para>
/// This string has a maximum limit of 256 unicode characters. If left blank the PNRP Bootstrap Provider will use all PNRP clouds available.
/// </para>
/// </param>
/// <param name="publishingId">
/// The PeerIdentity that is publishing into the PNRP cloud utilized for bootstrapping. This string has a maximum limit of 137 unicode
/// characters. The PublishingIdentity must be allowed to publish the PeerName specified.
/// </param>
/// <returns>A PNRP <see cref="DrtBootstrapProvider"/> instance.</returns>
/// <remarks>
/// The default PNRP Bootstrap Resolver created by this function is specific to the DRT it is created for. As a result it cannot be
/// re-used across multiple DRTs.
/// </remarks>
public static DrtBootstrapProvider CreatePnrpBootstrapResolver(string peerName, string? cloudName = null, string? publishingId = null)
{
DrtCreatePnrpBootstrapResolver(true, peerName, cloudName, publishingId, out IntPtr pbp).ThrowIfFailed();
return new(pbp, 'p');
}
/// <summary>Performs an explicit conversion from <see cref="Vanara.Net.DrtBootstrapProvider"/> to <see cref="System.IntPtr"/>.</summary>
/// <param name="prov">The prov.</param>
/// <returns>The result of the conversion.</returns>
public static explicit operator IntPtr(DrtBootstrapProvider prov) => prov.pProv;
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
if (pProv != default)
{
if (pProvType == 'd')
{
DrtDeleteDnsBootstrapResolver(pProv);
}
else if (pProvType == 'p')
{
DrtDeletePnrpBootstrapResolver(pProv);
}
else if (pProvType == 'h')
{
GCHandle.FromIntPtr(pProv).Free();
}
}
}
}
/// <summary>Abstract base class for a custom DRT bootstrap provider.</summary>
public abstract class DrtCustomBootstrapProvider : DrtBootstrapProvider
{
private int refCount;
/// <summary>Initializes a new instance of the <see cref="DrtBootstrapProvider"/> class.</summary>
protected DrtCustomBootstrapProvider(object? context = null) : base(default)
{
unsafe
{
prov.Attach = InternalAttach;
prov.Detach = InternalDetach;
prov.InitResolve = InternalInitResolve;
prov.IssueResolve = InternalIssueResolve;
prov.EndResolve = InternalEndResolve;
prov.Register = InternalRegister;
prov.Unregister = InternalUnregister;
if (context != null)
prov.pvContext = GCHandle.Alloc(context).AddrOfPinnedObject();
}
AddRef();
}
/// <summary>Gets the context provided for all methods.</summary>
/// <value>The context object.</value>
protected virtual object? Context => prov.pvContext == IntPtr.Zero ? null : GCHandle.FromIntPtr(prov.pvContext).Target;
/// <summary>Gets a value indicating whether this instance is attached.</summary>
/// <value><see langword="true"/> if this instance is attached; otherwise, <see langword="false"/>.</value>
protected bool IsAttached => refCount > 0;
/// <summary>Adds the reference.</summary>
protected void AddRef()
{
if (refCount == 0)
GC.SuppressFinalize(this);
InterlockedIncrement(ref refCount);
}
/// <summary>Ends the resolution of an endpoint.</summary>
/// <param name="ResolveContext">
/// The <c>BOOTSTRAP_RESOLVE_CONTEXT</c> received from the Resolve function of the specified bootstrap provider.
/// </param>
protected abstract void EndResolve([In] DRT_BOOTSTRAP_RESOLVE_CONTEXT ResolveContext);
/// <summary>Called by the DRT infrastructure to supply configuration information about upcoming name resolutions.</summary>
/// <param name="fSplitDetect">Specifies if the resolve operation is being utilized for network split detection and recovery.</param>
/// <param name="timeout">Specifies the maximum time a resolve should take before timing out. This value is represented in milliseconds.</param>
/// <param name="cMaxResults">Specifies the maximum number of results to return during the resolve operation.</param>
/// <param name="ResolveContext">Pointer to resolver specific data.</param>
/// <param name="fFatalError">
/// If the bootstrap provider encounters an irrecoverable error, this parameter must be set to <c>TRUE</c> when the function complete in
/// order for the DRT to transition to the faulted state. The <c>HRESULT</c> that is made available to the higher layer application for
/// debugging will appear in the <c>hr</c> member of the DRT_EVENT_DATA structure associated with the event signaling the transition to
/// the faulted state. This bootstrap provider function should not return S_OK if setting the fFatalError flag to <c>TRUE</c>.
/// </param>
protected abstract HRESULT InitResolve(bool fSplitDetect, TimeSpan timeout, uint cMaxResults,
out DRT_BOOTSTRAP_RESOLVE_CONTEXT ResolveContext, out bool fFatalError);
/// <summary>
/// Called by the DRT infrastructure to issue a resolution to determine the endpoints of nodes already active in the DRT cloud.
/// </summary>
/// <param name="pvCallbackContext">Pointer to the context data that is passed back to the callback defined by the next parameter.</param>
/// <param name="callback">A BOOTSTRAP_RESOLVE_CALLBACK that is called back for each result and DRT_E_NO_MORE.</param>
/// <param name="ResolveContext">Pointer to resolver specific data.</param>
/// <param name="fFatalError">
/// If the bootstrap provider encounters an irrecoverable error, this parameter must be set to <c>TRUE</c> when the function complete in
/// order for the DRT to transition to the faulted state. The <c>HRESULT</c> that is made available to the higher layer application for
/// debugging will appear in the <c>hr</c> member of the DRT_EVENT_DATA structure associated with the event signaling the transition to
/// the faulted state. This bootstrap provider function should not return S_OK if setting the fFatalError flag to <c>TRUE</c>.
/// </param>
protected abstract HRESULT IssueResolve(DRT_BOOTSTRAP_RESOLVE_CALLBACK callback, [In] IntPtr pvCallbackContext,
[In] DRT_BOOTSTRAP_RESOLVE_CONTEXT ResolveContext, out bool fFatalError);
/// <summary>
/// Registers an endpoint with the bootstrapping mechanism. This process makes it possible for other nodes find the endpoint via the
/// bootstrap resolver.
/// </summary>
/// <param name="pAddressList">
/// Pointer to <see cref="SOCKET_ADDRESS_LIST"/> containing the list of addresses to register with the bootstrapping mechanism.
/// </param>
protected virtual HRESULT Register([In] IPEndPoint[]? pAddressList) => HRESULT.S_OK;
/// <summary>Releases this instance.</summary>
protected void Release()
{
if (InterlockedDecrement(ref refCount) == 0)
{
if (prov.pvContext != default)
GCHandle.FromIntPtr(prov.pvContext).Free();
GC.ReRegisterForFinalize(this);
}
}
/// <summary>
/// This function deregisters an endpoint with the bootstrapping mechanism. As a result, other nodes will be unable to find the local
/// node via the bootstrap resolver.
/// </summary>
protected virtual void Unregister() { }
private HRESULT InternalAttach(IntPtr pvContext)
{
if (InterlockedCompareExchange(ref refCount, 1, 0) != 0)
return HRESULT.DRT_E_BOOTSTRAPPROVIDER_IN_USE;
AddRef();
return HRESULT.S_OK;
}
private void InternalDetach(IntPtr pvContext)
{
InterlockedCompareExchange(ref refCount, 0, 1);
Release();
}
private void InternalEndResolve(IntPtr pvContext, DRT_BOOTSTRAP_RESOLVE_CONTEXT ResolveContext) => EndResolve(ResolveContext);
private HRESULT InternalInitResolve(IntPtr pvContext, bool fSplitDetect, uint timeout, uint cMaxResults,
out DRT_BOOTSTRAP_RESOLVE_CONTEXT ResolveContext, out bool fFatalError) =>
InitResolve(fSplitDetect, TimeSpan.FromMilliseconds(timeout), cMaxResults, out ResolveContext, out fFatalError);
private HRESULT InternalIssueResolve(IntPtr pvContext, IntPtr pvCallbackContext, DRT_BOOTSTRAP_RESOLVE_CALLBACK callback,
DRT_BOOTSTRAP_RESOLVE_CONTEXT ResolveContext, out bool fFatalError) =>
IssueResolve(callback, pvCallbackContext, ResolveContext, out fFatalError);
private HRESULT InternalRegister(IntPtr pvContext, IntPtr pAddressList) => Register(ToEndPoints(pAddressList.ToNullableStructure<SOCKET_ADDRESS_LIST>()));
private void InternalUnregister(IntPtr pvContext) => Unregister();
}
/// <summary>Abstract base class for DRT event arguments.</summary>
/// <seealso cref="System.EventArgs"/>
public abstract class DrtEventArgs : EventArgs
{
/// <summary>Initializes a new instance of the <see cref="DrtEventArgs"/> class.</summary>
/// <param name="data">The data.</param>
protected DrtEventArgs(in DRT_EVENT_DATA data)
{
LastError = data.hr;
Context = data.pvContext;
}
/// <summary>
/// Pointer to the context data passed to the API that generated the event. For example, if data is passed into the pvContext parameter
/// of DrtOpen, that data is returned through this field.
/// </summary>
public IntPtr Context { get; }
/// <summary>
/// The HRESULT of the operation for which the event was signaled that indicates if a result is the last result within a search.
/// </summary>
public HRESULT LastError { get; }
}
/// <summary>Arguments associated with a DRT leaf set key change event.</summary>
/// <seealso cref="Vanara.Net.DrtEventArgs"/>
public class DrtLeafSetKeyChangeEventArgs : DrtEventArgs
{
internal DrtLeafSetKeyChangeEventArgs(in DRT_EVENT_DATA data) : base(data)
{
Type = data.union.leafsetKeyChange.change;
LocalKey = (byte[])data.union.leafsetKeyChange.localKey!;
RemoteKey = (byte[])data.union.leafsetKeyChange.remoteKey!;
}
/// <summary>Specifies the local key associated with the leaf set that has changed.</summary>
public byte[]? LocalKey { get; private set; }
/// <summary>Specifies the remote key that changed.</summary>
public byte[]? RemoteKey { get; private set; }
/// <summary>Specifies the type of key change that has occurred.</summary>
public DRT_LEAFSET_KEY_CHANGE_TYPE Type { get; private set; }
}
/// <summary>Arguments associated with a DRT registration state change event.</summary>
/// <seealso cref="Vanara.Net.DrtEventArgs"/>
public class DrtRegistrationStateChangeEventArgs : DrtEventArgs
{
internal DrtRegistrationStateChangeEventArgs(in DRT_EVENT_DATA data) : base(data)
{
State = data.union.registrationStateChange.state;
LocalKey = (byte[])data.union.registrationStateChange.localKey!;
}
/// <summary>Specifies the local key associated with the registration that has changed.</summary>
public byte[]? LocalKey { get; }
/// <summary>Specifies the type of registration state change that has occurred.</summary>
public DRT_REGISTRATION_STATE State { get; }
}
/// <summary>Base class for a DRT security provider.</summary>
public class DrtSecurityProvider : IDisposable
{
/// <summary>The security provider structure.</summary>
protected DRT_SECURITY_PROVIDER prov;
private readonly IntPtr pProv;
private readonly char pProvType;
/// <summary>Initializes a new instance of the <see cref="DrtSecurityProvider"/> class.</summary>
/// <param name="prov">The prov.</param>
protected DrtSecurityProvider(in DRT_SECURITY_PROVIDER prov)
{
this.prov = prov;
pProv = GCHandle.Alloc(this.prov, GCHandleType.Pinned).AddrOfPinnedObject();
pProvType = 'h';
}
private DrtSecurityProvider() { }
private DrtSecurityProvider(IntPtr ptr, char provType)
{
pProv = ptr;
pProvType = provType;
}
/// <summary>Creates the derived key security provider for a Distributed Routing Table.</summary>
/// <param name="pRootCert">
/// Pointer to the certificate that is the "root" portion of the chain. This is used to ensure that keys derived from the same chain can
/// be verified.
/// </param>
/// <param name="pLocalCert">Pointer to the DRT_SECURITY_PROVIDER module to be included in the DRT_SETTINGS structure.</param>
/// <returns>A derived key <see cref="DrtSecurityProvider"/> instance.</returns>
/// <remarks>
/// The security provider created by this function is specific to the DRT it was created for. It cannot be shared by multiple DRT instances.
/// </remarks>
public static DrtSecurityProvider CreateDerivedKeySecurityProvider(PCCERT_CONTEXT pRootCert, PCCERT_CONTEXT pLocalCert)
{
DrtCreateDerivedKeySecurityProvider(pRootCert, pLocalCert, out IntPtr psp).ThrowIfFailed();
return new(psp, 'd');
}
/// <summary>Creates a null security provider. This security provider does not require nodes to authenticate keys.</summary>
/// <returns>A null <see cref="DrtSecurityProvider"/> instance.</returns>
public static DrtSecurityProvider CreateNullSecurityProvider()
{
DrtCreateNullSecurityProvider(out IntPtr psp).ThrowIfFailed();
return new DrtSecurityProvider(psp, 'n');
}
/// <summary>Performs an explicit conversion from <see cref="Vanara.Net.DrtSecurityProvider"/> to <see cref="System.IntPtr"/>.</summary>
/// <param name="prov">The prov.</param>
/// <returns>The result of the conversion.</returns>
public static explicit operator IntPtr(DrtSecurityProvider prov) => prov.pProv;
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
if (pProv != default)
{
if (pProvType == 'n')
{
DrtDeleteNullSecurityProvider(pProv);
}
else if (pProvType == 'd')
{
DrtDeleteDerivedKeySecurityProvider(pProv);
}
else if (pProvType == 'h')
{
GCHandle.FromIntPtr(pProv).Free();
}
}
}
}
/// <summary>Arguments associated with a DRT status change event.</summary>
/// <seealso cref="Vanara.Net.DrtEventArgs"/>
public class DrtStatusChangeEventArgs : DrtEventArgs
{
internal DrtStatusChangeEventArgs(in DRT_EVENT_DATA data) : base(data)
{
Status = data.union.statusChange.status;
BootstrapAddresses = data.union.statusChange.bootstrapAddresses.Addresses?.Select(Cvt).ToArray();
static IPEndPoint Cvt(SOCKADDR_STORAGE input)
{
var inet = (SOCKADDR_INET)input;
return inet.si_family is ADDRESS_FAMILY.AF_INET or ADDRESS_FAMILY.AF_UNSPEC
? new IPEndPoint(inet.Ipv4.sin_addr, inet.Ipv4.sin_port)
: new IPEndPoint(new IPAddress(inet.Ipv6.sin6_addr.bytes, inet.Ipv6.sin6_scope_id), inet.Ipv6.sin6_port);
}
}
/// <summary>Contains an array of <see cref="IPEndPoint"/> returned by the bootstrap provider.</summary>
public IPEndPoint[]? BootstrapAddresses { get; }
/// <summary>Contains the current DRT_STATUS of the local DRT instance.</summary>
public DRT_STATUS Status { get; }
}
internal static class DrtUtil
{
public static IntPtr Alloc(int size) => Marshal.AllocCoTaskMem(size);
public static void Free(IntPtr ptr) => Marshal.FreeCoTaskMem(ptr);
public static IntPtr ToAddrListPtr(IPEndPoint[]? pts)
{
if (pts is null || pts.Length == 0)
{
return default;
}
SocketAddress[] sa = Array.ConvertAll(pts, p => p.Serialize());
int ptsz = sa.Sum(a => a.Size);
int strsz = Marshal.SizeOf(typeof(SOCKET_ADDRESS_LIST));
int sasz = Marshal.SizeOf(typeof(SOCKET_ADDRESS));
SafeCoTaskMemHandle psal = new(strsz + sasz * (pts.Length - 1) + ptsz);
psal.Write(pts.Length);
Span<byte> salSpan = psal.AsSpan<byte>(psal.Size);
for (int i = 0, aoff = Marshal.OffsetOf(typeof(SOCKET_ADDRESS_LIST), "Address").ToInt32(), asoff = strsz + sasz * (pts.Length - 1); i < pts.Length; i++, aoff += sasz)
{
psal.Write(new SOCKET_ADDRESS { iSockaddrLength = sa[i].Size, lpSockaddr = ((IntPtr)psal).Offset(asoff) }, false, aoff);
for (int j = 0; j < sa[i].Size; j++)
{
salSpan[asoff + j] = sa[i][j];
}
asoff += sa[i].Size;
}
return psal.TakeOwnership();
}
public static DRT_DATA ToData(byte[]? data)
{
DRT_DATA ret = default;
if (data is not null)
{
ret.pb = data.MarshalToPtr(Alloc, out int cb);
ret.cb = (uint)cb;
}
return ret;
}
public static IPEndPoint[]? ToEndPoints(SOCKET_ADDRESS_LIST? al)
{
return al.HasValue ? Array.ConvertAll(al.Value.Address, Cvt) : null;
static IPEndPoint Cvt(SOCKET_ADDRESS a)
{
SOCKADDR sa = new(a.lpSockaddr, false, a.iSockaddrLength);
SocketAddress nsa = new((System.Net.Sockets.AddressFamily)sa.sa_family, sa.Size);
Span<byte> saspan = sa.AsBytes();
for (int i = 2; i < sa.Size; i++)
{
nsa[i] = saspan[i];
}
IPEndPoint ep = new(0, 0);
return (IPEndPoint)ep.Create(nsa);
}
}
}
/// <summary>Abstract base class for a custom DRT security provider.</summary>
public abstract class DrtCustomSecurityProvider : DrtSecurityProvider
{
private int refCount;
/// <summary>Initializes a new instance of the <see cref="DrtSecurityProvider"/> class.</summary>
/// <param name="context">The context.</param>
protected DrtCustomSecurityProvider(object? context) : base(default)
{
unsafe
{
prov.Attach = InternalAttach;
prov.Detach = InternalDetach;
prov.RegisterKey = InternalRegisterKey;
prov.UnregisterKey = InternalUnregisterKey;
prov.ValidateAndUnpackPayload = InternalValidateAndUnpackPayload;
prov.SecureAndPackPayload = InternalSecureAndPackPayload;
prov.FreeData = InternalFreeData;
prov.EncryptData = InternalEncryptData;
prov.DecryptData = InternalDecryptData;
prov.GetSerializedCredential = InternalGetSerializedCredential;
prov.ValidateRemoteCredential = InternalValidateRemoteCredential;
prov.SignData = InternalSignData;
prov.VerifyData = InternalVerifyData;
if (context != null)
prov.pvContext = GCHandle.Alloc(context).AddrOfPinnedObject();
}
AddRef();
}
/// <summary>Gets the context provided for all methods.</summary>
/// <value>The context object.</value>
protected virtual object? Context => prov.pvContext == IntPtr.Zero ? null : GCHandle.FromIntPtr(prov.pvContext).Target;
/// <summary>Gets a value indicating whether this instance is attached.</summary>
/// <value><see langword="true"/> if this instance is attached; otherwise, <see langword="false"/>.</value>
protected bool IsAttached => refCount > 0;
/// <summary>Adds the reference.</summary>
protected void AddRef()
{
if (refCount == 0)
GC.SuppressFinalize(this);
InterlockedIncrement(ref refCount);
}
/// <summary>
/// Called when the DRT receives a message containing encrypted data. This function is only called when the DRT is operating in the
/// <c>DRT_SECURE_CONFIDENTIALPAYLOAD</c> security mode defined by DRT_SECURITY_MODE.
/// </summary>
/// <param name="pKeyToken">
/// Contains the encrypted session key that can be decrypted by the recipient of the message and used to decrypt the protected fields.
/// </param>
/// <param name="pvKeyContext">Contains the context passed into DrtRegisterKey when the key was registered.</param>
/// <param name="pData">Contains the decrypted data upon completion of the function.</param>
protected virtual HRESULT DecryptData([In] byte[] pKeyToken, [Optional] IntPtr pvKeyContext, byte[][] pData) => HRESULT.S_OK;
/// <summary>
/// Called when the DRT sends a message containing data that must be encrypted. This function is only called when the DRT is operating in
/// the <c>DRT_SECURE_CONFIDENTIALPAYLOAD</c> security mode defined by DRT_SECURITY_MODE.
/// </summary>
/// <param name="pRemoteCredential">Contains the credential of the peer that will receive the protected message.</param>
/// <param name="pDataBuffers">Contains the unencrypted buffer.</param>
/// <param name="pEncryptedBuffers">Contains the encrypted content upon completion of the function.</param>
/// <param name="pKeyToken">
/// Contains the encrypted session key that can be decrypted by the recipient of the message and used to decrypted the protected fields.
/// </param>
/// <returns></returns>
protected abstract HRESULT EncryptData([In] byte[] pRemoteCredential, byte[][] pDataBuffers, byte[][] pEncryptedBuffers, out byte[]? pKeyToken);
/// <summary>Called to release resources previously allocated for a security provider function.</summary>
/// <param name="pv">Specifies what data to free.</param>
protected virtual void FreeData([In, Optional] IntPtr pv) => Free(pv);
/// <summary>
/// Called when the DRT must provide a credential used to authorize the local node. This function is only called when the DRT is
/// operating in the <c>DRT_SECURE_MEMBERSHIP</c> and <c>DRT_SECURE_CONFIDENTIALPAYLOAD</c> security modes defined by DRT_SECURITY_MODE.
/// </summary>
/// <returns>Contains the serialized credential upon completion of the function.</returns>
protected virtual byte[]? GetSerializedCredential() => null;
/// <summary>Called to register a key with the Security Provider.</summary>
/// <param name="pRegistration">
/// Pointer to the DRT_REGISTRATION structure created by an application and passed to the DrtRegisterKey function.
/// </param>
/// <param name="pvKeyContext">Pointer to the context data created by an application and passed to the DrtRegisterKey function.</param>
/// <returns></returns>
protected virtual HRESULT RegisterKey(in DRT_REGISTRATION pRegistration, [In, Optional] IntPtr pvKeyContext) => HRESULT.S_OK;
/// <summary>Releases this instance.</summary>
protected void Release()
{
if (InterlockedDecrement(ref refCount) == 0)
{
if (prov.pvContext != default)
GCHandle.FromIntPtr(prov.pvContext).Free();
GC.ReRegisterForFinalize(this);
}
}
/// <summary>
/// Called when an Authority message is about to be sent on the wire. It is responsible for securing the data before it is sent, and for
/// packing the service addresses, revoked flag, nonce, and other application data into the Secured Address Payload.
/// </summary>
/// <param name="pvKeyContext">Contains the context passed into DrtRegisterKey when the key was registered.</param>
/// <param name="bProtocolMajor">Pointer to the byte array that represents the protocol major version.</param>
/// <param name="bProtocolMinor">Pointer to the byte array that represents the protocol minor version.</param>
/// <param name="dwFlags">
/// <para>
/// Any DRT specific flags, currently defined only to be the revoked or deleted flag that need to be packed, secured and sent to another
/// instance for processing.
/// </para>
/// <para><c>Note</c> Currently the only allowed value is: <c>DRT_PAYLOAD_REVOKED</c></para>
/// </param>
/// <param name="pKey">Pointer to the key to which this payload is registered.</param>
/// <param name="pPayload">Pointer to the payload specified by the application when calling DrtRegisterKey.</param>
/// <param name="pAddressList">Pointer to the service addresses that are placed in the Secured Address Payload.</param>
/// <param name="pNonce">
/// Pointer to the nonce that was sent in the original <c>Inquire</c> or <c>Lookup</c> message. This value is fixed at 16 bytes.
/// </param>
/// <param name="pSecuredAddressPayload">
/// Pointer to the payload to send on the wire which contains the service addresses, revoked flag, nonce, and other data required by the
/// security provider. <c>pSecuredAddressPayload.pb</c> is allocated by the security provider.
/// </param>
/// <param name="pClassifier">
/// Pointer to the classifier to send in the Authority message. <c>pClassifier.pb</c> is allocated by the security provider.
/// </param>
/// <param name="pSecuredPayload">
/// Pointer to the application data payload received in the Authority message. After validation, the original data (after decryption,
/// removal of signature, and so on.) is output as pPayload. <c>pSecuredPayload.pb</c> is allocated by the security provider.
/// </param>
/// <param name="pCertChain">
/// Pointer to the cert chain to send in the Authority message. <c>pCertChain.pb</c> is allocated by the security provider.
/// </param>
/// <returns></returns>
protected abstract HRESULT SecureAndPackPayload([In, Optional] IntPtr pvKeyContext, byte bProtocolMajor, byte bProtocolMinor, uint dwFlags,
[In] byte[] pKey, [In] byte[]? pPayload, [In] IPEndPoint[]? pAddressList, [In] byte[] pNonce, out byte[] pSecuredAddressPayload,
out byte[]? pClassifier, out byte[]? pSecuredPayload, out byte[]? pCertChain);
/// <summary>
/// Called when the DRT must sign a data blob for inclusion in a DRT protocol message. This function is only called when the DRT is
/// operating in the <c>DRT_SECURE_MEMBERSHIP</c> and <c>DRT_SECURE_CONFIDENTIALPAYLOAD</c> security modes defined by DRT_SECURITY_MODE.
/// </summary>
/// <param name="dataBuffers">Contains the data to be signed.</param>
/// <param name="keyIdentifier">
/// Upon completion of this function, contains an index that can be used to select from multiple credentials for use in calculating the signature.
/// </param>
/// <param name="signature">Upon completion of this function, contains the signature data.</param>
/// <returns></returns>
protected virtual HRESULT SignData(byte[][] dataBuffers, out byte[]? keyIdentifier, out byte[]? signature)
{
keyIdentifier = signature = null;
return HRESULT.S_OK;
}
/// <summary>Called to deregister a key with the Security Provider.</summary>
/// <param name="key">Pointer to the key to which the payload is registered.</param>
/// <param name="pvKeyContext">Pointer to the context data created by an application and passed to the DrtRegisterKey function.</param>
/// <returns>Pointer to the context data created by the application and passed to DrtRegisterKey.</returns>
protected virtual HRESULT UnregisterKey(byte[] key, [In, Optional] IntPtr pvKeyContext) => HRESULT.S_OK;
/// <summary>
/// Called when an Authority message is received on the wire. It is responsible for validating the data received, and for unpacking the
/// service addresses, revoked flag, and nonce from the Secured Address Payload.
/// </summary>
/// <param name="pSecuredAddressPayload">
/// Pointer to the payload received on the wire that contains the service addresses, revoked flag, nonce, and any other data required by
/// the security provider.
/// </param>
/// <param name="pCertChain">Pointer to the cert chain received in the authority message.</param>
/// <param name="pClassifier">Pointer to the classifier received in the authority message.</param>
/// <param name="pNonce">
/// Pointer to the nonce that was sent in the original <c>Inquire</c> or <c>Lookup</c> message. This value must be compared to the value
/// embedded in the Secured Address Payload to ensure they are the same. This value is fixed at 16 bytes.
/// </param>
/// <param name="pSecuredPayload">
/// Pointer to the application data payload received in the Authority message. After validation, the original data (after decryption,
/// removal of signature, and so on.) is output as pPayload.
/// </param>
/// <param name="pbProtocolMajor">
/// Pointer to the byte array that represents the protocol major version. This is packed in every DRT packet to identify the version of
/// the security provider in use when a single DRT instance is supporting multiple Security Providers.
/// </param>
/// <param name="pbProtocolMinor">
/// Pointer to the byte array that represents the protocol minor version. This is packed in every DRT packet to identify the version of
/// the security provider in use when a single DRT instance is supporting multiple Security Providers.
/// </param>
/// <param name="pKey">Pointer to the key to which the payload is registered.</param>
/// <param name="pPayload">
/// Pointer to the original payload specified by the remote application. <c>pPayload.pb</c> is allocated by the security provider.
/// </param>
/// <param name="ppPublicKey">Pointer to a pointer to the number of service addresses embedded in the secured address payload.</param>
/// <param name="ppAddressList">
/// Pointer to a pointer to the service addresses that are embedded in the Secured Address Payload. <c>pAddresses</c> is allocated by the
/// security provider.
/// </param>
/// <param name="pdwFlags">
/// Any DRT flags currently defined only to be the revoked or deleted flag that need to be unpacked for the local DRT instance processing.
/// <para><c>Note</c> Currently the only allowed value is: <c>DRT_PAYLOAD_REVOKED (1)</c></para>
/// </param>
/// <returns></returns>
protected abstract HRESULT ValidateAndUnpackPayload([In] byte[] pSecuredAddressPayload, [In] byte[]? pCertChain,
[In] byte[]? pClassifier, [In] byte[]? pNonce, [In] byte[]? pSecuredPayload,
out byte pbProtocolMajor, out byte pbProtocolMinor, out byte[] pKey, out byte[]? pPayload,
out SafeCoTaskMemStruct<CERT_PUBLIC_KEY_INFO> ppPublicKey, out IPEndPoint[]? ppAddressList,
out uint pdwFlags);
/// <summary>Called when the DRT must validate a credential provided by a peer node.</summary>
/// <param name="pRemoteCredential">Contains the serialized credential provided by the peer node.</param>
/// <returns></returns>
protected virtual HRESULT ValidateRemoteCredential(byte[] pRemoteCredential) => HRESULT.S_OK;
/// <summary>
/// Called when the DRT must verify a signature calculated over a block of data included in a DRT message. This function is only called
/// when the DRT is operating in the <c>DRT_SECURE_MEMBERSHIP</c> and <c>DRT_SECURE_CONFIDENTIALPAYLOAD</c> security modes defined by DRT_SECURITY_MODE.
/// </summary>
/// <param name="pDataBuffers">Contains the data over which the signature was calculated.</param>
/// <param name="remoteCredentials">Contains the credentials of the remote node used to calculate the signature.</param>
/// <param name="keyIdentifier">Contains an index that may be used to select from multiple credentials provided in pRemoteCredentials.</param>
/// <param name="signature">Contains the signature to be verified.</param>
/// <returns></returns>
protected virtual HRESULT VerifyData(byte[][] pDataBuffers, byte[] remoteCredentials, byte[] keyIdentifier, byte[] signature) =>
signature is null || signature.Length == 0 ? HRESULT.DRT_E_INVALID_MESSAGE : (HRESULT)HRESULT.S_OK;
private HRESULT InternalAttach(IntPtr pvContext)
{
if (InterlockedCompareExchange(ref refCount, 1, 0) != 0)
return HRESULT.DRT_E_SECURITYPROVIDER_IN_USE;
AddRef();
return HRESULT.S_OK;
}
private HRESULT InternalDecryptData(IntPtr pvContext, in DRT_DATA pKeyToken, IntPtr pvKeyContext, uint dwBuffers, DRT_DATA[] pData) =>
DecryptData((byte[])pKeyToken!, pvKeyContext, Array.ConvertAll(pData, p => (byte[])p!)!);
private void InternalDetach(IntPtr pvContext)
{
InterlockedCompareExchange(ref refCount, 0, 1);
Release();
}
private HRESULT InternalEncryptData(IntPtr pvContext, in DRT_DATA pRemoteCredential, uint dwBuffers, DRT_DATA[] pDataBuffers, DRT_DATA[] pEncryptedBuffers, out DRT_DATA pKeyToken)
{
var hr = EncryptData((byte[])pRemoteCredential!, Array.ConvertAll(pDataBuffers, p => (byte[])p!)!, Array.ConvertAll(pEncryptedBuffers, p => (byte[])p!), out byte[]? pkt);
pKeyToken = ToData(pkt);
return hr;
}
private void InternalFreeData(IntPtr pvContext, IntPtr pv) => FreeData(pv);
private HRESULT InternalGetSerializedCredential(IntPtr pvContext, out DRT_DATA pSelfCredential)
{
pSelfCredential = ToData(GetSerializedCredential());
return HRESULT.S_OK;
}
private HRESULT InternalRegisterKey(IntPtr pvContext, in DRT_REGISTRATION pRegistration, IntPtr pvKeyContext) =>
RegisterKey(pRegistration, pvKeyContext);
private unsafe HRESULT InternalSecureAndPackPayload(IntPtr pvContext, IntPtr pvKeyContext, byte bProtocolMajor, byte bProtocolMinor,
uint dwFlags, in DRT_DATA pKey, DRT_DATA* pPayload, IntPtr pAddressList, in DRT_DATA pNonce, out DRT_DATA pSecuredAddressPayload,
DRT_DATA* pClassifier, DRT_DATA* pSecuredPayload, DRT_DATA* pCertChain)
{
HRESULT hr = SecureAndPackPayload(pvContext, bProtocolMajor, bProtocolMinor, dwFlags, (byte[])pKey!, pPayload is null ? null : (byte[])(*pPayload)!,
ToEndPoints(pAddressList.ToNullableStructure<SOCKET_ADDRESS_LIST>()), (byte[])pNonce!, out byte[]? sap, out byte[]? cl, out byte[]? sp, out byte[]? cc);
pSecuredAddressPayload = ToData(sap);
if (cl is not null)
*pClassifier = ToData(cl);
if (sp is not null)
*pSecuredPayload = ToData(sp);
if (cc is not null)
*pCertChain = ToData(cc);
return hr;
}
private HRESULT InternalSignData(IntPtr pvContext, uint dwBuffers, DRT_DATA[] pDataBuffers, out DRT_DATA pKeyIdentifier, out DRT_DATA pSignature)
{
HRESULT hr = SignData(Array.ConvertAll(pDataBuffers, b => (byte[])b!)!, out byte[]? id, out byte[]? sig);
pKeyIdentifier = ToData(id);
pSignature = ToData(sig);
return hr;
}
private HRESULT InternalUnregisterKey(IntPtr pvContext, in DRT_DATA pKey, IntPtr pvKeyContext) =>
UnregisterKey((byte[])pKey!);
private unsafe HRESULT InternalValidateAndUnpackPayload(IntPtr pvContext, in DRT_DATA pSecuredAddressPayload, DRT_DATA* pCertChain,
DRT_DATA* pClassifier, DRT_DATA* pNonce, DRT_DATA* pSecuredPayload, byte* pbProtocolMajor, byte* pbProtocolMinor,
out DRT_DATA pKey, DRT_DATA* pPayload, CERT_PUBLIC_KEY_INFO** ppPublicKey, void** ppAddressList, out uint pdwFlags)
{
HRESULT hr = ValidateAndUnpackPayload(pSecuredAddressPayload!, pCertChain is null ? null : (byte[])(*pCertChain)!,
pClassifier is null ? null : (byte[])(*pClassifier)!, pNonce is null ? null : (byte[])(*pNonce)!,
pSecuredPayload is null ? null : (byte[])(*pSecuredPayload)!, out byte maj, out byte min, out byte[]? k,
out byte[]? pl, out SafeCoTaskMemStruct<CERT_PUBLIC_KEY_INFO>? pk, out IPEndPoint[]? al, out pdwFlags);
*pbProtocolMajor = maj;
*pbProtocolMinor = min;
pKey = ToData(k);
if (pl is not null)
*pPayload = ToData(pl);
*ppPublicKey = (CERT_PUBLIC_KEY_INFO*)(pk?.TakeOwnership() ?? IntPtr.Zero);
*ppAddressList = (void*)ToAddrListPtr(al);
return hr;
}
private HRESULT InternalValidateRemoteCredential(IntPtr pvContext, in DRT_DATA pRemoteCredential) =>
ValidateRemoteCredential((byte[])pRemoteCredential!);
private HRESULT InternalVerifyData(IntPtr pvContext, uint dwBuffers, DRT_DATA[] pDataBuffers, in DRT_DATA pRemoteCredentials, in DRT_DATA pKeyIdentifier, in DRT_DATA pSignature) =>
VerifyData(Array.ConvertAll(pDataBuffers, b => (byte[])b!), (byte[])pRemoteCredentials!, (byte[])pKeyIdentifier!, (byte[])pSignature!);
}
/*
/// <summary></summary>
/// <seealso cref="Vanara.Net.DrtCustomSecurityProvider"/>
public class CustomNullSecurityProvider : DrtCustomSecurityProvider
{
internal unsafe class CCustomNullSecuredAddressPayload : IDisposable
{
public const ALG_ID DRT_ALGORITHM = ALG_ID.CALG_SHA_256;
public const string DRT_ALGORITHM_OID = AlgOID.szOID_RSA_SHA1RSA;
public const uint DRT_DERIVED_KEY_SIZE = 32;
// default security provider constants
public const byte DRT_SECURITY_VERSION_MAJOR = 1;
public const byte DRT_SECURITY_VERSION_MINOR = 0;
public const uint DRT_SHA2_LENGTH = 32;
public const uint DRT_SIG_LENGTH = SHA2_SIG_LENGTH;
// Original 0x8000 + space for extended payload (4k plus some overhead)
public const uint MAX_MESSAGE_SIZE = 0x8000 + 0x1200;
public const uint SHA1_SIG_LENGTH = 0x80;
public const uint SHA2_SIG_LENGTH = 0x80;
private readonly byte[] m_signature = new byte[DRT_SIG_LENGTH];
private IPEndPoint[]? m_addressList;
private byte m_bProtocolVersionMajor;
private byte m_bProtocolVersionMinor;
private byte[] m_ddKey;
private byte[] m_ddNonce;
private bool m_fAllocated; // set if the data needs to be freed when destroyed (true when deserializing)
//CERT_PUBLIC_KEY_INFO*
private SafeCoTaskMemStruct<CERT_PUBLIC_KEY_INFO>? m_pPublicKey;
/// <summary>Initializes a new instance of the <see cref="CCustomNullSecuredAddressPayload"/> class.</summary>
/// <param name="bMajor">The b major.</param>
/// <param name="bMinor">The b minor.</param>
/// <param name="key">The key.</param>
/// <param name="nonce">The nonce.</param>
/// <param name="pAddressList">The p address list.</param>
/// <param name="flags">The flags.</param>
public CCustomNullSecuredAddressPayload(byte bMajor, byte bMinor, byte[] key, byte[] nonce, IPEndPoint[]? pAddressList, uint flags)
{
m_bProtocolVersionMajor = bMajor;
m_bProtocolVersionMinor = bMinor;
m_ddKey = key;
m_ddNonce = nonce;
m_addressList = pAddressList;
Flags = flags;
}
// Purpose: Retrieve or set the flags
//
// Args: dwFlags:
public uint Flags { get; set; }
// Serialized SecureAddressPayload format: bytes name 1 protocol major version 1 protocol minor version 1 security major version 1
// security minor version 2 key length (KL) KL key 1 signature length (SL) SL signature 1 nonce length (NL) NL nonce 4 flags
// ----- public key ----------- 1 algorithm length (AL) 2 key parameters length (PL) 2 public key length (KL) 1 unused bits AL
// algorithm (byte) PL key parameters KL public key
// ----- end public key ------- 1 address count
// ----- for each address ----- 2 address length (AL) AL address data
// ----- end each address -----
// Function: CCustomNullSecuredAddressPayload::DeserializeAndValidate
//
// Purpose: Deserialize and validate the payload.
//
// Args: pData: data to deserialize
// pNonce: expected nonce
// pCertChain: opt. remote cert chain (if one was in the message)
// hCryptProv: crypt provider to use with remote public key
//
// Notes: The deserialized data is later retrieved via Get* methods.
public HRESULT DeserializeAndValidate(byte[] pData, byte[]? pNonce)
{
HRESULT hr = HRESULT.S_OK;
using var deserializer = new MemoryStream(pData, true);
m_fAllocated = true;
// protocol version
m_bProtocolVersionMajor = (byte)deserializer.ReadByte();
m_bProtocolVersionMinor = (byte)deserializer.ReadByte();
// security version
var bVersionMajor = (byte)deserializer.ReadByte();
var bVersionMinor = (byte)deserializer.ReadByte();
// ensure we are receiving a version we understand
if (bVersionMajor != DRT_SECURITY_VERSION_MAJOR || bVersionMinor != DRT_SECURITY_VERSION_MINOR)
{
hr = HRESULT.DRT_E_INVALID_MESSAGE;
goto cleanup;
}
// extract key
var cb = deserializer.Read<ushort>();
m_ddKey = new byte[cb];
deserializer.Read(m_ddKey, 0, cb);
// extract signature
var b = (byte)deserializer.ReadByte();
if (b != DRT_SIG_LENGTH)
{
hr = HRESULT.DRT_E_INVALID_MESSAGE;
goto cleanup;
}
var pbSignature = deserializer.Position;
deserializer.Position += DRT_SIG_LENGTH; //deserializer.ReadArray(DRT_SIG_LENGTH, &ddSignature);
// extract and validate nonce
cb = (byte)deserializer.ReadByte();
m_ddNonce = new byte[cb];
deserializer.Read(m_ddNonce, 0, cb);
// if a nonce was supplied, ensure it matches the nonce in the message
if (pNonce != null && (hr = CompareNonce(pNonce)).Failed)
{
goto cleanup;
}
// extract flags
Flags = deserializer.Read<uint>();
// extract public key
hr = ReadPublicKey(deserializer, out m_pPublicKey);
// extract addresses
var addressList = new SOCKET_ADDRESS_LIST { iAddressCount = (byte)deserializer.ReadByte() };
addressList.Address = new SOCKET_ADDRESS[addressList.iAddressCount];
for (var i = 0; i < addressList.iAddressCount; i++)
{
addressList.Address[i] = new SOCKET_ADDRESS { iSockaddrLength = deserializer.Read<ushort>() };
// Store just the pointer and then pull that into the packed object
addressList.Address[i].lpSockaddr = deserializer.Pointer.Offset(deserializer.Position);
deserializer.Seek(addressList.Address[i].iSockaddrLength, System.IO.SeekOrigin.Current);
}
m_addressList = addressList.Pack();
if (deserializer.Position != deserializer.Length)
{
hr = HRESULT.DRT_E_INVALID_MESSAGE;
goto cleanup;
}
cleanup:
// the remaining allocated memory is Marshal.FreeCoTaskMem in the destructor, or ownership is passed via GetAddresses
return hr;
}
public void Dispose()
{
m_addressList?.Dispose();
m_pPublicKey?.Dispose();
if (m_fAllocated)
{
Marshal.FreeCoTaskMem(m_ddKey.pb);
Marshal.FreeCoTaskMem(m_ddNonce.pb);
}
}
// Purpose: Retrieve the addresses. This returns the memory allocated during de-serialization, so can only be called once. Since it
// will only be called once, there isn't benefit to making another copy of the data.
public void GetAddresses(out SafeCoTaskMemStruct<SOCKET_ADDRESS_LIST> pAddressList)
{
pAddressList = m_addressList;
// this object no longer owns the address list
m_addressList = default;
}
// Purpose: Retrieve the key deserialized earlier. This returns memory allocated during deserialization, and passes ownership to the
// caller. This method may only be called once.
public void GetKey(out DRT_DATA pData)
{
pData = m_ddKey;
// this object no longer owns the public key
m_ddKey = default;
}
// Purpose: Retrieve the flags
public void GetProtocolVersion(out byte pbMajor, out byte pbMinor)
{
pbMajor = m_bProtocolVersionMajor;
pbMinor = m_bProtocolVersionMinor;
}
// Purpose: Retrieve the public key deserialized earlier. This returns memory allocated during deserialization, and passes ownership
// to the caller. This method may only be called once.
public void GetPublicKey(out SafeCoTaskMemStruct<CERT_PUBLIC_KEY_INFO> pKey)
{
pKey = m_pPublicKey;
m_pPublicKey = null;
}
// Purpose: Serialize the SecuredAddressPayload according to the format specified above, and sign it using the specified credentials.
//
// Args: pCertChain: [out] pData: serialized/signed data. pData->pb is allocated.
//
// Notes: The data to be serialized has already been set using the Set* methods.
public HRESULT SerializeAndSign(out byte[] pData)
{
CERT_PUBLIC_KEY_INFO publicKey = default;
using var emptyAddress = new SafeCoTaskMemString("0.0.0.0", CharSet.Ansi);
publicKey.Algorithm.pszObjId = (IntPtr)emptyAddress;
publicKey.PublicKey.cbData = sizeof(uint);
var dwBaadFood = 0xbaadf00d;
publicKey.PublicKey.pbData = (IntPtr)(&dwBaadFood);
pData = default;
uint cbAlgorithmId = emptyAddress.Size;
// validate that the lengths are all reasonable (fit in the space provided for their count)
var addressList = m_addressList.Value;
if (m_ddNonce.cb > byte.MaxValue ||
addressList.iAddressCount > byte.MaxValue ||
m_ddKey.cb > ushort.MaxValue || cbAlgorithmId > byte.MaxValue ||
publicKey.Algorithm.Parameters.cbData > ushort.MaxValue ||
publicKey.PublicKey.cbData > ushort.MaxValue ||
publicKey.PublicKey.cUnusedBits > byte.MaxValue)
{
return HRESULT.E_INVALIDARG;
}
// serialize away
using var mem = new SafeCoTaskMemHandle(1024);
var ddDataPtr = new NativeMemoryStream(mem);
// protocol version
ddDataPtr.Write(m_bProtocolVersionMajor);
ddDataPtr.Write(m_bProtocolVersionMinor);
// security version
ddDataPtr.Write(DRT_SECURITY_VERSION_MAJOR);
ddDataPtr.Write(DRT_SECURITY_VERSION_MINOR);
// key
ddDataPtr.Write((ushort)m_ddKey.cb);
ddDataPtr.WriteFromPtr(m_ddKey.pb, m_ddKey.cb);
// skip over the signature for now (leave it zero while we calculate the signature)
ddDataPtr.Write((byte)DRT_SIG_LENGTH);
var pbSignature = ddDataPtr.Position; // save the location of the signature for later
ddDataPtr.Position += DRT_SIG_LENGTH;
// nonce
ddDataPtr.Write((byte)m_ddNonce.cb);
ddDataPtr.WriteFromPtr(m_ddNonce.pb, m_ddNonce.cb);
// flags
ddDataPtr.Write(Flags);
// public key sizes
ddDataPtr.Write((byte)cbAlgorithmId);
ddDataPtr.Write((ushort)publicKey.Algorithm.Parameters.cbData);
ddDataPtr.Write((ushort)publicKey.PublicKey.cbData);
ddDataPtr.Write((byte)publicKey.PublicKey.cUnusedBits);
// public key data
ddDataPtr.Write(publicKey.Algorithm.pszObjId.ToString(), CharSet.Ansi);
if (publicKey.Algorithm.Parameters.cbData > 0)
ddDataPtr.WriteFromPtr(publicKey.Algorithm.Parameters.pbData, publicKey.Algorithm.Parameters.cbData);
ddDataPtr.WriteFromPtr(publicKey.PublicKey.pbData, publicKey.PublicKey.cbData);
// addresses
ddDataPtr.Write((byte)addressList.iAddressCount);
for (var i = 0; i < addressList.iAddressCount; i++)
{
ddDataPtr.Write((ushort)addressList.Address[i].iSockaddrLength);
ddDataPtr.WriteFromPtr(addressList.Address[i].lpSockaddr, addressList.Address[i].iSockaddrLength);
}
// pass the data back to the caller
pData = new DRT_DATA { cb = (uint)ddDataPtr.Length, pb = mem.TakeOwnership() };
return HRESULT.S_OK;
}
// Purpose: Read a public key from the stream
//
// Args: [out] ppPublicKey: public key allocated as a single block of memory (with self-refertial embedded pointers)
private static HRESULT ReadPublicKey(NativeMemoryStream deserializer, out SafeCoTaskMemStruct<CERT_PUBLIC_KEY_INFO> ppPublicKey)
{
ppPublicKey = default;
try
{
var cbAlgorithmId = (byte)deserializer.ReadByte();
var cbParameters = deserializer.Read<ushort>();
var cbPublicKey = deserializer.Read<ushort>();
var cUnusedBits = (byte)deserializer.ReadByte();
var szAlgId = cbAlgorithmId == 0 ? null : deserializer.Read<string>(CharSet.Ansi);
var pParamData = cbParameters == 0 ? new byte[0] : deserializer.ReadArray<byte>(cbParameters, false).ToArray();
var pKeyData = cbPublicKey == 0 ? new byte[0] : deserializer.ReadArray<byte>(cbPublicKey, false).ToArray();
var cbTotal = sizeof(CERT_PUBLIC_KEY_INFO) + Macros.ALIGN_TO_MULTIPLE(cbAlgorithmId + 1, IntPtr.Size) +
Macros.ALIGN_TO_MULTIPLE(cbParameters, IntPtr.Size) + Macros.ALIGN_TO_MULTIPLE(cbPublicKey, IntPtr.Size);
var pPublicKey = new SafeCoTaskMemStruct<CERT_PUBLIC_KEY_INFO>(cbTotal);
ref var rpk = ref pPublicKey.AsRef();
var pbStructIter = ((IntPtr)pPublicKey).Offset(sizeof(CERT_PUBLIC_KEY_INFO)); // skip the structure
// copy the algorithm id
rpk.Algorithm.pszObjId = pbStructIter;
StringHelper.Write(szAlgId, pbStructIter, out var written, true, CharSet.Ansi);
pbStructIter += (int)Macros.ALIGN_TO_MULTIPLE(written, IntPtr.Size);
// copy the key parameters
if (cbParameters > 0)
{
rpk.Algorithm.Parameters.cbData = cbParameters;
rpk.Algorithm.Parameters.pbData = pbStructIter;
pbStructIter.Write(pParamData);
pbStructIter += (int)Macros.ALIGN_TO_MULTIPLE(pParamData.Length, IntPtr.Size);
}
// copy the key
rpk.PublicKey.cbData = cbPublicKey;
rpk.PublicKey.cUnusedBits = cUnusedBits;
rpk.PublicKey.pbData = pbStructIter;
pbStructIter.Write(pKeyData);
ppPublicKey = pPublicKey;
return HRESULT.S_OK;
}
catch (Exception ex)
{
return HRESULT.FromException(ex);
}
}
// Purpose: Compare the nonce provided by the DRT to the nonce received on the wire, returning HRESULT.DRT_E_INVALID_MESSAGE if they
// don't match.
//
// Args: pNonce:
private HRESULT CompareNonce(byte[] pNonce) => pNonce.SequenceEqual(m_ddNonce) ? (HRESULT)HRESULT.S_OK : HRESULT.DRT_E_INVALID_MESSAGE;
}
/// <summary>Initializes a new instance of the <see cref="CustomNullSecurityProvider"/> class.</summary>
/// <param name="context">The context.</param>
public CustomNullSecurityProvider(object? context) : base(context) { }
/// <inheritdoc/>
protected override HRESULT EncryptData([In] byte[] pRemoteCredential, byte[][] pDataBuffers, byte[][] pEncryptedBuffers, out byte[]? pKeyToken)
{
HRESULT hr = HRESULT.S_OK;
pKeyToken = default;
//copy all input buffers into out buffers unmodified
for (uint dwIdx = 0; dwIdx < pDataBuffers.GetLength(0); dwIdx++)
{
pDataBuffers[dwIdx].CopyTo(pEncryptedBuffers[dwIdx], 0);
}
return hr;
}
/// <inheritdoc/>
protected override HRESULT SecureAndPackPayload([In, Optional] IntPtr pvKeyContext, byte bProtocolMajor, byte bProtocolMinor, uint dwFlags,
[In] byte[] pKey, [In] byte[]? pPayload, [In] IPEndPoint[]? pAddressList, [In] byte[] pNonce, out byte[] pSecuredAddressPayload,
out byte[]? pClassifier, out byte[]? pSecuredPayload, out byte[]? pCertChain)
{
// NULL out the out params
pClassifier = default;
pSecuredPayload = default;
pCertChain = default;
// set the payload contents
var sap = new CCustomNullSecuredAddressPayload(bProtocolMajor, bProtocolMinor, pKey, pNonce, pAddressList, dwFlags);
var hr = sap.SerializeAndSign(out pSecuredAddressPayload);
if (hr.Failed)
{
goto cleanup;
}
if (pPayload != null && pSecuredPayload != null)
{
pSecuredPayload->cb = pPayload->cb;
pSecuredPayload->pb = Marshal.AllocCoTaskMem((int)pSecuredPayload->cb);
if (pSecuredPayload->pb == default)
{
hr = HRESULT.E_OUTOFMEMORY;
goto cleanup;
}
pPayload->pb.CopyTo(pSecuredPayload->pb, pSecuredPayload->cb);
}
// make a copy of the serialized local cert chain
if (pCertChain != null)
{
pCertChain->cb = sizeof(uint);
pCertChain->pb = Marshal.AllocCoTaskMem((int)pCertChain->cb);
if (pCertChain->pb == default)
{
hr = HRESULT.E_OUTOFMEMORY;
goto cleanup;
}
pCertChain->pb.Write(0xdeadbeefU, 0, sizeof(uint));
}
cleanup:
// if something failed, free all the out params and NULL them out
if (hr.Failed)
{
Marshal.FreeCoTaskMem(pSecuredAddressPayload.pb);
pSecuredAddressPayload = default;
if (pSecuredPayload != null)
{
Marshal.FreeCoTaskMem(pSecuredPayload->pb);
*pSecuredPayload = default;
}
if (pCertChain != null)
{
Marshal.FreeCoTaskMem(pCertChain->pb);
*pCertChain = default;
}
}
return hr;
}
/// <inheritdoc/>
protected override HRESULT ValidateAndUnpackPayload([In] byte[] pSecuredAddressPayload, [In] byte[]? pCertChain, [In] byte[]? pClassifier,
[In] byte[]? pNonce, [In] byte[]? pSecuredPayload, out byte pbProtocolMajor, out byte pbProtocolMinor, out byte[] pKey,
out byte[]? pPayload, out SafeCoTaskMemStruct<CERT_PUBLIC_KEY_INFO> ppPublicKey, out IPEndPoint[]? ppAddressList, out uint pdwFlags)
{
var sap = new CCustomNullSecuredAddressPayload();
HRESULT hr = HRESULT.S_OK;
// NULL out the out params
*pbProtocolMajor = 0;
*pbProtocolMinor = 0;
pKey = default;
if (pPayload != null)
*pPayload = default;
*ppPublicKey = null;
pdwFlags = 0;
// deserialize Secured Address Payload
hr = sap.DeserializeAndValidate(pSecuredAddressPayload, pNonce);
if (hr.Failed)
{
goto cleanup;
}
// When we asked for the payload validate signature of payload
if (pPayload != null && pSecuredPayload != null)
{
pPayload->cb = pSecuredPayload->cb;
pPayload->pb = Marshal.AllocCoTaskMem((int)pPayload->cb);
if (pPayload->pb == default)
{
hr = HRESULT.E_OUTOFMEMORY;
goto cleanup;
}
pSecuredPayload->pb.CopyTo(pPayload->pb, pPayload->cb);
}
pdwFlags = sap.Flags;
// everything is valid, time to extract the data
if (ppAddressList != null)
{
sap.GetAddresses(out var addr);
*ppAddressList = (void*)addr.TakeOwnership();
}
sap.GetPublicKey(out var pk);
*ppPublicKey = (CERT_PUBLIC_KEY_INFO*)pk.TakeOwnership();
sap.GetKey(out pKey);
sap.GetProtocolVersion(out *pbProtocolMajor, out *pbProtocolMinor);
cleanup:
// if something failed, free all the out params and NULL them out
if (hr.Failed)
{
*pbProtocolMajor = 0;
*pbProtocolMinor = 0;
pdwFlags = 0;
Marshal.FreeCoTaskMem(pKey.pb);
pKey = default;
if (pPayload != null)
{
Marshal.FreeCoTaskMem(pPayload->pb);
*pPayload = default;
}
Marshal.FreeCoTaskMem((IntPtr)(*ppPublicKey));
*ppPublicKey = null;
// free all the addresses
if (ppAddressList != null)
{
Marshal.FreeCoTaskMem((IntPtr)(*ppAddressList));
*ppAddressList = null;
}
}
return hr;
}
}
*/