From 13898323dd5d0875404a56686ed6c61ec0b513c8 Mon Sep 17 00:00:00 2001 From: ahmed Date: Tue, 14 Jul 2026 06:35:52 +0300 Subject: [PATCH] SessionHeaders --- Libraries/Esiur/Protocol/EpConnection.cs | 86 +++++++------------ .../Esiur/Protocol/EpConnectionProtocol.cs | 9 +- Libraries/Esiur/Security/Authority/Session.cs | 66 +++++++++++++- Tests/Unit/IndexedStructureTests.cs | 37 ++++++++ .../SessionHeadersIntegrationTests.cs | 22 +++++ 5 files changed, 157 insertions(+), 63 deletions(-) create mode 100644 Tests/Unit/Integration/SessionHeadersIntegrationTests.cs diff --git a/Libraries/Esiur/Protocol/EpConnection.cs b/Libraries/Esiur/Protocol/EpConnection.cs index c34211e..70da282 100644 --- a/Libraries/Esiur/Protocol/EpConnection.cs +++ b/Libraries/Esiur/Protocol/EpConnection.cs @@ -193,7 +193,7 @@ public partial class EpConnection : NetworkConnection, IStore set { _remoteDomain = value; - _session.RemoteHeaders[EpAuthPacketHeader.Domain] = value; + _session.RemoteHeaders.Domain = value; } } @@ -203,7 +203,7 @@ public partial class EpConnection : NetworkConnection, IStore set { _localDomain = value; - _session.LocalHeaders[EpAuthPacketHeader.Domain] = value; + _session.LocalHeaders.Domain = value; } } @@ -262,8 +262,7 @@ public partial class EpConnection : NetworkConnection, IStore { base.Assign(socket); - _session.LocalHeaders[EpAuthPacketHeader.IPAddress] - = socket.RemoteEndPoint.Address.GetAddressBytes(); + _session.LocalHeaders.IPAddress = socket.RemoteEndPoint.Address.GetAddressBytes(); if (socket.State == SocketState.Established && _authDirection == AuthenticationDirection.Initiator) @@ -277,8 +276,7 @@ public partial class EpConnection : NetworkConnection, IStore if (_authDirection != AuthenticationDirection.Initiator) return; - // change to Map for compatibility - var headers = _session.LocalHeaders.Select(x => new KeyValuePair((byte)x.Key, x.Value)); + var headers = _session.LocalHeaders.Copy(); if (_session.AuthenticationMode != AuthenticationMode.None) { @@ -287,9 +285,9 @@ public partial class EpConnection : NetworkConnection, IStore var initAuthResult = _session.AuthenticationHandler.Process(null); - headers.Add((byte)EpAuthPacketHeader.AuthenticationProtocol, _session.AuthenticationHandler.Protocol); - headers.Add((byte)EpAuthPacketHeader.AuthenticationData, initAuthResult.AuthenticationData); - headers.Add((byte)EpAuthPacketHeader.Domain, _remoteDomain); + headers.AuthenticationProtocol = _session.AuthenticationHandler.Protocol; + headers.AuthenticationData = initAuthResult.AuthenticationData; + headers.Domain = _remoteDomain; } @@ -308,10 +306,9 @@ public partial class EpConnection : NetworkConnection, IStore /// Create a new distributed connection. /// /// Socket to transfer data through. - /// Working domain. - /// Username. - /// Password. - public EpConnection(ISocket socket, IAuthenticationHandler authenticationHandler, Map headers) + /// Authentication handler for the session. + /// Initial local session headers. + public EpConnection(ISocket socket, IAuthenticationHandler authenticationHandler, SessionHeaders headers) { _session = new Session(); @@ -728,30 +725,18 @@ public partial class EpConnection : NetworkConnection, IStore if (_authPacket.Command == EpAuthPacketCommand.Initialize) { - var remoteHeaders = new Map(); + var remoteHeaders = new SessionHeaders(); object remoteAuthData = null; if (_authPacket.Tdu != null) { - var parsed = Codec.ParseSync(_authPacket.Tdu.Value, null); - - if (parsed is Map headers) - { - remoteHeaders = headers; - - foreach (var header in headers) - { - if (header.Key == (byte)EpAuthPacketHeader.AuthenticationData) - remoteAuthData = header.Value; - else - _session.RemoteHeaders.Add((EpAuthPacketHeader)header.Key, header.Value); - } - } + remoteHeaders = Codec.ParseIndexedType(_authPacket.Tdu.Value, null); + remoteAuthData = remoteHeaders.AuthenticationData; + remoteHeaders.AuthenticationData = null; } - var localHeaders = new Map(); - foreach (var header in _session.LocalHeaders) - localHeaders.Add((byte)header.Key, header.Value); + _session.RemoteHeaders = remoteHeaders; + var localHeaders = _session.LocalHeaders.Copy(); if (_authPacket.AuthMode == AuthenticationMode.None) { @@ -773,7 +758,7 @@ public partial class EpConnection : NetworkConnection, IStore return offset; } - if (!_session.RemoteHeaders.ContainsKey(EpAuthPacketHeader.AuthenticationProtocol)) + if (_session.RemoteHeaders.AuthenticationProtocol == null) { SendAuthHeaders(EpAuthPacketMethod.NotSupported, localHeaders); _invalidCredentials = true; @@ -781,13 +766,13 @@ public partial class EpConnection : NetworkConnection, IStore return offset; } - var provider = _serverWarehouse.GetAuthenticationProvider(_session.RemoteHeaders[EpAuthPacketHeader.AuthenticationProtocol].ToString()); + var provider = _serverWarehouse.GetAuthenticationProvider(_session.RemoteHeaders.AuthenticationProtocol); var handler = provider.CreateAuthenticationHandler(new AuthenticationContext() { Direction = AuthenticationDirection.Responder, Mode = _authPacket.AuthMode, - Domain = _session.RemoteHeaders.ContainsKey(EpAuthPacketHeader.Domain) ? _session.RemoteHeaders[EpAuthPacketHeader.Domain].ToString() : null, + Domain = _session.RemoteHeaders.Domain, Materials = new AuthenticationMaterial[] { new AuthenticationMaterial() { Type = AuthenticationMaterialType.Data, Value = remoteAuthData } } }); @@ -807,8 +792,7 @@ public partial class EpConnection : NetworkConnection, IStore // send acknowledgements - localHeaders.Add((byte)EpAuthPacketHeader.AuthenticationData, - authResult.AuthenticationData); + localHeaders.AuthenticationData = authResult.AuthenticationData; if (authResult.Ruling == AuthenticationRuling.Failed) { @@ -853,32 +837,20 @@ public partial class EpConnection : NetworkConnection, IStore if (_authPacket.Method == EpAuthPacketMethod.ProceedToHandshake || _authPacket.Method == EpAuthPacketMethod.ProceedToFinalHandshake) { - var remoteHeaders - = new Map(); + var remoteHeaders = new SessionHeaders(); object remoteAuthData = null; if (_authPacket.Tdu != null) { - var parsed = Codec.ParseSync(_authPacket.Tdu.Value, Instance.Warehouse); - - if (parsed is Map headers) - { - foreach (var header in headers) - { - if (header.Key == (byte)EpAuthPacketHeader.AuthenticationData) - { - remoteAuthData = header.Value; - } - else - { - remoteHeaders.Add((EpAuthPacketHeader)header.Key, header.Value); - } - } - - _session.RemoteHeaders = remoteHeaders;// headers.Select(x => new KeyValuePair((EpAuthPacketHeader)x.Key, x.Value)); - } + remoteHeaders = Codec.ParseIndexedType( + _authPacket.Tdu.Value, + Instance.Warehouse); + remoteAuthData = remoteHeaders.AuthenticationData; + remoteHeaders.AuthenticationData = null; } + _session.RemoteHeaders = remoteHeaders; + if (_session.AuthenticationMode == AuthenticationMode.None) { if (_authPacket.Method == EpAuthPacketMethod.SessionEstablished) @@ -1972,7 +1944,7 @@ public partial class EpConnection : NetworkConnection, IStore _authDirection = AuthenticationDirection.Initiator; _invalidCredentials = false; - _session.LocalHeaders[EpAuthPacketHeader.Domain] = domain; + _session.LocalHeaders.Domain = domain; _hostname = hostname; } diff --git a/Libraries/Esiur/Protocol/EpConnectionProtocol.cs b/Libraries/Esiur/Protocol/EpConnectionProtocol.cs index 97ab4f6..f760ede 100644 --- a/Libraries/Esiur/Protocol/EpConnectionProtocol.cs +++ b/Libraries/Esiur/Protocol/EpConnectionProtocol.cs @@ -375,7 +375,7 @@ partial class EpConnection } void SendAuthHeaders(EpAuthPacketMethod method, - Map authHeaders) + SessionHeaders authHeaders) { if (authHeaders != null) { @@ -388,9 +388,10 @@ partial class EpConnection var bl = new BinaryList(); bl.AddUInt8((byte)((byte)method | 0x20)); - bl.AddUInt8Array(Codec.Compose(authHeaders, - this.Instance?.Warehouse ?? _serverWarehouse, - this)); + bl.AddUInt8Array(Codec.ComposeIndexedType( + authHeaders, + this.Instance?.Warehouse ?? _serverWarehouse, + this)); Send(bl.ToArray()); } else diff --git a/Libraries/Esiur/Security/Authority/Session.cs b/Libraries/Esiur/Security/Authority/Session.cs index 68605dc..3a28da3 100644 --- a/Libraries/Esiur/Security/Authority/Session.cs +++ b/Libraries/Esiur/Security/Authority/Session.cs @@ -34,6 +34,68 @@ using Esiur.Security.Cryptography; using Esiur.Net.Packets; namespace Esiur.Security.Authority; + +/// +/// Authentication metadata exchanged while establishing a session. +/// Members retain the indexes defined by , so this +/// structure has the same wire representation as the former header map. +/// +public sealed class SessionHeaders : IndexedStructure +{ + [Index((int)EpAuthPacketHeader.Version)] + public object? Version { get; set; } + + [Index((int)EpAuthPacketHeader.Domain)] + public string? Domain { get; set; } + + [Index((int)EpAuthPacketHeader.SupportedAuthentications)] + public object? SupportedAuthentications { get; set; } + + [Index((int)EpAuthPacketHeader.SupportedHashAlgorithms)] + public object? SupportedHashAlgorithms { get; set; } + + [Index((int)EpAuthPacketHeader.SupportedCiphers)] + public object? SupportedCiphers { get; set; } + + [Index((int)EpAuthPacketHeader.SupportedCompression)] + public object? SupportedCompression { get; set; } + + [Index((int)EpAuthPacketHeader.SupportedMultiFactorAuthentications)] + public object? SupportedMultiFactorAuthentications { get; set; } + + [Index((int)EpAuthPacketHeader.CipherType)] + public object? CipherType { get; set; } + + [Index((int)EpAuthPacketHeader.CipherKey)] + public byte[]? CipherKey { get; set; } + + [Index((int)EpAuthPacketHeader.SoftwareIdentity)] + public string? SoftwareIdentity { get; set; } + + [Index((int)EpAuthPacketHeader.Referrer)] + public string? Referrer { get; set; } + + [Index((int)EpAuthPacketHeader.Time)] + public DateTime? Time { get; set; } + + [Index((int)EpAuthPacketHeader.IPAddress)] + public byte[]? IPAddress { get; set; } + + [Index((int)EpAuthPacketHeader.Identity)] + public string? Identity { get; set; } + + [Index((int)EpAuthPacketHeader.AuthenticationProtocol)] + public string? AuthenticationProtocol { get; set; } + + [Index((int)EpAuthPacketHeader.AuthenticationData)] + public object? AuthenticationData { get; set; } + + [Index((int)EpAuthPacketHeader.ErrorMessage)] + public string? ErrorMessage { get; set; } + + internal SessionHeaders Copy() => (SessionHeaders)MemberwiseClone(); +} + public class Session { public byte[] Id { get; set; } @@ -47,8 +109,8 @@ public class Session public ISymetricCipher SymetricCipher { get; set; } = null; - public Map LocalHeaders { get; set; } = new Map(); - public Map RemoteHeaders { get; set; } = new Map(); + public SessionHeaders LocalHeaders { get; set; } = new SessionHeaders(); + public SessionHeaders RemoteHeaders { get; set; } = new SessionHeaders(); //public AuthenticationMethod AuthenticationMethod { get; set; } //public AuthenticationMethod RemoteMethod { get; set; } diff --git a/Tests/Unit/IndexedStructureTests.cs b/Tests/Unit/IndexedStructureTests.cs index 052148b..f0c9b24 100644 --- a/Tests/Unit/IndexedStructureTests.cs +++ b/Tests/Unit/IndexedStructureTests.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using Esiur.Data; +using Esiur.Net.Packets; using Esiur.Resource; +using Esiur.Security.Authority; namespace Esiur.Tests.Unit; @@ -81,6 +83,41 @@ public class IndexedStructureTests Assert.Contains("index 1", error.Message, StringComparison.OrdinalIgnoreCase); } + [Fact] + public void SessionHeaders_RoundTripUsingAuthenticationHeaderIndexes() + { + var source = new SessionHeaders + { + Version = (byte)3, + Domain = "example.test", + IPAddress = new byte[] { 127, 0, 0, 1 }, + AuthenticationProtocol = "hash", + AuthenticationData = new byte[] { 1, 2, 3 }, + }; + + var bytes = Codec.ComposeIndexedType(source, Warehouse.Default, null); + var legacyBytes = Codec.Compose(new Map + { + [(byte)EpAuthPacketHeader.Version] = source.Version, + [(byte)EpAuthPacketHeader.Domain] = source.Domain, + [(byte)EpAuthPacketHeader.IPAddress] = source.IPAddress, + [(byte)EpAuthPacketHeader.AuthenticationProtocol] = source.AuthenticationProtocol, + [(byte)EpAuthPacketHeader.AuthenticationData] = source.AuthenticationData, + }, Warehouse.Default, null); + var (_, raw) = Codec.ParseSync(bytes, 0, Warehouse.Default); + var map = Assert.IsType>(raw); + var (_, parsed) = Codec.ParseIndexedType(bytes, 0, Warehouse.Default); + + Assert.Equal(legacyBytes, bytes); + Assert.Equal("example.test", map[(byte)EpAuthPacketHeader.Domain]); + Assert.Equal("hash", map[(byte)EpAuthPacketHeader.AuthenticationProtocol]); + Assert.False(map.ContainsKey((byte)EpAuthPacketHeader.ErrorMessage)); + Assert.Equal(source.Domain, parsed.Domain); + Assert.Equal(source.IPAddress, parsed.IPAddress); + Assert.Equal(source.AuthenticationProtocol, parsed.AuthenticationProtocol); + Assert.Equal(source.AuthenticationData, parsed.AuthenticationData); + } + private sealed class InvalidStructure : IndexedStructure { [Index(1)] public int First { get; set; } diff --git a/Tests/Unit/Integration/SessionHeadersIntegrationTests.cs b/Tests/Unit/Integration/SessionHeadersIntegrationTests.cs new file mode 100644 index 0000000..4213e60 --- /dev/null +++ b/Tests/Unit/Integration/SessionHeadersIntegrationTests.cs @@ -0,0 +1,22 @@ +namespace Esiur.Tests.Unit.Integration; + +[Collection("Integration")] +public class SessionHeadersIntegrationTests +{ + [Fact] + public async Task AuthenticatedHandshake_StoresTypedHeadersWithoutAuthenticationData() + { + await using var cluster = await IntegrationCluster + .StartAsync(_ => Task.CompletedTask) + .WaitAsync(TimeSpan.FromSeconds(10)); + + var serverConnection = Assert.Single(cluster.Server.Connections); + + Assert.NotNull(cluster.Connection.Session.LocalHeaders.IPAddress); + Assert.Null(cluster.Connection.Session.RemoteHeaders.AuthenticationData); + + Assert.Equal("test", serverConnection.Session.RemoteHeaders.Domain); + Assert.Equal("hash", serverConnection.Session.RemoteHeaders.AuthenticationProtocol); + Assert.Null(serverConnection.Session.RemoteHeaders.AuthenticationData); + } +}