Restore SEBPatch
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Configuration.ConfigurationData.DataMapping;
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||
{
|
||||
internal class DataMapper
|
||||
{
|
||||
private readonly BaseDataMapper[] mappers =
|
||||
{
|
||||
new ApplicationDataMapper(),
|
||||
new AudioDataMapper(),
|
||||
new BrowserDataMapper(),
|
||||
new ConfigurationFileDataMapper(),
|
||||
new DisplayDataMapper(),
|
||||
new GeneralDataMapper(),
|
||||
new InputDataMapper(),
|
||||
new ProctoringDataMapper(),
|
||||
new SecurityDataMapper(),
|
||||
new ServerDataMapper(),
|
||||
new ServiceDataMapper(),
|
||||
new SystemDataMapper(),
|
||||
new UserInterfaceDataMapper()
|
||||
};
|
||||
|
||||
internal void Map(IDictionary<string, object> rawData, AppSettings settings)
|
||||
{
|
||||
foreach (var item in rawData)
|
||||
{
|
||||
foreach (var mapper in mappers)
|
||||
{
|
||||
mapper.Map(item.Key, item.Value, settings);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mapper in mappers)
|
||||
{
|
||||
mapper.MapGlobal(rawData, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class ApplicationDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Applications.Blacklist:
|
||||
MapApplicationBlacklist(settings, value);
|
||||
break;
|
||||
case Keys.Applications.Whitelist:
|
||||
MapApplicationWhitelist(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapApplicationBlacklist(AppSettings settings, object value)
|
||||
{
|
||||
if (value is IList<object> applications)
|
||||
{
|
||||
foreach (var item in applications)
|
||||
{
|
||||
if (item is IDictionary<string, object> applicationData)
|
||||
{
|
||||
var isWindowsProcess = applicationData.TryGetValue(Keys.Applications.OperatingSystem, out var v) && v is int os && os == Keys.WINDOWS;
|
||||
|
||||
if (isWindowsProcess)
|
||||
{
|
||||
var application = new BlacklistApplication();
|
||||
var isActive = applicationData.TryGetValue(Keys.Applications.Active, out v) && v is bool active && active;
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.AutoTerminate, out v) && v is bool autoTerminate)
|
||||
{
|
||||
application.AutoTerminate = autoTerminate;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.ExecutableName, out v) && v is string executableName)
|
||||
{
|
||||
application.ExecutableName = executableName;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.OriginalName, out v) && v is string originalName)
|
||||
{
|
||||
application.OriginalName = originalName;
|
||||
}
|
||||
|
||||
var defaultEntry = settings.Applications.Blacklist.FirstOrDefault(a =>
|
||||
{
|
||||
return a.ExecutableName?.Equals(application.ExecutableName, StringComparison.OrdinalIgnoreCase) == true
|
||||
&& a.OriginalName?.Equals(application.OriginalName, StringComparison.OrdinalIgnoreCase) == true;
|
||||
});
|
||||
|
||||
if (defaultEntry != default(BlacklistApplication))
|
||||
{
|
||||
settings.Applications.Blacklist.Remove(defaultEntry);
|
||||
}
|
||||
|
||||
if (isActive)
|
||||
{
|
||||
settings.Applications.Blacklist.Add(application);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MapApplicationWhitelist(AppSettings settings, object value)
|
||||
{
|
||||
if (value is IList<object> applications)
|
||||
{
|
||||
foreach (var item in applications)
|
||||
{
|
||||
if (item is IDictionary<string, object> applicationData)
|
||||
{
|
||||
var isActive = applicationData.TryGetValue(Keys.Applications.Active, out var v) && v is bool active && active;
|
||||
var isWindowsProcess = applicationData.TryGetValue(Keys.Applications.OperatingSystem, out v) && v is int os && os == Keys.WINDOWS;
|
||||
|
||||
if (isActive && isWindowsProcess)
|
||||
{
|
||||
var application = new WhitelistApplication();
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.AllowCustomPath, out v) && v is bool allowCustomPath)
|
||||
{
|
||||
application.AllowCustomPath = allowCustomPath;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.AllowRunning, out v) && v is bool allowRunning)
|
||||
{
|
||||
application.AllowRunning = allowRunning;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.Arguments, out v) && v is IList<object> arguments)
|
||||
{
|
||||
foreach (var argumentItem in arguments)
|
||||
{
|
||||
if (argumentItem is IDictionary<string, object> argumentData)
|
||||
{
|
||||
var argActive = argumentData.TryGetValue(Keys.Applications.Active, out v) && v is bool a && a;
|
||||
|
||||
if (argActive && argumentData.TryGetValue(Keys.Applications.Argument, out v) && v is string argument)
|
||||
{
|
||||
application.Arguments.Add(argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.AutoStart, out v) && v is bool autoStart)
|
||||
{
|
||||
application.AutoStart = autoStart;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.AutoTerminate, out v) && v is bool autoTerminate)
|
||||
{
|
||||
application.AutoTerminate = autoTerminate;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.Description, out v) && v is string description)
|
||||
{
|
||||
application.Description = description;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.DisplayName, out v) && v is string displayName)
|
||||
{
|
||||
application.DisplayName = displayName;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.ExecutableName, out v) && v is string executableName)
|
||||
{
|
||||
application.ExecutableName = executableName;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.ExecutablePath, out v) && v is string executablePath)
|
||||
{
|
||||
application.ExecutablePath = executablePath;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.OriginalName, out v) && v is string originalName)
|
||||
{
|
||||
application.OriginalName = originalName;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.ShowInShell, out v) && v is bool showInShell)
|
||||
{
|
||||
application.ShowInShell = showInShell;
|
||||
}
|
||||
|
||||
if (applicationData.TryGetValue(Keys.Applications.Signature, out v) && v is string signature)
|
||||
{
|
||||
application.Signature = signature;
|
||||
}
|
||||
|
||||
settings.Applications.Whitelist.Add(application);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class AudioDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Audio.InitialVolumeLevel:
|
||||
MapInitialVolumeLevel(settings, value);
|
||||
break;
|
||||
case Keys.Audio.MuteAudio:
|
||||
MapMuteAudio(settings, value);
|
||||
break;
|
||||
case Keys.Audio.SetInitialVolumeLevel:
|
||||
MapSetInitialVolumeLevel(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapInitialVolumeLevel(AppSettings settings, object value)
|
||||
{
|
||||
//if (value is int volume)
|
||||
//{
|
||||
// settings.Audio.InitialVolume = volume;
|
||||
//}
|
||||
settings.Audio.InitialVolume = 67;
|
||||
}
|
||||
|
||||
private void MapMuteAudio(AppSettings settings, object value)
|
||||
{
|
||||
//if (value is bool mute)
|
||||
//{
|
||||
// settings.Audio.MuteAudio = mute;
|
||||
//}
|
||||
settings.Audio.MuteAudio = false;
|
||||
}
|
||||
|
||||
private void MapSetInitialVolumeLevel(AppSettings settings, object value)
|
||||
{
|
||||
//if (value is bool initialize)
|
||||
//{
|
||||
// settings.Audio.InitializeVolume = initialize;
|
||||
//}
|
||||
settings.Audio.InitializeVolume = true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal abstract class BaseDataMapper
|
||||
{
|
||||
internal abstract void Map(string key, object value, AppSettings settings);
|
||||
internal virtual void MapGlobal(IDictionary<string, object> rawData, AppSettings settings) { }
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class ConfigurationFileDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.ConfigurationFile.ConfigurationPurpose:
|
||||
MapConfigurationMode(settings, value);
|
||||
break;
|
||||
case Keys.ConfigurationFile.SessionMode:
|
||||
MapSessionMode(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapConfigurationMode(AppSettings settings, object value)
|
||||
{
|
||||
const int CONFIGURE_CLIENT = 1;
|
||||
|
||||
if (value is int mode)
|
||||
{
|
||||
settings.ConfigurationMode = mode == CONFIGURE_CLIENT ? ConfigurationMode.ConfigureClient : ConfigurationMode.Exam;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapSessionMode(AppSettings settings, object value)
|
||||
{
|
||||
const int SERVER = 1;
|
||||
|
||||
if (value is int mode)
|
||||
{
|
||||
settings.SessionMode = mode == SERVER ? SessionMode.Server : SessionMode.Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class DisplayDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Display.AllowedDisplays:
|
||||
MapAllowedDisplays(settings, value);
|
||||
break;
|
||||
case Keys.Display.AlwaysOn:
|
||||
MapAlwaysOn(settings, value);
|
||||
break;
|
||||
case Keys.Display.IgnoreError:
|
||||
MapIgnoreError(settings, value);
|
||||
break;
|
||||
case Keys.Display.InternalDisplayOnly:
|
||||
MapInternalDisplayOnly(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapAllowedDisplays(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is int count)
|
||||
{
|
||||
settings.Display.AllowedDisplays = count;
|
||||
}
|
||||
*/
|
||||
settings.Display.AllowedDisplays = 500;
|
||||
}
|
||||
|
||||
private void MapAlwaysOn(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool alwaysOn)
|
||||
{
|
||||
settings.Display.AlwaysOn = alwaysOn;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapIgnoreError(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool ignore)
|
||||
{
|
||||
settings.Display.IgnoreError = ignore;
|
||||
}
|
||||
*/
|
||||
settings.Display.IgnoreError = true;
|
||||
}
|
||||
|
||||
private void MapInternalDisplayOnly(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool internalOnly)
|
||||
{
|
||||
settings.Display.InternalDisplayOnly = internalOnly;
|
||||
}
|
||||
*/
|
||||
settings.Display.InternalDisplayOnly = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class GeneralDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.General.LogLevel:
|
||||
MapLogLevel(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapLogLevel(AppSettings settings, object value)
|
||||
{
|
||||
const int ERROR = 0, WARNING = 1, INFO = 2;
|
||||
|
||||
if (value is int level)
|
||||
{
|
||||
settings.LogLevel = level == ERROR ? LogLevel.Error : (level == WARNING ? LogLevel.Warning : (level == INFO ? LogLevel.Info : LogLevel.Debug));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class InputDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Keyboard.EnableAltEsc:
|
||||
MapEnableAltEsc(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableAltF4:
|
||||
MapEnableAltF4(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableAltTab:
|
||||
MapEnableAltTab(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableCtrlEsc:
|
||||
MapEnableCtrlEsc(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableEsc:
|
||||
MapEnableEsc(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF1:
|
||||
MapEnableF1(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF2:
|
||||
MapEnableF2(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF3:
|
||||
MapEnableF3(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF4:
|
||||
MapEnableF4(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF5:
|
||||
MapEnableF5(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF6:
|
||||
MapEnableF6(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF7:
|
||||
MapEnableF7(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF8:
|
||||
MapEnableF8(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF9:
|
||||
MapEnableF9(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF10:
|
||||
MapEnableF10(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF11:
|
||||
MapEnableF11(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableF12:
|
||||
MapEnableF12(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnablePrintScreen:
|
||||
MapEnablePrintScreen(settings, value);
|
||||
break;
|
||||
case Keys.Keyboard.EnableSystemKey:
|
||||
MapEnableSystemKey(settings, value);
|
||||
break;
|
||||
case Keys.Mouse.EnableMiddleMouseButton:
|
||||
MapEnableMiddleMouseButton(settings, value);
|
||||
break;
|
||||
case Keys.Mouse.EnableRightMouseButton:
|
||||
MapEnableRightMouseButton(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapEnableAltEsc(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowAltEsc = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowAltEsc = true;
|
||||
}
|
||||
|
||||
private void MapEnableAltF4(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowAltF4 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowAltF4 = true;
|
||||
}
|
||||
|
||||
private void MapEnableAltTab(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowAltTab = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowAltTab = true;
|
||||
}
|
||||
|
||||
private void MapEnableCtrlEsc(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowCtrlEsc = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowCtrlEsc = true;
|
||||
}
|
||||
|
||||
private void MapEnableEsc(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowEsc = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowEsc = true;
|
||||
}
|
||||
|
||||
private void MapEnableF1(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF1 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF1 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF2(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF2 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF2 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF3(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF3 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF3 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF4(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF4 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF4 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF5(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF5 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF5 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF6(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF6 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF6 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF7(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF7 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF7 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF8(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF8 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF8 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF9(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF9 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF9 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF10(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF10 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF10 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF11(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF11 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF11 = true;
|
||||
}
|
||||
|
||||
private void MapEnableF12(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowF12 = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowF12 = true;
|
||||
}
|
||||
|
||||
private void MapEnablePrintScreen(AppSettings settings, object value)
|
||||
{
|
||||
settings.Keyboard.AllowPrintScreen = true;
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowPrintScreen = enabled;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void MapEnableSystemKey(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Keyboard.AllowSystemKey = enabled;
|
||||
}
|
||||
*/
|
||||
settings.Keyboard.AllowSystemKey = true;
|
||||
}
|
||||
|
||||
private void MapEnableMiddleMouseButton(AppSettings settings, object value)
|
||||
{
|
||||
settings.Mouse.AllowMiddleButton = true;
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Mouse.AllowMiddleButton = enabled;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private void MapEnableRightMouseButton(AppSettings settings, object value)
|
||||
{
|
||||
settings.Mouse.AllowRightButton = true;
|
||||
/*
|
||||
if (value is bool enabled)
|
||||
{
|
||||
settings.Mouse.AllowRightButton = enabled;
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class ProctoringDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Proctoring.ForceRaiseHandMessage:
|
||||
MapForceRaiseHandMessage(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.ClientId:
|
||||
MapClientId(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.ClientSecret:
|
||||
MapClientSecret(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.Enabled:
|
||||
MapScreenProctoringEnabled(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.GroupId:
|
||||
MapGroupId(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.ImageDownscaling:
|
||||
MapImageDownscaling(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.ImageFormat:
|
||||
MapImageFormat(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.ImageQuantization:
|
||||
MapImageQuantization(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.MaxInterval:
|
||||
MapMaxInterval(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.MetaData.CaptureApplicationData:
|
||||
MapCaptureApplicationData(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.MetaData.CaptureBrowserData:
|
||||
MapCaptureBrowserData(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.MetaData.CaptureWindowTitle:
|
||||
MapCaptureWindowTitle(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.MinInterval:
|
||||
MapMinInterval(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ScreenProctoring.ServiceUrl:
|
||||
MapServiceUrl(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ShowRaiseHand:
|
||||
MapShowRaiseHand(settings, value);
|
||||
break;
|
||||
case Keys.Proctoring.ShowTaskbarNotification:
|
||||
MapShowTaskbarNotification(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapForceRaiseHandMessage(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool force)
|
||||
{
|
||||
settings.Proctoring.ForceRaiseHandMessage = force;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapCaptureApplicationData(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool capture)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.MetaData.CaptureApplicationData = capture;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapCaptureBrowserData(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool capture)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.MetaData.CaptureBrowserData = capture;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapCaptureWindowTitle(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool capture)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.MetaData.CaptureWindowTitle = capture;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapClientId(AppSettings settings, object value)
|
||||
{
|
||||
if (value is string clientId)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.ClientId = clientId;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapClientSecret(AppSettings settings, object value)
|
||||
{
|
||||
if (value is string secret)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.ClientSecret = secret;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapGroupId(AppSettings settings, object value)
|
||||
{
|
||||
if (value is string groupId)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.GroupId = groupId;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapImageDownscaling(AppSettings settings, object value)
|
||||
{
|
||||
if (value is double downscaling)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.ImageDownscaling = downscaling;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapImageFormat(AppSettings settings, object value)
|
||||
{
|
||||
if (value is string s && Enum.TryParse<ImageFormat>(s, true, out var format))
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.ImageFormat = format;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapImageQuantization(AppSettings settings, object value)
|
||||
{
|
||||
if (value is int quantization)
|
||||
{
|
||||
switch (quantization)
|
||||
{
|
||||
case 0:
|
||||
settings.Proctoring.ScreenProctoring.ImageQuantization = ImageQuantization.BlackAndWhite1bpp;
|
||||
break;
|
||||
case 1:
|
||||
settings.Proctoring.ScreenProctoring.ImageQuantization = ImageQuantization.Grayscale2bpp;
|
||||
break;
|
||||
case 2:
|
||||
settings.Proctoring.ScreenProctoring.ImageQuantization = ImageQuantization.Grayscale4bpp;
|
||||
break;
|
||||
case 3:
|
||||
settings.Proctoring.ScreenProctoring.ImageQuantization = ImageQuantization.Grayscale8bpp;
|
||||
break;
|
||||
case 4:
|
||||
settings.Proctoring.ScreenProctoring.ImageQuantization = ImageQuantization.Color8bpp;
|
||||
break;
|
||||
case 5:
|
||||
settings.Proctoring.ScreenProctoring.ImageQuantization = ImageQuantization.Color16bpp;
|
||||
break;
|
||||
case 6:
|
||||
settings.Proctoring.ScreenProctoring.ImageQuantization = ImageQuantization.Color24bpp;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void MapMaxInterval(AppSettings settings, object value)
|
||||
{
|
||||
if (value is int interval)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.MaxInterval = interval;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapMinInterval(AppSettings settings, object value)
|
||||
{
|
||||
if (value is int interval)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.MinInterval = interval;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapScreenProctoringEnabled(AppSettings settings, object value)
|
||||
{
|
||||
//if (value is bool enabled)
|
||||
//{
|
||||
// settings.Proctoring.ScreenProctoring.Enabled = enabled;
|
||||
//}
|
||||
settings.Proctoring.ScreenProctoring.Enabled = false;
|
||||
}
|
||||
|
||||
private void MapServiceUrl(AppSettings settings, object value)
|
||||
{
|
||||
if (value is string url)
|
||||
{
|
||||
settings.Proctoring.ScreenProctoring.ServiceUrl = url;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapShowRaiseHand(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool show)
|
||||
{
|
||||
settings.Proctoring.ShowRaiseHandNotification = show;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapShowTaskbarNotification(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool show)
|
||||
{
|
||||
settings.Proctoring.ShowTaskbarNotification = show;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Security;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class SecurityDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Security.AdminPasswordHash:
|
||||
MapAdminPasswordHash(settings, value);
|
||||
break;
|
||||
case Keys.Security.AllowReconfiguration:
|
||||
MapAllowReconfiguration(settings, value);
|
||||
break;
|
||||
case Keys.Security.AllowStickyKeys:
|
||||
MapAllowStickyKeys(settings, value);
|
||||
break;
|
||||
case Keys.Security.AllowTermination:
|
||||
MapAllowTermination(settings, value);
|
||||
break;
|
||||
case Keys.Security.AllowVirtualMachine:
|
||||
MapVirtualMachinePolicy(settings, value);
|
||||
break;
|
||||
case Keys.Security.ClipboardPolicy:
|
||||
MapClipboardPolicy(settings, value);
|
||||
break;
|
||||
case Keys.Security.DisableSessionChangeLockScreen:
|
||||
MapDisableSessionChangeLockScreen(settings, value);
|
||||
break;
|
||||
case Keys.Security.QuitPasswordHash:
|
||||
MapQuitPasswordHash(settings, value);
|
||||
break;
|
||||
case Keys.Security.ReconfigurationUrl:
|
||||
MapReconfigurationUrl(settings, value);
|
||||
break;
|
||||
case Keys.Security.VerifyCursorConfiguration:
|
||||
MapVerifyCursorConfiguration(settings, value);
|
||||
break;
|
||||
case Keys.Security.VerifySessionIntegrity:
|
||||
MapVerifySessionIntegrity(settings, value);
|
||||
break;
|
||||
case Keys.Security.VersionRestrictions:
|
||||
MapVersionRestrictions(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void MapGlobal(IDictionary<string, object> rawData, AppSettings settings)
|
||||
{
|
||||
MapApplicationLogAccess(rawData, settings);
|
||||
MapKioskMode(rawData, settings);
|
||||
}
|
||||
|
||||
private void MapAdminPasswordHash(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is string hash)
|
||||
{
|
||||
settings.Security.AdminPasswordHash = hash;
|
||||
}
|
||||
*/
|
||||
settings.Security.AdminPasswordHash = "";
|
||||
}
|
||||
|
||||
private void MapAllowReconfiguration(AppSettings settings, object value)
|
||||
{
|
||||
//if (value is bool allow)
|
||||
//{
|
||||
// settings.Security.AllowReconfiguration = allow;
|
||||
//}
|
||||
settings.Security.AllowReconfiguration = true;
|
||||
}
|
||||
|
||||
private void MapAllowStickyKeys(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool allow)
|
||||
{
|
||||
settings.Security.AllowStickyKeys = allow;
|
||||
}
|
||||
*/
|
||||
settings.Security.AllowStickyKeys = true;
|
||||
}
|
||||
|
||||
private void MapAllowTermination(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool allow)
|
||||
{
|
||||
settings.Security.AllowTermination = allow;
|
||||
}
|
||||
*/
|
||||
settings.Security.AllowTermination = true;
|
||||
}
|
||||
|
||||
private void MapApplicationLogAccess(IDictionary<string, object> rawData, AppSettings settings)
|
||||
{
|
||||
/*
|
||||
var hasValue = rawData.TryGetValue(Keys.Security.AllowApplicationLog, out var value);
|
||||
|
||||
if (hasValue && value is bool allow)
|
||||
{
|
||||
settings.Security.AllowApplicationLogAccess = allow;
|
||||
}
|
||||
|
||||
if (settings.Security.AllowApplicationLogAccess)
|
||||
{
|
||||
settings.UserInterface.ActionCenter.ShowApplicationLog = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
settings.UserInterface.ActionCenter.ShowApplicationLog = false;
|
||||
settings.UserInterface.Taskbar.ShowApplicationLog = false;
|
||||
}
|
||||
*/
|
||||
settings.UserInterface.ActionCenter.ShowApplicationLog = false;
|
||||
settings.UserInterface.Taskbar.ShowApplicationLog = false;
|
||||
}
|
||||
|
||||
private void MapKioskMode(IDictionary<string, object> rawData, AppSettings settings)
|
||||
{
|
||||
/*
|
||||
var hasCreateNewDesktop = rawData.TryGetValue(Keys.Security.KioskModeCreateNewDesktop, out var createNewDesktop);
|
||||
var hasDisableExplorerShell = rawData.TryGetValue(Keys.Security.KioskModeDisableExplorerShell, out var disableExplorerShell);
|
||||
|
||||
if (hasDisableExplorerShell && disableExplorerShell as bool? == true)
|
||||
{
|
||||
settings.Security.KioskMode = KioskMode.DisableExplorerShell;
|
||||
}
|
||||
|
||||
if (hasCreateNewDesktop && createNewDesktop as bool? == true)
|
||||
{
|
||||
settings.Security.KioskMode = KioskMode.CreateNewDesktop;
|
||||
}
|
||||
|
||||
if (hasCreateNewDesktop && hasDisableExplorerShell && createNewDesktop as bool? == false && disableExplorerShell as bool? == false)
|
||||
{
|
||||
settings.Security.KioskMode = KioskMode.None;
|
||||
}
|
||||
*/
|
||||
settings.Security.KioskMode = KioskMode.None;
|
||||
}
|
||||
|
||||
private void MapQuitPasswordHash(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is string hash)
|
||||
{
|
||||
settings.Security.QuitPasswordHash = hash;
|
||||
}
|
||||
*/
|
||||
settings.Security.QuitPasswordHash = "";
|
||||
}
|
||||
|
||||
private void MapClipboardPolicy(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
const int ALLOW = 0;
|
||||
const int BLOCK = 1;
|
||||
|
||||
if (value is int policy)
|
||||
{
|
||||
settings.Security.ClipboardPolicy = policy == ALLOW ? ClipboardPolicy.Allow : (policy == BLOCK ? ClipboardPolicy.Block : ClipboardPolicy.Isolated);
|
||||
}
|
||||
*/
|
||||
settings.Security.ClipboardPolicy = ClipboardPolicy.Allow;
|
||||
}
|
||||
|
||||
private void MapDisableSessionChangeLockScreen(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool disable)
|
||||
{
|
||||
settings.Security.DisableSessionChangeLockScreen = disable;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapVirtualMachinePolicy(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool allow)
|
||||
{
|
||||
settings.Security.VirtualMachinePolicy = allow ? VirtualMachinePolicy.Allow : VirtualMachinePolicy.Deny;
|
||||
}
|
||||
*/
|
||||
settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Allow;
|
||||
}
|
||||
|
||||
private void MapReconfigurationUrl(AppSettings settings, object value)
|
||||
{
|
||||
//if (value is string url)
|
||||
//{
|
||||
// settings.Security.ReconfigurationUrl = url;
|
||||
//}
|
||||
settings.Security.ReconfigurationUrl = "*";
|
||||
}
|
||||
|
||||
private void MapVerifyCursorConfiguration(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool verify)
|
||||
{
|
||||
settings.Security.VerifyCursorConfiguration = verify;
|
||||
}
|
||||
*/
|
||||
settings.Security.VerifyCursorConfiguration = false;
|
||||
}
|
||||
|
||||
private void MapVerifySessionIntegrity(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool verify)
|
||||
{
|
||||
settings.Security.VerifySessionIntegrity = verify;
|
||||
}
|
||||
*/
|
||||
settings.Security.VerifySessionIntegrity = false;
|
||||
}
|
||||
|
||||
private void MapVersionRestrictions(AppSettings settings, object value)
|
||||
{
|
||||
if (value is IList<object> restrictions)
|
||||
{
|
||||
foreach (var restriction in restrictions.Cast<string>())
|
||||
{
|
||||
var parts = restriction.Split('.');
|
||||
var os = parts.Length > 0 ? parts[0] : default;
|
||||
|
||||
if (os?.Equals("win", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
var major = parts.Length > 1 ? int.Parse(parts[1]) : default;
|
||||
var minor = parts.Length > 2 ? int.Parse(parts[2]) : default;
|
||||
var patch = parts.Length > 3 && int.TryParse(parts[3], out _) ? int.Parse(parts[3]) : default(int?);
|
||||
var build = parts.Length > 4 && int.TryParse(parts[4], out _) ? int.Parse(parts[4]) : default(int?);
|
||||
|
||||
//settings.Security.VersionRestrictions.Add(new VersionRestriction
|
||||
//{
|
||||
// Major = major,
|
||||
// Minor = minor,
|
||||
// Patch = patch,
|
||||
// Build = build,
|
||||
// IsMinimumRestriction = restriction.Contains("min"),
|
||||
// RequiresAllianceEdition = restriction.Contains("AE")
|
||||
//});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class ServerDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Server.Configuration:
|
||||
MapConfiguration(settings, value);
|
||||
break;
|
||||
case Keys.Server.FallbackPasswordHash:
|
||||
MapFallbackPasswordHash(settings, value);
|
||||
break;
|
||||
case Keys.Server.PerformFallback:
|
||||
MapPerformFallback(settings, value);
|
||||
break;
|
||||
case Keys.Server.RequestAttempts:
|
||||
MapRequestAttempts(settings, value);
|
||||
break;
|
||||
case Keys.Server.RequestAttemptInterval:
|
||||
MapRequestAttemptInterval(settings, value);
|
||||
break;
|
||||
case Keys.Server.RequestTimeout:
|
||||
MapRequestTimeout(settings, value);
|
||||
break;
|
||||
case Keys.Server.ServerUrl:
|
||||
MapServerUrl(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapConfiguration(AppSettings settings, object value)
|
||||
{
|
||||
if (value is IDictionary<string, object> configuration)
|
||||
{
|
||||
if (configuration.TryGetValue(Keys.Server.ApiUrl, out var v) && v is string url)
|
||||
{
|
||||
settings.Server.ApiUrl = url;
|
||||
}
|
||||
|
||||
if (configuration.TryGetValue(Keys.Server.ClientName, out v) && v is string name)
|
||||
{
|
||||
settings.Server.ClientName = name;
|
||||
}
|
||||
|
||||
if (configuration.TryGetValue(Keys.Server.ClientSecret, out v) && v is string secret)
|
||||
{
|
||||
settings.Server.ClientSecret = secret;
|
||||
}
|
||||
|
||||
if (configuration.TryGetValue(Keys.Server.ExamId, out v) && v is string examId)
|
||||
{
|
||||
settings.Server.ExamId = examId;
|
||||
}
|
||||
|
||||
if (configuration.TryGetValue(Keys.Server.Institution, out v) && v is string institution)
|
||||
{
|
||||
settings.Server.Institution = institution;
|
||||
}
|
||||
|
||||
if (configuration.TryGetValue(Keys.Server.PingInterval, out v) && v is int interval)
|
||||
{
|
||||
settings.Server.PingInterval = interval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MapFallbackPasswordHash(AppSettings settings, object value)
|
||||
{
|
||||
//if (value is string hash)
|
||||
//{
|
||||
// settings.Server.FallbackPasswordHash = hash;
|
||||
//}
|
||||
settings.Server.FallbackPasswordHash = "";
|
||||
}
|
||||
|
||||
private void MapPerformFallback(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool perform)
|
||||
{
|
||||
settings.Server.PerformFallback = perform;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapRequestAttempts(AppSettings settings, object value)
|
||||
{
|
||||
if (value is int attempts)
|
||||
{
|
||||
settings.Server.RequestAttempts = attempts;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapRequestAttemptInterval(AppSettings settings, object value)
|
||||
{
|
||||
if (value is int interval)
|
||||
{
|
||||
settings.Server.RequestAttemptInterval = interval;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapRequestTimeout(AppSettings settings, object value)
|
||||
{
|
||||
if (value is int timeout)
|
||||
{
|
||||
settings.Server.RequestTimeout = timeout;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapServerUrl(AppSettings settings, object value)
|
||||
{
|
||||
if (value is string url)
|
||||
{
|
||||
settings.Server.ServerUrl = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Service;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class ServiceDataMapper : BaseDataMapper
|
||||
{
|
||||
public static bool enable = true;
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.Service.EnableChromeNotifications:
|
||||
MapEnableChromeNotifications(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableEaseOfAccessOptions:
|
||||
MapEnableEaseOfAccessOptions(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableFindPrinter:
|
||||
MapEnableFindPrinter(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableNetworkOptions:
|
||||
MapEnableNetworkOptions(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnablePasswordChange:
|
||||
MapEnablePasswordChange(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnablePowerOptions:
|
||||
MapEnablePowerOptions(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableRemoteConnections:
|
||||
MapEnableRemoteConnections(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableSignout:
|
||||
MapEnableSignout(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableTaskManager:
|
||||
MapEnableTaskManager(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableUserLock:
|
||||
MapEnableUserLock(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableUserSwitch:
|
||||
MapEnableUserSwitch(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableVmwareOverlay:
|
||||
MapEnableVmwareOverlay(settings, value);
|
||||
break;
|
||||
case Keys.Service.EnableWindowsUpdate:
|
||||
MapEnableWindowsUpdate(settings, value);
|
||||
break;
|
||||
case Keys.Service.IgnoreService:
|
||||
MapIgnoreService(settings, value);
|
||||
break;
|
||||
case Keys.Service.Policy:
|
||||
MapPolicy(settings, value);
|
||||
break;
|
||||
case Keys.Service.SetVmwareConfiguration:
|
||||
MapSetVmwareConfiguration(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapEnableChromeNotifications(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableChromeNotifications = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableChromeNotifications = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableEaseOfAccessOptions(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableEaseOfAccessOptions = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableEaseOfAccessOptions = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableFindPrinter(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableFindPrinter = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableFindPrinter = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableNetworkOptions(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableNetworkOptions = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableNetworkOptions = !enable;
|
||||
}
|
||||
|
||||
private void MapEnablePasswordChange(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisablePasswordChange = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisablePasswordChange = !enable;
|
||||
}
|
||||
|
||||
private void MapEnablePowerOptions(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisablePowerOptions = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisablePowerOptions = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableRemoteConnections(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableRemoteConnections = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableRemoteConnections = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableSignout(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableSignout = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableSignout = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableTaskManager(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableTaskManager = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableTaskManager = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableUserLock(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableUserLock = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableUserLock = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableUserSwitch(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableUserSwitch = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableUserSwitch = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableVmwareOverlay(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableVmwareOverlay = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableVmwareOverlay = !enable;
|
||||
}
|
||||
|
||||
private void MapEnableWindowsUpdate(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.Service.DisableWindowsUpdate = !enable;
|
||||
}
|
||||
*/
|
||||
settings.Service.DisableWindowsUpdate = !enable;
|
||||
}
|
||||
|
||||
private void MapIgnoreService(AppSettings settings, object value)
|
||||
{
|
||||
//if (value is bool ignore)
|
||||
//{
|
||||
// settings.Service.IgnoreService = ignore;
|
||||
//}
|
||||
settings.Service.IgnoreService = enable;
|
||||
}
|
||||
|
||||
private void MapPolicy(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
const int WARN = 1;
|
||||
const int FORCE = 2;
|
||||
|
||||
if (value is int policy)
|
||||
{
|
||||
settings.Service.Policy = policy == FORCE ? ServicePolicy.Mandatory : (policy == WARN ? ServicePolicy.Warn : ServicePolicy.Optional);
|
||||
}
|
||||
*/
|
||||
settings.Service.Policy = ServicePolicy.Optional;
|
||||
}
|
||||
|
||||
private void MapSetVmwareConfiguration(AppSettings settings, object value)
|
||||
{
|
||||
/*
|
||||
if (value is bool set)
|
||||
{
|
||||
settings.Service.SetVmwareConfiguration = set;
|
||||
}
|
||||
*/
|
||||
settings.Service.SetVmwareConfiguration = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class SystemDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.System.AlwaysOn:
|
||||
MapAlwaysOn(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapAlwaysOn(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool alwaysOn)
|
||||
{
|
||||
settings.System.AlwaysOn = alwaysOn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.UserInterface;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
|
||||
{
|
||||
internal class UserInterfaceDataMapper : BaseDataMapper
|
||||
{
|
||||
internal override void Map(string key, object value, AppSettings settings)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case Keys.UserInterface.ActionCenter.EnableActionCenter:
|
||||
MapEnableActionCenter(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.LockScreen.BackgroundColor:
|
||||
MapLockScreenBackgroundColor(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.SystemControls.Audio.Show:
|
||||
MapShowAudio(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.SystemControls.Clock.Show:
|
||||
MapShowClock(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.SystemControls.KeyboardLayout.Show:
|
||||
MapShowKeyboardLayout(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.SystemControls.Network.Show:
|
||||
MapShowNetwork(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.SystemControls.PowerSupply.ChargeThresholdCritical:
|
||||
MapChargeThresholdCritical(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.SystemControls.PowerSupply.ChargeThresholdLow:
|
||||
MapChargeThresholdLow(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.Taskbar.EnableTaskbar:
|
||||
MapEnableTaskbar(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.Taskbar.ShowApplicationLog:
|
||||
MapShowApplicationLog(settings, value);
|
||||
break;
|
||||
case Keys.UserInterface.UserInterfaceMode:
|
||||
MapUserInterfaceMode(settings, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapEnableActionCenter(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.UserInterface.ActionCenter.EnableActionCenter = enable;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapLockScreenBackgroundColor(AppSettings settings, object value)
|
||||
{
|
||||
if (value is string color)
|
||||
{
|
||||
settings.UserInterface.LockScreen.BackgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapShowAudio(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool show)
|
||||
{
|
||||
settings.UserInterface.ActionCenter.ShowAudio = show;
|
||||
settings.UserInterface.Taskbar.ShowAudio = show;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapShowClock(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool show)
|
||||
{
|
||||
settings.UserInterface.ActionCenter.ShowClock = show;
|
||||
settings.UserInterface.Taskbar.ShowClock = show;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapShowKeyboardLayout(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool show)
|
||||
{
|
||||
settings.UserInterface.ActionCenter.ShowKeyboardLayout = show;
|
||||
settings.UserInterface.Taskbar.ShowKeyboardLayout = show;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapShowNetwork(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool show)
|
||||
{
|
||||
settings.UserInterface.ActionCenter.ShowNetwork = show;
|
||||
settings.UserInterface.Taskbar.ShowNetwork = show;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapChargeThresholdCritical(AppSettings settings, object value)
|
||||
{
|
||||
if (value is double threshold)
|
||||
{
|
||||
settings.PowerSupply.ChargeThresholdCritical = threshold;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapChargeThresholdLow(AppSettings settings, object value)
|
||||
{
|
||||
if (value is double threshold)
|
||||
{
|
||||
settings.PowerSupply.ChargeThresholdLow = threshold;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapEnableTaskbar(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool enable)
|
||||
{
|
||||
settings.UserInterface.Taskbar.EnableTaskbar = enable;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapShowApplicationLog(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool show)
|
||||
{
|
||||
settings.UserInterface.Taskbar.ShowApplicationLog = show;
|
||||
}
|
||||
}
|
||||
|
||||
private void MapUserInterfaceMode(AppSettings settings, object value)
|
||||
{
|
||||
if (value is bool mobile)
|
||||
{
|
||||
settings.UserInterface.Mode = mobile ? UserInterfaceMode.Mobile : UserInterfaceMode.Desktop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
101
SafeExamBrowser.Configuration/ConfigurationData/DataProcessor.cs
Normal file
101
SafeExamBrowser.Configuration/ConfigurationData/DataProcessor.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
using SafeExamBrowser.Settings.Security;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||
{
|
||||
internal class DataProcessor
|
||||
{
|
||||
internal void Process(IDictionary<string, object> rawData, AppSettings settings)
|
||||
{
|
||||
ProcessDefault(settings);
|
||||
CalculateConfigurationKey(rawData, settings);
|
||||
}
|
||||
|
||||
internal void ProcessDefault(AppSettings settings)
|
||||
{
|
||||
AllowBrowserToolbarForReloading(settings);
|
||||
InitializeBrowserHomeFunctionality(settings);
|
||||
InitializeClipboardSettings(settings);
|
||||
InitializeProctoringSettings(settings);
|
||||
RemoveLegacyBrowsers(settings);
|
||||
}
|
||||
|
||||
private void AllowBrowserToolbarForReloading(AppSettings settings)
|
||||
{
|
||||
settings.Browser.AdditionalWindow.ShowToolbar = true;
|
||||
settings.Browser.MainWindow.ShowToolbar = true;
|
||||
}
|
||||
|
||||
private void CalculateConfigurationKey(IDictionary<string, object> rawData, AppSettings settings)
|
||||
{
|
||||
using (var algorithm = new SHA256Managed())
|
||||
using (var stream = new MemoryStream())
|
||||
using (var writer = new StreamWriter(stream))
|
||||
{
|
||||
Json.Serialize(rawData, writer);
|
||||
|
||||
writer.Flush();
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var hash = algorithm.ComputeHash(stream);
|
||||
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
|
||||
|
||||
settings.Browser.ConfigurationKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeBrowserHomeFunctionality(AppSettings settings)
|
||||
{
|
||||
settings.Browser.MainWindow.ShowHomeButton = settings.Browser.UseStartUrlAsHomeUrl || !string.IsNullOrWhiteSpace(settings.Browser.HomeUrl);
|
||||
settings.Browser.HomePasswordHash = "";
|
||||
}
|
||||
|
||||
private void InitializeClipboardSettings(AppSettings settings)
|
||||
{
|
||||
settings.Browser.UseIsolatedClipboard = false;
|
||||
settings.Keyboard.AllowCtrlC = true;
|
||||
settings.Keyboard.AllowCtrlV = true;
|
||||
settings.Keyboard.AllowCtrlX = true;
|
||||
}
|
||||
|
||||
private void InitializeProctoringSettings(AppSettings settings)
|
||||
{
|
||||
settings.Proctoring.Enabled = settings.Proctoring.ScreenProctoring.Enabled;
|
||||
}
|
||||
|
||||
private void RemoveLegacyBrowsers(AppSettings settings)
|
||||
{
|
||||
var legacyBrowsers = new List<WhitelistApplication>();
|
||||
|
||||
foreach (var application in settings.Applications.Whitelist)
|
||||
{
|
||||
var isEnginePath = application.ExecutablePath?.Contains("xulrunner") == true;
|
||||
var isFirefox = application.ExecutableName?.Equals("firefox.exe", StringComparison.OrdinalIgnoreCase) == true;
|
||||
var isXulRunner = application.ExecutableName?.Equals("xulrunner.exe", StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
if (isEnginePath && (isFirefox || isXulRunner))
|
||||
{
|
||||
legacyBrowsers.Add(application);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var legacyBrowser in legacyBrowsers)
|
||||
{
|
||||
settings.Applications.Whitelist.Remove(legacyBrowser);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
311
SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs
Normal file
311
SafeExamBrowser.Configuration/ConfigurationData/DataValues.cs
Normal file
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
using SafeExamBrowser.Settings.Applications;
|
||||
using SafeExamBrowser.Settings.Browser;
|
||||
using SafeExamBrowser.Settings.Browser.Proxy;
|
||||
using SafeExamBrowser.Settings.Logging;
|
||||
using SafeExamBrowser.Settings.Proctoring;
|
||||
using SafeExamBrowser.Settings.Security;
|
||||
using SafeExamBrowser.Settings.Service;
|
||||
using SafeExamBrowser.Settings.UserInterface;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||
{
|
||||
internal class DataValues
|
||||
{
|
||||
private const string DEFAULT_CONFIGURATION_NAME = "SebClientSettings.seb";
|
||||
private AppConfig appConfig;
|
||||
|
||||
internal string GetAppDataFilePath()
|
||||
{
|
||||
return appConfig.AppDataFilePath;
|
||||
}
|
||||
|
||||
internal AppConfig InitializeAppConfig()
|
||||
{
|
||||
var executable = Assembly.GetEntryAssembly();
|
||||
var certificate = executable.Modules.First().GetSignerCertificate();
|
||||
var programBuild = FileVersionInfo.GetVersionInfo(executable.Location).FileVersion;
|
||||
var programCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright;
|
||||
var programTitle = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title;
|
||||
var programVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
var appDataLocalFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), nameof(SafeExamBrowser));
|
||||
var appDataRoamingFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser));
|
||||
var programDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), nameof(SafeExamBrowser));
|
||||
var temporaryFolder = Path.Combine(appDataLocalFolder, "Temp");
|
||||
var startTime = DateTime.Now;
|
||||
var logFolder = Path.Combine(appDataLocalFolder, "Logs");
|
||||
var logFilePrefix = startTime.ToString("yyyy-MM-dd\\_HH\\hmm\\mss\\s");
|
||||
|
||||
appConfig = new AppConfig();
|
||||
appConfig.AppDataFilePath = Path.Combine(appDataRoamingFolder, DEFAULT_CONFIGURATION_NAME);
|
||||
appConfig.ApplicationStartTime = startTime;
|
||||
appConfig.BrowserCachePath = Path.Combine(appDataLocalFolder, "Cache");
|
||||
appConfig.BrowserLogFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Browser.log");
|
||||
appConfig.ClientId = Guid.NewGuid();
|
||||
appConfig.ClientAddress = $"{AppConfig.BASE_ADDRESS}/client/{Guid.NewGuid()}";
|
||||
appConfig.ClientExecutablePath = Path.Combine(Path.GetDirectoryName(executable.Location), $"{nameof(SafeExamBrowser)}.Client.exe");
|
||||
appConfig.ClientLogFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Client.log");
|
||||
appConfig.CodeSignatureHash = certificate?.GetCertHashString();
|
||||
appConfig.ConfigurationFileExtension = ".seb";
|
||||
appConfig.ConfigurationFileMimeType = "application/seb";
|
||||
appConfig.ProgramBuildVersion = programBuild;
|
||||
appConfig.ProgramCopyright = programCopyright;
|
||||
appConfig.ProgramDataFilePath = Path.Combine(programDataFolder, DEFAULT_CONFIGURATION_NAME);
|
||||
appConfig.ProgramTitle = programTitle;
|
||||
appConfig.ProgramInformationalVersion = programVersion;
|
||||
appConfig.RuntimeId = Guid.NewGuid();
|
||||
appConfig.RuntimeAddress = $"{AppConfig.BASE_ADDRESS}/runtime/{Guid.NewGuid()}";
|
||||
appConfig.RuntimeLogFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Runtime.log");
|
||||
appConfig.SebUriScheme = "seb";
|
||||
appConfig.SebUriSchemeSecure = "sebs";
|
||||
appConfig.ServiceAddress = $"{AppConfig.BASE_ADDRESS}/service";
|
||||
appConfig.ServiceEventName = $@"Global\{nameof(SafeExamBrowser)}-{Guid.NewGuid()}";
|
||||
appConfig.ServiceLogFilePath = Path.Combine(logFolder, $"{logFilePrefix}_Service.log");
|
||||
appConfig.SessionCacheFilePath = Path.Combine(temporaryFolder, "cache.bin");
|
||||
appConfig.TemporaryDirectory = temporaryFolder;
|
||||
|
||||
return appConfig;
|
||||
}
|
||||
|
||||
internal SessionConfiguration InitializeSessionConfiguration()
|
||||
{
|
||||
var configuration = new SessionConfiguration();
|
||||
|
||||
appConfig.ClientId = Guid.NewGuid();
|
||||
appConfig.ClientAddress = $"{AppConfig.BASE_ADDRESS}/client/{Guid.NewGuid()}";
|
||||
appConfig.ServiceEventName = $@"Global\{nameof(SafeExamBrowser)}-{Guid.NewGuid()}";
|
||||
|
||||
configuration.AppConfig = appConfig.Clone();
|
||||
configuration.ClientAuthenticationToken = Guid.NewGuid();
|
||||
configuration.SessionId = Guid.NewGuid();
|
||||
|
||||
return configuration;
|
||||
}
|
||||
|
||||
internal AppSettings LoadDefaultSettings()
|
||||
{
|
||||
var settings = new AppSettings();
|
||||
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "AA_v3.exe", OriginalName = "AA_v3.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "AeroAdmin.exe", OriginalName = "AeroAdmin.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "beamyourscreen-host.exe", OriginalName = "beamyourscreen-host.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "CamPlay.exe", OriginalName = "CamPlay.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Camtasia.exe", OriginalName = "Camtasia.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "CamtasiaStudio.exe", OriginalName = "CamtasiaStudio.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Camtasia_Studio.exe", OriginalName = "Camtasia_Studio.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "CamRecorder.exe", OriginalName = "CamRecorder.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "CamtasiaUtl.exe", OriginalName = "CamtasiaUtl.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "chromoting.exe", OriginalName = "chromoting.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "CiscoCollabHost.exe", OriginalName = "CiscoCollabHost.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "CiscoWebExStart.exe", OriginalName = "CiscoWebExStart.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Discord.exe", OriginalName = "Discord.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Element.exe", OriginalName = "Element.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "g2mcomm.exe", OriginalName = "g2mcomm.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "g2mlauncher.exe", OriginalName = "g2mlauncher.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "g2mstart.exe", OriginalName = "g2mstart.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "GotoMeetingWinStore.exe", OriginalName = "GotoMeetingWinStore.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "join.me.exe", OriginalName = "join.me.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "join.me.sentinel.exe", OriginalName = "join.me.sentinel.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Microsoft.Media.player.exe", OriginalName = "Microsoft.Media.player.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Mikogo-host.exe", OriginalName = "Mikogo-host.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "MS-teams.exe", OriginalName = "MS-Teams.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "obs32.exe", OriginalName = "obs32.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "obs64.exe", OriginalName = "obs64.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "PCMonitorSrv.exe", OriginalName = "PCMonitorSrv.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "pcmontask.exe", OriginalName = "pcmontask.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "ptoneclk.exe", OriginalName = "ptoneclk.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "RemotePCDesktop.exe", OriginalName = "RemotePCDesktop.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "remoting_host.exe", OriginalName = "remoting_host.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "RPCService.exe", OriginalName = "RPCService.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "RPCSuite.exe", OriginalName = "RPCSuite.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "sethc.exe", OriginalName = "sethc.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Skype.exe", OriginalName = "Skype.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "SkypeApp.exe", OriginalName = "SkypeApp.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "SkypeHost.exe", OriginalName = "SkypeHost.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "slack.exe", OriginalName = "slack.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "spotify.exe", OriginalName = "spotify.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "SRServer.exe", OriginalName = "SRServer.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "strwinclt.exe", OriginalName = "strwinclt.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Teams.exe", OriginalName = "Teams.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "TeamViewer.exe", OriginalName = "TeamViewer.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Telegram.exe", OriginalName = "Telegram.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "VLC.exe", OriginalName = "VLC.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "vncserver.exe", OriginalName = "vncserver.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "vncviewer.exe", OriginalName = "vncviewer.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "vncserverui.exe", OriginalName = "vncserverui.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "webexmta.exe", OriginalName = "webexmta.exe" });
|
||||
settings.Applications.Blacklist.Add(new BlacklistApplication { ExecutableName = "Zoom.exe", OriginalName = "Zoom.exe" });
|
||||
|
||||
settings.Browser.AdditionalWindow.AllowAddressBar = false;
|
||||
settings.Browser.AdditionalWindow.AllowBackwardNavigation = true;
|
||||
settings.Browser.AdditionalWindow.AllowDeveloperConsole = false;
|
||||
settings.Browser.AdditionalWindow.AllowForwardNavigation = true;
|
||||
settings.Browser.AdditionalWindow.AllowReloading = true;
|
||||
settings.Browser.AdditionalWindow.FullScreenMode = false;
|
||||
settings.Browser.AdditionalWindow.Position = WindowPosition.Right;
|
||||
settings.Browser.AdditionalWindow.RelativeHeight = 100;
|
||||
settings.Browser.AdditionalWindow.RelativeWidth = 50;
|
||||
settings.Browser.AdditionalWindow.ShowHomeButton = false;
|
||||
settings.Browser.AdditionalWindow.ShowReloadWarning = false;
|
||||
settings.Browser.AdditionalWindow.ShowToolbar = false;
|
||||
settings.Browser.AdditionalWindow.UrlPolicy = UrlPolicy.Never;
|
||||
settings.Browser.AllowConfigurationDownloads = true;
|
||||
settings.Browser.AllowCustomDownAndUploadLocation = false;
|
||||
settings.Browser.AllowDownloads = true;
|
||||
settings.Browser.AllowFind = true;
|
||||
settings.Browser.AllowPageZoom = true;
|
||||
settings.Browser.AllowPdfReader = true;
|
||||
settings.Browser.AllowPdfReaderToolbar = false;
|
||||
settings.Browser.AllowPrint = false;
|
||||
settings.Browser.AllowUploads = true;
|
||||
settings.Browser.DeleteCacheOnShutdown = true;
|
||||
settings.Browser.DeleteCookiesOnShutdown = true;
|
||||
settings.Browser.DeleteCookiesOnStartup = true;
|
||||
settings.Browser.EnableBrowser = true;
|
||||
settings.Browser.MainWindow.AllowAddressBar = false;
|
||||
settings.Browser.MainWindow.AllowBackwardNavigation = false;
|
||||
settings.Browser.MainWindow.AllowDeveloperConsole = false;
|
||||
settings.Browser.MainWindow.AllowForwardNavigation = false;
|
||||
settings.Browser.MainWindow.AllowReloading = true;
|
||||
settings.Browser.MainWindow.FullScreenMode = false;
|
||||
settings.Browser.MainWindow.RelativeHeight = 100;
|
||||
settings.Browser.MainWindow.RelativeWidth = 100;
|
||||
settings.Browser.MainWindow.ShowHomeButton = false;
|
||||
settings.Browser.MainWindow.ShowReloadWarning = true;
|
||||
settings.Browser.MainWindow.ShowToolbar = false;
|
||||
settings.Browser.MainWindow.UrlPolicy = UrlPolicy.Never;
|
||||
settings.Browser.PopupPolicy = PopupPolicy.Allow;
|
||||
settings.Browser.Proxy.Policy = ProxyPolicy.System;
|
||||
settings.Browser.ResetOnQuitUrl = false;
|
||||
settings.Browser.SendBrowserExamKey = false;
|
||||
settings.Browser.SendConfigurationKey = false;
|
||||
settings.Browser.ShowFileSystemElementPath = true;
|
||||
settings.Browser.StartUrl = "https://www.safeexambrowser.org/start";
|
||||
settings.Browser.UseCustomUserAgent = false;
|
||||
settings.Browser.UseIsolatedClipboard = true;
|
||||
settings.Browser.UseQueryParameter = false;
|
||||
settings.Browser.UseTemporaryDownAndUploadDirectory = false;
|
||||
|
||||
settings.ConfigurationMode = ConfigurationMode.Exam;
|
||||
|
||||
settings.Display.AllowedDisplays = 1;
|
||||
settings.Display.AlwaysOn = true;
|
||||
settings.Display.IgnoreError = false;
|
||||
settings.Display.InternalDisplayOnly = false;
|
||||
|
||||
settings.Keyboard.AllowAltEsc = false;
|
||||
settings.Keyboard.AllowAltF4 = false;
|
||||
settings.Keyboard.AllowAltTab = true;
|
||||
settings.Keyboard.AllowCtrlC = true;
|
||||
settings.Keyboard.AllowCtrlEsc = false;
|
||||
settings.Keyboard.AllowCtrlV = true;
|
||||
settings.Keyboard.AllowCtrlX = true;
|
||||
settings.Keyboard.AllowEsc = true;
|
||||
settings.Keyboard.AllowF1 = true;
|
||||
settings.Keyboard.AllowF2 = true;
|
||||
settings.Keyboard.AllowF3 = true;
|
||||
settings.Keyboard.AllowF4 = true;
|
||||
settings.Keyboard.AllowF5 = true;
|
||||
settings.Keyboard.AllowF6 = true;
|
||||
settings.Keyboard.AllowF7 = true;
|
||||
settings.Keyboard.AllowF8 = true;
|
||||
settings.Keyboard.AllowF9 = true;
|
||||
settings.Keyboard.AllowF10 = true;
|
||||
settings.Keyboard.AllowF11 = true;
|
||||
settings.Keyboard.AllowF12 = true;
|
||||
settings.Keyboard.AllowPrintScreen = false;
|
||||
settings.Keyboard.AllowSystemKey = false;
|
||||
|
||||
settings.LogLevel = LogLevel.Debug;
|
||||
|
||||
settings.Mouse.AllowMiddleButton = false;
|
||||
settings.Mouse.AllowRightButton = true;
|
||||
|
||||
settings.PowerSupply.ChargeThresholdCritical = 0.1;
|
||||
settings.PowerSupply.ChargeThresholdLow = 0.2;
|
||||
|
||||
settings.Proctoring.Enabled = false;
|
||||
settings.Proctoring.ForceRaiseHandMessage = false;
|
||||
settings.Proctoring.ScreenProctoring.Enabled = false;
|
||||
settings.Proctoring.ScreenProctoring.ImageDownscaling = 1.0;
|
||||
settings.Proctoring.ScreenProctoring.ImageFormat = ImageFormat.Png;
|
||||
settings.Proctoring.ScreenProctoring.ImageQuantization = ImageQuantization.Grayscale4bpp;
|
||||
settings.Proctoring.ScreenProctoring.MaxInterval = 5000;
|
||||
settings.Proctoring.ScreenProctoring.MetaData.CaptureApplicationData = true;
|
||||
settings.Proctoring.ScreenProctoring.MetaData.CaptureBrowserData = true;
|
||||
settings.Proctoring.ScreenProctoring.MetaData.CaptureWindowTitle = true;
|
||||
settings.Proctoring.ScreenProctoring.MinInterval = 1000;
|
||||
settings.Proctoring.ShowRaiseHandNotification = true;
|
||||
settings.Proctoring.ShowTaskbarNotification = true;
|
||||
|
||||
settings.Security.AllowApplicationLogAccess = false;
|
||||
settings.Security.AllowTermination = true;
|
||||
settings.Security.AllowReconfiguration = false;
|
||||
settings.Security.AllowStickyKeys = false;
|
||||
settings.Security.ClipboardPolicy = ClipboardPolicy.Isolated;
|
||||
settings.Security.DisableSessionChangeLockScreen = false;
|
||||
settings.Security.KioskMode = KioskMode.CreateNewDesktop;
|
||||
settings.Security.VerifyCursorConfiguration = true;
|
||||
settings.Security.VerifySessionIntegrity = true;
|
||||
settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Deny;
|
||||
|
||||
settings.Server.PingInterval = 1000;
|
||||
settings.Server.RequestAttemptInterval = 2000;
|
||||
settings.Server.RequestAttempts = 5;
|
||||
settings.Server.RequestTimeout = 30000;
|
||||
settings.Server.PerformFallback = false;
|
||||
|
||||
settings.Service.DisableChromeNotifications = true;
|
||||
settings.Service.DisableEaseOfAccessOptions = true;
|
||||
settings.Service.DisableFindPrinter = true;
|
||||
settings.Service.DisableNetworkOptions = true;
|
||||
settings.Service.DisablePasswordChange = true;
|
||||
settings.Service.DisablePowerOptions = true;
|
||||
settings.Service.DisableRemoteConnections = true;
|
||||
settings.Service.DisableSignout = true;
|
||||
settings.Service.DisableTaskManager = true;
|
||||
settings.Service.DisableUserLock = true;
|
||||
settings.Service.DisableUserSwitch = true;
|
||||
settings.Service.DisableVmwareOverlay = true;
|
||||
settings.Service.DisableWindowsUpdate = true;
|
||||
settings.Service.IgnoreService = true;
|
||||
settings.Service.Policy = ServicePolicy.Mandatory;
|
||||
settings.Service.SetVmwareConfiguration = false;
|
||||
|
||||
settings.SessionMode = SessionMode.Normal;
|
||||
|
||||
settings.System.AlwaysOn = true;
|
||||
|
||||
settings.UserInterface.ActionCenter.EnableActionCenter = true;
|
||||
settings.UserInterface.ActionCenter.ShowApplicationInfo = true;
|
||||
settings.UserInterface.ActionCenter.ShowApplicationLog = false;
|
||||
settings.UserInterface.ActionCenter.ShowClock = true;
|
||||
settings.UserInterface.ActionCenter.ShowKeyboardLayout = true;
|
||||
settings.UserInterface.ActionCenter.ShowNetwork = false;
|
||||
settings.UserInterface.LockScreen.BackgroundColor = "#ff0000";
|
||||
settings.UserInterface.Mode = UserInterfaceMode.Desktop;
|
||||
settings.UserInterface.Taskbar.EnableTaskbar = true;
|
||||
settings.UserInterface.Taskbar.ShowApplicationInfo = false;
|
||||
settings.UserInterface.Taskbar.ShowApplicationLog = false;
|
||||
settings.UserInterface.Taskbar.ShowClock = true;
|
||||
settings.UserInterface.Taskbar.ShowKeyboardLayout = true;
|
||||
settings.UserInterface.Taskbar.ShowNetwork = false;
|
||||
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
107
SafeExamBrowser.Configuration/ConfigurationData/Json.cs
Normal file
107
SafeExamBrowser.Configuration/ConfigurationData/Json.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||
{
|
||||
internal static class Json
|
||||
{
|
||||
internal static void Serialize(IDictionary<string, object> dictionary, StreamWriter stream)
|
||||
{
|
||||
var orderedByKey = dictionary.OrderBy(d => d.Key, StringComparer.InvariantCulture).ToList();
|
||||
|
||||
stream.Write('{');
|
||||
|
||||
foreach (var kvp in orderedByKey)
|
||||
{
|
||||
var process = true;
|
||||
|
||||
process &= !kvp.Key.Equals(Keys.General.OriginatorVersion, StringComparison.OrdinalIgnoreCase);
|
||||
process &= !(kvp.Value is IDictionary<string, object> d) || d.Any();
|
||||
|
||||
if (process)
|
||||
{
|
||||
stream.Write('"');
|
||||
stream.Write(kvp.Key);
|
||||
stream.Write('"');
|
||||
stream.Write(':');
|
||||
|
||||
Serialize(kvp.Value, stream);
|
||||
|
||||
if (kvp.Key != orderedByKey.Last().Key)
|
||||
{
|
||||
stream.Write(',');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stream.Write('}');
|
||||
}
|
||||
|
||||
private static void Serialize(IList<object> list, StreamWriter stream)
|
||||
{
|
||||
stream.Write('[');
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
Serialize(item, stream);
|
||||
|
||||
if (item != list.Last())
|
||||
{
|
||||
stream.Write(',');
|
||||
}
|
||||
}
|
||||
|
||||
stream.Write(']');
|
||||
}
|
||||
|
||||
private static void Serialize(object value, StreamWriter stream)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case IDictionary<string, object> dictionary:
|
||||
Serialize(dictionary, stream);
|
||||
break;
|
||||
case IList<object> list:
|
||||
Serialize(list, stream);
|
||||
break;
|
||||
case byte[] data:
|
||||
stream.Write('"');
|
||||
stream.Write(Convert.ToBase64String(data));
|
||||
stream.Write('"');
|
||||
break;
|
||||
case DateTime date:
|
||||
stream.Write(date.ToString("o"));
|
||||
break;
|
||||
case bool boolean:
|
||||
stream.Write(boolean.ToString().ToLower());
|
||||
break;
|
||||
case int integer:
|
||||
stream.Write(integer.ToString(NumberFormatInfo.InvariantInfo));
|
||||
break;
|
||||
case double number:
|
||||
stream.Write(number.ToString(NumberFormatInfo.InvariantInfo));
|
||||
break;
|
||||
case string text:
|
||||
stream.Write('"');
|
||||
stream.Write(text);
|
||||
stream.Write('"');
|
||||
break;
|
||||
case null:
|
||||
stream.Write('"');
|
||||
stream.Write('"');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
371
SafeExamBrowser.Configuration/ConfigurationData/Keys.cs
Normal file
371
SafeExamBrowser.Configuration/ConfigurationData/Keys.cs
Normal file
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ConfigurationData
|
||||
{
|
||||
internal static class Keys
|
||||
{
|
||||
internal const int WINDOWS = 1;
|
||||
|
||||
internal static class Applications
|
||||
{
|
||||
internal const string Active = "active";
|
||||
internal const string AllowCustomPath = "allowUserToChooseApp";
|
||||
internal const string AllowRunning = "runInBackground";
|
||||
internal const string Argument = "argument";
|
||||
internal const string Arguments = "arguments";
|
||||
internal const string AutoStart = "autostart";
|
||||
internal const string AutoTerminate = "strongKill";
|
||||
internal const string Blacklist = "prohibitedProcesses";
|
||||
internal const string Description = "description";
|
||||
internal const string DisplayName = "title";
|
||||
internal const string ExecutableName = "executable";
|
||||
internal const string ExecutablePath = "path";
|
||||
internal const string OperatingSystem = "os";
|
||||
internal const string OriginalName = "originalName";
|
||||
internal const string ShowInShell = "iconInTaskbar";
|
||||
internal const string Signature = "signature";
|
||||
internal const string Whitelist = "permittedProcesses";
|
||||
}
|
||||
|
||||
internal static class Audio
|
||||
{
|
||||
internal const string InitialVolumeLevel = "audioVolumeLevel";
|
||||
internal const string MuteAudio = "audioMute";
|
||||
internal const string SetInitialVolumeLevel = "audioSetVolumeLevel";
|
||||
}
|
||||
|
||||
internal static class Browser
|
||||
{
|
||||
internal const string AllowConfigurationDownloads = "downloadAndOpenSebConfig";
|
||||
internal const string AllowCustomDownUploadLocation = "allowCustomDownUploadLocation";
|
||||
internal const string AllowDeveloperConsole = "allowDeveloperConsole";
|
||||
internal const string AllowDownloads = "allowDownloads";
|
||||
internal const string AllowDownloadsAndUploads = "allowDownUploads";
|
||||
internal const string AllowFind = "allowFind";
|
||||
internal const string AllowPageZoom = "enableZoomPage";
|
||||
internal const string AllowPdfReaderToolbar = "allowPDFReaderToolbar";
|
||||
internal const string AllowPrint = "allowPrint";
|
||||
internal const string AllowSpellChecking = "allowSpellCheck";
|
||||
internal const string AllowUploads = "allowUploads";
|
||||
internal const string CustomUserAgentDesktop = "browserUserAgentWinDesktopModeCustom";
|
||||
internal const string CustomUserAgentMobile = "browserUserAgentWinTouchModeCustom";
|
||||
internal const string DeleteCacheOnShutdown = "removeBrowserProfile";
|
||||
internal const string DeleteCookiesOnShutdown = "examSessionClearCookiesOnEnd";
|
||||
internal const string DeleteCookiesOnStartup = "examSessionClearCookiesOnStart";
|
||||
internal const string DownloadDirectory = "downloadDirectoryWin";
|
||||
internal const string DownloadPdfFiles = "downloadPDFFiles";
|
||||
internal const string EnableBrowser = "enableSebBrowser";
|
||||
internal const string ExamKeySalt = "examKeySalt";
|
||||
internal const string HomeButtonMessage = "restartExamText";
|
||||
internal const string HomeButtonRequiresPassword = "restartExamPasswordProtected";
|
||||
internal const string HomeButtonUrl = "restartExamURL";
|
||||
internal const string HomeButtonUseStartUrl = "restartExamUseStartURL";
|
||||
internal const string PopupPolicy = "newBrowserWindowByLinkPolicy";
|
||||
internal const string PopupBlockForeignHost = "newBrowserWindowByLinkBlockForeign";
|
||||
internal const string QuitUrl = "quitURL";
|
||||
internal const string QuitUrlConfirmation = "quitURLConfirm";
|
||||
internal const string ResetOnQuitUrl = "quitURLRestart";
|
||||
internal const string SendCustomHeaders = "sendBrowserExamKey";
|
||||
internal const string ShowFileSystemElementPath = "browserShowFileSystemElementPath";
|
||||
internal const string ShowReloadButton = "showReloadButton";
|
||||
internal const string ShowToolbar = "enableBrowserWindowToolbar";
|
||||
internal const string StartUrl = "startURL";
|
||||
internal const string UserAgentModeDesktop = "browserUserAgentWinDesktopMode";
|
||||
internal const string UserAgentModeMobile = "browserUserAgentWinTouchMode";
|
||||
internal const string UserAgentSuffix = "browserUserAgent";
|
||||
internal const string UseStartUrlQuery = "startURLAppendQueryParameter";
|
||||
internal const string UseTemporaryDownUploadDirectory = "useTemporaryDownUploadDirectory";
|
||||
|
||||
internal static class AdditionalWindow
|
||||
{
|
||||
internal const string AllowAddressBar = "newBrowserWindowAllowAddressBar";
|
||||
internal const string AllowNavigation = "newBrowserWindowNavigation";
|
||||
internal const string AllowReload = "newBrowserWindowAllowReload";
|
||||
internal const string ShowReloadWarning = "newBrowserWindowShowReloadWarning";
|
||||
internal const string UrlPolicy = "newBrowserWindowShowURL";
|
||||
internal const string WindowHeight = "newBrowserWindowByLinkHeight";
|
||||
internal const string WindowWidth = "newBrowserWindowByLinkWidth";
|
||||
internal const string WindowPosition = "newBrowserWindowByLinkPositioning";
|
||||
}
|
||||
|
||||
internal static class Filter
|
||||
{
|
||||
internal const string EnableContentRequestFilter = "URLFilterEnableContentFilter";
|
||||
internal const string EnableMainRequestFilter = "URLFilterEnable";
|
||||
internal const string FilterRules = "URLFilterRules";
|
||||
internal const string RuleAction = "action";
|
||||
internal const string RuleIsActive = "active";
|
||||
internal const string RuleExpression = "expression";
|
||||
internal const string RuleExpressionIsRegex = "regex";
|
||||
}
|
||||
|
||||
internal static class MainWindow
|
||||
{
|
||||
internal const string AllowAddressBar = "browserWindowAllowAddressBar";
|
||||
internal const string AllowNavigation = "allowBrowsingBackForward";
|
||||
internal const string AllowReload = "browserWindowAllowReload";
|
||||
internal const string ShowReloadWarning = "showReloadWarning";
|
||||
internal const string UrlPolicy = "browserWindowShowURL";
|
||||
internal const string WindowHeight = "mainBrowserWindowHeight";
|
||||
internal const string WindowMode = "browserViewMode";
|
||||
internal const string WindowWidth = "mainBrowserWindowWidth";
|
||||
internal const string WindowPosition = "mainBrowserWindowPositioning";
|
||||
}
|
||||
|
||||
internal static class Proxy
|
||||
{
|
||||
internal const string AutoConfigure = "AutoConfigurationEnabled";
|
||||
internal const string AutoConfigureUrl = "AutoConfigurationURL";
|
||||
internal const string AutoDetect = "AutoDiscoveryEnabled";
|
||||
internal const string BypassList = "ExceptionsList";
|
||||
internal const string Policy = "proxySettingsPolicy";
|
||||
internal const string Settings = "proxies";
|
||||
|
||||
internal static class Ftp
|
||||
{
|
||||
internal const string Enable = "FTPEnable";
|
||||
internal const string Host = "FTPProxy";
|
||||
internal const string Password = "FTPPassword";
|
||||
internal const string Port = "FTPPort";
|
||||
internal const string RequiresAuthentication = "FTPRequiresPassword";
|
||||
internal const string Username = "FTPUsername";
|
||||
}
|
||||
|
||||
internal static class Http
|
||||
{
|
||||
internal const string Enable = "HTTPEnable";
|
||||
internal const string Host = "HTTPProxy";
|
||||
internal const string Password = "HTTPPassword";
|
||||
internal const string Port = "HTTPPort";
|
||||
internal const string RequiresAuthentication = "HTTPRequiresPassword";
|
||||
internal const string Username = "HTTPUsername";
|
||||
}
|
||||
|
||||
internal static class Https
|
||||
{
|
||||
internal const string Enable = "HTTPSEnable";
|
||||
internal const string Host = "HTTPSProxy";
|
||||
internal const string Password = "HTTPSPassword";
|
||||
internal const string Port = "HTTPSPort";
|
||||
internal const string RequiresAuthentication = "HTTPSRequiresPassword";
|
||||
internal const string Username = "HTTPSUsername";
|
||||
}
|
||||
|
||||
internal static class Socks
|
||||
{
|
||||
internal const string Enable = "SOCKSEnable";
|
||||
internal const string Host = "SOCKSProxy";
|
||||
internal const string Password = "SOCKSPassword";
|
||||
internal const string Port = "SOCKSPort";
|
||||
internal const string RequiresAuthentication = "SOCKSRequiresPassword";
|
||||
internal const string Username = "SOCKSUsername";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ConfigurationFile
|
||||
{
|
||||
internal const string ConfigurationPurpose = "sebConfigPurpose";
|
||||
internal const string KeepClientConfigEncryption = "clientConfigKeepEncryption";
|
||||
internal const string SessionMode = "sebMode";
|
||||
}
|
||||
|
||||
internal static class Display
|
||||
{
|
||||
internal const string AllowedDisplays = "allowedDisplaysMaxNumber";
|
||||
internal const string AlwaysOn = "displayAlwaysOn";
|
||||
internal const string IgnoreError = "allowedDisplaysIgnoreFailure";
|
||||
internal const string InternalDisplayOnly = "allowedDisplayBuiltinEnforce";
|
||||
}
|
||||
|
||||
internal static class General
|
||||
{
|
||||
internal const string LogLevel = "logLevel";
|
||||
internal const string OriginatorVersion = "originatorVersion";
|
||||
}
|
||||
|
||||
internal static class Keyboard
|
||||
{
|
||||
internal const string EnableAltEsc = "enableAltEsc";
|
||||
internal const string EnableAltTab = "enableAltTab";
|
||||
internal const string EnableAltF4 = "enableAltF4";
|
||||
internal const string EnableCtrlEsc = "enableCtrlEsc";
|
||||
internal const string EnableEsc = "enableEsc";
|
||||
internal const string EnableF1 = "enableF1";
|
||||
internal const string EnableF2 = "enableF2";
|
||||
internal const string EnableF3 = "enableF3";
|
||||
internal const string EnableF4 = "enableF4";
|
||||
internal const string EnableF5 = "enableF5";
|
||||
internal const string EnableF6 = "enableF6";
|
||||
internal const string EnableF7 = "enableF7";
|
||||
internal const string EnableF8 = "enableF8";
|
||||
internal const string EnableF9 = "enableF9";
|
||||
internal const string EnableF10 = "enableF10";
|
||||
internal const string EnableF11 = "enableF11";
|
||||
internal const string EnableF12 = "enableF12";
|
||||
internal const string EnablePrintScreen = "enablePrintScreen";
|
||||
internal const string EnableSystemKey = "enableStartMenu";
|
||||
}
|
||||
|
||||
internal static class Mouse
|
||||
{
|
||||
internal const string EnableMiddleMouseButton = "enableMiddleMouse";
|
||||
internal const string EnableRightMouseButton = "enableRightMouse";
|
||||
}
|
||||
|
||||
internal static class Network
|
||||
{
|
||||
internal static class Certificates
|
||||
{
|
||||
internal const string CertificateData = "certificateData";
|
||||
internal const string CertificateType = "type";
|
||||
internal const string EmbeddedCertificates = "embeddedCertificates";
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Proctoring
|
||||
{
|
||||
internal const string ForceRaiseHandMessage = "raiseHandButtonAlwaysPromptMessage";
|
||||
internal const string ShowRaiseHand = "raiseHandButtonShow";
|
||||
internal const string ShowTaskbarNotification = "showProctoringViewButton";
|
||||
|
||||
internal static class ScreenProctoring
|
||||
{
|
||||
internal const string ClientId = "screenProctoringClientId";
|
||||
internal const string ClientSecret = "screenProctoringClientSecret";
|
||||
internal const string Enabled = "enableScreenProctoring";
|
||||
internal const string GroupId = "screenProctoringGroupId";
|
||||
internal const string ImageDownscaling = "screenProctoringImageDownscale";
|
||||
internal const string ImageFormat = "screenProctoringImageFormat";
|
||||
internal const string ImageQuantization = "screenProctoringImageQuantization";
|
||||
internal const string MaxInterval = "screenProctoringScreenshotMaxInterval";
|
||||
internal const string MinInterval = "screenProctoringScreenshotMinInterval";
|
||||
internal const string ServiceUrl = "screenProctoringServiceURL";
|
||||
|
||||
internal static class MetaData
|
||||
{
|
||||
internal const string CaptureApplicationData = "screenProctoringMetadataActiveAppEnabled";
|
||||
internal const string CaptureBrowserData = "screenProctoringMetadataURLEnabled";
|
||||
internal const string CaptureWindowTitle = "screenProctoringMetadataWindowTitleEnabled";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Security
|
||||
{
|
||||
internal const string AdminPasswordHash = "hashedAdminPassword";
|
||||
internal const string AllowApplicationLog = "allowApplicationLog";
|
||||
internal const string AllowReconfiguration = "examSessionReconfigureAllow";
|
||||
internal const string AllowStickyKeys = "allowStickyKeys";
|
||||
internal const string AllowTermination = "allowQuit";
|
||||
internal const string AllowVirtualMachine = "allowVirtualMachine";
|
||||
internal const string ClipboardPolicy = "clipboardPolicy";
|
||||
internal const string DisableSessionChangeLockScreen = "disableSessionChangeLockScreen";
|
||||
internal const string KioskModeCreateNewDesktop = "createNewDesktop";
|
||||
internal const string KioskModeDisableExplorerShell = "killExplorerShell";
|
||||
internal const string QuitPasswordHash = "hashedQuitPassword";
|
||||
internal const string ReconfigurationUrl = "examSessionReconfigureConfigURL";
|
||||
internal const string VerifyCursorConfiguration = "enableCursorVerification";
|
||||
internal const string VerifySessionIntegrity = "enableSessionVerification";
|
||||
internal const string VersionRestrictions = "sebAllowedVersions";
|
||||
}
|
||||
|
||||
internal static class Server
|
||||
{
|
||||
internal const string ApiUrl = "apiDiscovery";
|
||||
internal const string ClientName = "clientName";
|
||||
internal const string ClientSecret = "clientSecret";
|
||||
internal const string Configuration = "sebServerConfiguration";
|
||||
internal const string ExamId = "exam";
|
||||
internal const string FallbackPasswordHash = "sebServerFallbackPasswordHash";
|
||||
internal const string Institution = "institution";
|
||||
internal const string PerformFallback = "sebServerFallback";
|
||||
internal const string PingInterval = "pingInterval";
|
||||
internal const string RequestAttempts = "sebServerFallbackAttempts";
|
||||
internal const string RequestAttemptInterval = "sebServerFallbackAttemptInterval";
|
||||
internal const string RequestTimeout = "sebServerFallbackTimeout";
|
||||
internal const string ServerUrl = "sebServerURL";
|
||||
}
|
||||
|
||||
internal static class Service
|
||||
{
|
||||
internal const string EnableChromeNotifications = "enableChromeNotifications";
|
||||
internal const string EnableEaseOfAccessOptions = "insideSebEnableEaseOfAccess";
|
||||
internal const string EnableFindPrinter = "enableFindPrinter";
|
||||
internal const string EnableNetworkOptions = "insideSebEnableNetworkConnectionSelector";
|
||||
internal const string EnablePasswordChange = "insideSebEnableChangeAPassword";
|
||||
internal const string EnablePowerOptions = "insideSebEnableShutDown";
|
||||
internal const string EnableRemoteConnections = "allowScreenSharing";
|
||||
internal const string EnableSignout = "insideSebEnableLogOff";
|
||||
internal const string EnableTaskManager = "insideSebEnableStartTaskManager";
|
||||
internal const string EnableUserLock = "insideSebEnableLockThisComputer";
|
||||
internal const string EnableUserSwitch = "insideSebEnableSwitchUser";
|
||||
internal const string EnableVmwareOverlay = "insideSebEnableVmWareClientShade";
|
||||
internal const string EnableWindowsUpdate = "enableWindowsUpdate";
|
||||
internal const string IgnoreService = "sebServiceIgnore";
|
||||
internal const string Policy = "sebServicePolicy";
|
||||
internal const string SetVmwareConfiguration = "setVmwareConfiguration";
|
||||
}
|
||||
|
||||
internal static class System
|
||||
{
|
||||
internal const string AlwaysOn = "systemAlwaysOn";
|
||||
}
|
||||
|
||||
internal static class UserInterface
|
||||
{
|
||||
internal const string UserInterfaceMode = "touchOptimized";
|
||||
|
||||
internal static class ActionCenter
|
||||
{
|
||||
internal const string EnableActionCenter = "showSideMenu";
|
||||
}
|
||||
|
||||
internal static class LockScreen
|
||||
{
|
||||
internal const string BackgroundColor = "lockScreenBackgroundColor";
|
||||
}
|
||||
|
||||
internal static class SystemControls
|
||||
{
|
||||
internal static class Audio
|
||||
{
|
||||
internal const string Show = "audioControlEnabled";
|
||||
}
|
||||
|
||||
internal static class Clock
|
||||
{
|
||||
internal const string Show = "showTime";
|
||||
}
|
||||
|
||||
internal static class KeyboardLayout
|
||||
{
|
||||
internal const string Show = "showInputLanguage";
|
||||
}
|
||||
|
||||
internal static class Network
|
||||
{
|
||||
internal const string Show = "allowWlan";
|
||||
}
|
||||
|
||||
internal static class PowerSupply
|
||||
{
|
||||
internal const string ChargeThresholdCritical = "batteryChargeThresholdCritical";
|
||||
internal const string ChargeThresholdLow = "batteryChargeThresholdLow";
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Taskbar
|
||||
{
|
||||
internal const string EnableTaskbar = "showTaskBar";
|
||||
internal const string ShowApplicationLog = "showApplicationLogButton";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
259
SafeExamBrowser.Configuration/ConfigurationRepository.cs
Normal file
259
SafeExamBrowser.Configuration/ConfigurationRepository.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SafeExamBrowser.Configuration.ConfigurationData;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataFormats;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataResources;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Settings;
|
||||
|
||||
namespace SafeExamBrowser.Configuration
|
||||
{
|
||||
public class ConfigurationRepository : IConfigurationRepository
|
||||
{
|
||||
private readonly ICertificateStore certificateStore;
|
||||
private readonly IList<IDataParser> dataParsers;
|
||||
private readonly IList<IDataSerializer> dataSerializers;
|
||||
private readonly DataMapper dataMapper;
|
||||
private readonly DataProcessor dataProcessor;
|
||||
private readonly DataValues dataValues;
|
||||
private readonly ILogger logger;
|
||||
private readonly IList<IResourceLoader> resourceLoaders;
|
||||
private readonly IList<IResourceSaver> resourceSavers;
|
||||
|
||||
public ConfigurationRepository(ICertificateStore certificateStore, IModuleLogger logger)
|
||||
{
|
||||
this.certificateStore = certificateStore;
|
||||
this.logger = logger;
|
||||
|
||||
dataParsers = new List<IDataParser>();
|
||||
dataSerializers = new List<IDataSerializer>();
|
||||
dataMapper = new DataMapper();
|
||||
dataProcessor = new DataProcessor();
|
||||
dataValues = new DataValues();
|
||||
resourceLoaders = new List<IResourceLoader>();
|
||||
resourceSavers = new List<IResourceSaver>();
|
||||
}
|
||||
|
||||
public SaveStatus ConfigureClientWith(Uri resource, PasswordParameters password = null)
|
||||
{
|
||||
logger.Info($"Attempting to configure local client with '{resource}'...");
|
||||
|
||||
try
|
||||
{
|
||||
TryLoadData(resource, out var data);
|
||||
|
||||
using (data)
|
||||
{
|
||||
TryParseData(data, out var encryption, out var format, out var rawData, password);
|
||||
|
||||
certificateStore.ExtractAndImportIdentities(rawData);
|
||||
encryption = DetermineEncryptionForClientConfiguration(rawData, encryption);
|
||||
|
||||
var status = TrySerializeData(rawData, format, out var serialized, encryption);
|
||||
|
||||
using (serialized)
|
||||
{
|
||||
if (status == SaveStatus.Success)
|
||||
{
|
||||
status = TrySaveData(new Uri(dataValues.GetAppDataFilePath()), serialized);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Unexpected error while trying to configure local client with '{resource}'!", e);
|
||||
|
||||
return SaveStatus.UnexpectedError;
|
||||
}
|
||||
}
|
||||
|
||||
public AppConfig InitializeAppConfig()
|
||||
{
|
||||
return dataValues.InitializeAppConfig();
|
||||
}
|
||||
|
||||
public SessionConfiguration InitializeSessionConfiguration()
|
||||
{
|
||||
return dataValues.InitializeSessionConfiguration();
|
||||
}
|
||||
|
||||
public AppSettings LoadDefaultSettings()
|
||||
{
|
||||
return dataValues.LoadDefaultSettings();
|
||||
}
|
||||
|
||||
public void Register(IDataParser parser)
|
||||
{
|
||||
dataParsers.Add(parser);
|
||||
}
|
||||
|
||||
public void Register(IDataSerializer serializer)
|
||||
{
|
||||
dataSerializers.Add(serializer);
|
||||
}
|
||||
|
||||
public void Register(IResourceLoader loader)
|
||||
{
|
||||
resourceLoaders.Add(loader);
|
||||
}
|
||||
|
||||
public void Register(IResourceSaver saver)
|
||||
{
|
||||
resourceSavers.Add(saver);
|
||||
}
|
||||
|
||||
public LoadStatus TryLoadSettings(Uri resource, out AppSettings settings, PasswordParameters password = null)
|
||||
{
|
||||
var status = default(LoadStatus);
|
||||
|
||||
settings = LoadDefaultSettings();
|
||||
dataProcessor.ProcessDefault(settings);
|
||||
|
||||
logger.Info($"Initialized default settings, now attempting to load '{resource}'...");
|
||||
|
||||
try
|
||||
{
|
||||
status = TryLoadData(resource, out var stream);
|
||||
|
||||
using (stream)
|
||||
{
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
status = TryParseData(stream, out _, out _, out var data, password);
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
dataMapper.Map(data, settings);
|
||||
dataProcessor.Process(data, settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
status = LoadStatus.UnexpectedError;
|
||||
logger.Error($"Unexpected error while trying to load '{resource}'!", e);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private EncryptionParameters DetermineEncryptionForClientConfiguration(IDictionary<string, object> data, EncryptionParameters encryption)
|
||||
{
|
||||
var hasKey = data.TryGetValue(Keys.ConfigurationFile.KeepClientConfigEncryption, out var value);
|
||||
var useDefaultEncryption = value is bool keepEncryption && !keepEncryption;
|
||||
|
||||
if (!hasKey || (hasKey && useDefaultEncryption))
|
||||
{
|
||||
encryption = new PasswordParameters { Password = string.Empty, IsHash = true };
|
||||
}
|
||||
|
||||
return encryption;
|
||||
}
|
||||
|
||||
private LoadStatus TryLoadData(Uri resource, out Stream data)
|
||||
{
|
||||
var status = LoadStatus.NotSupported;
|
||||
var resourceLoader = resourceLoaders.FirstOrDefault(l => l.CanLoad(resource));
|
||||
|
||||
data = default;
|
||||
|
||||
if (resourceLoader != null)
|
||||
{
|
||||
status = resourceLoader.TryLoad(resource, out data);
|
||||
logger.Info($"Tried to load data from '{resource}' using {resourceLoader.GetType().Name} -> Result: {status}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"No resource loader found for '{resource}'!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private LoadStatus TryParseData(Stream data, out EncryptionParameters encryption, out FormatType format, out IDictionary<string, object> rawData, PasswordParameters password = null)
|
||||
{
|
||||
var parser = dataParsers.FirstOrDefault(p => p.CanParse(data));
|
||||
var status = LoadStatus.NotSupported;
|
||||
|
||||
encryption = default;
|
||||
format = default;
|
||||
rawData = default(Dictionary<string, object>);
|
||||
|
||||
if (parser != null)
|
||||
{
|
||||
var result = parser.TryParse(data, password);
|
||||
|
||||
encryption = result.Encryption;
|
||||
format = result.Format;
|
||||
rawData = result.RawData;
|
||||
status = result.Status;
|
||||
|
||||
logger.Info($"Tried to parse data from '{data}' using {parser.GetType().Name} -> Result: {status}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"No data parser found which can parse '{data}'!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private SaveStatus TrySaveData(Uri destination, Stream data)
|
||||
{
|
||||
var status = SaveStatus.NotSupported;
|
||||
var resourceSaver = resourceSavers.FirstOrDefault(s => s.CanSave(destination));
|
||||
|
||||
if (resourceSaver != null)
|
||||
{
|
||||
status = resourceSaver.TrySave(destination, data);
|
||||
logger.Info($"Tried to save data as '{destination}' using {resourceSaver.GetType().Name} -> Result: {status}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"No resource saver found for '{destination}'!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private SaveStatus TrySerializeData(IDictionary<string, object> data, FormatType format, out Stream serialized, EncryptionParameters encryption = null)
|
||||
{
|
||||
var serializer = dataSerializers.FirstOrDefault(s => s.CanSerialize(format));
|
||||
var status = SaveStatus.NotSupported;
|
||||
|
||||
serialized = default;
|
||||
|
||||
if (serializer != null)
|
||||
{
|
||||
var result = serializer.TrySerialize(data, encryption);
|
||||
|
||||
serialized = result.Data;
|
||||
status = result.Status;
|
||||
|
||||
logger.Info($"Tried to serialize data as '{format}' using {serializer.GetType().Name} -> Result: {status}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"No data serializer found which can serialize '{format}'!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
122
SafeExamBrowser.Configuration/Cryptography/CertificateStore.cs
Normal file
122
SafeExamBrowser.Configuration/Cryptography/CertificateStore.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using SafeExamBrowser.Configuration.ConfigurationData;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
public class CertificateStore : ICertificateStore
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
private readonly X509Store[] stores = new[]
|
||||
{
|
||||
new X509Store(StoreLocation.CurrentUser),
|
||||
new X509Store(StoreLocation.LocalMachine),
|
||||
new X509Store(StoreName.TrustedPeople)
|
||||
};
|
||||
|
||||
public CertificateStore(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool TryGetCertificateWith(byte[] keyHash, out X509Certificate2 certificate)
|
||||
{
|
||||
certificate = default(X509Certificate2);
|
||||
|
||||
using (var algorithm = new SHA1CryptoServiceProvider())
|
||||
{
|
||||
foreach (var store in stores)
|
||||
{
|
||||
try
|
||||
{
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
|
||||
foreach (var current in store.Certificates)
|
||||
{
|
||||
var publicKey = current.PublicKey.EncodedKeyValue.RawData;
|
||||
var publicKeyHash = algorithm.ComputeHash(publicKey);
|
||||
|
||||
if (publicKeyHash.SequenceEqual(keyHash))
|
||||
{
|
||||
certificate = current;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ExtractAndImportIdentities(IDictionary<string, object> data)
|
||||
{
|
||||
const int IDENTITY_CERTIFICATE = 1;
|
||||
var hasCertificates = data.TryGetValue(Keys.Network.Certificates.EmbeddedCertificates, out var value);
|
||||
|
||||
if (hasCertificates && value is IList<IDictionary<string, object>> certificates)
|
||||
{
|
||||
var toRemove = new List<IDictionary<string, object>>();
|
||||
|
||||
foreach (var certificate in certificates)
|
||||
{
|
||||
var hasData = certificate.TryGetValue(Keys.Network.Certificates.CertificateData, out var dataValue);
|
||||
var hasType = certificate.TryGetValue(Keys.Network.Certificates.CertificateType, out var typeValue);
|
||||
var isIdentity = typeValue is int type && type == IDENTITY_CERTIFICATE;
|
||||
|
||||
if (hasData && hasType && isIdentity && dataValue is byte[] certificateData)
|
||||
{
|
||||
ImportIdentityCertificate(certificateData, new X509Store(StoreLocation.CurrentUser));
|
||||
ImportIdentityCertificate(certificateData, new X509Store(StoreName.TrustedPeople, StoreLocation.LocalMachine));
|
||||
|
||||
toRemove.Add(certificate);
|
||||
}
|
||||
}
|
||||
|
||||
toRemove.ForEach(c => certificates.Remove(c));
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportIdentityCertificate(byte[] certificateData, X509Store store)
|
||||
{
|
||||
try
|
||||
{
|
||||
var certificate = new X509Certificate2();
|
||||
|
||||
certificate.Import(certificateData, "Di𝈭l𝈖Ch𝈒aht𝈁aHai1972", X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
|
||||
|
||||
store.Open(OpenFlags.ReadWrite);
|
||||
store.Add(certificate);
|
||||
|
||||
logger.Info($"Successfully imported identity certificate into {store.Location}.{store.Name}.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to import identity certificate into {store.Location}.{store.Name}!", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
store.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
SafeExamBrowser.Configuration/Cryptography/HashAlgorithm.cs
Normal file
31
SafeExamBrowser.Configuration/Cryptography/HashAlgorithm.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
public class HashAlgorithm : IHashAlgorithm
|
||||
{
|
||||
public string GenerateHashFor(string password)
|
||||
{
|
||||
using (var algorithm = new SHA256Managed())
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(password);
|
||||
var hash = algorithm.ComputeHash(bytes);
|
||||
var hashString = String.Join(String.Empty, hash.Select(b => b.ToString("x2")));
|
||||
|
||||
return hashString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
129
SafeExamBrowser.Configuration/Cryptography/KeyGenerator.cs
Normal file
129
SafeExamBrowser.Configuration/Cryptography/KeyGenerator.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Contracts.Integrity;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
public class KeyGenerator : IKeyGenerator
|
||||
{
|
||||
private readonly object @lock = new object();
|
||||
|
||||
private readonly ThreadLocal<SHA256Managed> algorithm;
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly IIntegrityModule integrityModule;
|
||||
private readonly ILogger logger;
|
||||
|
||||
private string browserExamKey;
|
||||
|
||||
public KeyGenerator(AppConfig appConfig, IIntegrityModule integrityModule, ILogger logger)
|
||||
{
|
||||
this.algorithm = new ThreadLocal<SHA256Managed>(() => new SHA256Managed());
|
||||
this.appConfig = appConfig;
|
||||
this.integrityModule = integrityModule;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public string CalculateAppSignatureKey(string connectionToken, string salt)
|
||||
{
|
||||
if (integrityModule.TryCalculateAppSignatureKey(connectionToken, salt, out var appSignatureKey))
|
||||
{
|
||||
logger.Debug("Successfully calculated app signature key using integrity module.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to calculate app signature key using integrity module!");
|
||||
}
|
||||
|
||||
return appSignatureKey;
|
||||
}
|
||||
|
||||
public string CalculateBrowserExamKeyHash(string configurationKey, byte[] salt, string url)
|
||||
{
|
||||
var urlWithoutFragment = url.Split('#')[0];
|
||||
var hash = algorithm.Value.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey(configurationKey, salt))));
|
||||
var key = ToString(hash);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
public string CalculateConfigurationKeyHash(string configurationKey, string url)
|
||||
{
|
||||
var urlWithoutFragment = url.Split('#')[0];
|
||||
var hash = algorithm.Value.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + configurationKey));
|
||||
var key = ToString(hash);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
public void UseCustomBrowserExamKey(string browserExamKey)
|
||||
{
|
||||
if (browserExamKey != default)
|
||||
{
|
||||
this.browserExamKey = browserExamKey;
|
||||
logger.Debug("Initialized custom browser exam key.");
|
||||
}
|
||||
}
|
||||
|
||||
private string ComputeBrowserExamKey(string configurationKey, byte[] salt)
|
||||
{
|
||||
lock (@lock)
|
||||
{
|
||||
if (browserExamKey == default)
|
||||
{
|
||||
logger.Debug("Initializing browser exam key...");
|
||||
|
||||
if (configurationKey == default)
|
||||
{
|
||||
configurationKey = "";
|
||||
logger.Warn("The current configuration does not contain a value for the configuration key!");
|
||||
}
|
||||
|
||||
if (salt == default || salt.Length == 0)
|
||||
{
|
||||
salt = new byte[0];
|
||||
logger.Warn("The current configuration does not contain a salt value for the browser exam key!");
|
||||
}
|
||||
|
||||
if (integrityModule.TryCalculateBrowserExamKey(configurationKey, ToString(salt), out browserExamKey))
|
||||
{
|
||||
logger.Debug("Successfully calculated browser exam key using integrity module.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Failed to calculate browser exam key using integrity module! Falling back to simplified calculation...");
|
||||
|
||||
using (var algorithm = new HMACSHA256(salt))
|
||||
{
|
||||
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(appConfig.CodeSignatureHash + appConfig.ProgramBuildVersion + configurationKey));
|
||||
var key = ToString(hash);
|
||||
|
||||
browserExamKey = key;
|
||||
}
|
||||
|
||||
logger.Debug("Successfully calculated browser exam key using simplified calculation.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return browserExamKey;
|
||||
}
|
||||
|
||||
private string ToString(byte[] bytes)
|
||||
{
|
||||
return BitConverter.ToString(bytes).ToLower().Replace("-", string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
223
SafeExamBrowser.Configuration/Cryptography/PasswordEncryption.cs
Normal file
223
SafeExamBrowser.Configuration/Cryptography/PasswordEncryption.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
public class PasswordEncryption : IPasswordEncryption
|
||||
{
|
||||
private const int BLOCK_SIZE = 16;
|
||||
private const int HEADER_SIZE = 2;
|
||||
private const int ITERATIONS = 10000;
|
||||
private const int KEY_SIZE = 32;
|
||||
private const int OPTIONS = 0x1;
|
||||
private const int SALT_SIZE = 8;
|
||||
private const int VERSION = 0x2;
|
||||
|
||||
private ILogger logger;
|
||||
|
||||
public PasswordEncryption(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public LoadStatus Decrypt(Stream data, string password, out Stream decrypted)
|
||||
{
|
||||
decrypted = default(Stream);
|
||||
|
||||
if (password == null)
|
||||
{
|
||||
return LoadStatus.PasswordNeeded;
|
||||
}
|
||||
|
||||
var (version, options) = ParseHeader(data);
|
||||
var (authenticationKey, encryptionKey) = GenerateKeysForDecryption(data, password);
|
||||
var (originalHmac, computedHmac) = GenerateHmacForDecryption(authenticationKey, data);
|
||||
|
||||
if (!computedHmac.SequenceEqual(originalHmac))
|
||||
{
|
||||
return FailForInvalidHmac();
|
||||
}
|
||||
|
||||
decrypted = Decrypt(data, encryptionKey, originalHmac.Length);
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
public SaveStatus Encrypt(Stream data, string password, out Stream encrypted)
|
||||
{
|
||||
var (authKey, authSalt, encrKey, encrSalt) = GenerateKeysForEncryption(password);
|
||||
|
||||
encrypted = Encrypt(data, encrKey, out var initVector);
|
||||
encrypted = WriteEncryptionParameters(authKey, authSalt, encrSalt, initVector, encrypted);
|
||||
|
||||
return SaveStatus.Success;
|
||||
}
|
||||
|
||||
private (int version, int options) ParseHeader(Stream data)
|
||||
{
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
logger.Debug("Parsing encryption header...");
|
||||
|
||||
var version = data.ReadByte();
|
||||
var options = data.ReadByte();
|
||||
|
||||
if (version != VERSION || options != OPTIONS)
|
||||
{
|
||||
logger.Debug($"Unknown encryption header! Expected: [{VERSION},{OPTIONS},...] - Actual: [{version},{options},...]");
|
||||
}
|
||||
|
||||
return (version, options);
|
||||
}
|
||||
|
||||
private (byte[] authenticationKey, byte[] encryptionKey) GenerateKeysForDecryption(Stream data, string password)
|
||||
{
|
||||
var authenticationSalt = new byte[SALT_SIZE];
|
||||
var encryptionSalt = new byte[SALT_SIZE];
|
||||
|
||||
logger.Debug("Generating keys for authentication and decryption...");
|
||||
|
||||
data.Seek(HEADER_SIZE, SeekOrigin.Begin);
|
||||
data.Read(encryptionSalt, 0, SALT_SIZE);
|
||||
data.Read(authenticationSalt, 0, SALT_SIZE);
|
||||
|
||||
using (var authenticationGenerator = new Rfc2898DeriveBytes(password, authenticationSalt, ITERATIONS))
|
||||
using (var encryptionGenerator = new Rfc2898DeriveBytes(password, encryptionSalt, ITERATIONS))
|
||||
{
|
||||
var authenticationKey = authenticationGenerator.GetBytes(KEY_SIZE);
|
||||
var encryptionKey = encryptionGenerator.GetBytes(KEY_SIZE);
|
||||
|
||||
return (authenticationKey, encryptionKey);
|
||||
}
|
||||
}
|
||||
|
||||
private (byte[] authKey, byte[] authSalt, byte[] encrKey, byte[] encrSalt) GenerateKeysForEncryption(string password)
|
||||
{
|
||||
logger.Debug("Generating keys for authentication and encryption...");
|
||||
|
||||
using (var authenticationGenerator = new Rfc2898DeriveBytes(password, SALT_SIZE, ITERATIONS))
|
||||
using (var encryptionGenerator = new Rfc2898DeriveBytes(password, SALT_SIZE, ITERATIONS))
|
||||
{
|
||||
var authenticationSalt = authenticationGenerator.Salt;
|
||||
var authenticationKey = authenticationGenerator.GetBytes(KEY_SIZE);
|
||||
var encryptionSalt = encryptionGenerator.Salt;
|
||||
var encryptionKey = encryptionGenerator.GetBytes(KEY_SIZE);
|
||||
|
||||
return (authenticationKey, authenticationSalt, encryptionKey, encryptionSalt);
|
||||
}
|
||||
}
|
||||
|
||||
private (byte[] originalHmac, byte[] computedHmac) GenerateHmacForDecryption(byte[] authenticationKey, Stream data)
|
||||
{
|
||||
logger.Debug("Generating HMACs for authentication...");
|
||||
|
||||
using (var algorithm = new HMACSHA256(authenticationKey))
|
||||
{
|
||||
var originalHmac = new byte[algorithm.HashSize / 8];
|
||||
var hashStream = new SubStream(data, 0, data.Length - originalHmac.Length);
|
||||
var computedHmac = algorithm.ComputeHash(hashStream);
|
||||
|
||||
data.Seek(-originalHmac.Length, SeekOrigin.End);
|
||||
data.Read(originalHmac, 0, originalHmac.Length);
|
||||
|
||||
return (originalHmac, computedHmac);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] GenerateHmacForEncryption(byte[] authenticationKey, Stream data)
|
||||
{
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
logger.Debug("Generating HMAC for authentication...");
|
||||
|
||||
using (var algorithm = new HMACSHA256(authenticationKey))
|
||||
{
|
||||
return algorithm.ComputeHash(data);
|
||||
}
|
||||
}
|
||||
|
||||
private LoadStatus FailForInvalidHmac()
|
||||
{
|
||||
logger.Debug($"The authentication failed due to an invalid password or corrupted data!");
|
||||
|
||||
return LoadStatus.PasswordNeeded;
|
||||
}
|
||||
|
||||
private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength)
|
||||
{
|
||||
var initializationVector = new byte[BLOCK_SIZE];
|
||||
|
||||
data.Seek(HEADER_SIZE + 2 * SALT_SIZE, SeekOrigin.Begin);
|
||||
data.Read(initializationVector, 0, BLOCK_SIZE);
|
||||
|
||||
var decryptedData = new MemoryStream();
|
||||
var encryptedData = new SubStream(data, data.Position, data.Length - data.Position - hmacLength);
|
||||
|
||||
logger.Debug("Decrypting data...");
|
||||
|
||||
using (var algorithm = new AesManaged { KeySize = KEY_SIZE * 8, BlockSize = BLOCK_SIZE * 8, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
|
||||
using (var decryptor = algorithm.CreateDecryptor(encryptionKey, initializationVector))
|
||||
using (var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
cryptoStream.CopyTo(decryptedData);
|
||||
}
|
||||
|
||||
return decryptedData;
|
||||
}
|
||||
|
||||
private Stream Encrypt(Stream data, byte[] encryptionKey, out byte[] initializationVector)
|
||||
{
|
||||
var encryptedData = new MemoryStream();
|
||||
|
||||
logger.Debug("Encrypting data...");
|
||||
|
||||
using (var algorithm = new AesManaged { KeySize = KEY_SIZE * 8, BlockSize = BLOCK_SIZE * 8, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
|
||||
{
|
||||
algorithm.GenerateIV();
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
initializationVector = algorithm.IV;
|
||||
|
||||
using (var encryptor = algorithm.CreateEncryptor(encryptionKey, initializationVector))
|
||||
using (var cryptoStream = new CryptoStream(data, encryptor, CryptoStreamMode.Read))
|
||||
{
|
||||
cryptoStream.CopyTo(encryptedData);
|
||||
}
|
||||
|
||||
return encryptedData;
|
||||
}
|
||||
}
|
||||
|
||||
private Stream WriteEncryptionParameters(byte[] authKey, byte[] authSalt, byte[] encrSalt, byte[] initVector, Stream encryptedData)
|
||||
{
|
||||
var data = new MemoryStream();
|
||||
var header = new byte[] { VERSION, OPTIONS };
|
||||
|
||||
logger.Debug("Writing encryption parameters...");
|
||||
|
||||
data.Write(header, 0, header.Length);
|
||||
data.Write(encrSalt, 0, encrSalt.Length);
|
||||
data.Write(authSalt, 0, authSalt.Length);
|
||||
data.Write(initVector, 0, initVector.Length);
|
||||
|
||||
encryptedData.Seek(0, SeekOrigin.Begin);
|
||||
encryptedData.CopyTo(data);
|
||||
|
||||
var hmac = GenerateHmacForEncryption(authKey, data);
|
||||
|
||||
data.Seek(0, SeekOrigin.End);
|
||||
data.Write(hmac, 0, hmac.Length);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
public class PublicKeyEncryption : IPublicKeyEncryption
|
||||
{
|
||||
protected const int PUBLIC_KEY_HASH_SIZE = 20;
|
||||
|
||||
protected ICertificateStore store;
|
||||
protected ILogger logger;
|
||||
|
||||
public PublicKeyEncryption(ICertificateStore store, ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public virtual LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate)
|
||||
{
|
||||
var publicKeyHash = ParsePublicKeyHash(data);
|
||||
var found = store.TryGetCertificateWith(publicKeyHash, out certificate);
|
||||
|
||||
decryptedData = default(Stream);
|
||||
|
||||
if (!found)
|
||||
{
|
||||
return FailForMissingCertificate();
|
||||
}
|
||||
|
||||
decryptedData = Decrypt(data, PUBLIC_KEY_HASH_SIZE, certificate);
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
public virtual SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData)
|
||||
{
|
||||
var publicKeyHash = GeneratePublicKeyHash(certificate);
|
||||
|
||||
encryptedData = Encrypt(data, certificate);
|
||||
encryptedData = WriteEncryptionParameters(encryptedData, publicKeyHash);
|
||||
|
||||
return SaveStatus.Success;
|
||||
}
|
||||
|
||||
protected LoadStatus FailForMissingCertificate()
|
||||
{
|
||||
logger.Error($"Could not find certificate which matches the given public key hash!");
|
||||
|
||||
return LoadStatus.InvalidData;
|
||||
}
|
||||
|
||||
protected byte[] GeneratePublicKeyHash(X509Certificate2 certificate)
|
||||
{
|
||||
var publicKey = certificate.PublicKey.EncodedKeyValue.RawData;
|
||||
|
||||
using (var sha = new SHA1CryptoServiceProvider())
|
||||
{
|
||||
return sha.ComputeHash(publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] ParsePublicKeyHash(Stream data)
|
||||
{
|
||||
var keyHash = new byte[PUBLIC_KEY_HASH_SIZE];
|
||||
|
||||
logger.Debug("Parsing public key hash...");
|
||||
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
data.Read(keyHash, 0, keyHash.Length);
|
||||
|
||||
return keyHash;
|
||||
}
|
||||
|
||||
protected MemoryStream Decrypt(Stream data, long offset, X509Certificate2 certificate)
|
||||
{
|
||||
var algorithm = certificate.PrivateKey as RSACryptoServiceProvider;
|
||||
var blockSize = algorithm.KeySize / 8;
|
||||
var blockCount = (data.Length - offset) / blockSize;
|
||||
var decrypted = new MemoryStream();
|
||||
var decryptedBuffer = new byte[blockSize];
|
||||
var encryptedBuffer = new byte[blockSize];
|
||||
var remainingBytes = data.Length - offset - (blockSize * blockCount);
|
||||
|
||||
data.Seek(offset, SeekOrigin.Begin);
|
||||
logger.Debug("Decrypting data...");
|
||||
|
||||
using (algorithm)
|
||||
{
|
||||
for (var block = 0; block < blockCount; block++)
|
||||
{
|
||||
data.Read(encryptedBuffer, 0, encryptedBuffer.Length);
|
||||
decryptedBuffer = algorithm.Decrypt(encryptedBuffer, false);
|
||||
decrypted.Write(decryptedBuffer, 0, decryptedBuffer.Length);
|
||||
}
|
||||
|
||||
if (remainingBytes > 0)
|
||||
{
|
||||
encryptedBuffer = new byte[remainingBytes];
|
||||
data.Read(encryptedBuffer, 0, encryptedBuffer.Length);
|
||||
decryptedBuffer = algorithm.Decrypt(encryptedBuffer, false);
|
||||
decrypted.Write(decryptedBuffer, 0, decryptedBuffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
protected Stream Encrypt(Stream data, X509Certificate2 certificate)
|
||||
{
|
||||
var algorithm = certificate.PublicKey.Key as RSACryptoServiceProvider;
|
||||
var blockSize = (algorithm.KeySize / 8) - 32;
|
||||
var blockCount = data.Length / blockSize;
|
||||
var decryptedBuffer = new byte[blockSize];
|
||||
var encrypted = new MemoryStream();
|
||||
var encryptedBuffer = new byte[blockSize];
|
||||
var remainingBytes = data.Length - (blockCount * blockSize);
|
||||
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
logger.Debug("Encrypting data...");
|
||||
|
||||
using (algorithm)
|
||||
{
|
||||
for (var block = 0; block < blockCount; block++)
|
||||
{
|
||||
data.Read(decryptedBuffer, 0, decryptedBuffer.Length);
|
||||
encryptedBuffer = algorithm.Encrypt(decryptedBuffer, false);
|
||||
encrypted.Write(encryptedBuffer, 0, encryptedBuffer.Length);
|
||||
}
|
||||
|
||||
if (remainingBytes > 0)
|
||||
{
|
||||
decryptedBuffer = new byte[remainingBytes];
|
||||
data.Read(decryptedBuffer, 0, decryptedBuffer.Length);
|
||||
encryptedBuffer = algorithm.Encrypt(decryptedBuffer, false);
|
||||
encrypted.Write(encryptedBuffer, 0, encryptedBuffer.Length);
|
||||
}
|
||||
}
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
private Stream WriteEncryptionParameters(Stream encryptedData, byte[] keyHash)
|
||||
{
|
||||
var data = new MemoryStream();
|
||||
|
||||
logger.Debug("Writing encryption parameters...");
|
||||
data.Write(keyHash, 0, keyHash.Length);
|
||||
encryptedData.Seek(0, SeekOrigin.Begin);
|
||||
encryptedData.CopyTo(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
public class PublicKeySymmetricEncryption : PublicKeyEncryption
|
||||
{
|
||||
private const int ENCRYPTION_KEY_LENGTH = 32;
|
||||
private const int KEY_LENGTH_SIZE = 4;
|
||||
|
||||
private PasswordEncryption passwordEncryption;
|
||||
|
||||
public PublicKeySymmetricEncryption(ICertificateStore store, ILogger logger, PasswordEncryption passwordEncryption) : base(store, logger)
|
||||
{
|
||||
this.passwordEncryption = passwordEncryption;
|
||||
}
|
||||
|
||||
public override LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate)
|
||||
{
|
||||
var publicKeyHash = ParsePublicKeyHash(data);
|
||||
var found = store.TryGetCertificateWith(publicKeyHash, out certificate);
|
||||
|
||||
decryptedData = default(Stream);
|
||||
|
||||
if (!found)
|
||||
{
|
||||
return FailForMissingCertificate();
|
||||
}
|
||||
|
||||
var symmetricKey = ParseSymmetricKey(data, certificate);
|
||||
var stream = new SubStream(data, data.Position, data.Length - data.Position);
|
||||
var status = passwordEncryption.Decrypt(stream, symmetricKey, out decryptedData);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public override SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData)
|
||||
{
|
||||
var publicKeyHash = GeneratePublicKeyHash(certificate);
|
||||
var symmetricKey = GenerateSymmetricKey();
|
||||
var symmetricKeyString = Convert.ToBase64String(symmetricKey);
|
||||
var status = passwordEncryption.Encrypt(data, symmetricKeyString, out encryptedData);
|
||||
|
||||
if (status != SaveStatus.Success)
|
||||
{
|
||||
return FailForUnsuccessfulPasswordEncryption(status);
|
||||
}
|
||||
|
||||
encryptedData = WriteEncryptionParameters(encryptedData, certificate, publicKeyHash, symmetricKey);
|
||||
|
||||
return SaveStatus.Success;
|
||||
}
|
||||
|
||||
private SaveStatus FailForUnsuccessfulPasswordEncryption(SaveStatus status)
|
||||
{
|
||||
logger.Error($"Password encryption has failed with status '{status}'!");
|
||||
|
||||
return SaveStatus.UnexpectedError;
|
||||
}
|
||||
|
||||
private byte[] GenerateSymmetricKey()
|
||||
{
|
||||
var key = new byte[ENCRYPTION_KEY_LENGTH];
|
||||
|
||||
using (var generator = RandomNumberGenerator.Create())
|
||||
{
|
||||
generator.GetBytes(key);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
private string ParseSymmetricKey(Stream data, X509Certificate2 certificate)
|
||||
{
|
||||
var keyLengthData = new byte[KEY_LENGTH_SIZE];
|
||||
|
||||
logger.Debug("Parsing symmetric key...");
|
||||
|
||||
data.Seek(PUBLIC_KEY_HASH_SIZE, SeekOrigin.Begin);
|
||||
data.Read(keyLengthData, 0, keyLengthData.Length);
|
||||
|
||||
var encryptedKeyLength = BitConverter.ToInt32(keyLengthData, 0);
|
||||
var encryptedKey = new byte[encryptedKeyLength];
|
||||
|
||||
data.Read(encryptedKey, 0, encryptedKey.Length);
|
||||
|
||||
var stream = new SubStream(data, PUBLIC_KEY_HASH_SIZE + KEY_LENGTH_SIZE, encryptedKeyLength);
|
||||
var decryptedKey = Decrypt(stream, 0, certificate);
|
||||
var symmetricKey = Convert.ToBase64String(decryptedKey.ToArray());
|
||||
|
||||
return symmetricKey;
|
||||
}
|
||||
|
||||
private Stream WriteEncryptionParameters(Stream encryptedData, X509Certificate2 certificate, byte[] publicKeyHash, byte[] symmetricKey)
|
||||
{
|
||||
var data = new MemoryStream();
|
||||
var symmetricKeyData = new MemoryStream(symmetricKey);
|
||||
var encryptedKey = Encrypt(symmetricKeyData, certificate);
|
||||
// IMPORTANT: The key length must be exactly 4 Bytes, thus the cast to integer!
|
||||
var encryptedKeyLength = BitConverter.GetBytes((int) encryptedKey.Length);
|
||||
|
||||
logger.Debug("Writing encryption parameters...");
|
||||
|
||||
data.Write(publicKeyHash, 0, publicKeyHash.Length);
|
||||
data.Write(encryptedKeyLength, 0, encryptedKeyLength.Length);
|
||||
|
||||
encryptedKey.Seek(0, SeekOrigin.Begin);
|
||||
encryptedKey.CopyTo(data);
|
||||
|
||||
encryptedData.Seek(0, SeekOrigin.Begin);
|
||||
encryptedData.CopyTo(data);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
120
SafeExamBrowser.Configuration/DataCompression/GZipCompressor.cs
Normal file
120
SafeExamBrowser.Configuration/DataCompression/GZipCompressor.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataCompression;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Data compression using the GNU-Zip format (see https://en.wikipedia.org/wiki/Gzip).
|
||||
/// </summary>
|
||||
public class GZipCompressor : IDataCompressor
|
||||
{
|
||||
private const int ID1 = 0x1F;
|
||||
private const int ID2 = 0x8B;
|
||||
private const int CM = 8;
|
||||
private const int FOOTER_LENGTH = 8;
|
||||
private const int HEADER_LENGTH = 10;
|
||||
|
||||
private ILogger logger;
|
||||
|
||||
public GZipCompressor(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public Stream Compress(Stream data)
|
||||
{
|
||||
var compressed = new MemoryStream();
|
||||
var originalSize = data.Length / 1000.0;
|
||||
|
||||
logger.Debug($"Starting compression of '{data}' with {originalSize} KB data...");
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (var stream = new GZipStream(compressed, CompressionMode.Compress, true))
|
||||
{
|
||||
data.CopyTo(stream);
|
||||
}
|
||||
|
||||
logger.Debug($"Successfully compressed {originalSize} KB to {compressed.Length / 1000.0} KB data.");
|
||||
|
||||
return compressed;
|
||||
}
|
||||
|
||||
public Stream Decompress(Stream data)
|
||||
{
|
||||
var decompressed = new MemoryStream();
|
||||
var originalSize = data.Length / 1000.0;
|
||||
|
||||
logger.Debug($"Starting decompression of '{data}' with {originalSize} KB data...");
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (var stream = new GZipStream(data, CompressionMode.Decompress))
|
||||
{
|
||||
stream.CopyTo(decompressed);
|
||||
}
|
||||
|
||||
logger.Debug($"Successfully decompressed {originalSize} KB to {decompressed.Length / 1000.0} KB data.");
|
||||
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// All gzip-compressed data has a 10-byte header and 8-byte footer. The header starts with two magic numbers (ID1 and ID2) and
|
||||
/// the used compression method (CM), which normally denotes the DEFLATE algorithm. See https://tools.ietf.org/html/rfc1952 for
|
||||
/// the original data format specification.
|
||||
/// </remarks>
|
||||
public bool IsCompressed(Stream data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var longEnough = data.Length > HEADER_LENGTH + FOOTER_LENGTH;
|
||||
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
if (longEnough)
|
||||
{
|
||||
var id1 = data.ReadByte();
|
||||
var id2 = data.ReadByte();
|
||||
var cm = data.ReadByte();
|
||||
var compressed = id1 == ID1 && id2 == ID2 && cm == CM;
|
||||
|
||||
return compressed;
|
||||
}
|
||||
|
||||
logger.Debug($"'{data}' is not long enough ({data.Length} bytes) to be a gzip-compressed stream.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to check whether '{data}' with {data?.Length / 1000.0} KB data is a gzip-compressed stream!", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public byte[] Peek(Stream data, int count)
|
||||
{
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (var stream = new GZipStream(data, CompressionMode.Decompress, true))
|
||||
using (var decompressed = new MemoryStream())
|
||||
{
|
||||
var buffer = new byte[count];
|
||||
var bytesRead = stream.Read(buffer, 0, buffer.Length);
|
||||
|
||||
decompressed.Write(buffer, 0, bytesRead);
|
||||
|
||||
return decompressed.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
SafeExamBrowser.Configuration/DataFormats/BinaryBlock.cs
Normal file
19
SafeExamBrowser.Configuration/DataFormats/BinaryBlock.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
internal static class BinaryBlock
|
||||
{
|
||||
internal const string Password = "pswd";
|
||||
internal const string PasswordConfigureClient = "pwcc";
|
||||
internal const string PlainData = "plnd";
|
||||
internal const string PublicKey = "pkhs";
|
||||
internal const string PublicKeySymmetric = "phsk";
|
||||
}
|
||||
}
|
211
SafeExamBrowser.Configuration/DataFormats/BinaryParser.cs
Normal file
211
SafeExamBrowser.Configuration/DataFormats/BinaryParser.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataCompression;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataFormats;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public class BinaryParser : IDataParser
|
||||
{
|
||||
private const int PREFIX_LENGTH = 4;
|
||||
|
||||
private IDataCompressor compressor;
|
||||
private IHashAlgorithm hashAlgorithm;
|
||||
private ILogger logger;
|
||||
private IPasswordEncryption passwordEncryption;
|
||||
private IPublicKeyEncryption publicKeyEncryption;
|
||||
private IPublicKeyEncryption publicKeySymmetricEncryption;
|
||||
private readonly IDataParser xmlParser;
|
||||
|
||||
public BinaryParser(
|
||||
IDataCompressor compressor,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
ILogger logger,
|
||||
IPasswordEncryption passwordEncryption,
|
||||
IPublicKeyEncryption publicKeyEncryption,
|
||||
IPublicKeyEncryption publicKeySymmetricEncryption,
|
||||
IDataParser xmlParser)
|
||||
{
|
||||
this.compressor = compressor;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.logger = logger;
|
||||
this.passwordEncryption = passwordEncryption;
|
||||
this.publicKeyEncryption = publicKeyEncryption;
|
||||
this.publicKeySymmetricEncryption = publicKeySymmetricEncryption;
|
||||
this.xmlParser = xmlParser;
|
||||
}
|
||||
|
||||
public bool CanParse(Stream data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var longEnough = data.Length > PREFIX_LENGTH;
|
||||
|
||||
if (longEnough)
|
||||
{
|
||||
var prefix = ReadPrefix(data);
|
||||
var isValid = IsValid(prefix);
|
||||
|
||||
logger.Debug($"'{data}' starting with '{prefix}' {(isValid ? "matches" : "does not match")} the {FormatType.Binary} format.");
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
logger.Debug($"'{data}' is not long enough ({data.Length} bytes) to match the {FormatType.Binary} format.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to determine whether '{data}' with {data?.Length / 1000.0} KB data matches the {FormatType.Binary} format!", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ParseResult TryParse(Stream data, PasswordParameters password = null)
|
||||
{
|
||||
var prefix = ReadPrefix(data);
|
||||
var isValid = IsValid(prefix);
|
||||
var result = new ParseResult { Status = LoadStatus.InvalidData };
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
data = compressor.IsCompressed(data) ? compressor.Decompress(data) : data;
|
||||
data = new SubStream(data, PREFIX_LENGTH, data.Length - PREFIX_LENGTH);
|
||||
|
||||
switch (prefix)
|
||||
{
|
||||
case BinaryBlock.Password:
|
||||
case BinaryBlock.PasswordConfigureClient:
|
||||
result = ParsePasswordBlock(data, prefix, password);
|
||||
break;
|
||||
case BinaryBlock.PlainData:
|
||||
result = ParsePlainDataBlock(data);
|
||||
break;
|
||||
case BinaryBlock.PublicKey:
|
||||
case BinaryBlock.PublicKeySymmetric:
|
||||
result = ParsePublicKeyBlock(data, prefix, password);
|
||||
break;
|
||||
}
|
||||
|
||||
result.Format = FormatType.Binary;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"'{data}' starting with '{prefix}' does not match the {FormatType.Binary} format!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ParseResult ParsePasswordBlock(Stream data, string prefix, PasswordParameters password = null)
|
||||
{
|
||||
var result = new ParseResult();
|
||||
|
||||
if (password != null)
|
||||
{
|
||||
var parameters = DetermineEncryptionParametersFor(prefix, password);
|
||||
|
||||
logger.Debug($"Attempting to parse password block with prefix '{prefix}'...");
|
||||
result.Status = passwordEncryption.Decrypt(data, parameters.Password, out var decrypted);
|
||||
|
||||
if (result.Status == LoadStatus.Success)
|
||||
{
|
||||
result = ParsePlainDataBlock(decrypted);
|
||||
result.Encryption = parameters;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Status = LoadStatus.PasswordNeeded;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private ParseResult ParsePlainDataBlock(Stream data)
|
||||
{
|
||||
data = compressor.IsCompressed(data) ? compressor.Decompress(data) : data;
|
||||
logger.Debug("Attempting to parse plain data block...");
|
||||
|
||||
return xmlParser.TryParse(data);
|
||||
}
|
||||
|
||||
private ParseResult ParsePublicKeyBlock(Stream data, string prefix, PasswordParameters password = null)
|
||||
{
|
||||
var encryption = prefix == BinaryBlock.PublicKey ? publicKeyEncryption : publicKeySymmetricEncryption;
|
||||
var result = new ParseResult();
|
||||
|
||||
logger.Debug($"Attempting to parse public key hash block with prefix '{prefix}'...");
|
||||
result.Status = encryption.Decrypt(data, out var decrypted, out var certificate);
|
||||
|
||||
if (result.Status == LoadStatus.Success)
|
||||
{
|
||||
result = TryParse(decrypted, password);
|
||||
result.Encryption = new PublicKeyParameters
|
||||
{
|
||||
Certificate = certificate,
|
||||
InnerEncryption = result.Encryption as PasswordParameters,
|
||||
SymmetricEncryption = prefix == BinaryBlock.PublicKeySymmetric
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private PasswordParameters DetermineEncryptionParametersFor(string prefix, PasswordParameters password)
|
||||
{
|
||||
var parameters = new PasswordParameters();
|
||||
|
||||
if (prefix == BinaryBlock.PasswordConfigureClient)
|
||||
{
|
||||
parameters.Password = password.IsHash ? password.Password : hashAlgorithm.GenerateHashFor(password.Password);
|
||||
parameters.IsHash = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.Password = password.Password;
|
||||
parameters.IsHash = password.IsHash;
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private string ReadPrefix(Stream data)
|
||||
{
|
||||
var prefix = new byte[PREFIX_LENGTH];
|
||||
|
||||
if (compressor.IsCompressed(data))
|
||||
{
|
||||
prefix = compressor.Peek(data, PREFIX_LENGTH);
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
data.Read(prefix, 0, PREFIX_LENGTH);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(prefix);
|
||||
}
|
||||
|
||||
private bool IsValid(string prefix)
|
||||
{
|
||||
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static;
|
||||
|
||||
return typeof(BinaryBlock).GetFields(bindingFlags).Any(f => f.GetRawConstantValue() as string == prefix);
|
||||
}
|
||||
}
|
||||
}
|
162
SafeExamBrowser.Configuration/DataFormats/BinarySerializer.cs
Normal file
162
SafeExamBrowser.Configuration/DataFormats/BinarySerializer.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataCompression;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataFormats;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public class BinarySerializer : IDataSerializer
|
||||
{
|
||||
private IDataCompressor compressor;
|
||||
private ILogger logger;
|
||||
private IPasswordEncryption passwordEncryption;
|
||||
private IPublicKeyEncryption publicKeyEncryption;
|
||||
private IPublicKeyEncryption symmetricEncryption;
|
||||
private IDataSerializer xmlSerializer;
|
||||
|
||||
public BinarySerializer(
|
||||
IDataCompressor compressor,
|
||||
ILogger logger,
|
||||
IPasswordEncryption passwordEncryption,
|
||||
IPublicKeyEncryption publicKeyEncryption,
|
||||
IPublicKeyEncryption symmetricEncryption,
|
||||
IDataSerializer xmlSerializer)
|
||||
{
|
||||
this.compressor = compressor;
|
||||
this.logger = logger;
|
||||
this.passwordEncryption = passwordEncryption;
|
||||
this.publicKeyEncryption = publicKeyEncryption;
|
||||
this.symmetricEncryption = symmetricEncryption;
|
||||
this.xmlSerializer = xmlSerializer;
|
||||
}
|
||||
|
||||
public bool CanSerialize(FormatType format)
|
||||
{
|
||||
return format == FormatType.Binary;
|
||||
}
|
||||
|
||||
public SerializeResult TrySerialize(IDictionary<string, object> data, EncryptionParameters encryption = null)
|
||||
{
|
||||
var result = new SerializeResult();
|
||||
|
||||
switch (encryption)
|
||||
{
|
||||
case PasswordParameters p:
|
||||
result = SerializePasswordBlock(data, p);
|
||||
break;
|
||||
case PublicKeyParameters p:
|
||||
result = SerializePublicKeyHashBlock(data, p);
|
||||
break;
|
||||
default:
|
||||
result = SerializePlainDataBlock(data, true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.Status == SaveStatus.Success)
|
||||
{
|
||||
result.Data = compressor.Compress(result.Data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SerializeResult SerializePasswordBlock(IDictionary<string, object> data, PasswordParameters password)
|
||||
{
|
||||
var result = SerializePlainDataBlock(data);
|
||||
|
||||
if (result.Status == SaveStatus.Success)
|
||||
{
|
||||
var prefix = password.IsHash ? BinaryBlock.PasswordConfigureClient : BinaryBlock.Password;
|
||||
|
||||
logger.Debug("Attempting to serialize password block...");
|
||||
|
||||
var status = passwordEncryption.Encrypt(result.Data, password.Password, out var encrypted);
|
||||
|
||||
if (status == SaveStatus.Success)
|
||||
{
|
||||
result.Data = WritePrefix(prefix, encrypted);
|
||||
}
|
||||
|
||||
result.Status = status;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SerializeResult SerializePlainDataBlock(IDictionary<string, object> data, bool writePrefix = false)
|
||||
{
|
||||
logger.Debug("Attempting to serialize plain data block...");
|
||||
|
||||
var result = xmlSerializer.TrySerialize(data);
|
||||
|
||||
if (result.Status == SaveStatus.Success)
|
||||
{
|
||||
if (writePrefix)
|
||||
{
|
||||
result.Data = WritePrefix(BinaryBlock.PlainData, result.Data);
|
||||
}
|
||||
|
||||
result.Data = compressor.Compress(result.Data);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SerializeResult SerializePublicKeyHashBlock(IDictionary<string, object> data, PublicKeyParameters parameters)
|
||||
{
|
||||
var result = SerializePublicKeyHashInnerBlock(data, parameters);
|
||||
|
||||
if (result.Status == SaveStatus.Success)
|
||||
{
|
||||
var encryption = parameters.SymmetricEncryption ? symmetricEncryption : publicKeyEncryption;
|
||||
var prefix = parameters.SymmetricEncryption ? BinaryBlock.PublicKeySymmetric : BinaryBlock.PublicKey;
|
||||
|
||||
logger.Debug("Attempting to serialize public key hash block...");
|
||||
|
||||
var status = encryption.Encrypt(result.Data, parameters.Certificate, out var encrypted);
|
||||
|
||||
if (status == SaveStatus.Success)
|
||||
{
|
||||
result.Data = WritePrefix(prefix, encrypted);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SerializeResult SerializePublicKeyHashInnerBlock(IDictionary<string, object> data, PublicKeyParameters parameters)
|
||||
{
|
||||
if (parameters.InnerEncryption is PasswordParameters password)
|
||||
{
|
||||
return SerializePasswordBlock(data, password);
|
||||
}
|
||||
|
||||
return SerializePlainDataBlock(data, true);
|
||||
}
|
||||
|
||||
private Stream WritePrefix(string prefix, Stream data)
|
||||
{
|
||||
var prefixBytes = Encoding.UTF8.GetBytes(prefix);
|
||||
var stream = new MemoryStream();
|
||||
|
||||
stream.Write(prefixBytes, 0, prefixBytes.Length);
|
||||
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
data.CopyTo(stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
}
|
25
SafeExamBrowser.Configuration/DataFormats/XmlElement.cs
Normal file
25
SafeExamBrowser.Configuration/DataFormats/XmlElement.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
internal static class XmlElement
|
||||
{
|
||||
public const string Array = "array";
|
||||
public const string Data = "data";
|
||||
public const string Date = "date";
|
||||
public const string Dictionary = "dict";
|
||||
public const string False = "false";
|
||||
public const string Integer = "integer";
|
||||
public const string Key = "key";
|
||||
public const string Real = "real";
|
||||
public const string Root = "plist";
|
||||
public const string String = "string";
|
||||
public const string True = "true";
|
||||
}
|
||||
}
|
301
SafeExamBrowser.Configuration/DataFormats/XmlParser.cs
Normal file
301
SafeExamBrowser.Configuration/DataFormats/XmlParser.cs
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataCompression;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataFormats;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public class XmlParser : IDataParser
|
||||
{
|
||||
private const string XML_PREFIX = "<?xm";
|
||||
|
||||
private readonly IDataCompressor compressor;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public XmlParser(IDataCompressor compressor, ILogger logger)
|
||||
{
|
||||
this.compressor = compressor;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanParse(Stream data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var longEnough = data.Length > XML_PREFIX.Length;
|
||||
|
||||
if (longEnough)
|
||||
{
|
||||
var prefix = ReadPrefix(data);
|
||||
var isValid = XML_PREFIX.Equals(prefix, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
logger.Debug($"'{data}' starting with '{prefix}' {(isValid ? "matches" : "does not match")} the {FormatType.Xml} format.");
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
logger.Debug($"'{data}' is not long enough ({data.Length} bytes) to match the {FormatType.Xml} format.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to determine whether '{data}' with {data?.Length / 1000.0} KB data matches the {FormatType.Xml} format!", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ParseResult TryParse(Stream data, PasswordParameters password = null)
|
||||
{
|
||||
var prefix = ReadPrefix(data);
|
||||
var isValid = XML_PREFIX.Equals(prefix, StringComparison.OrdinalIgnoreCase);
|
||||
var result = new ParseResult { Status = LoadStatus.InvalidData };
|
||||
|
||||
if (isValid)
|
||||
{
|
||||
data = compressor.IsCompressed(data) ? compressor.Decompress(data) : data;
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
using (var reader = XmlReader.Create(data, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }))
|
||||
{
|
||||
var hasRoot = reader.ReadToFollowing(XmlElement.Root);
|
||||
var hasDictionary = reader.ReadToDescendant(XmlElement.Dictionary);
|
||||
|
||||
if (hasRoot && hasDictionary)
|
||||
{
|
||||
logger.Debug($"Found root node, starting to parse data...");
|
||||
|
||||
result.Status = ParseDictionary(reader, out var rawData);
|
||||
result.RawData = rawData;
|
||||
|
||||
logger.Debug($"Finished parsing -> Result: {result.Status}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Could not find root {(!hasRoot ? $"node '{XmlElement.Root}'" : $"dictionary '{XmlElement.Dictionary}'")}!");
|
||||
}
|
||||
}
|
||||
|
||||
result.Format = FormatType.Xml;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"'{data}' starting with '{prefix}' does not match the {FormatType.Xml} format!");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private LoadStatus ParseArray(XmlReader reader, out List<object> array)
|
||||
{
|
||||
array = new List<object>();
|
||||
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
reader.MoveToContent();
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
reader.Read();
|
||||
reader.MoveToContent();
|
||||
|
||||
while (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
var status = ParseElement(reader, out object element);
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
array.Add(element);
|
||||
}
|
||||
else
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
reader.MoveToContent();
|
||||
}
|
||||
|
||||
if (reader.NodeType == XmlNodeType.EndElement && reader.Name == XmlElement.Array)
|
||||
{
|
||||
reader.Read();
|
||||
reader.MoveToContent();
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
logger.Error($"Expected closing tag for '{XmlElement.Array}', but found '{reader.Name}{reader.Value}'!");
|
||||
|
||||
return LoadStatus.InvalidData;
|
||||
}
|
||||
|
||||
private LoadStatus ParseDictionary(XmlReader reader, out Dictionary<string, object> dictionary)
|
||||
{
|
||||
dictionary = new Dictionary<string, object>();
|
||||
|
||||
if (reader.IsEmptyElement)
|
||||
{
|
||||
reader.Read();
|
||||
reader.MoveToContent();
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
reader.Read();
|
||||
reader.MoveToContent();
|
||||
|
||||
while (reader.NodeType == XmlNodeType.Element)
|
||||
{
|
||||
var status = ParseKeyValuePair(reader, out var pair);
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
dictionary[pair.Key] = pair.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
reader.MoveToContent();
|
||||
}
|
||||
|
||||
if (reader.NodeType == XmlNodeType.EndElement && reader.Name == XmlElement.Dictionary)
|
||||
{
|
||||
reader.Read();
|
||||
reader.MoveToContent();
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
logger.Error($"Expected closing tag for '{XmlElement.Dictionary}', but found '{reader.Name}{reader.Value}'!");
|
||||
|
||||
return LoadStatus.InvalidData;
|
||||
}
|
||||
|
||||
private LoadStatus ParseKeyValuePair(XmlReader reader, out KeyValuePair<string, object> pair)
|
||||
{
|
||||
var status = LoadStatus.InvalidData;
|
||||
var key = XNode.ReadFrom(reader) as XElement;
|
||||
|
||||
pair = default(KeyValuePair<string, object>);
|
||||
|
||||
if (key.Name.LocalName == XmlElement.Key)
|
||||
{
|
||||
reader.MoveToContent();
|
||||
status = ParseElement(reader, out object value);
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
{
|
||||
pair = new KeyValuePair<string, object>(key.Value, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Expected element '{XmlElement.Key}', but found '{key}'!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private LoadStatus ParseElement(XmlReader reader, out object element)
|
||||
{
|
||||
var array = default(List<object>);
|
||||
var dictionary = default(Dictionary<string, object>);
|
||||
var status = default(LoadStatus);
|
||||
var value = default(object);
|
||||
|
||||
if (reader.Name == XmlElement.Array)
|
||||
{
|
||||
status = ParseArray(reader, out array);
|
||||
}
|
||||
else if (reader.Name == XmlElement.Dictionary)
|
||||
{
|
||||
status = ParseDictionary(reader, out dictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = ParseSimpleType(XNode.ReadFrom(reader) as XElement, out value);
|
||||
}
|
||||
|
||||
element = array ?? dictionary ?? value;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private LoadStatus ParseSimpleType(XElement element, out object value)
|
||||
{
|
||||
var status = LoadStatus.Success;
|
||||
|
||||
value = null;
|
||||
|
||||
switch (element.Name.LocalName)
|
||||
{
|
||||
case XmlElement.Data:
|
||||
value = Convert.FromBase64String(element.Value);
|
||||
break;
|
||||
case XmlElement.Date:
|
||||
value = XmlConvert.ToDateTime(element.Value, XmlDateTimeSerializationMode.Utc);
|
||||
break;
|
||||
case XmlElement.False:
|
||||
value = false;
|
||||
break;
|
||||
case XmlElement.Integer:
|
||||
value = Convert.ToInt32(element.Value);
|
||||
break;
|
||||
case XmlElement.Real:
|
||||
value = Convert.ToDouble(element.Value, CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case XmlElement.String:
|
||||
value = element.IsEmpty ? null : element.Value;
|
||||
break;
|
||||
case XmlElement.True:
|
||||
value = true;
|
||||
break;
|
||||
default:
|
||||
status = LoadStatus.InvalidData;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status != LoadStatus.Success)
|
||||
{
|
||||
logger.Error($"Element '{element}' is not a supported value type!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private string ReadPrefix(Stream data)
|
||||
{
|
||||
var prefixData = new byte[XML_PREFIX.Length];
|
||||
|
||||
if (compressor.IsCompressed(data))
|
||||
{
|
||||
prefixData = compressor.Peek(data, XML_PREFIX.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
data.Read(prefixData, 0, XML_PREFIX.Length);
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(prefixData);
|
||||
}
|
||||
}
|
||||
}
|
199
SafeExamBrowser.Configuration/DataFormats/XmlSerializer.cs
Normal file
199
SafeExamBrowser.Configuration/DataFormats/XmlSerializer.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataFormats;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
{
|
||||
public class XmlSerializer : IDataSerializer
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
|
||||
public XmlSerializer(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanSerialize(FormatType format)
|
||||
{
|
||||
return format == FormatType.Xml;
|
||||
}
|
||||
|
||||
public SerializeResult TrySerialize(IDictionary<string, object> data, EncryptionParameters encryption = null)
|
||||
{
|
||||
var result = new SerializeResult();
|
||||
var settings = new XmlWriterSettings { Encoding = new UTF8Encoding(), Indent = true };
|
||||
var stream = new MemoryStream();
|
||||
|
||||
logger.Debug("Starting to serialize data...");
|
||||
|
||||
using (var writer = XmlWriter.Create(stream, settings))
|
||||
{
|
||||
writer.WriteStartDocument();
|
||||
writer.WriteDocType("plist", "-//Apple Computer//DTD PLIST 1.0//EN", "https://www.apple.com/DTDs/PropertyList-1.0.dtd", null);
|
||||
writer.WriteStartElement(XmlElement.Root);
|
||||
writer.WriteAttributeString("version", "1.0");
|
||||
|
||||
result.Status = WriteDictionary(writer, data);
|
||||
result.Data = stream;
|
||||
|
||||
writer.WriteEndElement();
|
||||
writer.WriteEndDocument();
|
||||
writer.Flush();
|
||||
writer.Close();
|
||||
}
|
||||
|
||||
logger.Debug($"Finished serialization -> Result: {result.Status}.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private SaveStatus WriteArray(XmlWriter writer, List<object> array)
|
||||
{
|
||||
var status = SaveStatus.Success;
|
||||
|
||||
writer.WriteStartElement(XmlElement.Array);
|
||||
|
||||
foreach (var item in array)
|
||||
{
|
||||
status = WriteElement(writer, item);
|
||||
|
||||
if (status != SaveStatus.Success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndElement();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private SaveStatus WriteDictionary(XmlWriter writer, IDictionary<string, object> data)
|
||||
{
|
||||
var status = SaveStatus.Success;
|
||||
|
||||
writer.WriteStartElement(XmlElement.Dictionary);
|
||||
|
||||
foreach (var item in data.OrderBy(i => i.Key))
|
||||
{
|
||||
status = WriteKeyValuePair(writer, item);
|
||||
|
||||
if (status != SaveStatus.Success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteEndElement();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private SaveStatus WriteKeyValuePair(XmlWriter writer, KeyValuePair<string, object> item)
|
||||
{
|
||||
var status = SaveStatus.InvalidData;
|
||||
|
||||
if (item.Key != null)
|
||||
{
|
||||
writer.WriteElementString(XmlElement.Key, item.Key);
|
||||
status = WriteElement(writer, item.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Key of item '{item}' is null!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private SaveStatus WriteElement(XmlWriter writer, object element)
|
||||
{
|
||||
var status = default(SaveStatus);
|
||||
|
||||
if (element is List<object> array)
|
||||
{
|
||||
status = WriteArray(writer, array);
|
||||
}
|
||||
else if (element is Dictionary<string, object> dictionary)
|
||||
{
|
||||
status = WriteDictionary(writer, dictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = WriteSimpleType(writer, element);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
private SaveStatus WriteSimpleType(XmlWriter writer, object element)
|
||||
{
|
||||
var name = default(string);
|
||||
var value = default(string);
|
||||
var status = SaveStatus.Success;
|
||||
|
||||
switch (element)
|
||||
{
|
||||
case byte[] data:
|
||||
name = XmlElement.Data;
|
||||
value = Convert.ToBase64String(data);
|
||||
break;
|
||||
case DateTime date:
|
||||
name = XmlElement.Date;
|
||||
value = XmlConvert.ToString(date, XmlDateTimeSerializationMode.Utc);
|
||||
break;
|
||||
case bool boolean when boolean == false:
|
||||
name = XmlElement.False;
|
||||
break;
|
||||
case bool boolean when boolean == true:
|
||||
name = XmlElement.True;
|
||||
break;
|
||||
case int integer:
|
||||
name = XmlElement.Integer;
|
||||
value = integer.ToString(NumberFormatInfo.InvariantInfo);
|
||||
break;
|
||||
case double real:
|
||||
name = XmlElement.Real;
|
||||
value = real.ToString(NumberFormatInfo.InvariantInfo);
|
||||
break;
|
||||
case string text:
|
||||
name = XmlElement.String;
|
||||
value = text;
|
||||
break;
|
||||
case null:
|
||||
name = XmlElement.String;
|
||||
break;
|
||||
default:
|
||||
status = SaveStatus.InvalidData;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status == SaveStatus.Success)
|
||||
{
|
||||
writer.WriteElementString(name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error($"Element '{element}' is not supported!");
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataResources;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataResources
|
||||
{
|
||||
public class FileResourceLoader : IResourceLoader
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public FileResourceLoader(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanLoad(Uri resource)
|
||||
{
|
||||
var exists = resource.IsFile && File.Exists(resource.LocalPath);
|
||||
|
||||
if (exists)
|
||||
{
|
||||
logger.Debug($"Can load '{resource}' as it is an existing file.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Can't load '{resource}' as it isn't an existing file.");
|
||||
}
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
public LoadStatus TryLoad(Uri resource, out Stream data)
|
||||
{
|
||||
logger.Debug($"Loading data from '{resource}'...");
|
||||
data = new FileStream(resource.LocalPath, FileMode.Open, FileAccess.Read);
|
||||
logger.Debug($"Created '{data}' for {data.Length / 1000.0} KB data in '{resource}'.");
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataResources;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataResources
|
||||
{
|
||||
public class FileResourceSaver : IResourceSaver
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public FileResourceSaver(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanSave(Uri destination)
|
||||
{
|
||||
var isFullPath = destination.IsFile && Path.IsPathRooted(destination.LocalPath);
|
||||
|
||||
if (isFullPath)
|
||||
{
|
||||
logger.Debug($"Can save data as '{destination}' since it defines an absolute file path.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Can't save data as '{destination}' since it doesn't define an absolute file path.");
|
||||
}
|
||||
|
||||
return isFullPath;
|
||||
}
|
||||
|
||||
public SaveStatus TrySave(Uri destination, Stream data)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(destination.LocalPath);
|
||||
|
||||
logger.Debug($"Attempting to save '{data}' with {data.Length / 1000.0} KB data as '{destination}'...");
|
||||
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
logger.Debug($"Creating directory '{directory}'...");
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
using (var fileStream = new FileStream(destination.LocalPath, FileMode.Create))
|
||||
{
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
data.CopyTo(fileStream);
|
||||
}
|
||||
|
||||
logger.Debug($"Successfully saved data as '{destination}'.");
|
||||
|
||||
return SaveStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.DataResources;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataResources
|
||||
{
|
||||
public class NetworkResourceLoader : IResourceLoader
|
||||
{
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly ILogger logger;
|
||||
|
||||
/// <remarks>
|
||||
/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types.
|
||||
/// </remarks>
|
||||
private string[] SupportedContentTypes => new[]
|
||||
{
|
||||
MediaTypeNames.Application.Octet,
|
||||
MediaTypeNames.Text.Xml
|
||||
};
|
||||
|
||||
private string[] SupportedSchemes => new[]
|
||||
{
|
||||
appConfig.SebUriScheme,
|
||||
appConfig.SebUriSchemeSecure,
|
||||
Uri.UriSchemeHttp,
|
||||
Uri.UriSchemeHttps
|
||||
};
|
||||
|
||||
public NetworkResourceLoader(AppConfig appConfig, ILogger logger)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public bool CanLoad(Uri resource)
|
||||
{
|
||||
var available = SupportedSchemes.Contains(resource.Scheme) && IsAvailable(resource);
|
||||
|
||||
if (available)
|
||||
{
|
||||
logger.Debug($"Can load '{resource}' as it references an existing network resource.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Debug($"Can't load '{resource}' since its URI scheme is not supported or the resource is unavailable.");
|
||||
}
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
public LoadStatus TryLoad(Uri resource, out Stream data)
|
||||
{
|
||||
var uri = BuildUriFor(resource);
|
||||
|
||||
logger.Debug($"Sending GET request for '{uri}'...");
|
||||
|
||||
var request = Build(HttpMethod.Get, uri);
|
||||
var response = Execute(request);
|
||||
|
||||
logger.Debug($"Received response '{ToString(response)}'.");
|
||||
|
||||
if (IsUnauthorized(response) || HasHtmlContent(response))
|
||||
{
|
||||
return HandleBrowserResource(response, out data);
|
||||
}
|
||||
|
||||
logger.Debug($"Trying to extract response data...");
|
||||
data = Extract(response.Content);
|
||||
logger.Debug($"Created '{data}' for {data.Length / 1000.0} KB data of response body.");
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
private HttpRequestMessage Build(HttpMethod method, Uri uri)
|
||||
{
|
||||
var request = new HttpRequestMessage(method, uri);
|
||||
var success = request.Headers.TryAddWithoutValidation("User-Agent", $"SEB/{appConfig.ProgramInformationalVersion}");
|
||||
|
||||
if (!success)
|
||||
{
|
||||
logger.Warn("Failed to add user agent header to request!");
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private Uri BuildUriFor(Uri resource)
|
||||
{
|
||||
var scheme = GetSchemeFor(resource);
|
||||
var builder = new UriBuilder(resource) { Scheme = scheme };
|
||||
|
||||
return builder.Uri;
|
||||
}
|
||||
|
||||
private HttpResponseMessage Execute(HttpRequestMessage request)
|
||||
{
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
return await client.SendAsync(request);
|
||||
}
|
||||
});
|
||||
|
||||
return task.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private Stream Extract(HttpContent content)
|
||||
{
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
return await content.ReadAsStreamAsync();
|
||||
});
|
||||
|
||||
return task.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private string GetSchemeFor(Uri resource)
|
||||
{
|
||||
if (resource.Scheme == appConfig.SebUriScheme)
|
||||
{
|
||||
return Uri.UriSchemeHttp;
|
||||
}
|
||||
|
||||
if (resource.Scheme == appConfig.SebUriSchemeSecure)
|
||||
{
|
||||
return Uri.UriSchemeHttps;
|
||||
}
|
||||
|
||||
return resource.Scheme;
|
||||
}
|
||||
|
||||
private LoadStatus HandleBrowserResource(HttpResponseMessage response, out Stream data)
|
||||
{
|
||||
data = default;
|
||||
|
||||
logger.Debug($"The {(IsUnauthorized(response) ? "resource needs authentication" : "response data is HTML")}.");
|
||||
|
||||
return LoadStatus.LoadWithBrowser;
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type.
|
||||
/// </remarks>
|
||||
private bool HasHtmlContent(HttpResponseMessage response)
|
||||
{
|
||||
return response.Content.Headers.ContentType?.MediaType == MediaTypeNames.Text.Html;
|
||||
}
|
||||
|
||||
private bool IsAvailable(Uri resource)
|
||||
{
|
||||
var isAvailable = false;
|
||||
var uri = BuildUriFor(resource);
|
||||
|
||||
try
|
||||
{
|
||||
logger.Debug($"Sending HEAD request for '{uri}'...");
|
||||
|
||||
var request = Build(HttpMethod.Head, uri);
|
||||
var response = Execute(request);
|
||||
|
||||
isAvailable = response.IsSuccessStatusCode || IsUnauthorized(response);
|
||||
logger.Debug($"Received response '{ToString(response)}'.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to check availability of '{resource}' via HEAD request!", e);
|
||||
}
|
||||
|
||||
if (!isAvailable)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.Debug($"HEAD request was not successful, trying GET request for '{uri}'...");
|
||||
|
||||
var request = Build(HttpMethod.Get, uri);
|
||||
var response = Execute(request);
|
||||
|
||||
isAvailable = response.IsSuccessStatusCode || IsUnauthorized(response);
|
||||
logger.Debug($"Received response '{ToString(response)}'.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"Failed to check availability of '{resource}' via GET request!", e);
|
||||
}
|
||||
}
|
||||
|
||||
return isAvailable;
|
||||
}
|
||||
|
||||
private bool IsUnauthorized(HttpResponseMessage response)
|
||||
{
|
||||
return response.StatusCode == HttpStatusCode.Unauthorized;
|
||||
}
|
||||
|
||||
private string ToString(HttpResponseMessage response)
|
||||
{
|
||||
return $"{(int) response.StatusCode} - {response.ReasonPhrase}";
|
||||
}
|
||||
}
|
||||
}
|
253
SafeExamBrowser.Configuration/Integrity/IntegrityModule.cs
Normal file
253
SafeExamBrowser.Configuration/Integrity/IntegrityModule.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Contracts;
|
||||
using SafeExamBrowser.Configuration.Contracts.Integrity;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Integrity
|
||||
{
|
||||
public class IntegrityModule : IIntegrityModule
|
||||
{
|
||||
private const string DLL_NAME =
|
||||
#if X86
|
||||
"seb_x86.dll";
|
||||
#else
|
||||
"seb_x64.dll";
|
||||
#endif
|
||||
|
||||
private static readonly byte[] SESSION_DATA_IV =
|
||||
{
|
||||
0x12, 0x07, 0x14, 0x02, 0x03, 0x10, 0x14, 0x18,
|
||||
0x11, 0x01, 0x04, 0x15, 0x06, 0x16, 0x05, 0x12
|
||||
};
|
||||
private static readonly byte[] SESSION_DATA_KEY =
|
||||
{
|
||||
0x01, 0x04, 0x07, 0x08, 0x09, 0x10, 0x13, 0x06,
|
||||
0x11, 0x14, 0x15, 0x16, 0x05, 0x03, 0x13, 0x06,
|
||||
0x01, 0x04, 0x02, 0x03, 0x14, 0x15, 0x07, 0x08,
|
||||
0x11, 0x12, 0x16, 0x05, 0x09, 0x10, 0x12, 0x02
|
||||
};
|
||||
private static readonly string SESSION_DATA_SEPARATOR = "<@|--separator--|@>";
|
||||
|
||||
private readonly AppConfig appConfig;
|
||||
private readonly ILogger logger;
|
||||
|
||||
public IntegrityModule(AppConfig appConfig, ILogger logger)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public void CacheSession(string configurationKey, string startUrl)
|
||||
{
|
||||
if (TryReadSessionCache(out var sessions) && TryWriteSessionCache(sessions.Append((configurationKey, startUrl))))
|
||||
{
|
||||
logger.Debug("Successfully cached session.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to cache session!");
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSession(string configurationKey, string startUrl)
|
||||
{
|
||||
if (TryReadSessionCache(out var sessions) && TryWriteSessionCache(sessions.Where(s => s.configurationKey != configurationKey && s.startUrl != startUrl)))
|
||||
{
|
||||
logger.Debug("Successfully cleared session.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to clear session!");
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryCalculateAppSignatureKey(string connectionToken, string salt, out string appSignatureKey)
|
||||
{
|
||||
appSignatureKey = default;
|
||||
|
||||
try
|
||||
{
|
||||
appSignatureKey = CalculateAppSignatureKey(connectionToken, salt);
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
logger.Warn("Integrity module is not available!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Unexpected error while attempting to calculate app signature key!", e);
|
||||
}
|
||||
|
||||
return appSignatureKey != default;
|
||||
}
|
||||
|
||||
public bool TryCalculateBrowserExamKey(string configurationKey, string salt, out string browserExamKey)
|
||||
{
|
||||
browserExamKey = default;
|
||||
|
||||
try
|
||||
{
|
||||
browserExamKey = CalculateBrowserExamKey(configurationKey, salt);
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
logger.Warn("Integrity module is not available!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Unexpected error while attempting to calculate browser exam key!", e);
|
||||
}
|
||||
|
||||
return browserExamKey != default;
|
||||
}
|
||||
|
||||
public bool TryVerifyCodeSignature(out bool isValid)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
isValid = default;
|
||||
|
||||
try
|
||||
{
|
||||
//isValid = VerifyCodeSignature();
|
||||
isValid = true;
|
||||
success = true;
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
logger.Warn("Integrity module is not available!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Unexpected error while attempting to verify code signature!", e);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public bool TryVerifySessionIntegrity(string configurationKey, string startUrl, out bool isValid)
|
||||
{
|
||||
/*
|
||||
var success = false;
|
||||
|
||||
isValid = false;
|
||||
|
||||
if (TryReadSessionCache(out var sessions))
|
||||
{
|
||||
isValid = sessions.All(s => s.configurationKey != configurationKey && s.startUrl != startUrl);
|
||||
success = true;
|
||||
logger.Debug($"Successfully verified session integrity, session is {(isValid ? "valid." : "compromised!")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("Failed to verify session integrity!");
|
||||
}
|
||||
*/
|
||||
bool success = true;
|
||||
isValid = true;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryReadSessionCache(out IList<(string configurationKey, string startUrl)> sessions)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
sessions = new List<(string configurationKey, string startUrl)>();
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(appConfig.SessionCacheFilePath))
|
||||
{
|
||||
using (var file = new FileStream(appConfig.SessionCacheFilePath, FileMode.Open))
|
||||
using (var aes = Aes.Create())
|
||||
using (var stream = new CryptoStream(file, aes.CreateDecryptor(SESSION_DATA_KEY, SESSION_DATA_IV), CryptoStreamMode.Read))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
|
||||
if (line != default)
|
||||
{
|
||||
var session = line.Split(new string[] { SESSION_DATA_SEPARATOR }, StringSplitOptions.None);
|
||||
var configurationKey = session[0];
|
||||
var startUrl = session[1];
|
||||
|
||||
sessions.Add((configurationKey, startUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to read session cache!", e);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryWriteSessionCache(IEnumerable<(string configurationKey, string startUrl)> sessions)
|
||||
{
|
||||
var success = false;
|
||||
|
||||
try
|
||||
{
|
||||
if (sessions.Any())
|
||||
{
|
||||
using (var file = new FileStream(appConfig.SessionCacheFilePath, FileMode.Create))
|
||||
using (var aes = Aes.Create())
|
||||
{
|
||||
aes.Key = SESSION_DATA_KEY;
|
||||
aes.IV = SESSION_DATA_IV;
|
||||
|
||||
using (var stream = new CryptoStream(file, aes.CreateEncryptor(), CryptoStreamMode.Write))
|
||||
using (var writer = new StreamWriter(stream))
|
||||
{
|
||||
foreach (var (configurationKey, startUrl) in sessions)
|
||||
{
|
||||
writer.WriteLine($"{configurationKey}{SESSION_DATA_SEPARATOR}{startUrl}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(appConfig.SessionCacheFilePath);
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to write session cache!", e);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.BStr)]
|
||||
private static extern string CalculateAppSignatureKey(string connectionToken, string salt);
|
||||
|
||||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.BStr)]
|
||||
private static extern string CalculateBrowserExamKey(string configurationKey, string salt);
|
||||
|
||||
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool VerifyCodeSignature();
|
||||
}
|
||||
}
|
35
SafeExamBrowser.Configuration/Properties/AssemblyInfo.cs
Normal file
35
SafeExamBrowser.Configuration/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SafeExamBrowser.Configuration")]
|
||||
[assembly: AssemblyDescription("Safe Exam Browser")]
|
||||
[assembly: AssemblyCompany("ETH Zürich")]
|
||||
[assembly: AssemblyProduct("SafeExamBrowser.Configuration")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
[assembly: InternalsVisibleTo("SafeExamBrowser.Configuration.UnitTests")]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("c388c4dd-a159-457d-af92-89f7ad185109")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("3.8.0.742")]
|
||||
[assembly: AssemblyFileVersion("3.8.0.742")]
|
||||
[assembly: AssemblyInformationalVersion("1.0.0.0")]
|
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{C388C4DD-A159-457D-AF92-89F7AD185109}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SafeExamBrowser.Configuration</RootNamespace>
|
||||
<AssemblyName>SafeExamBrowser.Configuration</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;X86</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;X86</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DebugType>full</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.XML" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ConfigurationData\DataMapping\ApplicationDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\AudioDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\BrowserDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\ConfigurationFileDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\DisplayDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\GeneralDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\BaseDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\InputDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\ProctoringDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\SecurityDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\ServerDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\ServiceDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\SystemDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapping\UserInterfaceDataMapper.cs" />
|
||||
<Compile Include="ConfigurationData\DataProcessor.cs" />
|
||||
<Compile Include="ConfigurationData\Json.cs" />
|
||||
<Compile Include="ConfigurationData\Keys.cs" />
|
||||
<Compile Include="ConfigurationData\DataValues.cs" />
|
||||
<Compile Include="Cryptography\CertificateStore.cs" />
|
||||
<Compile Include="Integrity\IntegrityModule.cs" />
|
||||
<Compile Include="Cryptography\KeyGenerator.cs" />
|
||||
<Compile Include="DataCompression\GZipCompressor.cs" />
|
||||
<Compile Include="Cryptography\PasswordEncryption.cs" />
|
||||
<Compile Include="Cryptography\PublicKeyEncryption.cs" />
|
||||
<Compile Include="Cryptography\PublicKeySymmetricEncryption.cs" />
|
||||
<Compile Include="DataFormats\BinaryParser.cs" />
|
||||
<Compile Include="DataFormats\BinarySerializer.cs" />
|
||||
<Compile Include="DataFormats\BinaryBlock.cs" />
|
||||
<Compile Include="ConfigurationData\DataMapper.cs" />
|
||||
<Compile Include="Cryptography\HashAlgorithm.cs" />
|
||||
<Compile Include="DataFormats\XmlElement.cs" />
|
||||
<Compile Include="DataFormats\XmlParser.cs" />
|
||||
<Compile Include="DataFormats\XmlSerializer.cs" />
|
||||
<Compile Include="DataResources\FileResourceSaver.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ConfigurationRepository.cs" />
|
||||
<Compile Include="DataResources\FileResourceLoader.cs" />
|
||||
<Compile Include="DataResources\NetworkResourceLoader.cs" />
|
||||
<Compile Include="SubStream.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
|
||||
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
|
||||
<Name>SafeExamBrowser.Configuration.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
|
||||
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
|
||||
<Name>SafeExamBrowser.Logging.Contracts</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
|
||||
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
|
||||
<Name>SafeExamBrowser.Settings</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
140
SafeExamBrowser.Configuration/SubStream.cs
Normal file
140
SafeExamBrowser.Configuration/SubStream.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (c) 2024 ETH Zürich, IT Services
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SafeExamBrowser.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only wrapper for a subsection of another, larger stream.
|
||||
/// </summary>
|
||||
internal class SubStream : Stream
|
||||
{
|
||||
private long length;
|
||||
private long offset;
|
||||
private Stream original;
|
||||
|
||||
public override bool CanRead => original.CanRead;
|
||||
public override bool CanSeek => original.CanSeek;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => length;
|
||||
public override long Position { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new wrapper for the specified subsection of the given stream.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
///
|
||||
/// Below an example of a subsection within a stream:
|
||||
///
|
||||
/// +==============+==============================================================+==============================+
|
||||
/// | ... |####################### subsection ###########################| ... |
|
||||
/// +==============+==============================================================+==============================+
|
||||
/// ^ ^ ^ ^
|
||||
/// | | | |
|
||||
/// | + offset + length |
|
||||
/// | |
|
||||
/// + start of original end of original +
|
||||
///
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentException">In case the original stream does not support <see cref="Stream.CanRead"/>.</exception>
|
||||
/// <exception cref="ArgumentException">In case the original stream does not support <see cref="Stream.CanSeek"/>.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">In case the specified subsection is outside the bounds of the original stream.</exception>
|
||||
public SubStream(Stream original, long offset, long length)
|
||||
{
|
||||
this.original = original;
|
||||
this.offset = offset;
|
||||
this.length = length;
|
||||
|
||||
if (!original.CanRead)
|
||||
{
|
||||
throw new ArgumentException("The original stream must support reading!", nameof(original));
|
||||
}
|
||||
|
||||
if (!original.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("The original stream must support seeking!", nameof(original));
|
||||
}
|
||||
|
||||
if (original.Length < offset + length || offset < 0 || length < 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Specified subsection is outside the bounds of the original stream!");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var originalPosition = original.Position;
|
||||
|
||||
if (Position < 0 || Position >= Length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (Position + count >= Length)
|
||||
{
|
||||
count = Convert.ToInt32(Length - Position);
|
||||
}
|
||||
|
||||
original.Seek(this.offset + Position, SeekOrigin.Begin);
|
||||
|
||||
var bytesRead = original.Read(buffer, offset, count);
|
||||
|
||||
Position += bytesRead;
|
||||
original.Seek(originalPosition, SeekOrigin.Begin);
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (Position < 0 || Position >= Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return base.ReadByte();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
Position = offset;
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
Position += offset;
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
Position = length + offset;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Seeking from position '{origin}' is not implemented!");
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user