diff --git a/Esiur/Esiur.csproj b/Esiur/Esiur.csproj index 50d2f13..d7f5cf2 100644 --- a/Esiur/Esiur.csproj +++ b/Esiur/Esiur.csproj @@ -62,9 +62,12 @@ + + + @@ -75,10 +78,13 @@ + + + diff --git a/Esiur/Net/Packets/EpAuthPacket.cs b/Esiur/Net/Packets/EpAuthPacket.cs index 3bb70d3..36ed262 100644 --- a/Esiur/Net/Packets/EpAuthPacket.cs +++ b/Esiur/Net/Packets/EpAuthPacket.cs @@ -24,6 +24,7 @@ SOFTWARE. using Esiur.Data; using Esiur.Security.Authority; +using Esiur.Security.Cryptography; using System; using System.Collections.Generic; using System.Data.Common; @@ -62,13 +63,13 @@ public class EpAuthPacket : Packet } - public EpAuthPacketAuthMode AuthMode + public AuthenticationMode AuthMode { get; set; } - public EpAuthPacketEncryptionMode EncryptionMode + public EncryptionMode EncryptionMode { get; set; @@ -145,8 +146,8 @@ public class EpAuthPacket : Packet if (Command == EpAuthPacketCommand.Initialize) { - AuthMode = (EpAuthPacketAuthMode)(data[offset] >> 3 & 0x7); - EncryptionMode = (EpAuthPacketEncryptionMode)(data[offset++] & 0x7); + AuthMode = (AuthenticationMode)(data[offset] >> 3 & 0x7); + EncryptionMode = (EncryptionMode)(data[offset++] & 0x7); } else if (Command == EpAuthPacketCommand.Acknowledge) { diff --git a/Esiur/Protocol/EpConnection.cs b/Esiur/Protocol/EpConnection.cs index 9d40eb7..9f9dd82 100644 --- a/Esiur/Protocol/EpConnection.cs +++ b/Esiur/Protocol/EpConnection.cs @@ -48,8 +48,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using System.Timers; -using static System.Collections.Specialized.BitVector32; - + namespace Esiur.Protocol; public partial class EpConnection : NetworkConnection, IStore @@ -251,7 +250,7 @@ public partial class EpConnection : NetworkConnection, IStore if (session.AuthenticationHandler == null) throw new Exception("Authentication handler must be assigned for the session."); - var initAuthData = session.AuthenticationHandler.Initialize(session); + var initAuthData = session.AuthenticationHandler.Initialize(session, null); session.LocalHeaders.Add(EpAuthPacketHeader.AuthenticationData, initAuthData); } @@ -278,16 +277,20 @@ public partial class EpConnection : NetworkConnection, IStore /// Working domain. /// Username. /// Password. - public EpConnection(ISocket socket, IAuthenticationHandler authenticationInitiator, Map headers) + public EpConnection(ISocket socket, IAuthenticationHandler authenticationHandler, Map headers) { this.session = new Session(); //if (authenticationHandler.Type != AuthenticationType.Initiator) // throw new Exception("" - session.AuthenticationType = AuthenticationMode.Initiator; + //session.AuthenticationType = AuthenticationMode.Initiator; session.LocalHeaders = headers; - session.AuthenticationInitiator = authenticationInitiator; + if (authenticationHandler != null) + { + session.AuthenticationHandler = authenticationHandler; + session.AuthenticationMode = authenticationHandler.Mode; + } //this.localPasswordOrToken = DC.ToBytes(password); @@ -317,13 +320,13 @@ public partial class EpConnection : NetworkConnection, IStore /// /// Create a new instance of a distributed connection /// - public EpConnection(IAuthenticationHandler authenticationResponder) + public EpConnection() { session = new Session(); - session.AuthenticationType = AuthenticationMode.Responder; - session.AuthenticationResponder = authenticationResponder; + //session.AuthenticationType = AuthenticationMode.Responder; + //session.AuthenticationResponder = authenticationResponder; - authenticationResponder.Initiate(session); + //authenticationResponder.Initiate(session); init(); } @@ -641,88 +644,116 @@ public partial class EpConnection : NetworkConnection, IStore { offset += (uint)rt; - if (session.AuthenticationMethod == AuthenticationMethod.None) - { - // establish session without authentication - } + if (authPacket.Command == EpAuthPacketCommand.Initialize && isInitiator) + throw new Exception("Bad authentication packet received. Connection is initiator but received an initialization packet."); - if (session.AuthenticationHandler == null) - { - throw new Exception("No authentication handler assigned for the session."); - } + if (authPacket.Command == EpAuthPacketCommand.Acknowledge && !isInitiator) + throw new Exception("Bad authentication packet received. Connection is responder but received an acknowledge packet."); - try + if (authPacket.Command == EpAuthPacketCommand.Initialize) { - var result = session.AuthenticationHandler.Process(authPacket); - if (result.Ruling == AuthenticationRuling.Succeeded) + if (authPacket.Tdu != null) { - if (this.Instance == null) + var (_, parsed) = Codec.ParseSync(authPacket.Tdu.Value, Instance.Warehouse); + + if (parsed is Map headers) { - Server.Instance.Warehouse.Put( - Server.Instance.Link + "/" + this.GetHashCode().ToString().Replace("/", "_"), this) - .Then(x => - { - session.AuthorizedIdentity = result.Identity; - - authenticated = true; - Status = EpConnectionStatus.Connected; - openReply?.Trigger(true); - openReply = null; - OnReady?.Invoke(this); - - Server?.Membership?.Login(session); - LoginDate = DateTime.Now; - - }).Error(x => - { - openReply?.TriggerError(x); - openReply = null; - }); - } - else - { - session.AuthorizedIdentity = result.Identity; - authenticated = true; - Status = EpConnectionStatus.Connected; - openReply?.Trigger(true); - openReply = null; - OnReady?.Invoke(this); - Server?.Membership?.Login(session); + session.RemoteHeaders = headers.Select(x => new KeyValuePair((EpAuthPacketHeader)x.Key, x.Value)); } } - else if (result.Ruling == AuthenticationRuling.InProgress) - { - SendParams() - .AddUInt8((byte)EpAuthPacketCommand.Acknowledge) - .AddUInt8Array(Codec.Compose( - result.HandshakePayload - , this.Instance.Warehouse, this)) - .Done(); - } - else if (result.Ruling == AuthenticationRuling.Failed) + + + + //@TODO: get the authentication handler + if (session.RemoteHeaders.ContainsKey(EpAuthPacketHeader.AuthenticationData)) { - // Send the server side error - SendParams() - .AddUInt8((byte)EpAuthPacketEvent.ErrorTerminate) - .AddUInt8Array(Codec.Compose( - new object[] {(ushort)result.ExceptionCode, - result.ExceptionMessage } - , this.Instance.Warehouse, this)) - .Done(); + var authResult = session.AuthenticationHandler.Initialize(session, session.RemoteHeaders[EpAuthPacketHeader.AuthenticationData]); } - } - catch (Exception ex) - { - // Send the server side error + + //@TODO allow all for testing SendParams() - .AddUInt8((byte)EpAuthPacketEvent.ErrorTerminate) - .AddUInt8Array(Codec.Compose( - new object[] { (ushort)ExceptionCode.GeneralFailure, - ex.Message } - , this.Instance.Warehouse, this)) - .Done(); + .AddUInt8((byte)EpAuthPacketAcknowledgement.SessionEstablished) + .Done(); + } + else if (authPacket.Command == EpAuthPacketCommand.Acknowledge) + { + //@TODO: get the authentication handler + + if (authPacket.Tdu != null) + { + var (_, parsed) = Codec.ParseSync(authPacket.Tdu.Value, Instance.Warehouse); + + if (parsed is Map headers) + { + session.RemoteHeaders = headers.Select(x => new KeyValuePair((EpAuthPacketHeader)x.Key, x.Value)); + } + } + + if (session.RemoteHeaders.ContainsKey(EpAuthPacketHeader.AuthenticationData)) + { + var authResult = session.AuthenticationHandler.Initialize(session, session.RemoteHeaders[EpAuthPacketHeader.AuthenticationData]); + } + + if (authPacket.Acknowledgement == EpAuthPacketAcknowledgement.SessionEstablished) + { + // session established, check if authentication is required + AuthenticatonCompleted("guest"); + } + } + + + //if (session.AuthenticationMode == AuthenticationMode.None) + //{ + // // establish session without authentication + //} + + //if (session.AuthenticationHandler == null) + //{ + // throw new Exception("No authentication handler assigned for the session."); + //} + + //try + //{ + // var result = session.AuthenticationHandler.Process(authPacket); + // if (result.Ruling == AuthenticationRuling.Succeeded) + // { + // AuthenticatonCompleted(result.Identity); + // } + // else if (result.Ruling == AuthenticationRuling.InProgress) + // { + // SendParams() + // .AddUInt8((byte)EpAuthPacketCommand.Acknowledge) + // .AddUInt8Array(Codec.Compose( + // result.HandshakePayload + // , this.Instance.Warehouse, this)) + // .Done(); + + // } + // else if (result.Ruling == AuthenticationRuling.Failed) + // { + // // Send the server side error + // SendParams() + // .AddUInt8((byte)EpAuthPacketEvent.ErrorTerminate) + // .AddUInt8Array(Codec.Compose( + // new object[] {(ushort)result.ExceptionCode, + // result.ExceptionMessage } + // , this.Instance.Warehouse, this)) + // .Done(); + // } + //} + //catch (Exception ex) + //{ + // // Send the server side error + // SendParams() + // .AddUInt8((byte)EpAuthPacketEvent.ErrorTerminate) + // .AddUInt8Array(Codec.Compose( + // new object[] { (ushort)ExceptionCode.GeneralFailure, + // ex.Message } + // , this.Instance.Warehouse, this)) + // .Done(); + //} } } @@ -730,6 +761,42 @@ public partial class EpConnection : NetworkConnection, IStore return offset; } + void AuthenticatonCompleted(string identity) + { + if (this.Instance == null) + { + Server.Instance.Warehouse.Put( + Server.Instance.Link + "/" + this.GetHashCode().ToString().Replace("/", "_"), this) + .Then(x => + { + session.AuthorizedIdentity = identity; + + authenticated = true; + Status = EpConnectionStatus.Connected; + openReply?.Trigger(true); + openReply = null; + OnReady?.Invoke(this); + + Server?.Membership?.Login(session); + LoginDate = DateTime.Now; + + }).Error(x => + { + openReply?.TriggerError(x); + openReply = null; + }); + } + else + { + session.AuthorizedIdentity = identity; + authenticated = true; + Status = EpConnectionStatus.Connected; + openReply?.Trigger(true); + openReply = null; + OnReady?.Invoke(this); + Server?.Membership?.Login(session); + } + } //private void ProcessClientAuth(byte[] data) //{ // if (authPacket.Command == EpAuthPacketCommand.Acknowledge) @@ -1493,11 +1560,10 @@ public partial class EpConnection : NetworkConnection, IStore if (hostname != null) { session = new Session(); - session.AuthenticationType = AuthenticationMode.Initiator; - //session.AuthenticationMethod = method; - session.LocalHeaders[EpAuthPacketHeader.Domain] = domain; - + isInitiator = true; invalidCredentials = false; + + session.LocalHeaders[EpAuthPacketHeader.Domain] = domain; } if (session == null) @@ -1651,7 +1717,7 @@ public partial class EpConnection : NetworkConnection, IStore protected override void Connected() { - if (session.AuthenticationType == AuthenticationMode.Initiator) + if (isInitiator) Declare(); } diff --git a/Esiur/Security/Authority/IAuthenticationHandler.cs b/Esiur/Security/Authority/IAuthenticationHandler.cs index aa84d6b..201e353 100644 --- a/Esiur/Security/Authority/IAuthenticationHandler.cs +++ b/Esiur/Security/Authority/IAuthenticationHandler.cs @@ -7,9 +7,11 @@ namespace Esiur.Security.Authority { public interface IAuthenticationHandler { - public AuthenticationResult Initialize(Session session); - public AuthenticationResult Process(object handshakePayload); + public AuthenticationMode Mode { get; } + public AuthenticationResult Initialize(Session session, object authenticationData); + + public AuthenticationResult Process(object authenticationData); public void Terminate(Session session); diff --git a/Esiur/Security/Authority/UserCertificate.cs b/Esiur/Security/Authority/UserCertificate.cs deleted file mode 100644 index 419ee62..0000000 --- a/Esiur/Security/Authority/UserCertificate.cs +++ /dev/null @@ -1,259 +0,0 @@ -/* - -Copyright (c) 2017 Ahmed Kh. Zamil - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -using Esiur.Data; -using Esiur.Security.Cryptography; -using Esiur.Security.Integrity; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; - -namespace Esiur.Security.Authority; - -public class UserCertificate : Certificate -{ - uint ip; - byte[] ip6; - byte[] signature; - string domain; - string username; - ulong domainId; - - public ulong DomainId - { - get { return domainId; } - } - - public string Username - { - get { return username; } - } - - public string Domain - { - get { return domain; } - } - - public byte[] Signature - { - get { return signature; } - } - - public uint IPAddress - { - get { return ip; } - } - - public byte[] IPv6Address - { - get { return ip6; } - } - - public UserCertificate(byte[] data, uint offset, uint length, bool privateKeyIncluded = false) - : base(0, DateTime.MinValue, DateTime.MinValue, HashFunctionType.MD5) - { - var oOffset = offset; - - this.id = DC.GetUInt64(data, offset, Endian.Little); - offset += 8; - - // load IPs - this.ip = DC.GetUInt32(data, offset, Endian.Little); - offset += 4; - ip6 = DC.Clip(data, offset, 16); - offset += 16; - - this.issueDate = DC.GetDateTime(data, offset, Endian.Little); - offset += 8; - this.expireDate = DC.GetDateTime(data, offset, Endian.Little); - offset += 8; - - this.domainId = DC.GetUInt64(data, offset, Endian.Little); - offset += 8; - - this.domain = Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); - offset += (uint)data[offset] + 1; - - this.username = Encoding.ASCII.GetString(data, (int)offset + 1, data[offset]); - offset += (uint)data[offset] + 1; - - // Hash Function - this.hashFunction = (HashFunctionType)(data[offset++] >> 4); - - // Public Key Encryption Algorithm - var aea = (AsymetricEncryptionAlgorithmType)(data[offset] >> 5); - - if (aea == AsymetricEncryptionAlgorithmType.RSA) - { - - var key = new RSAParameters(); - - uint exponentLength = (uint)data[offset++] & 0x1F; - - key.Exponent = DC.Clip(data, offset, exponentLength); - offset += exponentLength; - - - uint keySize = DC.GetUInt16(data, offset, Endian.Little); - offset += 2; - - key.Modulus = DC.Clip(data, offset, keySize); - - offset += keySize; - - // copy cert data - this.publicRawData = new byte[offset - oOffset]; - Buffer.BlockCopy(data, (int)oOffset, publicRawData, 0, publicRawData.Length); - - - if (privateKeyIncluded) - { - uint privateKeyLength = (keySize * 3) + (keySize / 2); - uint halfKeySize = keySize / 2; - - this.privateRawData = DC.Clip(data, offset, privateKeyLength); - - key.D = DC.Clip(data, offset, keySize); - offset += keySize; - key.DP = DC.Clip(data, offset, halfKeySize); - offset += halfKeySize; - key.DQ = DC.Clip(data, offset, halfKeySize); - offset += halfKeySize; - key.InverseQ = DC.Clip(data, offset, halfKeySize); - offset += halfKeySize; - key.P = DC.Clip(data, offset, halfKeySize); - offset += halfKeySize; - key.Q = DC.Clip(data, offset, halfKeySize); - offset += halfKeySize; - } - - // setup rsa - this.rsa = RSA.Create();// new RSACryptoServiceProvider(); - this.rsa.ImportParameters(key); - - this.signature = DC.Clip(data, offset, length - (offset - oOffset)); - } - - - } - - public UserCertificate(ulong id, string username, DomainCertificate domainCertificate, DateTime issueDate, - DateTime expireDate, HashFunctionType hashFunction = HashFunctionType.SHA1, uint ip = 0, byte[] ip6 = null) - : base(id, issueDate, expireDate, hashFunction) - { - // assign type - var cr = new BinaryList(); - - //id - cr.AddUInt64(id); - - // ip - this.ip = ip; - this.ip6 = ip6; - - cr.AddUInt32(ip); - - - if (ip6?.Length == 16) - cr.AddUInt8Array(ip6); - else - cr.AddUInt8Array(new byte[16]); - - - // dates - this.issueDate = DateTime.UtcNow; - this.expireDate = expireDate; - - cr.AddDateTime(issueDate) - .AddDateTime(expireDate); - - - // domain - this.domainId = domainCertificate.Id; - cr.AddUInt64(domainCertificate.Id); - this.domain = domainCertificate.Domain; - cr.AddUInt8((byte)domainCertificate.Domain.Length) - .AddUInt8Array(Encoding.ASCII.GetBytes(domainCertificate.Domain)); - - - // username - this.username = username; - - cr.AddUInt8((byte)(username.Length)) - .AddUInt8Array(Encoding.ASCII.GetBytes(username)); - - // hash function (SHA1) - cr.AddUInt8((byte)((byte)hashFunction << 4));// (byte)0x10); - - // public key - - rsa = RSA.Create();// new RSACryptoServiceProvider(2048); - rsa.KeySize = 2048; - // write public certificate file - - var key = rsa.ExportParameters(true); - publicRawData = new BinaryList().AddUInt8((byte)key.Exponent.Length) - .AddUInt8Array(key.Exponent) - .AddUInt16((ushort)key.Modulus.Length) - .AddUInt8Array(key.Modulus).ToArray(); - - - // sign it - this.signature = domainCertificate.Sign(publicRawData); - - - // store private info - privateRawData = DC.Merge(key.D, key.DP, key.DQ, key.InverseQ, key.P, key.Q, signature); - - } - - public override bool Save(string filename, bool includePrivate = false) - { - try - { - if (includePrivate) - File.WriteAllBytes(filename, DC.Merge(new byte[] { (byte)CertificateType.DomainPrivate }, publicRawData, signature, privateRawData)); - else - File.WriteAllBytes(filename, DC.Merge(new byte[] { (byte)CertificateType.DomainPublic }, publicRawData, signature)); - - return true; - } - catch - { - return false; - } - } - - public override byte[] Serialize(bool includePrivate = false) - { - if (includePrivate) - return DC.Merge(publicRawData, signature, privateRawData); - else - return DC.Merge(publicRawData, signature); - } -} diff --git a/Esiur/Security/Membership/IDomain.cs b/Esiur/Security/Membership/IDomain.cs deleted file mode 100644 index d0ce488..0000000 --- a/Esiur/Security/Membership/IDomain.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* - -Copyright (c) 2017 Ahmed Kh. Zamil - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -*/ - -using Esiur.Resource; -using Esiur.Security.Authority; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Esiur.Authority; - -public interface IDomain : IResource -{ - string Name { get; } - DomainCertificate Certificate { get; } -} diff --git a/Esiur/Security/Membership/IMembership.cs b/Esiur/Security/Membership/IMembership.cs index 2ab51d6..940b52d 100644 --- a/Esiur/Security/Membership/IMembership.cs +++ b/Esiur/Security/Membership/IMembership.cs @@ -44,10 +44,10 @@ public interface IMembership AsyncReply GetPassword(string username, string domain); AsyncReply GetToken(ulong tokenIndex, string domain); - AsyncReply Authorize(Session session); - AsyncReply AuthorizePlain(Session session, uint reference, object value); - AsyncReply AuthorizeHashed(Session session, uint reference, EpAuthPacketHashAlgorithm algorithm, byte[] value); - AsyncReply AuthorizeEncrypted(Session session, uint reference, EpAuthPacketPublicKeyAlgorithm algorithm, byte[] value); + //AsyncReply Authorize(Session session); + //AsyncReply AuthorizePlain(Session session, uint reference, object value); + //AsyncReply AuthorizeHashed(Session session, uint reference, EpAuthPacketHashAlgorithm algorithm, byte[] value); + //AsyncReply AuthorizeEncrypted(Session session, uint reference, EpAuthPacketPublicKeyAlgorithm algorithm, byte[] value); AsyncReply Login(Session session); AsyncReply Logout(Session session); diff --git a/Tests/Distribution/Program.cs b/Tests/Distribution/Program.cs index ecb9938..f019b16 100644 --- a/Tests/Distribution/Program.cs +++ b/Tests/Distribution/Program.cs @@ -127,25 +127,27 @@ class Program Console.WriteLine(ska.ToHex()); Console.WriteLine(skb.ToHex()); - // Simple membership provider - var membership = new SimpleMembership() { GuestsAllowed = true }; + //// Simple membership provider + //var membership = new SimpleMembership() { GuestsAllowed = true }; - membership.AddUser("user", "123456", new SimpleMembership.QuestionAnswer[0]); - membership.AddUser("admin", "admin", new SimpleMembership.QuestionAnswer[] - { - new SimpleMembership.QuestionAnswer() - { - Question = "What is 5+5", - Answer = 10, - Hashed = true, - } - }); + //membership.AddUser("user", "123456", new SimpleMembership.QuestionAnswer[0]); + //membership.AddUser("admin", "admin", new SimpleMembership.QuestionAnswer[] + //{ + // new SimpleMembership.QuestionAnswer() + // { + // Question = "What is 5+5", + // Answer = 10, + // Hashed = true, + // } + //}); var wh = new Warehouse(); // Create stores to keep objects. var system = await wh.Put("sys", new MemoryStore()); - var server = await wh.Put("sys/server", new EpServer() { Membership = membership }); + var server = await wh.Put("sys/server", new EpServer() { + // Membership = membership + }); var web = await wh.Put("sys/web", new HttpServer() { Port = 8088 });