From 722d84978c42752033940d04dccf75774eab1232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20B=C3=BCchel?= Date: Fri, 1 Sep 2023 12:28:03 +0200 Subject: [PATCH] SEBWIN-714, #606: Implemented basic cursor functionality. Minor refactoring of registry and file system dialog classes. --- SafeExamBrowser.Client/ClientController.cs | 108 +++++++-- SafeExamBrowser.Client/CompositionRoot.cs | 10 +- SafeExamBrowser.I18n.Contracts/TextKey.cs | 3 + SafeExamBrowser.I18n/Data/de.xml | 9 + SafeExamBrowser.I18n/Data/en.xml | 9 + SafeExamBrowser.I18n/Data/es.xml | 9 + SafeExamBrowser.I18n/Data/fr.xml | 9 + SafeExamBrowser.I18n/Data/it.xml | 9 + SafeExamBrowser.I18n/Data/zh.xml | 9 + SafeExamBrowser.Runtime/CompositionRoot.cs | 5 +- .../Operations/SessionIntegrityOperation.cs | 65 +++++- .../ISystemInfo.cs | 8 + .../RegistryValueChangedEventHandler.cs | 2 +- .../Registry/IRegistry.cs | 7 +- .../Registry/RegistryValue.cs | 29 +++ .../Registry/Registry.cs | 207 ++++++++---------- .../SystemInfo.cs | 22 +- .../VirtualMachineDetector.cs | 14 +- .../FileSystemDialogFactory.cs | 9 +- .../Windows/FileSystemDialog.xaml.cs | 21 +- .../FileSystemDialogFactory.cs | 9 +- .../Windows/FileSystemDialog.xaml.cs | 21 +- 22 files changed, 392 insertions(+), 202 deletions(-) diff --git a/SafeExamBrowser.Client/ClientController.cs b/SafeExamBrowser.Client/ClientController.cs index 1347d71e..0fe3af8f 100644 --- a/SafeExamBrowser.Client/ClientController.cs +++ b/SafeExamBrowser.Client/ClientController.cs @@ -335,36 +335,50 @@ namespace SafeExamBrowser.Client var timer = new System.Timers.Timer(); timer.AutoReset = false; - timer.Elapsed += (o, args) => - { - logger.Info($"Attempting to verify application integrity..."); - - if (IntegrityModule.TryVerifyCodeSignature(out var isValid)) - { - if (isValid) - { - logger.Info("Application integrity successfully verified."); - } - else - { - logger.Warn("Application integrity is compromised!"); - ShowLockScreen(text.Get(TextKey.LockScreen_ApplicationIntegrityMessage), text.Get(TextKey.LockScreen_Title), Enumerable.Empty()); - } - } - else - { - logger.Warn("Failed to verify application integrity!"); - } - }; + timer.Elapsed += (o, args) => VerifyApplicationIntegrity(); timer.Interval = TEN_MINUTES + (new Random().NextDouble() * FIVE_MINUTES); timer.Start(); + if (registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var names)) + { + foreach (var name in names) + { + registry.StartMonitoring(RegistryValue.UserHive.Cursors_Key, name); + } + } + else + { + logger.Warn("Failed to start monitoring cursor registry values!"); + } + if (Settings.Service.IgnoreService) { registry.StartMonitoring(RegistryValue.MachineHive.EaseOfAccess_Key, RegistryValue.MachineHive.EaseOfAccess_Name); } } + private void VerifyApplicationIntegrity() + { + logger.Info($"Attempting to verify application integrity..."); + + if (IntegrityModule.TryVerifyCodeSignature(out var isValid)) + { + if (isValid) + { + logger.Info("Application integrity successfully verified."); + } + else + { + logger.Warn("Application integrity is compromised!"); + ShowLockScreen(text.Get(TextKey.LockScreen_ApplicationIntegrityMessage), text.Get(TextKey.LockScreen_Title), Enumerable.Empty()); + } + } + else + { + logger.Warn("Failed to verify application integrity!"); + } + } + private void VerifySessionIntegrity() { var hasQuitPassword = !string.IsNullOrEmpty(Settings.Security.QuitPasswordHash); @@ -701,9 +715,55 @@ namespace SafeExamBrowser.Client splashScreen.UpdateStatus(status, true); } - private void Registry_ValueChanged(object oldValue, object newValue) + private void Registry_ValueChanged(string key, string name, object oldValue, object newValue) { - logger.Warn($"The ease of access registry value has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen..."); + if (key == RegistryValue.UserHive.Cursors_Key) + { + HandleCursorRegistryChange(key, name, oldValue, newValue); + } + else if (key == RegistryValue.MachineHive.EaseOfAccess_Key) + { + HandleEaseOfAccessRegistryChange(key, name, oldValue, newValue); + } + } + + private void HandleCursorRegistryChange(string key, string name, object oldValue, object newValue) + { + logger.Warn($@"The cursor registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen..."); + + if (!sessionLocked) + { + var message = text.Get(TextKey.LockScreen_CursorMessage); + var title = text.Get(TextKey.LockScreen_Title); + var continueOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorContinueOption) }; + var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_CursorTerminateOption) }; + + sessionLocked = true; + registry.StopMonitoring(key, name); + + var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption }); + + if (result.OptionId == continueOption.Id) + { + logger.Info("The session will be allowed to resume as requested by the user..."); + } + else if (result.OptionId == terminateOption.Id) + { + logger.Info("Attempting to shutdown as requested by the user..."); + TryRequestShutdown(); + } + + sessionLocked = false; + } + else + { + logger.Info("Lock screen is already active."); + } + } + + private void HandleEaseOfAccessRegistryChange(string key, string name, object oldValue, object newValue) + { + logger.Warn($@"The ease of access registry value '{key}\{name}' has changed from '{oldValue}' to '{newValue}'! Attempting to show lock screen..."); if (!sessionLocked) { @@ -713,7 +773,7 @@ namespace SafeExamBrowser.Client var terminateOption = new LockScreenOption { Text = text.Get(TextKey.LockScreen_EaseOfAccessTerminateOption) }; sessionLocked = true; - registry.StopMonitoring(); + registry.StopMonitoring(key, name); var result = ShowLockScreen(message, title, new[] { continueOption, terminateOption }); diff --git a/SafeExamBrowser.Client/CompositionRoot.cs b/SafeExamBrowser.Client/CompositionRoot.cs index 60a97fd4..23042243 100644 --- a/SafeExamBrowser.Client/CompositionRoot.cs +++ b/SafeExamBrowser.Client/CompositionRoot.cs @@ -94,8 +94,9 @@ namespace SafeExamBrowser.Client InitializeLogging(); InitializeText(); - uiFactory = BuildUserInterfaceFactory(); + var registry = new Registry(ModuleLogger(nameof(Registry))); + uiFactory = BuildUserInterfaceFactory(); actionCenter = uiFactory.CreateActionCenter(); context = new ClientContext(); messageBox = BuildMessageBox(); @@ -103,7 +104,7 @@ namespace SafeExamBrowser.Client networkAdapter = new NetworkAdapter(ModuleLogger(nameof(NetworkAdapter)), nativeMethods); powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply))); runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client); - systemInfo = new SystemInfo(); + systemInfo = new SystemInfo(registry); taskbar = uiFactory.CreateTaskbar(ModuleLogger("Taskbar")); taskview = uiFactory.CreateTaskview(); userInfo = new UserInfo(ModuleLogger(nameof(UserInfo))); @@ -116,7 +117,6 @@ namespace SafeExamBrowser.Client var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods); var fileSystemDialog = BuildFileSystemDialog(); var hashAlgorithm = new HashAlgorithm(); - var registry = new Registry(ModuleLogger(nameof(Registry))); var splashScreen = uiFactory.CreateSplashScreen(); var systemMonitor = new SystemMonitor(ModuleLogger(nameof(SystemMonitor))); @@ -335,9 +335,9 @@ namespace SafeExamBrowser.Client switch (uiMode) { case UserInterfaceMode.Mobile: - return new Mobile.FileSystemDialogFactory(text); + return new Mobile.FileSystemDialogFactory(systemInfo, text); default: - return new Desktop.FileSystemDialogFactory(text); + return new Desktop.FileSystemDialogFactory(systemInfo, text); } } diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs index 0d212b4e..433c8345 100644 --- a/SafeExamBrowser.I18n.Contracts/TextKey.cs +++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs @@ -68,6 +68,9 @@ namespace SafeExamBrowser.I18n.Contracts LockScreen_ApplicationsAllowOption, LockScreen_ApplicationsMessage, LockScreen_ApplicationsTerminateOption, + LockScreen_CursorContinueOption, + LockScreen_CursorMessage, + LockScreen_CursorTerminateOption, LockScreen_DisplayConfigurationContinueOption, LockScreen_DisplayConfigurationMessage, LockScreen_DisplayConfigurationTerminateOption, diff --git a/SafeExamBrowser.I18n/Data/de.xml b/SafeExamBrowser.I18n/Data/de.xml index 761709ca..846a77e8 100644 --- a/SafeExamBrowser.I18n/Data/de.xml +++ b/SafeExamBrowser.I18n/Data/de.xml @@ -162,6 +162,15 @@ Safe Exam Browser beenden. WARNUNG: Sie werden keine Möglichkeit haben, Daten zu speichern oder weitere Aktionen auszuführen, SEB wird sofort beendet! + + Die Cursor-Konfiguration vorübergehend zulassen. Dies gilt nur für die momentan laufende Sitzung! + + + Eine verbotene Cursor-Konfiguration wurde detektiert. Bitte wählen Sie eine der verfügbaren Optionen aus und geben Sie das korrekte Passwort ein, um SEB zu entsperren. + + + Safe Exam Browser beenden. WARNUNG: Sie werden keine Möglichkeit haben, Daten zu speichern oder weitere Aktionen auszuführen, SEB wird sofort beendet! + Bildschirm-Konfiguration temporär erlauben. Dies gilt nur für die momentan laufende Sitzung! diff --git a/SafeExamBrowser.I18n/Data/en.xml b/SafeExamBrowser.I18n/Data/en.xml index 57becdbc..12e2cd53 100644 --- a/SafeExamBrowser.I18n/Data/en.xml +++ b/SafeExamBrowser.I18n/Data/en.xml @@ -162,6 +162,15 @@ Terminate Safe Exam Browser. WARNING: There will be no possibility to save data or perform any further actions, the shutdown will be initiated immediately! + + Temporarily allow the cursor configuration. This applies only to the currently running session! + + + A prohibited cursor configuration has been detected. In order to unlock SEB, please select one of the available options and enter the correct unlock password. + + + Terminate Safe Exam Browser. WARNING: There will be no possibility to save data or perform any further actions, the shutdown will be initiated immediately! + Temporarily allow the display configuration. This applies only to the currently running session! diff --git a/SafeExamBrowser.I18n/Data/es.xml b/SafeExamBrowser.I18n/Data/es.xml index c9e8c65a..a656b600 100644 --- a/SafeExamBrowser.I18n/Data/es.xml +++ b/SafeExamBrowser.I18n/Data/es.xml @@ -162,6 +162,15 @@ Finalizar Safe Exam Browser. ADVERTENCIA: No habrá posibilidad de guardar datos o realizar ninguna otra acción, ¡el cierre se iniciará inmediatamente! + + Permitir temporalmente la configuración del cursor. Esto sólo se aplica a la sesión que se está ejecutando en ese momento. + + + Se ha detectado una configuración de cursor prohibida. Para desbloquear SEB, seleccione una de las opciones disponibles e introduzca la contraseña de desbloqueo correcta. + + + Finalizar Safe Exam Browser. ADVERTENCIA: No habrá posibilidad de guardar datos o realizar ninguna otra acción, ¡el cierre se iniciará inmediatamente! + Permitir temporalmente la configuración de la pantalla. Esto sólo se aplica a la sesión que se está ejecutando en ese momento. diff --git a/SafeExamBrowser.I18n/Data/fr.xml b/SafeExamBrowser.I18n/Data/fr.xml index e1dc84a3..0cb030d0 100644 --- a/SafeExamBrowser.I18n/Data/fr.xml +++ b/SafeExamBrowser.I18n/Data/fr.xml @@ -162,6 +162,15 @@ Terminer Safe Exam Browser. AVERTISSEMENT: Il n'y aura aucune possibilité de sauvegarder les données ou d'effectuer d'autres actions, la fermeture sera initiée immédiatement! + + Autoriser temporairement la configuration du curseur. Ceci s'applique uniquement à la session en cours! + + + Une configuration interdite du curseur a été détectée. Pour déverrouiller SEB, veuillez sélectionner l'une des options disponibles et saisir le mot de passe de déverrouillage correct. + + + Terminer Safe Exam Browser. AVERTISSEMENT: Il n'y aura aucune possibilité de sauvegarder les données ou d'effectuer d'autres actions, la fermeture sera initiée immédiatement! + Autoriser temporairement la configuration de l'affichage. Ceci s'applique uniquement à la session en cours! diff --git a/SafeExamBrowser.I18n/Data/it.xml b/SafeExamBrowser.I18n/Data/it.xml index 6a9ee373..3e1fa9bb 100644 --- a/SafeExamBrowser.I18n/Data/it.xml +++ b/SafeExamBrowser.I18n/Data/it.xml @@ -162,6 +162,15 @@ Terminare Safe Exam Browser. ATTENZIONE: Non sarà possibile salvare i dati o eseguire ulteriori azioni, la chiusura verrà avviata immediatamente! + + Consente temporaneamente la configurazione del cursore. Questo vale solo per la sessione in corso! + + + È stata rilevata una configurazione del cursore non consentita. Per sbloccare SEB, seleziona una delle opzioni disponibili e inserisci la password di sblocco corretta. + + + Terminare Safe Exam Browser. ATTENZIONE: Non sarà possibile salvare i dati o eseguire ulteriori azioni, la chiusura verrà avviata immediatamente! + Consenti temporaneamente la configurazione del display. Questo vale solo per la sessione attualmente in esecuzione! diff --git a/SafeExamBrowser.I18n/Data/zh.xml b/SafeExamBrowser.I18n/Data/zh.xml index 79c29adf..dedd472e 100644 --- a/SafeExamBrowser.I18n/Data/zh.xml +++ b/SafeExamBrowser.I18n/Data/zh.xml @@ -147,6 +147,15 @@ 强制关闭防作弊考试专用浏览器。警告:将无法保存数据或执行任何进一步的操作,关闭操作将立即启动。 + + 暂时允许光标配置。这仅适用于当前运行的会话! + + + 已检测到禁止的光标配置。要解锁 SEB,请选择一个可用选项并输入正确的解锁密码。 + + + 终止安全考试浏览器。警告:将无法保存数据或执行任何其他操作,系统将立即关闭! + 暂时允许显示配置。 这仅适用于当前正在运行的会话! diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index 43beafeb..1df5a697 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -54,16 +54,18 @@ namespace SafeExamBrowser.Runtime const int THIRTY_SECONDS = 30000; logger = new Logger(); - systemInfo = new SystemInfo(); InitializeConfiguration(); InitializeLogging(); InitializeText(); var nativeMethods = new NativeMethods(); + var registry = new Registry(ModuleLogger(nameof(Registry))); var uiFactory = new UserInterfaceFactory(text); var userInfo = new UserInfo(ModuleLogger(nameof(UserInfo))); + systemInfo = new SystemInfo(registry); + var args = Environment.GetCommandLineArgs(); var integrityModule = new IntegrityModule(appConfig, ModuleLogger(nameof(IntegrityModule))); var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory))); @@ -75,7 +77,6 @@ namespace SafeExamBrowser.Runtime var messageBox = new MessageBoxFactory(text); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory))); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), ModuleLogger(nameof(ProxyFactory))); - var registry = new Registry(ModuleLogger(nameof(Registry))); var remoteSessionDetector = new RemoteSessionDetector(ModuleLogger(nameof(RemoteSessionDetector))); var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS); var runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig); diff --git a/SafeExamBrowser.Runtime/Operations/SessionIntegrityOperation.cs b/SafeExamBrowser.Runtime/Operations/SessionIntegrityOperation.cs index 5d076fa8..defbf1f5 100644 --- a/SafeExamBrowser.Runtime/Operations/SessionIntegrityOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/SessionIntegrityOperation.cs @@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System; +using System.Linq; using SafeExamBrowser.Core.Contracts.OperationModel; using SafeExamBrowser.Core.Contracts.OperationModel.Events; using SafeExamBrowser.I18n.Contracts; @@ -32,14 +34,28 @@ namespace SafeExamBrowser.Runtime.Operations { StatusChanged?.Invoke(TextKey.OperationStatus_VerifySessionIntegrity); - return VerifyEaseOfAccessConfiguration(); + var success = VerifyCursorConfiguration(); + + if (success) + { + success = VerifyEaseOfAccessConfiguration(); + } + + return success ? OperationResult.Success : OperationResult.Failed; } public override OperationResult Repeat() { StatusChanged?.Invoke(TextKey.OperationStatus_VerifySessionIntegrity); - return VerifyEaseOfAccessConfiguration(); + var success = VerifyCursorConfiguration(); + + if (success) + { + success = VerifyEaseOfAccessConfiguration(); + } + + return success ? OperationResult.Success : OperationResult.Failed; } public override OperationResult Revert() @@ -47,9 +63,44 @@ namespace SafeExamBrowser.Runtime.Operations return OperationResult.Success; } - private OperationResult VerifyEaseOfAccessConfiguration() + private bool VerifyCursorConfiguration() { - var result = OperationResult.Failed; + var success = true; + var systemPath = $@"{Environment.ExpandEnvironmentVariables("%SystemRoot%")}\cursors\"; + + logger.Info($"Attempting to verify cursor configuration..."); + + if (registry.TryGetNames(RegistryValue.UserHive.Cursors_Key, out var cursors)) + { + foreach (var cursor in cursors.Where(c => !string.IsNullOrWhiteSpace(c))) + { + success &= registry.TryRead(RegistryValue.UserHive.Cursors_Key, cursor, out var value); + success &= value == default || !(value is string) || (value is string path && (string.IsNullOrWhiteSpace(path) || path.StartsWith(systemPath))); + + if (!success) + { + logger.Warn($"{(value != default ? $"Cursor configuration is compromised: '{value}'" : $"Failed to verify configuration of cursor '{cursor}'")}! Aborting session initialization..."); + + break; + } + } + + if (success) + { + logger.Info("Cursor configuration successfully verified."); + } + } + else + { + logger.Error("Failed to verify cursor configuration!"); + } + + return success; + } + + private bool VerifyEaseOfAccessConfiguration() + { + var success = false; logger.Info($"Attempting to verify ease of access configuration..."); @@ -57,12 +108,12 @@ namespace SafeExamBrowser.Runtime.Operations { if (value == default || (value is string s && string.IsNullOrWhiteSpace(s))) { - result = OperationResult.Success; + success = true; logger.Info("Ease of access configuration successfully verified."); } else if (!Context.Next.Settings.Service.IgnoreService) { - result = OperationResult.Success; + success = true; logger.Info($"Ease of access configuration is compromised ('{value}'), but service will be active in the next session."); } else @@ -75,7 +126,7 @@ namespace SafeExamBrowser.Runtime.Operations logger.Error("Failed to verify ease of access configuration!"); } - return result; + return success; } } } diff --git a/SafeExamBrowser.SystemComponents.Contracts/ISystemInfo.cs b/SafeExamBrowser.SystemComponents.Contracts/ISystemInfo.cs index c328f921..3680d512 100644 --- a/SafeExamBrowser.SystemComponents.Contracts/ISystemInfo.cs +++ b/SafeExamBrowser.SystemComponents.Contracts/ISystemInfo.cs @@ -6,6 +6,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +using System.Collections.Generic; +using System.IO; + namespace SafeExamBrowser.SystemComponents.Contracts { /// @@ -62,5 +65,10 @@ namespace SafeExamBrowser.SystemComponents.Contracts /// Provides the device ID information of the user's Plug and Play devices. /// string[] PlugAndPlayDeviceIds { get; } + + /// + /// Retrieves all logical drives of the computer system. + /// + IEnumerable GetDrives(); } } diff --git a/SafeExamBrowser.SystemComponents.Contracts/Registry/Events/RegistryValueChangedEventHandler.cs b/SafeExamBrowser.SystemComponents.Contracts/Registry/Events/RegistryValueChangedEventHandler.cs index 8ed4628d..92f4befd 100644 --- a/SafeExamBrowser.SystemComponents.Contracts/Registry/Events/RegistryValueChangedEventHandler.cs +++ b/SafeExamBrowser.SystemComponents.Contracts/Registry/Events/RegistryValueChangedEventHandler.cs @@ -11,5 +11,5 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Registry.Events /// /// Indicates that a registry value has changed. /// - public delegate void RegistryValueChangedEventHandler(object oldValue, object newValue); + public delegate void RegistryValueChangedEventHandler(string key, string name, object oldValue, object newValue); } diff --git a/SafeExamBrowser.SystemComponents.Contracts/Registry/IRegistry.cs b/SafeExamBrowser.SystemComponents.Contracts/Registry/IRegistry.cs index da6aabcd..33f0cbb1 100644 --- a/SafeExamBrowser.SystemComponents.Contracts/Registry/IRegistry.cs +++ b/SafeExamBrowser.SystemComponents.Contracts/Registry/IRegistry.cs @@ -6,8 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using SafeExamBrowser.SystemComponents.Contracts.Registry.Events; using System.Collections.Generic; +using SafeExamBrowser.SystemComponents.Contracts.Registry.Events; namespace SafeExamBrowser.SystemComponents.Contracts.Registry { @@ -31,6 +31,11 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Registry /// void StopMonitoring(); + /// + /// Stops the monitoring of the specified registry value. + /// + void StopMonitoring(string key, string name); + /// /// Attempts to read the value of the given name under the specified registry key. /// diff --git a/SafeExamBrowser.SystemComponents.Contracts/Registry/RegistryValue.cs b/SafeExamBrowser.SystemComponents.Contracts/Registry/RegistryValue.cs index 7ca03fb2..c85fd500 100644 --- a/SafeExamBrowser.SystemComponents.Contracts/Registry/RegistryValue.cs +++ b/SafeExamBrowser.SystemComponents.Contracts/Registry/RegistryValue.cs @@ -22,6 +22,35 @@ namespace SafeExamBrowser.SystemComponents.Contracts.Registry public const string AppPaths_Key = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths"; public const string EaseOfAccess_Key = @"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\Utilman.exe"; public const string EaseOfAccess_Name = "Debugger"; + public const string HardwareConfig_Key = @"HKEY_LOCAL_MACHINE\SYSTEM\HardwareConfig"; + } + + /// + /// All registry values located in the user hive. + /// + public static class UserHive + { + public const string Cursors_Key = @"HKEY_CURRENT_USER\Control Panel\Cursors"; + public const string Cursors_AppStarting_Name = "AppStarting"; + public const string Cursors_Arrow_Name = "Arrow"; + public const string Cursors_Crosshair_Name = "Crosshair"; + public const string Cursors_Hand_Name = "Hand"; + public const string Cursors_Help_Name = "Help"; + public const string Cursors_IBeam_Name = "IBeam"; + public const string Cursors_No_Name = "No"; + public const string Cursors_NWPen_Name = "NWPen"; + public const string Cursors_Person_Name = "Person"; + public const string Cursors_Pin_Name = "Pin"; + public const string Cursors_SizeAll_Name = "SizeAll"; + public const string Cursors_SizeNESW_Name = "SizeNESW"; + public const string Cursors_SizeNS_Name = "SizeNS"; + public const string Cursors_SizeNWSE_Name = "SizeNWSE"; + public const string Cursors_SizeWE_Name = "SizeWE"; + public const string Cursors_UpArrow_Name = "UpArrow"; + public const string Cursors_Wait_Name = "Wait"; + public const string DeviceCache_Key = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\TaskFlow\DeviceCache"; + public const string NoDrives_Key = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer"; + public const string NoDrives_Name = "NoDrives"; } } } diff --git a/SafeExamBrowser.SystemComponents/Registry/Registry.cs b/SafeExamBrowser.SystemComponents/Registry/Registry.cs index f70185a6..057e6b3c 100644 --- a/SafeExamBrowser.SystemComponents/Registry/Registry.cs +++ b/SafeExamBrowser.SystemComponents/Registry/Registry.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Security.Cryptography; using System.Timers; using Microsoft.Win32; using SafeExamBrowser.Logging.Contracts; @@ -21,7 +20,7 @@ namespace SafeExamBrowser.SystemComponents.Registry public class Registry : IRegistry { private readonly ILogger logger; - private readonly ConcurrentBag<(string key, string name, object value)> values; + private readonly ConcurrentDictionary<(string key, string name), object> values; private Timer timer; @@ -30,7 +29,7 @@ namespace SafeExamBrowser.SystemComponents.Registry public Registry(ILogger logger) { this.logger = logger; - this.values = new ConcurrentBag<(string key, string name, object value)>(); + this.values = new ConcurrentDictionary<(string key, string name), object>(); } public void StartMonitoring(string key, string name) @@ -47,7 +46,7 @@ namespace SafeExamBrowser.SystemComponents.Registry if (TryRead(key, name, out var value)) { - values.Add((key, name, value)); + values.TryAdd((key, name), value); logger.Debug($"Started monitoring value '{name}' from registry key '{key}'. Initial value: '{value}'."); } else @@ -58,10 +57,7 @@ namespace SafeExamBrowser.SystemComponents.Registry public void StopMonitoring() { - while (!values.IsEmpty) - { - values.TryTake(out _); - } + values.Clear(); if (timer != null) { @@ -70,19 +66,24 @@ namespace SafeExamBrowser.SystemComponents.Registry } } + public void StopMonitoring(string key, string name) + { + values.TryRemove((key, name), out _); + } + public bool TryRead(string key, string name, out object value) { - var success = true; + var success = false; value = default; try { value = Microsoft.Win32.Registry.GetValue(key, name, default); + success = true; } catch (Exception e) { - success = false; logger.Error($"Failed to read value '{name}' from registry key '{key}'!", e); } @@ -93,155 +94,133 @@ namespace SafeExamBrowser.SystemComponents.Registry { names = default; - if (!TryOpenKey(keyName, out var key)) + if (TryOpenKey(keyName, out var key)) { - return false; + using (key) + { + try + { + names = key.GetValueNames(); + } + catch (Exception e) + { + logger.Error($"Failed to get registry value names for '{keyName}'!", e); + } + } + } + else + { + logger.Warn($"Failed to get names for '{keyName}'."); } - var success = true; - using (key) - { - try - { - names = key.GetValueNames(); - } - catch (Exception e) - { - logger.Error($"Failed to get registry value names '{keyName}'!", e); - success = false; - } - - } - - return success; + return names != default; } public bool TryGetSubKeys(string keyName, out IEnumerable subKeys) { subKeys = default; - if (!TryOpenKey(keyName, out var key)) + if (TryOpenKey(keyName, out var key)) { - return false; - } - - var success = true; - using (key) - { - try + using (key) { - subKeys = key.GetSubKeyNames(); - } - catch (Exception e) - { - logger.Error($"Failed to get registry value names '{keyName}'!", e); - success = false; + try + { + subKeys = key.GetSubKeyNames(); + } + catch (Exception e) + { + logger.Error($"Failed to get registry sub key names for '{keyName}'!", e); + } } } + else + { + logger.Warn($"Failed to get sub keys for '{keyName}'."); + } - return success; + return subKeys != default; } private void Timer_Elapsed(object sender, ElapsedEventArgs e) { foreach (var item in values) { - if (TryRead(item.key, item.name, out var value)) + if (TryRead(item.Key.key, item.Key.name, out var value)) { - if (item.value != value) + if (!Equals(item.Value, value)) { - logger.Debug($"Value '{item.name}' from registry key '{item.key}' has changed from '{item.value}' to '{value}'!"); - ValueChanged?.Invoke(item.value, value); + logger.Debug($"Value '{item.Key.name}' from registry key '{item.Key.key}' has changed from '{item.Value}' to '{value}'!"); + ValueChanged?.Invoke(item.Key.key, item.Key.name, item.Value, value); } } else { - logger.Error($"Failed to monitor value '{item.name}' from registry key '{item.key}'!"); + logger.Error($"Failed to monitor value '{item.Key.name}' from registry key '{item.Key.key}'!"); } } } - private bool TryGetBaseKeyFromKeyName(string keyName, out RegistryKey baseKey, out string subKeyName) - { - baseKey = default; - subKeyName = default; - - string basekeyName; - var baseKeyLength = keyName.IndexOf('\\'); - if (baseKeyLength != -1) - { - basekeyName = keyName.Substring(0, baseKeyLength).ToUpper(System.Globalization.CultureInfo.InvariantCulture); - } - else - { - basekeyName = keyName.ToUpper(System.Globalization.CultureInfo.InvariantCulture); - } - - switch (basekeyName) - { - case "HKEY_CURRENT_USER": - case "HKCU": - baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); - break; - case "HKEY_LOCAL_MACHINE": - case "HKLM": - baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); - break; - case "HKEY_CLASSES_ROOT": - case "HKCR": - baseKey = RegistryKey.OpenBaseKey(RegistryHive.ClassesRoot, RegistryView.Registry64); - break; - case "HKEY_USERS": - case "HKU": - baseKey = RegistryKey.OpenBaseKey(RegistryHive.Users, RegistryView.Registry64); - break; - case "HKEY_PERFORMANCE_DATA": - case "HKPD": - baseKey = RegistryKey.OpenBaseKey(RegistryHive.PerformanceData, RegistryView.Registry64); - break; - case "HKEY_CURRENT_CONFIG": - case "HKCC": - baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentConfig, RegistryView.Registry64); - break; - case "HKEY_DYN_DATA": - case "HKDD": - baseKey = RegistryKey.OpenBaseKey(RegistryHive.DynData, RegistryView.Registry64); - break; - default: - return false; - } - - if (baseKeyLength == -1 || baseKeyLength == keyName.Length) - { - subKeyName = string.Empty; - } - else - { - subKeyName = keyName.Substring(baseKeyLength + 1, keyName.Length - baseKeyLength - 1); - } - - return true; - } - private bool TryOpenKey(string keyName, out RegistryKey key) { key = default; try { - if (TryGetBaseKeyFromKeyName(keyName, out var baseKey, out var subKey)) + if (TryGetHiveForKey(keyName, out var hive)) { - key = baseKey.OpenSubKey(subKey); + if (keyName == hive.Name) + { + key = hive; + } + else + { + key = hive.OpenSubKey(keyName.Replace($@"{hive.Name}\", "")); + } + } + else + { + logger.Warn($"Failed to get hive for key '{keyName}'!"); } - } catch (Exception e) { logger.Error($"Failed to open registry key '{keyName}'!", e); - return false; } return key != default; } + + private bool TryGetHiveForKey(string keyName, out RegistryKey hive) + { + var length = keyName.IndexOf('\\'); + var name = length != -1 ? keyName.Substring(0, length).ToUpperInvariant() : keyName.ToUpperInvariant(); + + hive = default; + + switch (name) + { + case "HKEY_CLASSES_ROOT": + hive = Microsoft.Win32.Registry.ClassesRoot; + break; + case "HKEY_CURRENT_CONFIG": + hive = Microsoft.Win32.Registry.CurrentConfig; + break; + case "HKEY_CURRENT_USER": + hive = Microsoft.Win32.Registry.CurrentUser; + break; + case "HKEY_LOCAL_MACHINE": + hive = Microsoft.Win32.Registry.LocalMachine; + break; + case "HKEY_PERFORMANCE_DATA": + hive = Microsoft.Win32.Registry.PerformanceData; + break; + case "HKEY_USERS": + hive = Microsoft.Win32.Registry.Users; + break; + } + + return hive != default; + } } } diff --git a/SafeExamBrowser.SystemComponents/SystemInfo.cs b/SafeExamBrowser.SystemComponents/SystemInfo.cs index 638bb2ce..c4f3cb22 100644 --- a/SafeExamBrowser.SystemComponents/SystemInfo.cs +++ b/SafeExamBrowser.SystemComponents/SystemInfo.cs @@ -8,10 +8,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Management; using System.Windows.Forms; using SafeExamBrowser.SystemComponents.Contracts; +using SafeExamBrowser.SystemComponents.Contracts.Registry; using BatteryChargeStatus = System.Windows.Forms.BatteryChargeStatus; using OperatingSystem = SafeExamBrowser.SystemComponents.Contracts.OperatingSystem; @@ -19,6 +21,8 @@ namespace SafeExamBrowser.SystemComponents { public class SystemInfo : ISystemInfo { + private readonly IRegistry registry; + public string BiosInfo { get; private set; } public string CpuName { get; private set; } public bool HasBattery { get; private set; } @@ -30,8 +34,10 @@ namespace SafeExamBrowser.SystemComponents public string OperatingSystemInfo => $"{OperatingSystemName()}, {Environment.OSVersion.VersionString} ({Architecture()})"; public string[] PlugAndPlayDeviceIds { get; private set; } - public SystemInfo() + public SystemInfo(IRegistry registry) { + this.registry = registry; + InitializeBattery(); InitializeBiosInfo(); InitializeCpuName(); @@ -41,6 +47,20 @@ namespace SafeExamBrowser.SystemComponents InitializePnPDevices(); } + public IEnumerable GetDrives() + { + var drives = DriveInfo.GetDrives(); + + registry.TryRead(RegistryValue.UserHive.NoDrives_Key, RegistryValue.UserHive.NoDrives_Name, out var value); + + if (value is int noDrives && noDrives > 0) + { + drives = drives.Where(drive => (noDrives & (int) Math.Pow(2, drive.RootDirectory.ToString()[0] - 65)) == 0).ToArray(); + } + + return drives; + } + private void InitializeBattery() { var status = SystemInformation.PowerStatus.BatteryChargeStatus; diff --git a/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs b/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs index 7e49711c..f48e084b 100644 --- a/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs +++ b/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs @@ -136,16 +136,14 @@ namespace SafeExamBrowser.SystemComponents private bool HasHistoricVirtualMachineHardwareConfiguration() { - const string HARDWARE_ROOT_KEY = @"HKEY_LOCAL_MACHINE\SYSTEM\HardwareConfig"; - var hasHistoricConfiguration = false; - if (registry.TryGetSubKeys(HARDWARE_ROOT_KEY, out var hardwareConfigSubkeys)) + if (registry.TryGetSubKeys(RegistryValue.MachineHive.HardwareConfig_Key, out var hardwareConfigSubkeys)) { foreach (var configId in hardwareConfigSubkeys) { - var hardwareConfigKey = $"{HARDWARE_ROOT_KEY}\\{configId}"; - var computerIdsKey = $"{hardwareConfigKey}\\ComputerIds"; + var hardwareConfigKey = $@"{RegistryValue.MachineHive.HardwareConfig_Key}\{configId}"; + var computerIdsKey = $@"{hardwareConfigKey}\ComputerIds"; var success = true; success &= registry.TryRead(hardwareConfigKey, "BIOSVendor", out var biosVendor); @@ -178,17 +176,15 @@ namespace SafeExamBrowser.SystemComponents private bool HasLocalVirtualMachineDeviceCache() { - const string DEVICE_CACHE_PARENT_KEY = @"HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\TaskFlow\DeviceCache"; - var deviceName = System.Environment.GetEnvironmentVariable("COMPUTERNAME"); var hasDeviceCache = false; - var hasDeviceCacheKeys = registry.TryGetSubKeys(DEVICE_CACHE_PARENT_KEY, out var deviceCacheKeys); + var hasDeviceCacheKeys = registry.TryGetSubKeys(RegistryValue.UserHive.DeviceCache_Key, out var deviceCacheKeys); if (deviceName != default && hasDeviceCacheKeys) { foreach (var cacheId in deviceCacheKeys) { - var cacheIdKey = $@"{DEVICE_CACHE_PARENT_KEY}\{cacheId}"; + var cacheIdKey = $@"{RegistryValue.UserHive.DeviceCache_Key}\{cacheId}"; var didReadKeys = true; didReadKeys &= registry.TryRead(cacheIdKey, "DeviceName", out var cacheDeviceName); diff --git a/SafeExamBrowser.UserInterface.Desktop/FileSystemDialogFactory.cs b/SafeExamBrowser.UserInterface.Desktop/FileSystemDialogFactory.cs index a6f1449a..5e351058 100644 --- a/SafeExamBrowser.UserInterface.Desktop/FileSystemDialogFactory.cs +++ b/SafeExamBrowser.UserInterface.Desktop/FileSystemDialogFactory.cs @@ -8,6 +8,7 @@ using System.Windows; using SafeExamBrowser.I18n.Contracts; +using SafeExamBrowser.SystemComponents.Contracts; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Desktop.Windows; @@ -16,10 +17,12 @@ namespace SafeExamBrowser.UserInterface.Desktop { public class FileSystemDialogFactory : IFileSystemDialog { + private readonly ISystemInfo systemInfo; private readonly IText text; - public FileSystemDialogFactory(IText text) + public FileSystemDialogFactory(ISystemInfo systemInfo, IText text) { + this.systemInfo = systemInfo; this.text = text; } @@ -35,11 +38,11 @@ namespace SafeExamBrowser.UserInterface.Desktop { if (parent is Window window) { - return window.Dispatcher.Invoke(() => new FileSystemDialog(element, operation, text, initialPath, message, title, parent, restrictNavigation, showElementPath).Show()); + return window.Dispatcher.Invoke(() => new FileSystemDialog(element, operation, systemInfo, text, initialPath, message, title, parent, restrictNavigation, showElementPath).Show()); } else { - return new FileSystemDialog(element, operation, text, initialPath, message, title, restrictNavigation: restrictNavigation, showElementPath: showElementPath).Show(); + return new FileSystemDialog(element, operation, systemInfo, text, initialPath, message, title, restrictNavigation: restrictNavigation, showElementPath: showElementPath).Show(); } } } diff --git a/SafeExamBrowser.UserInterface.Desktop/Windows/FileSystemDialog.xaml.cs b/SafeExamBrowser.UserInterface.Desktop/Windows/FileSystemDialog.xaml.cs index 75d2ba65..5779dc2d 100644 --- a/SafeExamBrowser.UserInterface.Desktop/Windows/FileSystemDialog.xaml.cs +++ b/SafeExamBrowser.UserInterface.Desktop/Windows/FileSystemDialog.xaml.cs @@ -15,8 +15,8 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using FontAwesome.WPF; -using Microsoft.Win32; using SafeExamBrowser.I18n.Contracts; +using SafeExamBrowser.SystemComponents.Contracts; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Shared.Utilities; @@ -30,6 +30,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows private readonly string message; private readonly FileSystemOperation operation; private readonly IWindow parent; + private readonly ISystemInfo systemInfo; private readonly bool restrictNavigation; private readonly bool showElementPath; private readonly IText text; @@ -38,6 +39,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows internal FileSystemDialog( FileSystemElement element, FileSystemOperation operation, + ISystemInfo systemInfo, IText text, string initialPath = default, string message = default, @@ -50,6 +52,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows this.initialPath = initialPath; this.message = message; this.operation = operation; + this.systemInfo = systemInfo; this.parent = parent; this.restrictNavigation = restrictNavigation; this.showElementPath = showElementPath; @@ -293,20 +296,6 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows InitializeFileSystem(); } - private DriveInfo[] GetDrives() - { - var drives = DriveInfo.GetDrives(); - var noDrives = Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer", "NoDrives", 0) as int?; - - if (noDrives.HasValue && noDrives > 0) - { - return drives.Where(drive => (noDrives & (int) Math.Pow(2, drive.RootDirectory.ToString()[0] - 65)) == 0).ToArray(); - } - - return drives; - } - - private void InitializeFileSystem() { if (restrictNavigation && !string.IsNullOrEmpty(initialPath)) @@ -337,7 +326,7 @@ namespace SafeExamBrowser.UserInterface.Desktop.Windows private void InitializeUnrestricted() { - foreach (var drive in GetDrives()) + foreach (var drive in systemInfo.GetDrives()) { FileSystem.Items.Add(CreateItem(drive.RootDirectory)); } diff --git a/SafeExamBrowser.UserInterface.Mobile/FileSystemDialogFactory.cs b/SafeExamBrowser.UserInterface.Mobile/FileSystemDialogFactory.cs index c9e66920..0bc53723 100644 --- a/SafeExamBrowser.UserInterface.Mobile/FileSystemDialogFactory.cs +++ b/SafeExamBrowser.UserInterface.Mobile/FileSystemDialogFactory.cs @@ -8,6 +8,7 @@ using System.Windows; using SafeExamBrowser.I18n.Contracts; +using SafeExamBrowser.SystemComponents.Contracts; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Mobile.Windows; @@ -16,10 +17,12 @@ namespace SafeExamBrowser.UserInterface.Mobile { public class FileSystemDialogFactory : IFileSystemDialog { + private readonly ISystemInfo systemInfo; private readonly IText text; - public FileSystemDialogFactory(IText text) + public FileSystemDialogFactory(ISystemInfo systemInfo, IText text) { + this.systemInfo = systemInfo; this.text = text; } @@ -35,11 +38,11 @@ namespace SafeExamBrowser.UserInterface.Mobile { if (parent is Window window) { - return window.Dispatcher.Invoke(() => new FileSystemDialog(element, operation, text, initialPath, message, title, parent, restrictNavigation, showElementPath).Show()); + return window.Dispatcher.Invoke(() => new FileSystemDialog(element, operation, systemInfo, text, initialPath, message, title, parent, restrictNavigation, showElementPath).Show()); } else { - return new FileSystemDialog(element, operation, text, initialPath, message, title, restrictNavigation: restrictNavigation, showElementPath: showElementPath).Show(); + return new FileSystemDialog(element, operation, systemInfo, text, initialPath, message, title, restrictNavigation: restrictNavigation, showElementPath: showElementPath).Show(); } } } diff --git a/SafeExamBrowser.UserInterface.Mobile/Windows/FileSystemDialog.xaml.cs b/SafeExamBrowser.UserInterface.Mobile/Windows/FileSystemDialog.xaml.cs index bc8e29a6..e00b82d4 100644 --- a/SafeExamBrowser.UserInterface.Mobile/Windows/FileSystemDialog.xaml.cs +++ b/SafeExamBrowser.UserInterface.Mobile/Windows/FileSystemDialog.xaml.cs @@ -15,8 +15,8 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using FontAwesome.WPF; -using Microsoft.Win32; using SafeExamBrowser.I18n.Contracts; +using SafeExamBrowser.SystemComponents.Contracts; using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog; using SafeExamBrowser.UserInterface.Contracts.Windows; using SafeExamBrowser.UserInterface.Shared.Utilities; @@ -30,6 +30,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows private readonly string message; private readonly FileSystemOperation operation; private readonly IWindow parent; + private readonly ISystemInfo systemInfo; private readonly bool restrictNavigation; private readonly bool showElementPath; private readonly IText text; @@ -38,6 +39,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows internal FileSystemDialog( FileSystemElement element, FileSystemOperation operation, + ISystemInfo systemInfo, IText text, string initialPath = default, string message = default, @@ -50,6 +52,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows this.initialPath = initialPath; this.message = message; this.operation = operation; + this.systemInfo = systemInfo; this.parent = parent; this.restrictNavigation = restrictNavigation; this.showElementPath = showElementPath; @@ -293,20 +296,6 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows InitializeFileSystem(); } - private DriveInfo[] GetDrives() - { - var drives = DriveInfo.GetDrives(); - var noDrives = Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer", "NoDrives", 0) as int?; - - if (noDrives.HasValue && noDrives > 0) - { - return drives.Where(drive => (noDrives & (int) Math.Pow(2, drive.RootDirectory.ToString()[0] - 65)) == 0).ToArray(); - } - - return drives; - } - - private void InitializeFileSystem() { if (restrictNavigation && !string.IsNullOrEmpty(initialPath)) @@ -337,7 +326,7 @@ namespace SafeExamBrowser.UserInterface.Mobile.Windows private void InitializeUnrestricted() { - foreach (var drive in GetDrives()) + foreach (var drive in systemInfo.GetDrives()) { FileSystem.Items.Add(CreateItem(drive.RootDirectory)); }