diff --git a/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Helpers/UrlReservationChecker.cs b/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Helpers/UrlReservationChecker.cs new file mode 100644 index 000000000..21e0ce2d0 --- /dev/null +++ b/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Helpers/UrlReservationChecker.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Security.Principal; +using fiskaltrust.storage.serialization.V0; +using Microsoft.Extensions.Logging; + +namespace fiskaltrust.Middleware.Queue.Test.Launcher.Helpers +{ + /// + /// Verifies that http/https URLs configured for the queues (e.g. retrieved from Helipad) + /// have a matching netsh URL ACL reservation, so hosting does not fail at runtime. + /// + public static class UrlReservationChecker + { + public static void CheckQueueUrlReservations(ftCashBoxConfiguration cashBoxConfiguration, ILogger logger) + { + if (cashBoxConfiguration?.ftQueues == null) + { + return; + } + + // Already elevated: HttpListener can self-register, no need to warn. + if (IsRunningAsAdministrator()) + { + return; + } + + string aclOutput = null; + var checkedPorts = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (var queue in cashBoxConfiguration.ftQueues) + { + if (queue?.Url == null) + { + continue; + } + + foreach (var rawUrl in queue.Url) + { + if (string.IsNullOrWhiteSpace(rawUrl)) + { + continue; + } + + var httpUrl = rawUrl.Replace("rest://", "http://"); + if (!Uri.TryCreate(httpUrl, UriKind.Absolute, out var uri)) + { + continue; + } + + if (!string.Equals(uri.Scheme, "http", StringComparison.OrdinalIgnoreCase) && + !string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var key = uri.Scheme.ToLowerInvariant() + ":" + uri.Port.ToString(CultureInfo.InvariantCulture); + if (!checkedPorts.Add(key)) + { + continue; + } + + aclOutput ??= QueryUrlAcls(); + + if (HasReservationForPort(aclOutput, uri.Scheme, uri.Port)) + { + logger.LogDebug("URL reservation found for {Scheme}://+:{Port}/", uri.Scheme, uri.Port); + continue; + } + + var urlPrefix = $"{uri.Scheme}://+:{uri.Port}{uri.AbsolutePath}"; + if (!urlPrefix.EndsWith("/", StringComparison.Ordinal)) + { + urlPrefix += "/"; + } + + var userName = WindowsIdentity.GetCurrent().Name; + logger.LogWarning( + "No URL reservation found for port {Port} (configured URL: {Url}). Run the following command once as administrator: netsh http add urlacl url={UrlPrefix} user={User}", + uri.Port, rawUrl, urlPrefix, userName); + } + } + } + + private static bool IsRunningAsAdministrator() + { + try + { + var identity = WindowsIdentity.GetCurrent(); + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + catch + { + return false; + } + } + + private static string QueryUrlAcls() + { + try + { + var psi = new ProcessStartInfo("netsh", "http show urlacl") + { + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + }; + using var process = Process.Start(psi); + var output = process.StandardOutput.ReadToEnd(); + process.WaitForExit(5000); + return output ?? string.Empty; + } + catch + { + return string.Empty; + } + } + + private static bool HasReservationForPort(string aclOutput, string scheme, int port) + { + if (string.IsNullOrEmpty(aclOutput)) + { + return false; + } + + var portToken = ":" + port.ToString(CultureInfo.InvariantCulture) + "/"; + foreach (var rawLine in aclOutput.Split('\n')) + { + var line = rawLine.Trim(); + var schemeIdx = line.IndexOf(scheme + "://", StringComparison.OrdinalIgnoreCase); + if (schemeIdx < 0) + { + continue; + } + if (line.IndexOf(portToken, schemeIdx, StringComparison.Ordinal) >= 0) + { + return true; + } + } + return false; + } + } +} diff --git a/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Program.cs b/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Program.cs index 4efc30d31..fb7743791 100644 --- a/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Program.cs +++ b/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Program.cs @@ -40,6 +40,15 @@ public static void Main(string configurationFilePath = "", string serviceFolder serviceFolder = Directory.GetCurrentDirectory(); } + var serviceCollectionForChecker = new ServiceCollection(); + serviceCollectionForChecker.AddStandardLoggers(LogLevel.Debug); + using (var checkerProvider = serviceCollectionForChecker.BuildServiceProvider()) + { + var checkerLogger = checkerProvider.GetRequiredService() + .CreateLogger(typeof(Helpers.UrlReservationChecker).FullName); + Helpers.UrlReservationChecker.CheckQueueUrlReservations(cashBoxConfiguration, checkerLogger); + } + var config = cashBoxConfiguration.ftQueues[0]; config.Configuration.Add("cashboxid", cashBoxConfiguration.ftCashBoxId); @@ -173,4 +182,4 @@ private static void ConfigureEF(PackageConfiguration queue, ServiceCollection se bootStrapper.ConfigureServices(serviceCollection); } } -} +} \ No newline at end of file diff --git a/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Wcf/RestService.cs b/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Wcf/RestService.cs index a919494d0..ba64034eb 100644 --- a/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Wcf/RestService.cs +++ b/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Wcf/RestService.cs @@ -150,7 +150,7 @@ private void TryAddUrlReservation(Uri baseAddress) urlPrefix += "/"; } - if (HasUrlReservation(urlPrefix)) + if (HasUrlReservationForPort(baseAddress.Scheme, baseAddress.Port)) { return; } @@ -159,7 +159,7 @@ private void TryAddUrlReservation(Uri baseAddress) _logger.LogWarning("No URL reservation found for {Url}. Run the following command once as administrator: netsh http add urlacl url={Url} user={User}", urlPrefix, urlPrefix, userName); } - private bool HasUrlReservation(string urlPrefix) + private bool HasUrlReservationForPort(string scheme, int port) { try { @@ -172,7 +172,24 @@ private bool HasUrlReservation(string urlPrefix) var process = Process.Start(psi); var output = process.StandardOutput.ReadToEnd(); process.WaitForExit(5000); - return output.IndexOf(urlPrefix, StringComparison.OrdinalIgnoreCase) >= 0; + + // Consider any reservation on the same scheme and port as sufficient, + // regardless of the path portion of the URL. + var portToken = ":" + port.ToString(System.Globalization.CultureInfo.InvariantCulture) + "/"; + foreach (var rawLine in output.Split('\n')) + { + var line = rawLine.Trim(); + var schemeIdx = line.IndexOf(scheme + "://", StringComparison.OrdinalIgnoreCase); + if (schemeIdx < 0) + { + continue; + } + if (line.IndexOf(portToken, schemeIdx, StringComparison.Ordinal) >= 0) + { + return true; + } + } + return false; } catch { diff --git a/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Wcf/WCFService.cs b/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Wcf/WCFService.cs index e337f3366..e3a2193ab 100644 --- a/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Wcf/WCFService.cs +++ b/queue/test/Manual/fiskaltrust.Middleware.Queue.Test.Launcher/Wcf/WCFService.cs @@ -162,7 +162,7 @@ private void TryAddUrlReservation(Uri baseAddress) urlPrefix += "/"; } - if (HasUrlReservation(urlPrefix)) + if (HasUrlReservationForPort(baseAddress.Scheme, baseAddress.Port)) { return; } @@ -171,7 +171,7 @@ private void TryAddUrlReservation(Uri baseAddress) _logger.LogWarning("No URL reservation found for {Url}. Run the following command once as administrator: netsh http add urlacl url={Url} user={User}", urlPrefix, urlPrefix, userName); } - private bool HasUrlReservation(string urlPrefix) + private bool HasUrlReservationForPort(string scheme, int port) { try { @@ -184,7 +184,24 @@ private bool HasUrlReservation(string urlPrefix) var process = Process.Start(psi); var output = process.StandardOutput.ReadToEnd(); process.WaitForExit(5000); - return output.IndexOf(urlPrefix, StringComparison.OrdinalIgnoreCase) >= 0; + + // Consider any reservation on the same scheme and port as sufficient, + // regardless of the path portion of the URL. + var portToken = ":" + port.ToString(System.Globalization.CultureInfo.InvariantCulture) + "/"; + foreach (var rawLine in output.Split('\n')) + { + var line = rawLine.Trim(); + var schemeIdx = line.IndexOf(scheme + "://", StringComparison.OrdinalIgnoreCase); + if (schemeIdx < 0) + { + continue; + } + if (line.IndexOf(portToken, schemeIdx, StringComparison.Ordinal) >= 0) + { + return true; + } + } + return false; } catch { diff --git a/scu-de/fiskaltrust.Middleware.SCU.DE.sln b/scu-de/fiskaltrust.Middleware.SCU.DE.sln index 858f1ae01..f1683fafb 100644 --- a/scu-de/fiskaltrust.Middleware.SCU.DE.sln +++ b/scu-de/fiskaltrust.Middleware.SCU.DE.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31521.260 +# Visual Studio Version 18 +VisualStudioVersion = 18.6.11822.322 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{2F690A2B-96D1-45AC-BD36-E27D68422352}" EndProject @@ -29,8 +29,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fiskaltrust.Middleware.SCU. EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Helpers", "Helpers", "{860D3016-9A5C-4162-ACC8-B2031B80F697}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fiskaltrust.Middleware.SCU.DE.Swissbit", "src\fiskaltrust.Middleware.SCU.DE.Swissbit\fiskaltrust.Middleware.SCU.DE.Swissbit.csproj", "{13BBFBA8-0991-484C-8626-A62062119E38}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fiskaltrust.Middleware.SCU.DE.Test.Launcher", "test\Manual\fiskaltrust.Middleware.SCU.DE.Test.Launcher\fiskaltrust.Middleware.SCU.DE.Test.Launcher.csproj", "{C00787F3-B5C1-413B-813A-57E6168E7661}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Manual", "Manual", "{800DAE8D-CCF0-43E6-A974-916FB22C112A}" @@ -65,6 +63,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "fiskaltrust.Middleware.SCU. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fiskaltrust.Middleware.SCU.DE.FiskalyCertified.UnitTest", "test\fiskaltrust.Middleware.SCU.DE.FiskalyCertified.UnitTest\fiskaltrust.Middleware.SCU.DE.FiskalyCertified.UnitTest.csproj", "{4DAA4C67-C0B0-48C3-B41F-851759D86DFB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fiskaltrust.Middleware.SCU.DE.Swissbit", "src\fiskaltrust.Middleware.SCU.DE.Swissbit\fiskaltrust.Middleware.SCU.DE.Swissbit.csproj", "{AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -147,18 +147,6 @@ Global {0AECD929-369B-4644-AF9A-16824AD9FFEE}.Release|x64.Build.0 = Release|Any CPU {0AECD929-369B-4644-AF9A-16824AD9FFEE}.Release|x86.ActiveCfg = Release|Any CPU {0AECD929-369B-4644-AF9A-16824AD9FFEE}.Release|x86.Build.0 = Release|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Debug|Any CPU.Build.0 = Debug|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Debug|x64.ActiveCfg = Debug|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Debug|x64.Build.0 = Debug|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Debug|x86.ActiveCfg = Debug|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Debug|x86.Build.0 = Debug|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Release|Any CPU.ActiveCfg = Release|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Release|Any CPU.Build.0 = Release|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Release|x64.ActiveCfg = Release|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Release|x64.Build.0 = Release|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Release|x86.ActiveCfg = Release|Any CPU - {13BBFBA8-0991-484C-8626-A62062119E38}.Release|x86.Build.0 = Release|Any CPU {C00787F3-B5C1-413B-813A-57E6168E7661}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C00787F3-B5C1-413B-813A-57E6168E7661}.Debug|Any CPU.Build.0 = Debug|Any CPU {C00787F3-B5C1-413B-813A-57E6168E7661}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -351,6 +339,18 @@ Global {4DAA4C67-C0B0-48C3-B41F-851759D86DFB}.Release|x64.Build.0 = Release|Any CPU {4DAA4C67-C0B0-48C3-B41F-851759D86DFB}.Release|x86.ActiveCfg = Release|Any CPU {4DAA4C67-C0B0-48C3-B41F-851759D86DFB}.Release|x86.Build.0 = Release|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Debug|x64.Build.0 = Debug|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Debug|x86.ActiveCfg = Debug|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Debug|x86.Build.0 = Debug|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Release|Any CPU.Build.0 = Release|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Release|x64.ActiveCfg = Release|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Release|x64.Build.0 = Release|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Release|x86.ActiveCfg = Release|Any CPU + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -363,7 +363,6 @@ Global {115522DE-DEC8-461B-AEDD-9D68631F9E57} = {7201C6FB-C25C-45DB-9C0B-1355FEF13939} {0AECD929-369B-4644-AF9A-16824AD9FFEE} = {2F690A2B-96D1-45AC-BD36-E27D68422352} {860D3016-9A5C-4162-ACC8-B2031B80F697} = {2F690A2B-96D1-45AC-BD36-E27D68422352} - {13BBFBA8-0991-484C-8626-A62062119E38} = {2F690A2B-96D1-45AC-BD36-E27D68422352} {C00787F3-B5C1-413B-813A-57E6168E7661} = {800DAE8D-CCF0-43E6-A974-916FB22C112A} {800DAE8D-CCF0-43E6-A974-916FB22C112A} = {7201C6FB-C25C-45DB-9C0B-1355FEF13939} {DFAC4C0A-A019-4E09-B9A1-0CBEF132335E} = {7201C6FB-C25C-45DB-9C0B-1355FEF13939} @@ -381,6 +380,7 @@ Global {61F3E070-5E01-4EBE-B6FC-64D05472CB19} = {2F690A2B-96D1-45AC-BD36-E27D68422352} {0728A325-37E5-4E23-AAC5-DB2860BB75F3} = {7201C6FB-C25C-45DB-9C0B-1355FEF13939} {4DAA4C67-C0B0-48C3-B41F-851759D86DFB} = {7201C6FB-C25C-45DB-9C0B-1355FEF13939} + {AB1DAD78-6EF3-72DD-D37A-D8A5089CCF3A} = {2F690A2B-96D1-45AC-BD36-E27D68422352} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C5E269B8-4A21-4B1B-8805-C8193C08FE3E} diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/.nuspec b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/.nuspec index 6ee7f87cb..a0efc0b10 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/.nuspec +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/.nuspec @@ -39,8 +39,8 @@ - - + + \ No newline at end of file diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/FunctionPointerFactory.cs b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/FunctionPointerFactory.cs index 148825bf7..472009d78 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/FunctionPointerFactory.cs +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/FunctionPointerFactory.cs @@ -1,4 +1,5 @@ -namespace fiskaltrust.Middleware.SCU.DE.Swissbit.Interop + +namespace fiskaltrust.Middleware.SCU.DE.Swissbit.Interop { public class FunctionPointerFactory : INativeFunctionPointerFactory { @@ -22,9 +23,6 @@ public class FunctionPointerFactory : INativeFunctionPointerFactory func_worm_info_isExportEnabledIfCspTestFails = NativeWormAPI.worm_info_isExportEnabledIfCspTestFails, func_worm_info_initializationState = NativeWormAPI.worm_info_initializationState, func_worm_info_isDataImportInProgress = NativeWormAPI.worm_info_isDataImportInProgress, - func_worm_info_hasChangedPuk = NativeWormAPI.worm_info_hasChangedPuk, - func_worm_info_hasChangedAdminPin = NativeWormAPI.worm_info_hasChangedAdminPin, - func_worm_info_hasChangedTimeAdminPin = NativeWormAPI.worm_info_hasChangedTimeAdminPin, func_worm_info_timeUntilNextSelfTest = NativeWormAPI.worm_info_timeUntilNextSelfTest, func_worm_info_startedTransactions = NativeWormAPI.worm_info_startedTransactions, func_worm_info_maxStartedTransactions = NativeWormAPI.worm_info_maxStartedTransactions, diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/ISwissbitProxy.cs b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/ISwissbitProxy.cs index c1ced39a6..9a6a6f508 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/ISwissbitProxy.cs +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/ISwissbitProxy.cs @@ -40,5 +40,8 @@ public interface ISwissbitProxy : IDisposable public Task ExportTarFilteredTransactionAsync(Stream stream, UInt64 startTransactionNumber, UInt64 endTransactionNumber, string clientId); public Task GetLogMessageCertificateAsync(); public Task DeleteStoredDataAsync(); + + public Task TseFactoryReset(bool throwException = true); + public Task IsV2Async(); } } diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/NativeFunctionPointer.cs b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/NativeFunctionPointer.cs index 4b257438e..04636eb23 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/NativeFunctionPointer.cs +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/NativeFunctionPointer.cs @@ -174,45 +174,11 @@ @param[in] info Info reference [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int worm_info_isDataImportInProgress(IntPtr info); - /* Returns whether the initial PUK has been changed. + // Removed in Swissbit TSE API v6.0.0: + // - worm_info_hasChangedPuk + // - worm_info_hasChangedAdminPin + // - worm_info_hasChangedTimeAdminPin - @param[in] info Info reference - */ - //WORMAPI int WORMAPI_CALL worm_info_hasChangedPuk(const WormInfo* info); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int worm_info_hasChangedPuk(IntPtr info); - - /* Returns whether the initial *Admin* PIN has been changed. - - @param[in] info Info reference - */ - //WORMAPI int WORMAPI_CALL worm_info_hasChangedAdminPin(const WormInfo* info); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int worm_info_hasChangedAdminPin(IntPtr info); - - - - - - /* Returns whether the initial *TimeAdmin* PIN has been changed. - - @param[in] info Info reference - */ - //WORMAPI int WORMAPI_CALL worm_info_hasChangedTimeAdminPin(const WormInfo* info); - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - public delegate int worm_info_hasChangedTimeAdminPin(IntPtr info); - - - /* Returns the timeout in seconds after which the next self test must be run. - - If this reaches 0, all following commands will fail until the self test is - executed again. - - @param[in] info Info reference - @returns the timeout - @see @ref worm_tse_runSelfTest - */ - //WORMAPI uint32_t WORMAPI_CALL worm_info_timeUntilNextSelfTest(const WormInfo* info); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate UInt32 worm_info_timeUntilNextSelfTest(IntPtr info); @@ -2108,9 +2074,7 @@ is allowed. */ public worm_info_isExportEnabledIfCspTestFails func_worm_info_isExportEnabledIfCspTestFails { get; set; } public worm_info_initializationState func_worm_info_initializationState { get; set; } public worm_info_isDataImportInProgress func_worm_info_isDataImportInProgress { get; set; } - public worm_info_hasChangedPuk func_worm_info_hasChangedPuk { get; set; } - public worm_info_hasChangedAdminPin func_worm_info_hasChangedAdminPin { get; set; } - public worm_info_hasChangedTimeAdminPin func_worm_info_hasChangedTimeAdminPin { get; set; } + // Removed in v6.0.0: func_worm_info_hasChangedPuk, func_worm_info_hasChangedAdminPin, func_worm_info_hasChangedTimeAdminPin public worm_info_timeUntilNextSelfTest func_worm_info_timeUntilNextSelfTest { get; set; } public worm_info_startedTransactions func_worm_info_startedTransactions { get; set; } public worm_info_maxStartedTransactions func_worm_info_maxStartedTransactions { get; set; } diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/NativeWormAPI.cs b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/NativeWormAPI.cs index f986c67a8..b47b173e8 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/NativeWormAPI.cs +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/NativeWormAPI.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using fiskaltrust.Middleware.SCU.DE.Swissbit.Interop; namespace fiskaltrust.Middleware.SCU.DE.Swissbit.Interop { @@ -172,40 +173,11 @@ @param[in] info Info reference [DllImport("WormAPI")] internal static extern int worm_info_isDataImportInProgress(IntPtr info); - /* Returns whether the initial PUK has been changed. + // Removed in Swissbit TSE API v6.0.0: + // - worm_info_hasChangedPuk + // - worm_info_hasChangedAdminPin + // - worm_info_hasChangedTimeAdminPin - @param[in] info Info reference - */ - //WORMAPI int WORMAPI_CALL worm_info_hasChangedPuk(const WormInfo* info); - [DllImport("WormAPI")] - internal static extern int worm_info_hasChangedPuk(IntPtr info); - - /* Returns whether the initial *Admin* PIN has been changed. - - @param[in] info Info reference - */ - //WORMAPI int WORMAPI_CALL worm_info_hasChangedAdminPin(const WormInfo* info); - [DllImport("WormAPI")] - internal static extern int worm_info_hasChangedAdminPin(IntPtr info); - - /* Returns whether the initial *TimeAdmin* PIN has been changed. - - @param[in] info Info reference - */ - //WORMAPI int WORMAPI_CALL worm_info_hasChangedTimeAdminPin(const WormInfo* info); - [DllImport("WormAPI")] - internal static extern int worm_info_hasChangedTimeAdminPin(IntPtr info); - - /* Returns the timeout in seconds after which the next self test must be run. - - If this reaches 0, all following commands will fail until the self test is - executed again. - - @param[in] info Info reference - @returns the timeout - @see @ref worm_tse_runSelfTest - */ - //WORMAPI uint32_t WORMAPI_CALL worm_info_timeUntilNextSelfTest(const WormInfo* info); [DllImport("WormAPI")] internal static extern uint worm_info_timeUntilNextSelfTest(IntPtr info); diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/SwissbitProxy.cs b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/SwissbitProxy.cs index c15c24fe3..1e0167c0d 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/SwissbitProxy.cs +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/Interop/SwissbitProxy.cs @@ -114,7 +114,13 @@ public async Task CleanupAsync(bool throwException = false) { await _lockingHelper.PerformWithLock(_hwSemaphore, () => { + if (context == IntPtr.Zero) + { + return; + } + var result = _nativeFunctionPointer.func_worm_cleanup(context); + context = IntPtr.Zero; if (throwException) { result.ThrowIfError(); @@ -156,9 +162,6 @@ public async Task GetTseStatusAsync() CreatedSignatures = _nativeFunctionPointer.func_worm_info_createdSignatures(infoPtr), HardwareVersion = _nativeFunctionPointer.func_worm_info_hardwareVersion(infoPtr), SoftwareVersion = _nativeFunctionPointer.func_worm_info_softwareVersion(infoPtr), - HasChangedAdminPin = _nativeFunctionPointer.func_worm_info_hasChangedAdminPin(infoPtr) != 0, - HasChangedPuk = _nativeFunctionPointer.func_worm_info_hasChangedPuk(infoPtr) != 0, - HasChangedTimeAdminPin = _nativeFunctionPointer.func_worm_info_hasChangedTimeAdminPin(infoPtr) != 0, HasPassedSelfTest = _nativeFunctionPointer.func_worm_info_hasPassedSelfTest(infoPtr) != 0, HasValidTime = _nativeFunctionPointer.func_worm_info_hasValidTime(infoPtr) != 0, initializationState = _nativeFunctionPointer.func_worm_info_initializationState(infoPtr), @@ -182,7 +185,8 @@ public async Task GetTseStatusAsync() _logger.LogTrace("GetTseStatusAsync: Reading customization identifier from TSE.."); _nativeFunctionPointer.func_worm_info_customizationIdentifier(infoPtr, ref idPtr, idLengthPtr); - status.CustomizationIdentifier = Marshal.PtrToStringAnsi(idPtr, Marshal.ReadInt16(idLengthPtr)); + // Fix: ReadInt32 instead of ReadInt16 — the C API declares int* idLength (4 bytes) + status.CustomizationIdentifier = Marshal.PtrToStringAnsi(idPtr, Marshal.ReadInt32(idLengthPtr)); _logger.LogTrace("GetTseStatusAsync: Reading public key from TSE.."); _nativeFunctionPointer.func_worm_info_tsePublicKey(infoPtr, ref publicKeyPtr, publicKeyLengthPtr); @@ -249,8 +253,20 @@ await _lockingHelper.PerformWithLock(_hwSemaphore, () => clientIdPtr) .ThrowIfError(); - _nativeFunctionPointer.func_worm_user_logout(context, WormUserId.WORM_USER_ADMIN).ThrowIfError(); - _nativeFunctionPointer.func_worm_user_logout(context, WormUserId.WORM_USER_TIME_ADMIN).ThrowIfError(); + // In Swissbit TSE API v6.0.0, worm_tse_setup no longer leaves users + // logged in after completion. Ignore logout failures to stay compatible + // with both old and new SDK versions. + var adminLogout = _nativeFunctionPointer.func_worm_user_logout(context, WormUserId.WORM_USER_ADMIN); + if (adminLogout != WormError.WORM_ERROR_NOERROR && adminLogout != WormError.WORM_ERROR_AUTHENTICATION_USER_NOT_LOGGED_IN) + { + adminLogout.ThrowIfError(); + } + + var timeAdminLogout = _nativeFunctionPointer.func_worm_user_logout(context, WormUserId.WORM_USER_TIME_ADMIN); + if (timeAdminLogout != WormError.WORM_ERROR_NOERROR && timeAdminLogout != WormError.WORM_ERROR_AUTHENTICATION_USER_NOT_LOGGED_IN) + { + timeAdminLogout.ThrowIfError(); + } } finally { @@ -290,7 +306,8 @@ await _lockingHelper.PerformWithLock(_hwSemaphore, () => } finally { - _nativeFunctionPointer.func_worm_user_logout(context, WormUserId.WORM_USER_ADMIN); + // Fix: was logging out WORM_USER_ADMIN but logged in WORM_USER_TIME_ADMIN + _nativeFunctionPointer.func_worm_user_logout(context, WormUserId.WORM_USER_TIME_ADMIN); } }); } @@ -351,6 +368,19 @@ await _lockingHelper.PerformWithLock(_hwSemaphore, () => }); } + public async Task TseFactoryReset(bool throwException = true) + { + await _lockingHelper.PerformWithLock(_hwSemaphore, () => + { + var error = _nativeFunctionPointer.func_worm_tse_factoryReset(context); + if (throwException) + { + error.ThrowIfError(); + } + + }); + } + public async Task> TseGetRegisteredClientsAsync() { return await _lockingHelper.PerformWithLock(_hwSemaphore, () => @@ -405,7 +435,8 @@ public async Task TransactionStartAsync(string clientId, by return await _lockingHelper.PerformWithLock(_hwSemaphore, () => { var clientIdPtr = Marshal.StringToHGlobalAnsi(clientId); - var processDataPtr = Marshal.AllocHGlobal(processData.Length); + // Fix: guard against zero-length allocation — AllocHGlobal(0) is platform-dependent + var processDataPtr = Marshal.AllocHGlobal(Math.Max(processData.Length, 1)); var processTypePtr = Marshal.StringToHGlobalAnsi(processType); var transactionResponsePtr = IntPtr.Zero; var serialNumberPtr = new IntPtr(); @@ -414,7 +445,10 @@ public async Task TransactionStartAsync(string clientId, by var signatureLengthPtr = Marshal.AllocHGlobal(sizeof(UInt64)); try { - Marshal.Copy(processData, 0, processDataPtr, processData.Length); + if (processData.Length > 0) + { + Marshal.Copy(processData, 0, processDataPtr, processData.Length); + } transactionResponsePtr = _nativeFunctionPointer.func_worm_transaction_response_new(context); @@ -470,7 +504,8 @@ public async Task TransactionUpdateAsync(string clientId, U return await _lockingHelper.PerformWithLock(_hwSemaphore, () => { var clientIdPtr = Marshal.StringToHGlobalAnsi(clientId); - var processDataPtr = Marshal.AllocHGlobal(processData.Length); + // Fix: guard against zero-length allocation + var processDataPtr = Marshal.AllocHGlobal(Math.Max(processData.Length, 1)); var processTypePtr = Marshal.StringToHGlobalAnsi(processType); var transactionResponsePtr = IntPtr.Zero; var serialNumberPtr = new IntPtr(); @@ -479,7 +514,10 @@ public async Task TransactionUpdateAsync(string clientId, U var signatureLengthPtr = Marshal.AllocHGlobal(sizeof(UInt64)); try { - Marshal.Copy(processData, 0, processDataPtr, processData.Length); + if (processData.Length > 0) + { + Marshal.Copy(processData, 0, processDataPtr, processData.Length); + } transactionResponsePtr = _nativeFunctionPointer.func_worm_transaction_response_new(context); @@ -538,7 +576,8 @@ public async Task TransactionFinishAsync(string clientId, U return await _lockingHelper.PerformWithLock(_hwSemaphore, () => { var clientIdPtr = Marshal.StringToHGlobalAnsi(clientId); - var processDataPtr = Marshal.AllocHGlobal(processData.Length); + // Fix: guard against zero-length allocation + var processDataPtr = Marshal.AllocHGlobal(Math.Max(processData.Length, 1)); var processTypePtr = Marshal.StringToHGlobalAnsi(processType); var transactionResponsePtr = IntPtr.Zero; var serialNumberPtr = new IntPtr(); @@ -547,7 +586,10 @@ public async Task TransactionFinishAsync(string clientId, U var signatureLengthPtr = Marshal.AllocHGlobal(sizeof(UInt64)); try { - Marshal.Copy(processData, 0, processDataPtr, processData.Length); + if (processData.Length > 0) + { + Marshal.Copy(processData, 0, processDataPtr, processData.Length); + } transactionResponsePtr = _nativeFunctionPointer.func_worm_transaction_response_new(context); @@ -643,17 +685,23 @@ public async Task ExportTarAsync(Stream stream) { await _lockingHelper.PerformWithLock(_hwSemaphore, () => { + // Fix: store delegate in local variable to prevent GC collection during native call + var callbackDelegate = new WormExportTarCallback((IntPtr chunk, uint chunkLength, IntPtr callbackData) => + { + var chunkBytes = new byte[chunkLength]; + Marshal.Copy(chunk, chunkBytes, 0, (int) chunkLength); + stream.Write(chunkBytes, 0, (int) chunkLength); + return WormError.WORM_ERROR_NOERROR; + }); + var callbackPtr = Marshal.GetFunctionPointerForDelegate(callbackDelegate); + _nativeFunctionPointer.func_worm_export_tar( context, - Marshal.GetFunctionPointerForDelegate(new WormExportTarCallback((IntPtr chunk, uint chunkLength, IntPtr callbackData) => - { - var chunkBytes = new byte[chunkLength]; - Marshal.Copy(chunk, chunkBytes, 0, (int) chunkLength); - stream.Write(chunkBytes, 0, (int) chunkLength); - return WormError.WORM_ERROR_NOERROR; - })), + callbackPtr, IntPtr.Zero) .ThrowIfError(); + + GC.KeepAlive(callbackDelegate); }); } @@ -674,13 +722,15 @@ public async Task ExportTarIncrementalAsync(Stream stream, byte[] lastSt var lastSignatureCounterPtr = Marshal.AllocHGlobal(sizeof(UInt64)); try { - var callback = Marshal.GetFunctionPointerForDelegate(new WormExportTarIncrementalCallback((IntPtr chunk, uint chunkLength, UInt32 processedBlocks, UInt32 totalBlocks, IntPtr callbackData) => + // Fix: store delegate in local variable to prevent GC collection during native call + var callbackDelegate = new WormExportTarIncrementalCallback((IntPtr chunk, uint chunkLength, UInt32 processedBlocks, UInt32 totalBlocks, IntPtr callbackData) => { var chunkBytes = new byte[chunkLength]; Marshal.Copy(chunk, chunkBytes, 0, (int) chunkLength); stream.Write(chunkBytes, 0, (int) chunkLength); return WormError.WORM_ERROR_NOERROR; - })); + }); + var callback = Marshal.GetFunctionPointerForDelegate(callbackDelegate); if (lastStateBytes.Length == 0) { @@ -704,6 +754,8 @@ public async Task ExportTarIncrementalAsync(Stream stream, byte[] lastSt .ThrowIfError(); } + GC.KeepAlive(callbackDelegate); + var newStateBytes = new byte[stateSize]; Marshal.Copy(newStatePtr, newStateBytes, 0, stateSize); return newStateBytes; @@ -723,17 +775,20 @@ await _lockingHelper.PerformWithLock(_hwSemaphore, () => var clientIdPtr = Marshal.StringToHGlobalAnsi(clientId); try { - - var callback = Marshal.GetFunctionPointerForDelegate(new WormExportTarCallback((IntPtr chunk, uint chunkLength, IntPtr callbackData) => - { - var chunkBytes = new byte[chunkLength]; - Marshal.Copy(chunk, chunkBytes, 0, (int) chunkLength); - stream.Write(chunkBytes, 0, (int) chunkLength); - return WormError.WORM_ERROR_NOERROR; - })); + // Fix: store delegate in local variable to prevent GC collection during native call + var callbackDelegate = new WormExportTarCallback((IntPtr chunk, uint chunkLength, IntPtr callbackData) => + { + var chunkBytes = new byte[chunkLength]; + Marshal.Copy(chunk, chunkBytes, 0, (int) chunkLength); + stream.Write(chunkBytes, 0, (int) chunkLength); + return WormError.WORM_ERROR_NOERROR; + }); + var callback = Marshal.GetFunctionPointerForDelegate(callbackDelegate); _nativeFunctionPointer.func_worm_export_tar_filtered_time(context, startDateUnixTime, endDateUnixTime, clientIdPtr, callback, IntPtr.Zero) .ThrowIfError(); + + GC.KeepAlive(callbackDelegate); } finally { @@ -749,15 +804,19 @@ await _lockingHelper.PerformWithLock(_hwSemaphore, () => var clientIdPtr = Marshal.StringToHGlobalAnsi(clientId); try { - var callback = Marshal.GetFunctionPointerForDelegate(new WormExportTarCallback((IntPtr chunk, uint chunkLength, IntPtr callbackData) => + // Fix: store delegate in local variable to prevent GC collection during native call + var callbackDelegate = new WormExportTarCallback((IntPtr chunk, uint chunkLength, IntPtr callbackData) => { var chunkBytes = new byte[chunkLength]; Marshal.Copy(chunk, chunkBytes, 0, (int) chunkLength); stream.Write(chunkBytes, 0, (int) chunkLength); return WormError.WORM_ERROR_NOERROR; - })); + }); + var callback = Marshal.GetFunctionPointerForDelegate(callbackDelegate); _nativeFunctionPointer.func_worm_export_tar_filtered_transaction(context, startTransactionNumber, endTransactionNumber, clientIdPtr, callback, IntPtr.Zero).ThrowIfError(); + + GC.KeepAlive(callbackDelegate); } finally { @@ -796,25 +855,91 @@ public async Task GetLogMessageCertificateAsync() public async Task DeleteStoredDataAsync() => await _lockingHelper.PerformWithLock(_hwSemaphore, () => _nativeFunctionPointer.func_worm_export_deleteStoredData(context).ThrowIfError()); + // Fix: added lock, proper worm_info_free, and try/finally for all three methods below public async Task HasValidTimeAsync() { - var infoPtr = _nativeFunctionPointer.func_worm_info_new(context); - _nativeFunctionPointer.func_worm_info_read(infoPtr).ThrowIfError(); - return await Task.FromResult(_nativeFunctionPointer.func_worm_info_hasValidTime(infoPtr) != 0); + return await _lockingHelper.PerformWithLock(_hwSemaphore, () => + { + var infoPtr = _nativeFunctionPointer.func_worm_info_new(context); + try + { + _nativeFunctionPointer.func_worm_info_read(infoPtr).ThrowIfError(); + return _nativeFunctionPointer.func_worm_info_hasValidTime(infoPtr) != 0; + } + finally + { + if (infoPtr != IntPtr.Zero) + { + _nativeFunctionPointer.func_worm_info_free(infoPtr); + } + } + }); } public async Task HasPassedSelfTestAsync() { - var infoPtr = _nativeFunctionPointer.func_worm_info_new(context); - _nativeFunctionPointer.func_worm_info_read(infoPtr).ThrowIfError(); - return await Task.FromResult(_nativeFunctionPointer.func_worm_info_hasPassedSelfTest(infoPtr) != 0); + return await _lockingHelper.PerformWithLock(_hwSemaphore, () => + { + var infoPtr = _nativeFunctionPointer.func_worm_info_new(context); + try + { + _nativeFunctionPointer.func_worm_info_read(infoPtr).ThrowIfError(); + return _nativeFunctionPointer.func_worm_info_hasPassedSelfTest(infoPtr) != 0; + } + finally + { + if (infoPtr != IntPtr.Zero) + { + _nativeFunctionPointer.func_worm_info_free(infoPtr); + } + } + }); } public async Task GetInitializationState() { - var infoPtr = _nativeFunctionPointer.func_worm_info_new(context); - _nativeFunctionPointer.func_worm_info_read(infoPtr).ThrowIfError(); - return await Task.FromResult(_nativeFunctionPointer.func_worm_info_initializationState(infoPtr).ToTseStates()); + return await _lockingHelper.PerformWithLock(_hwSemaphore, () => + { + var infoPtr = _nativeFunctionPointer.func_worm_info_new(context); + try + { + _nativeFunctionPointer.func_worm_info_read(infoPtr).ThrowIfError(); + return _nativeFunctionPointer.func_worm_info_initializationState(infoPtr).ToTseStates(); + } + finally + { + if (infoPtr != IntPtr.Zero) + { + _nativeFunctionPointer.func_worm_info_free(infoPtr); + } + } + }); + } + + public async Task IsV2Async() + { + return await _lockingHelper.PerformWithLock(_hwSemaphore, () => + { + var infoPtr = _nativeFunctionPointer.func_worm_info_new(context); + try + { + _nativeFunctionPointer.func_worm_info_read(infoPtr).ThrowIfError(); + + // Software version is encoded as: 2 bytes major | 1 byte minor | 1 byte patch + var softwareVersion = _nativeFunctionPointer.func_worm_info_softwareVersion(infoPtr); + var major = (softwareVersion >> 16) & 0xFFFF; + + // Swissbit V2 TSEs use major version >= 2 + return major >= 2; + } + finally + { + if (infoPtr != IntPtr.Zero) + { + _nativeFunctionPointer.func_worm_info_free(infoPtr); + } + } + }); } public void Dispose() diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/SwissbitSCU.cs b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/SwissbitSCU.cs index 2dd31258c..afe7c858c 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/SwissbitSCU.cs +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/SwissbitSCU.cs @@ -42,9 +42,8 @@ public sealed class SwissbitSCU : IDESSCD, IDisposable private TimeSpan _selftestInterval = TimeSpan.FromHours(24); private uint _hwSelftestIntervalSeconds = 0; private ISwissbitProxy _proxy = null; - private DateTime _nextSyncTime; - // Never change these values, as all existing installations are depending on them + // Never change these values, as all existing installations are dependent on them private readonly byte[] _adminPuk = Encoding.ASCII.GetBytes("123456"); private readonly byte[] _seed = Encoding.ASCII.GetBytes("SwissbitSwissbit"); @@ -59,7 +58,9 @@ public SwissbitSCU(SwissbitSCUConfiguration configuration, INativeFunctionPointe EnsureDevicePathIsCorrect(); - _selftestTimer = new Timer(SelftestCallback, null, Timeout.Infinite, Timeout.Infinite); + // Create the timer first (disabled) so SelftestAsync can call _selftestTimer.Change() + // during initialization without a NullReferenceException. + _selftestTimer = new Timer(SelftestCallback, null, Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); try { @@ -75,6 +76,12 @@ public SwissbitSCU(SwissbitSCUConfiguration configuration, INativeFunctionPointe _logger.LogCritical(ex, "An error occured while initializing the Swissbit SCU."); throw; } + + // At this point, SelftestAsync (called by ReadTseInfoAsync inside GetProxy) has already + // updated _selftestInterval from the TSE's TimeUntilNextSelfTest. If SelftestAsync + // successfully called _selftestTimer.Change(), the timer is already running with the + // correct interval. Otherwise, kick it off now with whatever interval we have. + _selftestTimer.Change(_selftestInterval, _selftestInterval); } private ISwissbitProxy GetProxy([CallerMemberName] string memberName = "", [CallerLineNumber] int sourceLineNumber = 0) @@ -87,14 +94,14 @@ private ISwissbitProxy GetProxy([CallerMemberName] string memberName = "", [Call { try { - //maybe move to proxy if (!Directory.Exists(_configuration.DevicePath)) { throw new SwissbitException($"The Swissbit TSE cannot be found at {_configuration.DevicePath}."); } - _logger.LogDebug("Try to initialize SwissbitProxy with devicePath {_configuration.DevicePath}", _configuration.DevicePath); + _logger.LogDebug($"Try to initialize SwissbitProxy with devicePath {_configuration.DevicePath}", _configuration.DevicePath); _proxy = new SwissbitProxy(_configuration.DevicePath, Encoding.ASCII.GetBytes(_configuration.AdminPin), Encoding.ASCII.GetBytes(_configuration.TimeAdminPin), _nativeFunctionPointerFactory, _lockingHelper, _logger); + ReadTseInfoAsync(_proxy, true).GetAwaiter().GetResult(); } catch (Exception ex) @@ -178,14 +185,10 @@ private async Task UpdateTimeAsync(ISwissbitProxy proxy, bool force = false) { _logger.LogWarning("Tried to call UpdateTime, but the Ctss Interface hasn´t been activated."); } - else if (!tseStatus.HasChangedTimeAdminPin) - { - _logger.LogWarning("Tried to call UpdateTime, but the TimeAdmin Pin has never been changed."); - } else { await proxy.TseUpdateTimeAsync(); - _nextSyncTime = currentTime.AddSeconds(tseStatus.MaxTimeSynchronizationDelay); + currentTime.AddSeconds(tseStatus.MaxTimeSynchronizationDelay); } } @@ -204,6 +207,20 @@ private async void SelftestCallback(object state) private async Task SelftestAsync(ISwissbitProxy proxy, bool force = false) { + var tseState = await proxy.GetInitializationState(); + + if (tseState == TseStates.Uninitialized) + { + try + { + await proxy.TseSetupAsync(_seed, _adminPuk, Encoding.ASCII.GetBytes(_configuration.AdminPin), Encoding.ASCII.GetBytes(_configuration.TimeAdminPin)); + } + catch (SwissbitException ex) + { + _logger.LogDebug(ex, "TseSetup skipped: TSE already initialized."); + } + } + if (!force && await proxy.HasPassedSelfTestAsync()) { _logger.LogDebug("Self test already passed, skipping."); @@ -290,12 +307,9 @@ private async Task ReadTseInfoAsync(ISwissbitProxy proxy, bool initLibrary = fal _logger.LogDebug("Initializing WORM library.."); await proxy.InitAsync(); await SelftestAsync(proxy); + await UpdateTimeAsync(proxy); - if (await proxy.UpdateFirmwareAsync(_configuration.EnableFirmwareUpdate)) - { - await SelftestAsync(proxy); - } await UpdateTimeAsync(GetProxy()); } @@ -307,7 +321,6 @@ private async Task ReadTseInfoAsync(ISwissbitProxy proxy, bool initLibrary = fal const long blockSize = 0x200; //512 byte - var softwareVersion = ConvertToVersion((int) status.SoftwareVersion); var hardwareVersion = ConvertToVersion((int) status.HardwareVersion); @@ -571,14 +584,7 @@ public async Task FinishTransactionAsync(FinishTransa { // If the TSE log memory is too full, this call takes too long, and transactions cannot be canceled anymore - basically creating a deadlock. // Thus, we skip reading the timestamp of the start-transaction and fall-back to the one of the finish-transaction. - if (!IsCancellationTransaction(request) || LastTseInfo?.CurrentLogMemorySize < _configuration.TooLargeToExportThreshold) - { - startTransactionTimeStamp = await GetStartTransactionTimeStamp(GetProxy(), request.TransactionNumber); - } - else - { - _logger.LogWarning("Could not set StartTransactionTimestamp, as the TSE's log storage is too full to extract it. Used finish-transaction log time as fallback value."); - } + startTransactionTimeStamp = await GetStartTransactionTimeStamp(GetProxy(), request.TransactionNumber); } var response = new FinishTransactionResponse() diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/SwissbitSCUConfiguration.cs b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/SwissbitSCUConfiguration.cs index c829e8644..ffd3c2fc5 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/SwissbitSCUConfiguration.cs +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/SwissbitSCUConfiguration.cs @@ -7,7 +7,6 @@ public class SwissbitSCUConfiguration public string TimeAdminPin { get; set; } = "98765"; public bool EnableTarFileExport { get; set; } = true; public int TooLargeToExportThreshold { get; set; } = 100 * 1024 * 1024; // 100 MB - public bool EnableFirmwareUpdate { get; set; } = false; public string NativeLibArch { get; set; } public bool StoreTemporaryExportFiles { get; set; } = false; public string ServiceFolder { get; set; } diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/WormLibraryManager.cs b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/WormLibraryManager.cs index e3c2c071c..bbc9e5afa 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/WormLibraryManager.cs +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/WormLibraryManager.cs @@ -10,7 +10,7 @@ public static class WormLibraryManager private const string LINUX_LIB = "libWormAPI.so"; private const string WINDOWS_LIB = "WormAPI.dll"; - private const string PATH_RUNTIMES = "runtimes"; + private const string PATH_RUNTIMES = "runtimesv2"; private const string PATH_NATIVE = "native"; private static readonly string _win32LibraryFile = Path.Combine(PATH_RUNTIMES, "win-x86", PATH_NATIVE, WINDOWS_LIB); @@ -27,7 +27,23 @@ public static void CopyLibraryToWorkingDirectory(SwissbitSCUConfiguration config : SelectPathBasedOnArchitecture(); var currentDirectory = Path.GetDirectoryName(Assembly.GetAssembly(typeof(SwissbitSCU)).Location); - File.Copy(Path.Combine(currentDirectory, libraryFile), Path.Combine(currentDirectory, Path.GetFileName(libraryFile)), true); + var source = Path.Combine(currentDirectory, libraryFile); + var destination = Path.Combine(currentDirectory, Path.GetFileName(libraryFile)); + + try + { + File.Copy(source, destination, true); + } + catch (IOException) + { + // The native library is already loaded by another process or a previous + // instance. If the file already exists we can safely skip the copy, + // since it will be loaded from the existing location. + if (!File.Exists(destination)) + { + throw; + } + } } private static string SelectPathBasedOnArchitecture() diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/fiskaltrust.Middleware.SCU.DE.Swissbit.csproj b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/fiskaltrust.Middleware.SCU.DE.Swissbit.csproj index 34ee64ea5..cdfa8c41e 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/fiskaltrust.Middleware.SCU.DE.Swissbit.csproj +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/fiskaltrust.Middleware.SCU.DE.Swissbit.csproj @@ -28,7 +28,7 @@ - + PreserveNewest diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-arm/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-arm/native/libWormAPI.so deleted file mode 100644 index 711202622..000000000 Binary files a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-arm/native/libWormAPI.so and /dev/null differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-arm64/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-arm64/native/libWormAPI.so deleted file mode 100644 index 376958637..000000000 Binary files a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-arm64/native/libWormAPI.so and /dev/null differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-armel/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-armel/native/libWormAPI.so deleted file mode 100644 index 711202622..000000000 Binary files a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-armel/native/libWormAPI.so and /dev/null differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-x64/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-x64/native/libWormAPI.so deleted file mode 100644 index 14105ce41..000000000 Binary files a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-x64/native/libWormAPI.so and /dev/null differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-x86/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-x86/native/libWormAPI.so deleted file mode 100644 index f495a4bf3..000000000 Binary files a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/linux-x86/native/libWormAPI.so and /dev/null differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/win-x64/native/WormAPI.dll b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/win-x64/native/WormAPI.dll deleted file mode 100644 index ed86558e6..000000000 Binary files a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/win-x64/native/WormAPI.dll and /dev/null differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/win-x86/native/WormAPI.dll b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/win-x86/native/WormAPI.dll deleted file mode 100644 index ebffc5983..000000000 Binary files a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimes/win-x86/native/WormAPI.dll and /dev/null differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-arm/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-arm/native/libWormAPI.so new file mode 100644 index 000000000..6712fe521 Binary files /dev/null and b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-arm/native/libWormAPI.so differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-arm64/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-arm64/native/libWormAPI.so new file mode 100644 index 000000000..ceb51040b Binary files /dev/null and b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-arm64/native/libWormAPI.so differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-x64/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-x64/native/libWormAPI.so new file mode 100644 index 000000000..76d3f280c Binary files /dev/null and b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-x64/native/libWormAPI.so differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-x86/native/libWormAPI.so b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-x86/native/libWormAPI.so new file mode 100644 index 000000000..a6673278f Binary files /dev/null and b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/linux-x86/native/libWormAPI.so differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/win-x64/native/WormAPI.dll b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/win-x64/native/WormAPI.dll new file mode 100644 index 000000000..2e9a28569 Binary files /dev/null and b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/win-x64/native/WormAPI.dll differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/win-x86/native/WormAPI.dll b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/win-x86/native/WormAPI.dll new file mode 100644 index 000000000..d57184011 Binary files /dev/null and b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/runtimesv2/win-x86/native/WormAPI.dll differ diff --git a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/version.json b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/version.json index 260ae598c..e97cc45f6 100644 --- a/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/version.json +++ b/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit/version.json @@ -4,10 +4,10 @@ "/scu-de/src/fiskaltrust.Middleware.SCU.DE.Swissbit" ], "publicReleaseRefSpec": [ - "^refs/tags/scu-de/swissbit/v.+" + "^refs/tags/scu-de/Swissbit/v.+" ], "release": { - "tagName": "scu-de/swissbit/v{version}" + "tagName": "scu-de/Swissbit/v{version}" }, "inherit": true } \ No newline at end of file diff --git a/scu-de/test/Manual/fiskaltrust.Middleware.SCU.DE.Test.Launcher/Program.cs b/scu-de/test/Manual/fiskaltrust.Middleware.SCU.DE.Test.Launcher/Program.cs index e3024db51..53f6c8007 100644 --- a/scu-de/test/Manual/fiskaltrust.Middleware.SCU.DE.Test.Launcher/Program.cs +++ b/scu-de/test/Manual/fiskaltrust.Middleware.SCU.DE.Test.Launcher/Program.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Runtime.InteropServices; using fiskaltrust.ifPOS.v1.de; using fiskaltrust.Middleware.Queue.Test.Launcher.Helpers; using fiskaltrust.Middleware.SCU.DE.Test.Launcher.Helpers; @@ -14,7 +15,7 @@ public static class Program { /* in vs 2026 use Debug - Attach to Process and select testlauncher after starting it */ - private static readonly bool useHelipad = false; + private static readonly bool useHelipad = true; private static readonly string cashBoxId = ""; private static readonly string accessToken = ""; private static readonly string fccDirectory = ""; @@ -22,6 +23,9 @@ public static class Program private static readonly string configurationFilePath = ""; private static readonly string serviceFolder = Directory.GetCurrentDirectory(); + // Set to true to perform a factory reset on the Swissbit TSE before startup (development TSEs only) + private static readonly bool performFactoryReset = false; + public static void Main() { ftCashBoxConfiguration cashBoxConfiguration; @@ -60,8 +64,26 @@ public static void Main() var serviceCollection = new ServiceCollection(); serviceCollection.AddStandardLoggers(LogLevel.Debug); + //only SWISSBIT usb testversion if (config.Package == "fiskaltrust.Middleware.SCU.DE.Swissbit") { + if (performFactoryReset) + { + var devicePath = config.Configuration.ContainsKey("devicePath") + ? config.Configuration["devicePath"]?.ToString() + : config.Configuration.ContainsKey("DevicePath") + ? config.Configuration["DevicePath"]?.ToString() + : null; + + if (string.IsNullOrEmpty(devicePath)) + { + Console.WriteLine("ERROR: Cannot perform factory reset - devicePath not found in configuration."); + } + else + { + PerformSwissbitFactoryReset(devicePath); + } + } ConfigureSwissbit(config, serviceCollection); }else if (config.Package == "fiskaltrust.Middleware.SCU.DE.SwissbitCloud") { @@ -99,6 +121,43 @@ public static void Main() Console.ReadLine(); } + private static void PerformSwissbitFactoryReset(string devicePath) + { + Console.WriteLine($"Performing TSE factory reset on device: {devicePath}"); + Console.WriteLine("WARNING: This will erase all data on the TSE. Only works on development TSEs."); + + var nativeFunctions = new Swissbit.Interop.FunctionPointerFactory().LoadLibrary(); + + var context = IntPtr.Zero; + var mountPointPtr = Marshal.StringToHGlobalAnsi(devicePath); + try + { + var initError = nativeFunctions.func_worm_init(ref context, mountPointPtr); + if (initError != Swissbit.Interop.NativeFunctionPointer.WormError.WORM_ERROR_NOERROR) + { + Console.WriteLine($"ERROR: worm_init failed with error {initError}."); + return; + } + + var resetError = nativeFunctions.func_worm_tse_factoryReset(context); + if (resetError != Swissbit.Interop.NativeFunctionPointer.WormError.WORM_ERROR_NOERROR) + { + Console.WriteLine($"ERROR: worm_tse_factoryReset failed with error {resetError}."); + return; + } + + Console.WriteLine("TSE factory reset completed successfully."); + } + finally + { + if (context != IntPtr.Zero) + { + nativeFunctions.func_worm_cleanup(context); + } + Marshal.FreeHGlobal(mountPointPtr); + } + } + private static ftCashBoxConfiguration GetDemoConfiguration() { var cashBoxConfiguration = new ftCashBoxConfiguration(Guid.NewGuid())