diff --git a/src/SecureElementReader.App/DependencyInjection/ServicesBootstrapper.cs b/src/SecureElementReader.App/DependencyInjection/ServicesBootstrapper.cs index a0db49b..220ae2b 100644 --- a/src/SecureElementReader.App/DependencyInjection/ServicesBootstrapper.cs +++ b/src/SecureElementReader.App/DependencyInjection/ServicesBootstrapper.cs @@ -14,6 +14,7 @@ using PCSC.Iso7816; using PCSC; using PCSC.Monitoring; +using SecureElementReader.App.Proxies; namespace SecureElementReader.App.DependencyInjection { @@ -48,6 +49,7 @@ public static void RegisterServices(IMutableDependencyResolver services, IReadon )); services.RegisterLazySingleton(() => new MainWindowProvider()); services.RegisterLazySingleton(() => new ApduCommandService()); + services.RegisterLazySingleton(() => new TaxCoreApiProxy()); services.RegisterLazySingleton(() => new CardReaderService( resolver.GetRequiredService(), resolver.GetRequiredService(), @@ -68,7 +70,8 @@ public static void RegisterServices(IMutableDependencyResolver services, IReadon resolver.GetRequiredService(), resolver.GetRequiredService(), resolver.GetRequiredService(), - resolver.GetRequiredService() + resolver.GetRequiredService(), + resolver.GetRequiredService() )); services.RegisterLazySingleton(() => new ApplicationCloser()); diff --git a/src/SecureElementReader.App/Enums/ApduInstructions.cs b/src/SecureElementReader.App/Enums/ApduInstructions.cs index 64701cd..561d568 100644 --- a/src/SecureElementReader.App/Enums/ApduInstructions.cs +++ b/src/SecureElementReader.App/Enums/ApduInstructions.cs @@ -9,6 +9,18 @@ namespace SecureElementReader.App.Enums public enum ApduInstructions : byte { ExportCert = 0x04, - PinVerify = 0x11 + PinVerify = 0x11, + + /// + /// EXPORT INTERNAL DATA – exports encrypted Internal Data structure + /// + /// Fiscalization + ExportInternalData = 0x12, + + /// + /// AMOUNT STATUS – 16 bytes long data structure ( 8 bytes for sum SALE and REFUND, and 8 bytes for MAXIMAL sum amount ) + /// + /// Fiscalization + AmountStatus = 0x14, } } diff --git a/src/SecureElementReader.App/Interfaces/IApduCommandService.cs b/src/SecureElementReader.App/Interfaces/IApduCommandService.cs index ce00633..98eb791 100644 --- a/src/SecureElementReader.App/Interfaces/IApduCommandService.cs +++ b/src/SecureElementReader.App/Interfaces/IApduCommandService.cs @@ -10,5 +10,9 @@ public interface IApduCommandService CommandApdu GetPKICert(); CommandApdu VerifyPkiPin(byte[] pin); CommandApdu VerifySEPin(byte[] pin); + + CommandApdu AmountStatus(); + + CommandApdu GetExportInternalData(); } } diff --git a/src/SecureElementReader.App/Interfaces/ICardReaderService.cs b/src/SecureElementReader.App/Interfaces/ICardReaderService.cs index 7cc9719..1bfc5f5 100644 --- a/src/SecureElementReader.App/Interfaces/ICardReaderService.cs +++ b/src/SecureElementReader.App/Interfaces/ICardReaderService.cs @@ -8,5 +8,9 @@ public interface ICardReaderService IEnumerable LoadReaders(); CertDetailsModel GetCertDetails(); VerifyPinModel VerifyPin(string pin); + byte[] GetInternalData(); + + byte[] GetAmountStatus(); + } } diff --git a/src/SecureElementReader.App/Interfaces/ITaxCoreApiProxy.cs b/src/SecureElementReader.App/Interfaces/ITaxCoreApiProxy.cs new file mode 100644 index 0000000..12893e0 --- /dev/null +++ b/src/SecureElementReader.App/Interfaces/ITaxCoreApiProxy.cs @@ -0,0 +1,14 @@ +using SecureElementReader.App.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SecureElementReader.App.Interfaces +{ + public interface ITaxCoreApiProxy + { + bool SendInternalData(SecureElementAuditRequest request, string commonName, string apiUrl); + } +} diff --git a/src/SecureElementReader.App/Models/SecureElementAuditRequest.cs b/src/SecureElementReader.App/Models/SecureElementAuditRequest.cs new file mode 100644 index 0000000..755d6e9 --- /dev/null +++ b/src/SecureElementReader.App/Models/SecureElementAuditRequest.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SecureElementReader.App.Models +{ + public class SecureElementAuditRequest + { + public byte[] AuditData { get; set; } + public byte[] LimitData { get; set; } + } +} diff --git a/src/SecureElementReader.App/Properties/Resources.Designer.cs b/src/SecureElementReader.App/Properties/Resources.Designer.cs index 1adb8f8..541e686 100644 --- a/src/SecureElementReader.App/Properties/Resources.Designer.cs +++ b/src/SecureElementReader.App/Properties/Resources.Designer.cs @@ -321,6 +321,15 @@ public static string NoCardReadesFounded { } } + /// + /// Looks up a localized string similar to Options. + /// + public static string Options { + get { + return ResourceManager.GetString("Options", resourceCulture); + } + } + /// /// Looks up a localized string similar to Taxpayer:. /// @@ -573,6 +582,15 @@ public static string Subject { } } + /// + /// Looks up a localized string similar to Submit Internal Data. + /// + public static string SubmitInternalData { + get { + return ResourceManager.GetString("SubmitInternalData", resourceCulture); + } + } + /// /// Looks up a localized string similar to Support:. /// diff --git a/src/SecureElementReader.App/Properties/Resources.resx b/src/SecureElementReader.App/Properties/Resources.resx index 9599df7..cb5ae49 100644 --- a/src/SecureElementReader.App/Properties/Resources.resx +++ b/src/SecureElementReader.App/Properties/Resources.resx @@ -204,6 +204,9 @@ No card readers found! + + Options + Taxpayer: @@ -288,6 +291,9 @@ Subject: + + Submit Internal Data + Support: diff --git a/src/SecureElementReader.App/Proxies/TaxCoreApiProxy.cs b/src/SecureElementReader.App/Proxies/TaxCoreApiProxy.cs new file mode 100644 index 0000000..5937b99 --- /dev/null +++ b/src/SecureElementReader.App/Proxies/TaxCoreApiProxy.cs @@ -0,0 +1,82 @@ +using SecureElementReader.App.Interfaces; +using SecureElementReader.App.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace SecureElementReader.App.Proxies +{ + public class TaxCoreApiProxy : ITaxCoreApiProxy + { + + private static string? CommonName; + private static string? ApiUrl; + + public bool SendInternalData(SecureElementAuditRequest request, string commonName, string apiUrl) + { + + CommonName = commonName; + ApiUrl = apiUrl; + + try + { + var httpContent = new StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json"); + HttpClient client; + HttpClientHandler handler; + + GetClientAndHandler(out handler, out client); + + var response = client.PostAsync($"/api/SecureElements/Audit", httpContent).Result; + + if (response.StatusCode == HttpStatusCode.OK) + { + var jsonString = response.Content.ReadAsStringAsync(); + jsonString.Wait(); + var invoiceResponse = jsonString.Result; + return true; + } + else + { + return false; + } + } + catch (Exception) + { + + return false; + } + } + + static void GetClientAndHandler(out HttpClientHandler handler, out HttpClient client) + { + handler = CreateWebRequestHandler(); + client = new HttpClient(handler); + + client.BaseAddress = new Uri(ApiUrl); + client.DefaultRequestHeaders.Accept.Clear(); + } + + static HttpClientHandler CreateWebRequestHandler() + { + var handler = new HttpClientHandler(); + var cert = GetClientCertificate(); + + handler.ClientCertificateOptions = ClientCertificateOption.Manual; + handler.ClientCertificates.Add(cert); + return handler; + } + + static X509Certificate2 GetClientCertificate() + { + var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); + + store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); + return store.Certificates.Find(X509FindType.FindBySubjectName, CommonName, false)[0]; + } + } +} diff --git a/src/SecureElementReader.App/SecureElementReader.App.csproj b/src/SecureElementReader.App/SecureElementReader.App.csproj index 5ffebfe..4cce35b 100644 --- a/src/SecureElementReader.App/SecureElementReader.App.csproj +++ b/src/SecureElementReader.App/SecureElementReader.App.csproj @@ -35,6 +35,7 @@ + diff --git a/src/SecureElementReader.App/Services/ApduCommandService.cs b/src/SecureElementReader.App/Services/ApduCommandService.cs index 2211f7f..df6bdc2 100644 --- a/src/SecureElementReader.App/Services/ApduCommandService.cs +++ b/src/SecureElementReader.App/Services/ApduCommandService.cs @@ -77,5 +77,31 @@ public CommandApdu VerifySEPin(byte[] pin) Data = pin }; } + + public CommandApdu AmountStatus() + { + return new CommandApdu(IsoCase.Case2Short, SCardProtocol.T1) + { + CLA = (byte)ApduClasses.SelectCommand, + INS = (byte)ApduInstructions.AmountStatus, + P1 = (byte)ApduP1.Default, + P2 = (byte)ApduP2.Default + }; + } + + /// + /// Exports encrypted Internal Data structure only. + /// + /// + public CommandApdu GetExportInternalData() + { + return new CommandApdu(IsoCase.Case2Extended, SCardProtocol.T1) + { + CLA = (byte)ApduClasses.SelectCommand, + INS = (byte)ApduInstructions.ExportInternalData, + P1 = (byte)ApduP1.Default, + P2 = (byte)ApduP2.Default + }; + } } } diff --git a/src/SecureElementReader.App/Services/CardReaderService.cs b/src/SecureElementReader.App/Services/CardReaderService.cs index b231863..fb298d6 100644 --- a/src/SecureElementReader.App/Services/CardReaderService.cs +++ b/src/SecureElementReader.App/Services/CardReaderService.cs @@ -25,25 +25,25 @@ public CardReaderService(IApduCommandService apduCommandService, ILogger logger, { this.apduCommandService = apduCommandService; this.logger = logger; - this.contextFactory = contextFactory; + this.contextFactory = contextFactory; } public VerifyPinModel VerifyPin(string pin) - { + { var verifyPinModel = new VerifyPinModel(); CheckPkiPin(pin, reader, verifyPinModel); CheckSePin(pin, reader, verifyPinModel); return verifyPinModel; - } + } private VerifyPinModel CheckPkiPin(string pin, IIsoReader reader, VerifyPinModel returnModel) { var response = reader.Transmit(apduCommandService.SelectPKIApp()); if (response.SW1 == 0x90) { - response = reader.Transmit(apduCommandService.VerifyPkiPin(StringToByteArray(ToHax(pin)))); + response = reader.Transmit(apduCommandService.VerifyPkiPin(StringToByteArray(ToHax(pin)))); if (response.SW1 == 0x90) { returnModel.PkiPinSuccess = true; @@ -52,8 +52,8 @@ private VerifyPinModel CheckPkiPin(string pin, IIsoReader reader, VerifyPinModel { returnModel.PKIAppletLocked = true; } - else if(response.SW1 == 0x63) - { + else if (response.SW1 == 0x63) + { returnModel.PkiTrysLeft = response.SW2; } else @@ -74,7 +74,7 @@ private VerifyPinModel CheckSePin(string pin, IIsoReader reader, VerifyPinModel var response = reader.Transmit(apduCommandService.SelectSEApp()); if (response.SW1 == 0x90) { - response = reader.Transmit(apduCommandService.VerifySEPin(ConvertPinToByteArray(pin))); + response = reader.Transmit(apduCommandService.VerifySEPin(ConvertPinToByteArray(pin))); if (response.SW1 == 0x90) { returnModel.SePinSuccess = true; @@ -97,7 +97,7 @@ private VerifyPinModel CheckSePin(string pin, IIsoReader reader, VerifyPinModel } public CertDetailsModel GetCertDetails() - { + { var certDetailsModel = new CertDetailsModel(); GetPkiDetails(reader, certDetailsModel); @@ -128,10 +128,10 @@ private CertDetailsModel GetSeDetails(IIsoReader reader, CertDetailsModel model) if (!model.ReadPkiSuccess) { PopulateModel(c, model); - } + } model.SEVerify = c.Verify(); VerifyChain(c, model, false); - model.SEReadSuccess = true; + model.SEReadSuccess = true; } else { @@ -224,7 +224,7 @@ private void PopulateModel(Certificate c, CertDetailsModel model) model.ExpiryDate = c.ExpiryDate; model.ApiUrl = c.ExtractTaxCoreApiUrl(); model.Tin = c.ExtractTIN(); - model.UniqueIdentifier = c.UniqueIdentifier; + model.UniqueIdentifier = c.UniqueIdentifier; model.IssuerName = c.IssuerName; } @@ -260,7 +260,7 @@ public IEnumerable LoadReaders() szReaders = c.GetReaders(); reader = new IsoReader(c); - + foreach (var sZReader in szReaders) { reader.Connect(sZReader, SCardShareMode.Shared, SCardProtocol.T1); @@ -272,13 +272,13 @@ public IEnumerable LoadReaders() } catch (Exception ex) { - return szReaders ?? Array.Empty(); + return szReaders ?? Array.Empty(); } } private byte[] ConvertPinToByteArray(string pin) { - return pin.Select(c => byte.Parse(c.ToString())).ToArray(); + return pin.Select(c => byte.Parse(c.ToString())).ToArray(); } private static byte[] StringToByteArray(string hex) @@ -286,15 +286,15 @@ private static byte[] StringToByteArray(string hex) return Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) - .ToArray(); + .ToArray(); } private static string ToHax(string value) { return string.Join("", value.Select(c => String.Format("{0:X2}", Convert.ToInt32(c)))); - } - + } + private void VerifyChain(Certificate cert, CertDetailsModel model, bool IsPKI) { using (X509Chain chain = new X509Chain()) @@ -342,10 +342,53 @@ private void VerifyChain(Certificate cert, CertDetailsModel model, bool IsPKI) model.SEVerificationInfo += Environment.NewLine; } model.SEVerificationInfo += "------------------------------"; - } + } } } } } + + public byte[] GetInternalData() + { + var response = reader.Transmit(apduCommandService.SelectSEApp()); + if (response.SW1 == 0x90) + { + response = reader.Transmit(apduCommandService.GetExportInternalData()); + if (response.SW1 == 0x90) + { + return response.GetData(); + } + else + { + return null; + } + } + else + { + return null; + } + } + + + public byte[] GetAmountStatus() + { + var response = reader.Transmit(apduCommandService.SelectSEApp()); + if (response.SW1 == 0x90) + { + response = reader.Transmit(apduCommandService.AmountStatus()); + if (response.SW1 == 0x90) + { + return response.GetData(); + } + else if (response.SW1 == 0x63 && response.SW2 == 0x10) ; + { + return null; + } + } + else + { + return null; + } + } } } diff --git a/src/SecureElementReader.App/ViewModels/AboutDialogViewModel.cs b/src/SecureElementReader.App/ViewModels/AboutDialogViewModel.cs index 3f7cf91..f9c76a3 100644 --- a/src/SecureElementReader.App/ViewModels/AboutDialogViewModel.cs +++ b/src/SecureElementReader.App/ViewModels/AboutDialogViewModel.cs @@ -2,17 +2,8 @@ using ReactiveUI; using SecureElementReader.App.ViewModels.Implementations.Dialogs; using System.Diagnostics; -using System.Runtime.InteropServices; -using Avalonia; -using Avalonia.Interactivity; -using Avalonia.Controls; -using Avalonia.Input; -using MessageBox.Avalonia.Extensions; using Microsoft.Extensions.PlatformAbstractions; using System.Windows.Input; -using System; -using SecureElementReader.App.ViewModels.Implementations.Dialogs; -using Microsoft.Extensions.PlatformAbstractions; namespace SecureElementReader.App.ViewModels { @@ -36,7 +27,6 @@ public AboutDialogViewModel() private void OnButtonClick() { Process.Start(new ProcessStartInfo("https://github.com/Data-Tech-International/Secure-Element-Reader") { UseShellExecute = true }); - } } } diff --git a/src/SecureElementReader.App/ViewModels/MainWindowViewModel.cs b/src/SecureElementReader.App/ViewModels/MainWindowViewModel.cs index 7eea6af..3822166 100644 --- a/src/SecureElementReader.App/ViewModels/MainWindowViewModel.cs +++ b/src/SecureElementReader.App/ViewModels/MainWindowViewModel.cs @@ -19,6 +19,8 @@ using MessageBox.Avalonia; using MessageBox.Avalonia.DTO; using SecureElementReader.App.Views; +using SecureElementReader.App.Models; +using System.Runtime.InteropServices; namespace SecureElementReader.App.ViewModels { @@ -34,13 +36,15 @@ public class MainWindowViewModel : ViewModelBase, IMainWindowViewModel private readonly ICardReaderService cardReaderService; private readonly IApplicationDispatcher applicationDispatcher; private readonly IMainWindowProvider mainWindowProvider; + private readonly ITaxCoreApiProxy taxCoreApiProxy; public string WelcomeMessage => Properties.Resources.Welcome; public IMenuViewModel MenuViewModel { get; } public ICertDetailsViewModel CertDetailsViewModel { get; } - public ITopLanguageViewModel LanguageViewModel { get; } - + public ITopLanguageViewModel LanguageViewModel { get; } + + [Reactive] public string CardReaderName { get; set; } @@ -57,12 +61,16 @@ public MainWindowViewModel(IDialogService dialogService, ITopLanguageViewModel languageViewModel, IMonitorFactory monitorFactory, IApplicationDispatcher applicationDispatcher, - IMainWindowProvider mainWindowProvider) + IMainWindowProvider mainWindowProvider, + ITaxCoreApiProxy taxCoreApiProxy) { this.dialogService = dialogService; this.cardReaderService = cardReaderService; this.applicationDispatcher = applicationDispatcher; this.mainWindowProvider = mainWindowProvider; + this.taxCoreApiProxy = taxCoreApiProxy; + + CertDetailsCommand = ReactiveCommand.Create(GetCertDetails); VerifyPinCommand = ReactiveCommand.CreateFromTask(ShowVerifyPinDialog); @@ -124,7 +132,8 @@ private void OnEvent(MonitorEvent obj) else if (string.Equals(obj.GetType().Name, "CardInserted")) { GetReaders(); - GetCertDetails(); + GetCertDetails(); + } } @@ -159,7 +168,13 @@ private void GetCertDetails() else { CertDetailsViewModel.CertDetailsModel = details; - CertDetailsViewModel.SetVerifyFields(); + CertDetailsViewModel.SetVerifyFields(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + SubmitInternalData(); + } + } applicationDispatcher.Dispatch(() => HideLoadingOverlay()); @@ -217,5 +232,25 @@ protected virtual void Dispose(bool disposing) _disposables.Dispose(); } + public void SubmitInternalData() + { + var internalData = cardReaderService.GetInternalData(); + var amountData = cardReaderService.GetAmountStatus(); + if (internalData != null && amountData != null) + { + var request = new SecureElementAuditRequest + { + AuditData = internalData, + LimitData = amountData, + }; + + var response = taxCoreApiProxy.SendInternalData(request, CertDetailsViewModel.CertDetailsModel.CommonName, CertDetailsViewModel.CertDetailsModel.ApiUrl); + + } + else + { + applicationDispatcher.DispatchAsync(() => ShowMessage(new List { "error" })); + } + } } } diff --git a/src/SecureElementReader.App/Views/CertDetailsView.axaml b/src/SecureElementReader.App/Views/CertDetailsView.axaml index 6d49983..f0ad18f 100644 --- a/src/SecureElementReader.App/Views/CertDetailsView.axaml +++ b/src/SecureElementReader.App/Views/CertDetailsView.axaml @@ -5,9 +5,9 @@ xmlns:model="using: SecureElementReader.App.Models" xmlns:vm="clr-namespace:SecureElementReader.App.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - xmlns:p="clr-namespace:SecureElementReader.App.Properties" + xmlns:p="clr-namespace:SecureElementReader.App.Properties" x:Class="SecureElementReader.App.Views.CertDetailsView" - FontFamily="Highway Sans Pro"> + FontFamily="Highway Sans Pro"> diff --git a/src/SecureElementReader.App/Views/MainWindow.axaml b/src/SecureElementReader.App/Views/MainWindow.axaml index 524f829..00c3d77 100644 --- a/src/SecureElementReader.App/Views/MainWindow.axaml +++ b/src/SecureElementReader.App/Views/MainWindow.axaml @@ -4,18 +4,18 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:main="clr-namespace:SecureElementReader.App.Views" - xmlns:local="clr-namespace:SecureElementReader.App.ViewModels" + xmlns:local="clr-namespace:SecureElementReader.App.ViewModels" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="400" - xmlns:p="clr-namespace:SecureElementReader.App.Properties" + xmlns:p="clr-namespace:SecureElementReader.App.Properties" x:Class="SecureElementReader.App.Views.MainWindow" Icon="/Assets/taxcore.png" Title="Secure Element Reader App" - ExtendClientAreaToDecorationsHint="True" - Background="#e8e8e8" + ExtendClientAreaToDecorationsHint="True" + Background="#e8e8e8" Width="830" Height="605" - MaxWidth="830" MaxHeight="605" + MaxWidth="830" MaxHeight="605" WindowStartupLocation="CenterScreen" - FontFamily="Highway Sans Pro"> + FontFamily="Highway Sans Pro">