| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329 |
- using ExtensionMethods;
- using MAX.Models;
- using Microsoft.Extensions.Logging;
- using System;
- using System.IO;
- using System.Net.Sockets;
- using System.Security.Cryptography;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Xml;
- namespace MAX
- {
- public class Client : IDisposable
- {
- private ILogger logger;
- private string host;
- private int port;
- private int vendorId;
- private string serialNumber;
- private int userId;
- private string username;
- private string password;
- private TcpClient connection = null;
- private NetworkStream connectionStream = null;
- private TripleDES des = null;
- private bool disposed = false;
- public Client(ILogger logger, string host, int port, int vendorId, string serialNumber, int userId, string username, string password)
- {
- this.logger = logger;
- this.host = host;
- this.port = port;
- this.vendorId = vendorId;
- this.serialNumber = serialNumber;
- this.userId = userId;
- this.username = username;
- this.password = password;
- ConnectTimeout = 10000;
- ReceiveTimeout = 10000;
- SendTimeout = 10000;
- }
- public void Close()
- {
- Dispose(true);
- }
- public async Task<User> ConnectAsync()
- {
- if (connection != null)
- throw new Exception("Already connected");
- connection = new TcpClient(AddressFamily.InterNetwork);
- connection.ReceiveTimeout = ReceiveTimeout;
- connection.SendTimeout = SendTimeout;
- // Connect to the server
- try
- {
- using (var cancellationSource = new CancellationTokenSource(ConnectTimeout))
- {
- await connection.ConnectAsync(host, port).WithCancellation(cancellationSource.Token).ConfigureAwait(false);
- }
- }
- catch (OperationCanceledException)
- {
- throw new Exception("Connect timeout");
- }
- connectionStream = connection.GetStream();
- // Device authentication
- await WriteMessageAsync(new MessageBuilder()
- .Append("Hi ")
- .Append(serialNumber)
- .Append("|V")
- .Append(vendorId)
- .Append("|123451234512345||||||")).ConfigureAwait(false);
- var response = await ReadMessageAsync().ConfigureAwait(false);
- if (!response.StartsWith("Hi "))
- {
- logger.LogError("Device authentication failed: {0}", response);
- return null;
- }
- // Request server RSA key
- await WriteMessageAsync(new MessageBuilder().Append("PK")).ConfigureAwait(false);
- response = await ReadMessageAsync().ConfigureAwait(false);
- // Key exchange
- des = TripleDES.Create();
- des.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
- await WriteMessageAsync(new MessageBuilder()
- .Append("3D ")
- .Append(EncryptRSA(response, BitConverter.ToString(des.Key).Replace("-", "")))).ConfigureAwait(false);
- response = await ReadMessageAsync().ConfigureAwait(false);
- if (!response.StartsWith("OK"))
- {
- throw new Exception(String.Format("Key exchange failed: {0}", response));
- }
- // User authentication
- await WriteMessageAsync(new MessageBuilder()
- .Append("User ")
- .Append(Encrypt(new StringBuilder()
- .Append(userId)
- .Append("|")
- .Append(username)
- .Append("|")
- .Append(password).ToString()))).ConfigureAwait(false);
- response = Decrypt(await ReadMessageAsync().ConfigureAwait(false));
- if (response.StartsWith("OK"))
- {
- var parts = response.Split('|');
- var user = new User()
- {
- Id = userId,
- Username = username,
- FirstName = parts[4],
- Surname = parts[3],
- Enabled = bool.Parse(parts[6]),
- Level = (User.UserLevel)int.Parse(parts[1]),
- System = int.Parse(parts[2]),
- LastLogin = DateTime.Parse(parts[5])
- };
- if (user.Level == User.UserLevel.CustomUser)
- {
- user.CanPrintOffline = bool.Parse(parts[7]);
- user.OfflinePrintValue = decimal.Parse(parts[8]);
- user.CanPrintOnline = bool.Parse(parts[9]);
- user.OnlinePrintValue = decimal.Parse(parts[10]);
- user.CanReprintOffline = bool.Parse(parts[11]);
- user.OfflineReprintValue = decimal.Parse(parts[12]);
- user.CanReprintOnline = bool.Parse(parts[13]);
- user.OnlineReprintValue = decimal.Parse(parts[14]);
- }
- // Account information
- await WriteMessageAsync(new MessageBuilder().Append("Acc")).ConfigureAwait(false);
- response = Decrypt(await ReadMessageAsync().ConfigureAwait(false));
- if (response.StartsWith("OK"))
- {
- parts = response.Split('|');
- user.Account = new Account()
- {
- Id = int.Parse(parts[1]),
- Name = parts[2],
- Balance = decimal.Parse(parts[3]),
- Status = (Account.AccountStatus)int.Parse(parts[4]),
- Reference = parts[5],
- Warehouse = new Warehouse()
- {
- Id = int.Parse(parts[6]),
- Name = parts[7]
- }
- };
- return user;
- }
- else if (response.StartsWith("ER"))
- {
- logger.LogError("Error retrieving account information: {0}", response);
- return null;
- }
- else
- {
- throw new Exception(String.Format("Invalid account information response: {0}", response));
- }
- }
- else if (response.StartsWith("ER"))
- {
- logger.LogInformation("User authentication failed: {0}", response);
- return null;
- }
- else
- {
- throw new Exception(String.Format("Invalid user information response: {0}", response));
- }
- }
- public int ConnectTimeout { get; set; }
- protected virtual void Dispose(bool disposing)
- {
- if (disposed)
- return;
- disposed = true;
- // No unmanaged resources are disposed so we don't need the full finalisation pattern.
- if (disposing)
- {
- if (des != null)
- {
- des.Dispose();
- des = null;
- }
- if (connectionStream != null)
- {
- connectionStream.Dispose();
- connectionStream = null;
- }
- if (connection != null)
- {
- connection.Dispose();
- connection = null;
- }
- }
- }
- public void Dispose()
- {
- Dispose(true);
- }
- private string Decrypt(string cipherText)
- {
- using (var decryptor = des.CreateDecryptor(des.Key, des.IV))
- {
- return Encoding.UTF8.GetString(Transform(decryptor, Convert.FromBase64String(cipherText)));
- }
- }
- private string Encrypt(string plainText)
- {
- using (var encryptor = des.CreateEncryptor(des.Key, des.IV))
- {
- return Convert.ToBase64String(Transform(encryptor, Encoding.UTF8.GetBytes(plainText)));
- }
- }
- private string EncryptRSA(string publicKey, string plainText)
- {
- RSAParameters parameters = new RSAParameters();
- var xml = new XmlDocument();
- xml.LoadXml(publicKey);
- if (! xml.DocumentElement.Name.Equals("RSAKeyValue"))
- throw new Exception("Invalid RSA key");
- foreach (XmlNode node in xml.DocumentElement.ChildNodes)
- {
- switch (node.Name)
- {
- case "Modulus": parameters.Modulus = Convert.FromBase64String(node.InnerText); break;
- case "Exponent": parameters.Exponent = Convert.FromBase64String(node.InnerText); break;
- case "P": parameters.P = Convert.FromBase64String(node.InnerText); break;
- case "Q": parameters.Q = Convert.FromBase64String(node.InnerText); break;
- case "DP": parameters.DP = Convert.FromBase64String(node.InnerText); break;
- case "DQ": parameters.DQ = Convert.FromBase64String(node.InnerText); break;
- case "InverseQ": parameters.InverseQ = Convert.FromBase64String(node.InnerText); break;
- case "D": parameters.D = Convert.FromBase64String(node.InnerText); break;
- }
- }
- using (var rsa = RSA.Create())
- {
- rsa.ImportParameters(parameters);
- var blockSize = rsa.KeySize / 8 - 42;
- var offset = 0;
- var input = Encoding.UTF32.GetBytes(plainText);
- StringBuilder output = new StringBuilder();
- while (offset < input.Length)
- {
- var length = input.Length - offset;
- if (length > blockSize)
- length = blockSize;
- var block = new byte[length];
- Array.Copy(input, offset, block, 0, length);
- var cipherText = rsa.Encrypt(block, RSAEncryptionPadding.OaepSHA1);
- Array.Reverse(cipherText);
- output.Append(Convert.ToBase64String(cipherText));
- offset += length;
- }
- return output.ToString();
- }
- }
- private async Task<byte[]> ReadBytesAsync(int count)
- {
- int totalBytesRead = 0;
- byte[] buffer = new byte[count];
- while (totalBytesRead < count)
- {
- int bytesRead = await connectionStream.ReadAsync(buffer, totalBytesRead, count - totalBytesRead).ConfigureAwait(false);
- if (bytesRead == 0)
- throw new Exception("Connection closed unexpectedly");
- totalBytesRead += bytesRead;
- }
- return buffer;
- }
- private async Task<string> ReadMessageAsync()
- {
- byte[] buffer = await ReadBytesAsync(2).ConfigureAwait(false);
- int size = buffer[0] * 256 + buffer[1];
- return Encoding.ASCII.GetString(await ReadBytesAsync(size).ConfigureAwait(false));
- }
- public int ReceiveTimeout { get; set; }
- public int SendTimeout { get; set; }
- private byte[] Transform(ICryptoTransform transform, byte[] input)
- {
- using (var memoryStream = new MemoryStream())
- using (var cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Write))
- {
- cryptoStream.Write(input, 0, input.Length);
- cryptoStream.FlushFinalBlock();
- return memoryStream.ToArray();
- }
- }
- private async Task WriteMessageAsync(MessageBuilder message)
- {
- byte[] data = message.GetBytes();
- await connectionStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
- }
- }
- }
|