using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Numerics; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using ArchestrAServices.Common; namespace Asb.Base.V2; public class SystemAuthenticationConnectionBase { private static readonly RNGCryptoServiceProvider random = new RNGCryptoServiceProvider(); private static readonly byte[] passwordsalt = Encoding.ASCII.GetBytes("ArchestrAService"); protected static string AsbAuthenticationVersion = "V2"; protected static readonly object MessageNumberLock = new object(); private readonly List outOfSyncMessageNumbers = new List(); private ulong highestMessageNumberReceived; private byte[] cryptoKey; private byte[] encryptionKey; protected SysAuthParameters SolutionParameters; protected byte[] PrivateKey; protected byte[] LocalPublicKey; protected byte[] RemotePublicKey; protected ulong NextMessageNumber = 1uL; internal static ISolutionParameters TestParameters { private get; set; } internal byte[] CryptoKey => cryptoKey ?? (cryptoKey = CalculateCryptoKey()); internal byte[] EncryptionKey => encryptionKey ?? (encryptionKey = CalculateEncryptionKey()); private byte[] SolutionPassphrase { get { byte[] result = null; if (SolutionParameters.AsbSolutionValid) { if (!string.IsNullOrEmpty(SolutionParameters.DhPassphrase)) { result = Encoding.UTF8.GetBytes(SolutionParameters.DhPassphrase); } if (SolutionParameters.DhCertificate != null && SolutionParameters.DhCertificate.Length != 0) { result = new X509Certificate(SolutionParameters.DhCertificate).GetPublicKey(); } } return result; } } public Guid ConnectionId { get; set; } = Guid.Empty; public ConnectionValidator ConnectionValidator { get; protected set; } public bool SecureSessionEstablished { get; protected set; } public string ReasonSecureSessionNotEstablished { get; protected set; } public string DhPassphrase { get { return SolutionParameters.DhPassphrase; } set { SolutionParameters.DhPassphrase = value; } } public string DhHashAlgorithm => SolutionParameters.HashAlgorithm; public string DhAsbSolutionName => SolutionParameters.AsbSolutionName; public SystemAuthenticationConnectionBase(string asbSolutionName = null) { if (TestParameters != null) { SolutionParameters = new SysAuthParameters(asbSolutionName, TestParameters); } else { SolutionParameters = new SysAuthParameters(asbSolutionName); } ReasonSecureSessionNotEstablished = "Constructed"; Reset(); } protected void Reset() { SecureSessionEstablished = false; ReasonSecureSessionNotEstablished = "Reset"; ConnectionId = Guid.NewGuid(); PrivateKey = GetPrivateKey(SolutionParameters.KeySize); LocalPublicKey = CalculatePublicKey(PrivateKey); cryptoKey = null; SecureSessionEstablished = false; } private byte[] GetPrivateKey(int length) { byte[] array = null; if (length > 0) { BigInteger bigInteger = SolutionParameters.DhP - new BigInteger(1); BigInteger bigInteger2 = new BigInteger(0); while (bigInteger2 >= bigInteger || bigInteger2 <= 0L) { array = new byte[length / 8]; random.GetBytes(array); bigInteger2 = new BigInteger(array); } } return array; } protected byte[] CalculatePublicKey(byte[] privateKey) { BigInteger exponent = new BigInteger(privateKey); BigInteger dhG = SolutionParameters.DhG; BigInteger dhP = SolutionParameters.DhP; return BigInteger.ModPow(dhG, exponent, dhP).ToByteArray(); } protected byte[] CalculateConnectionKey(byte[] remotePublicKey, byte[] localPrivateKey) { BigInteger value = new BigInteger(remotePublicKey); BigInteger exponent = new BigInteger(localPrivateKey); BigInteger dhP = SolutionParameters.DhP; return BigInteger.ModPow(value, exponent, dhP).ToByteArray(); } private HMAC NewSolutionHmac(bool forceHmac) { HMAC result; switch (DhHashAlgorithm.ToLower()) { case "md5": ServiceTrace.LogVerbose("Solution HMAC is MD5"); result = new HMACMD5(CryptoKey); break; case "sha1": ServiceTrace.LogVerbose("Solution HMAC is SHA1"); result = new HMACSHA1(CryptoKey); break; case "sha512": ServiceTrace.LogVerbose("Solution HMAC is SHA512"); result = new HMACSHA512(CryptoKey); break; default: ServiceTrace.LogVerbose("Solution HMAC is NONE"); result = null; if (forceHmac) { result = new HMACSHA1(CryptoKey); } break; } return result; } public bool ValidRequest(ConnectedRequest request, bool forceHmac) { bool flag = false; if (request == null) { ReasonSecureSessionNotEstablished = "ValidRequest: The message is null, cannot validate"; return false; } if (request.ConnectionValidator == null) { ReasonSecureSessionNotEstablished = "ValidRequest: The message ConnectionValidator field is null, cannot validate"; return false; } ConnectionValidator connectionValidator = request.ConnectionValidator; byte[] messageAuthenticationCode = connectionValidator.MessageAuthenticationCode; byte[] signatureInitializationVector = connectionValidator.SignatureInitializationVector; byte[] array = null; using (HMAC hMAC = NewSolutionHmac(forceHmac)) { if (hMAC != null) { connectionValidator.MessageAuthenticationCode = new byte[0]; connectionValidator.SignatureInitializationVector = new byte[0]; byte[] bytes = Encoding.UTF8.GetBytes(request.ToXml()); connectionValidator.MessageAuthenticationCode = messageAuthenticationCode; connectionValidator.SignatureInitializationVector = signatureInitializationVector; byte[] array2 = hMAC.ComputeHash(bytes); byte[] array3 = Decypher(messageAuthenticationCode, signatureInitializationVector); bool flag2 = false; if (array3 != null && array2.Length == array3.Length) { flag2 = true; for (int i = 0; i < array2.Length; i++) { if (array2[i] != array3[i]) { flag2 = false; break; } } } array = ((!flag2) ? new byte[0] : messageAuthenticationCode); } } if (array != null) { if (messageAuthenticationCode != null && array.Length == messageAuthenticationCode.Length) { flag = true; for (int j = 0; j < messageAuthenticationCode.Length; j++) { if (messageAuthenticationCode[j] != array[j]) { if (string.IsNullOrEmpty(ReasonSecureSessionNotEstablished)) { ReasonSecureSessionNotEstablished = string.Format(CultureInfo.CurrentCulture, "ValidRequest: Received message hmac correct length {0} but differs at byte {1}, cannot validate", new object[2] { messageAuthenticationCode.Length, j }); } flag = false; break; } } } else if (messageAuthenticationCode == null) { if (string.IsNullOrEmpty(ReasonSecureSessionNotEstablished)) { ReasonSecureSessionNotEstablished = "ValidRequest: Received message has null hmac, cannot validate"; } } else if (string.IsNullOrEmpty(ReasonSecureSessionNotEstablished)) { ReasonSecureSessionNotEstablished = string.Format(CultureInfo.CurrentCulture, "ValidRequest: Received message hmac wrong length, cannot validate (received {0}, computed {1})", new object[2] { messageAuthenticationCode.Length, array.Length }); } } else { flag = true; } if (flag) { lock (this) { if (connectionValidator.MessageNumber <= highestMessageNumberReceived) { ReasonSecureSessionNotEstablished = string.Format(CultureInfo.CurrentCulture, "ValidRequest: Received message out of sequence, cannot validate (current {0}, highest {1})", new object[2] { connectionValidator.MessageNumber, highestMessageNumberReceived }); flag = false; } else if (outOfSyncMessageNumbers.Contains(connectionValidator.MessageNumber)) { ReasonSecureSessionNotEstablished = string.Format(CultureInfo.CurrentCulture, "ValidRequest: Received message received late, cannot validate (current {0})", new object[1] { connectionValidator.MessageNumber }); flag = false; } else if (connectionValidator.MessageNumber == highestMessageNumberReceived + 1) { highestMessageNumberReceived = connectionValidator.MessageNumber; } else { outOfSyncMessageNumbers.Add(connectionValidator.MessageNumber); } outOfSyncMessageNumbers.Sort(); foreach (ulong outOfSyncMessageNumber in outOfSyncMessageNumbers) { if (outOfSyncMessageNumber == highestMessageNumberReceived + 1) { highestMessageNumberReceived = outOfSyncMessageNumber; } } List list = new List(); foreach (ulong outOfSyncMessageNumber2 in outOfSyncMessageNumbers) { if (outOfSyncMessageNumber2 <= highestMessageNumberReceived) { list.Add(outOfSyncMessageNumber2); } } foreach (ulong item in list) { outOfSyncMessageNumbers.Remove(item); } } } return flag; } public bool ValidResponse(ConnectedResponse response, bool forceHmac = false) { if (response != null && response.Result.Success) { return ValidRequest(response, forceHmac); } return false; } protected byte[] ReCalculateAuthenticationData(byte[] leftPart, byte[] rightPart, byte[] initializationVector) { List list = new List(); if (leftPart != null) { list.AddRange(leftPart); } if (rightPart != null) { list.AddRange(rightPart); } return ReEncypher(list.ToArray(), initializationVector); } protected byte[] CalculateAuthenticationData(byte[] leftPart, byte[] rightPart, out byte[] initializationVector) { List list = new List(); if (leftPart != null) { list.AddRange(leftPart); } if (rightPart != null) { list.AddRange(rightPart); } return EncypherWithNewInitializationVector(list.ToArray(), out initializationVector); } public void Sign(ConnectedRequest request, bool forceHmac) { if (request == null) { return; } lock (MessageNumberLock) { request.ConnectionValidator = new ConnectionValidator { ConnectionId = ConnectionId, MessageNumber = NextMessageNumber++, MessageAuthenticationCode = new byte[0], SignatureInitializationVector = new byte[0] }; using HMAC hMAC = NewSolutionHmac(forceHmac); if (hMAC != null) { string text = request.ToXml(); ServiceTrace.LogVerbose("Signing XML message is {0}", text); byte[] bytes = Encoding.UTF8.GetBytes(text); request.ConnectionValidator.MessageAuthenticationCode = EncypherWithNewInitializationVector(hMAC.ComputeHash(bytes), out var initializationVector); request.ConnectionValidator.SignatureInitializationVector = initializationVector; } } } public byte[] Encypher(byte[] clearData, byte[] initializationVector) { byte[] result = null; if (clearData != null) { using AesManaged aesManaged = new AesManaged(); using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(CryptoKey.ToBase64(), passwordsalt); aesManaged.Key = rfc2898DeriveBytes.GetBytes(16); aesManaged.IV = initializationVector; using ICryptoTransform transform = aesManaged.CreateEncryptor(); using MemoryStream memoryStream = new MemoryStream(); using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write)) { cryptoStream.Write(clearData, 0, clearData.Length); cryptoStream.Close(); } result = memoryStream.ToArray(); } return result; } public byte[] EncypherWithNewInitializationVector(byte[] clearData, out byte[] initializationVector) { byte[] result = null; initializationVector = null; if (clearData != null) { using AesManaged aesManaged = new AesManaged(); using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(CryptoKey.ToBase64(), passwordsalt); aesManaged.Key = rfc2898DeriveBytes.GetBytes(16); initializationVector = aesManaged.IV; using ICryptoTransform transform = aesManaged.CreateEncryptor(); using MemoryStream memoryStream = new MemoryStream(); using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write)) { cryptoStream.Write(clearData, 0, clearData.Length); cryptoStream.Close(); } result = memoryStream.ToArray(); } return result; } public string Encypher(string clearText, out byte[] initializationVector) { string result = string.Empty; initializationVector = null; if (!string.IsNullOrEmpty(clearText)) { byte[] bytes = Encoding.UTF8.GetBytes(clearText); result = EncypherWithNewInitializationVector(bytes, out initializationVector).ToBase64(); } return result; } public byte[] ReEncypher(byte[] clearData, byte[] initializationVector) { byte[] result = null; if (clearData != null) { using AesManaged aesManaged = new AesManaged(); using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(CryptoKey.ToBase64(), passwordsalt); aesManaged.Key = rfc2898DeriveBytes.GetBytes(16); aesManaged.IV = initializationVector; using ICryptoTransform transform = aesManaged.CreateEncryptor(); using MemoryStream memoryStream = new MemoryStream(); using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write)) { cryptoStream.Write(clearData, 0, clearData.Length); cryptoStream.Close(); } result = memoryStream.ToArray(); } return result; } public string ReEncypher(string clearText, byte[] initializationVector) { string result = string.Empty; if (!string.IsNullOrEmpty(clearText)) { byte[] bytes = Encoding.UTF8.GetBytes(clearText); result = ReEncypher(bytes, initializationVector).ToBase64(); } return result; } public byte[] Decypher(byte[] cypherData, byte[] initializationVector) { byte[] result = null; if (cypherData != null) { using AesManaged aesManaged = new AesManaged(); using Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(CryptoKey.ToBase64(), passwordsalt); aesManaged.Key = rfc2898DeriveBytes.GetBytes(16); aesManaged.IV = initializationVector; try { using ICryptoTransform transform = aesManaged.CreateDecryptor(); using MemoryStream stream = new MemoryStream(cypherData); using CryptoStream cryptoStream = new CryptoStream(stream, transform, CryptoStreamMode.Read); using MemoryStream memoryStream = new MemoryStream(); cryptoStream.CopyTo(memoryStream); result = memoryStream.ToArray(); } catch (Exception ex) { ReasonSecureSessionNotEstablished = "Decypher failed: " + ex.Message; result = null; } } return result; } public string Decypher(string cypherText, byte[] initializationVector) { string result = string.Empty; if (!string.IsNullOrEmpty(cypherText)) { byte[] cypherData = cypherText.FromBase64ToByteArray(); byte[] bytes = Decypher(cypherData, initializationVector); result = Encoding.UTF8.GetString(bytes); } return result; } private byte[] CalculateCryptoKey() { List list = new List(); list.AddRange(CalculateConnectionKey(RemotePublicKey, PrivateKey)); byte[] solutionPassphrase = SolutionPassphrase; if (solutionPassphrase != null) { list.AddRange(solutionPassphrase); } return list.ToArray(); } private byte[] CalculateEncryptionKey() { return new HMACMD5(CryptoKey).ComputeHash(CryptoKey); } }