SEBWIN-508: Implemented basic JavaScript API.

This commit is contained in:
Damian Büchel 2021-10-18 12:06:10 +02:00
parent deccb3340c
commit 0da587e521
19 changed files with 311 additions and 100 deletions

View file

@ -13,6 +13,7 @@ using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Handlers;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
@ -29,6 +30,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
private AppConfig appConfig;
private Mock<IRequestFilter> filter;
private Mock<IKeyGenerator> keyGenerator;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
@ -41,11 +43,12 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
appConfig = new AppConfig();
filter = new Mock<IRequestFilter>();
keyGenerator = new Mock<IKeyGenerator>();
logger = new Mock<ILogger>();
settings = new BrowserSettings();
windowSettings = new WindowSettings();
text = new Mock<IText>();
resourceHandler = new ResourceHandler(appConfig, filter.Object, logger.Object, settings, windowSettings, text.Object);
resourceHandler = new ResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, settings, windowSettings, text.Object);
sut = new TestableRequestHandler(appConfig, filter.Object, logger.Object, resourceHandler, settings, windowSettings, text.Object);
}

View file

@ -15,6 +15,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
@ -30,6 +31,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
private AppConfig appConfig;
private Mock<IRequestFilter> filter;
private Mock<IKeyGenerator> keyGenerator;
private Mock<ILogger> logger;
private BrowserSettings settings;
private WindowSettings windowSettings;
@ -41,12 +43,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
{
appConfig = new AppConfig();
filter = new Mock<IRequestFilter>();
keyGenerator = new Mock<IKeyGenerator>();
logger = new Mock<ILogger>();
settings = new BrowserSettings();
windowSettings = new WindowSettings();
text = new Mock<IText>();
sut = new TestableResourceHandler(appConfig, filter.Object, logger.Object, settings, windowSettings, text.Object);
sut = new TestableResourceHandler(appConfig, filter.Object, keyGenerator.Object, logger.Object, settings, windowSettings, text.Object);
}
[TestMethod]
@ -57,11 +60,13 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var request = new Mock<IRequest>();
browser.SetupGet(b => b.Address).Returns("http://www.host.org");
keyGenerator.Setup(g => g.CalculateBrowserExamKeyHash(It.IsAny<string>())).Returns(new Random().Next().ToString());
keyGenerator.Setup(g => g.CalculateConfigurationKeyHash(It.IsAny<string>())).Returns(new Random().Next().ToString());
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
settings.SendConfigurationKey = true;
settings.SendExamKey = true;
settings.SendBrowserExamKey = true;
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
@ -85,7 +90,7 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);
settings.SendConfigurationKey = true;
settings.SendExamKey = true;
settings.SendBrowserExamKey = true;
var result = sut.OnBeforeResourceLoad(browser.Object, Mock.Of<IBrowser>(), Mock.Of<IFrame>(), request.Object, Mock.Of<IRequestCallback>());
@ -291,10 +296,11 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
internal TestableResourceHandler(
AppConfig appConfig,
IRequestFilter filter,
IKeyGenerator keyGenerator,
ILogger logger,
BrowserSettings settings,
WindowSettings windowSettings,
IText text) : base(appConfig, filter, logger, settings, windowSettings, text)
IText text) : base(appConfig, filter, keyGenerator, logger, settings, windowSettings, text)
{
}

View file

@ -38,16 +38,17 @@ namespace SafeExamBrowser.Browser
{
private int instanceIdCounter = default(int);
private AppConfig appConfig;
private List<BrowserApplicationInstance> instances;
private IFileSystemDialog fileSystemDialog;
private IHashAlgorithm hashAlgorithm;
private INativeMethods nativeMethods;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private IText text;
private IUserInterfaceFactory uiFactory;
private readonly AppConfig appConfig;
private readonly IFileSystemDialog fileSystemDialog;
private readonly IHashAlgorithm hashAlgorithm;
private readonly List<BrowserApplicationInstance> instances;
private readonly IKeyGenerator keyGenerator;
private readonly IModuleLogger logger;
private readonly IMessageBox messageBox;
private readonly INativeMethods nativeMethods;
private readonly BrowserSettings settings;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
public bool AutoStart { get; private set; }
public IconResource Icon { get; private set; }
@ -65,6 +66,7 @@ namespace SafeExamBrowser.Browser
BrowserSettings settings,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
IKeyGenerator keyGenerator,
INativeMethods nativeMethods,
IMessageBox messageBox,
IModuleLogger logger,
@ -74,10 +76,11 @@ namespace SafeExamBrowser.Browser
this.appConfig = appConfig;
this.fileSystemDialog = fileSystemDialog;
this.hashAlgorithm = hashAlgorithm;
this.nativeMethods = nativeMethods;
this.instances = new List<BrowserApplicationInstance>();
this.keyGenerator = keyGenerator;
this.logger = logger;
this.messageBox = messageBox;
this.nativeMethods = nativeMethods;
this.settings = settings;
this.text = text;
this.uiFactory = uiFactory;
@ -172,7 +175,19 @@ namespace SafeExamBrowser.Browser
var isMainInstance = instances.Count == 0;
var instanceLogger = logger.CloneFor($"Browser Instance #{id}");
var startUrl = url ?? GenerateStartUrl();
var instance = new BrowserApplicationInstance(appConfig, settings, id, isMainInstance, fileSystemDialog, hashAlgorithm, messageBox, instanceLogger, text, uiFactory, startUrl);
var instance = new BrowserApplicationInstance(
appConfig,
settings,
id,
isMainInstance,
fileSystemDialog,
hashAlgorithm,
keyGenerator,
messageBox,
instanceLogger,
text,
uiFactory,
startUrl);
instance.ConfigurationDownloadRequested += (fileName, args) => ConfigurationDownloadRequested?.Invoke(fileName, args);
instance.PopupRequested += Instance_PopupRequested;

View file

@ -41,19 +41,21 @@ namespace SafeExamBrowser.Browser
{
private const double ZOOM_FACTOR = 0.2;
private AppConfig appConfig;
private readonly AppConfig appConfig;
private readonly IFileSystemDialog fileSystemDialog;
private readonly IHashAlgorithm hashAlgorithm;
private readonly HttpClient httpClient;
private readonly IKeyGenerator keyGenerator;
private readonly IModuleLogger logger;
private readonly IMessageBox messageBox;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
private IBrowserControl control;
private IBrowserWindow window;
private HttpClient httpClient;
private bool isMainInstance;
private IFileSystemDialog fileSystemDialog;
private IHashAlgorithm hashAlgorithm;
private IMessageBox messageBox;
private IModuleLogger logger;
private BrowserSettings settings;
private string startUrl;
private IText text;
private IUserInterfaceFactory uiFactory;
private double zoomLevel;
private WindowSettings WindowSettings
@ -84,6 +86,7 @@ namespace SafeExamBrowser.Browser
bool isMainInstance,
IFileSystemDialog fileSystemDialog,
IHashAlgorithm hashAlgorithm,
IKeyGenerator keyGenerator,
IMessageBox messageBox,
IModuleLogger logger,
IText text,
@ -96,6 +99,7 @@ namespace SafeExamBrowser.Browser
this.isMainInstance = isMainInstance;
this.fileSystemDialog = fileSystemDialog;
this.hashAlgorithm = hashAlgorithm;
this.keyGenerator = keyGenerator;
this.messageBox = messageBox;
this.logger = logger;
this.settings = settings;
@ -130,9 +134,11 @@ namespace SafeExamBrowser.Browser
var downloadHandler = new DownloadHandler(appConfig, downloadLogger, settings, WindowSettings);
var keyboardHandler = new KeyboardHandler();
var lifeSpanHandler = new LifeSpanHandler();
var renderProcessMessageLogger = logger.CloneFor($"{nameof(RenderProcessMessageHandler)} #{Id}");
var renderProcessMessageHandler = new RenderProcessMessageHandler(appConfig, renderProcessMessageLogger, keyGenerator, text);
var requestFilter = new RequestFilter();
var requestLogger = logger.CloneFor($"{nameof(RequestHandler)} #{Id}");
var resourceHandler = new ResourceHandler(appConfig, requestFilter, logger, settings, WindowSettings, text);
var resourceHandler = new ResourceHandler(appConfig, requestFilter, keyGenerator, logger, settings, WindowSettings, text);
var requestHandler = new RequestHandler(appConfig, requestFilter, requestLogger, resourceHandler, settings, WindowSettings, text);
Icon = new BrowserIconResource();
@ -162,6 +168,7 @@ namespace SafeExamBrowser.Browser
downloadHandler,
keyboardHandler,
lifeSpanHandler,
renderProcessMessageHandler,
requestHandler,
startUrl);
control.AddressChanged += Control_AddressChanged;

View file

@ -16,13 +16,14 @@ namespace SafeExamBrowser.Browser
{
internal class BrowserControl : ChromiumWebBrowser, IBrowserControl
{
private IContextMenuHandler contextMenuHandler;
private IDialogHandler dialogHandler;
private IDisplayHandler displayHandler;
private IDownloadHandler downloadHandler;
private IKeyboardHandler keyboardHandler;
private ILifeSpanHandler lifeSpanHandler;
private IRequestHandler requestHandler;
private readonly IContextMenuHandler contextMenuHandler;
private readonly IDialogHandler dialogHandler;
private readonly IDisplayHandler displayHandler;
private readonly IDownloadHandler downloadHandler;
private readonly IKeyboardHandler keyboardHandler;
private readonly ILifeSpanHandler lifeSpanHandler;
private readonly IRenderProcessMessageHandler renderProcessMessageHandler;
private readonly IRequestHandler requestHandler;
private AddressChangedEventHandler addressChanged;
private LoadFailedEventHandler loadFailed;
@ -63,6 +64,7 @@ namespace SafeExamBrowser.Browser
IDownloadHandler downloadHandler,
IKeyboardHandler keyboardHandler,
ILifeSpanHandler lifeSpanHandler,
IRenderProcessMessageHandler renderProcessMessageHandler,
IRequestHandler requestHandler,
string url) : base(url)
{
@ -72,6 +74,7 @@ namespace SafeExamBrowser.Browser
this.downloadHandler = downloadHandler;
this.keyboardHandler = keyboardHandler;
this.lifeSpanHandler = lifeSpanHandler;
this.renderProcessMessageHandler = renderProcessMessageHandler;
this.requestHandler = requestHandler;
}
@ -97,6 +100,7 @@ namespace SafeExamBrowser.Browser
KeyboardHandler = keyboardHandler;
LifeSpanHandler = lifeSpanHandler;
MenuHandler = contextMenuHandler;
RenderProcessMessageHandler = renderProcessMessageHandler;
RequestHandler = requestHandler;
}

View file

@ -0,0 +1,8 @@
SafeExamBrowser = {
version: 'SEB_Windows_%%_VERSION_%%',
security: {
browserExamKey: '%%_BEK_%%',
configKey: '%%_CK_%%',
updateKeys: (callback) => callback()
}
}

View file

@ -10,21 +10,45 @@ using System.IO;
using System.Reflection;
using SafeExamBrowser.I18n.Contracts;
namespace SafeExamBrowser.Browser.Pages
namespace SafeExamBrowser.Browser.Content
{
internal class HtmlLoader
internal class ContentLoader
{
private string api;
private IText text;
internal HtmlLoader(IText text)
internal ContentLoader(IText text)
{
this.text = text;
}
internal string LoadApi(string browserExamKey, string configurationKey, string version)
{
if (api == default(string))
{
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.Api.js";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
{
api = reader.ReadToEnd();
}
}
var js = api;
js = js.Replace("%%_BEK_%%", browserExamKey);
js = js.Replace("%%_CK_%%", configurationKey);
js = js.Replace("%%_VERSION_%%", version);
return js;
}
internal string LoadBlockedContent()
{
var assembly = Assembly.GetAssembly(typeof(HtmlLoader));
var path = $"{typeof(HtmlLoader).Namespace}.BlockedContent.html";
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.BlockedContent.html";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))
@ -39,8 +63,8 @@ namespace SafeExamBrowser.Browser.Pages
internal string LoadBlockedPage()
{
var assembly = Assembly.GetAssembly(typeof(HtmlLoader));
var path = $"{typeof(HtmlLoader).Namespace}.BlockedPage.html";
var assembly = Assembly.GetAssembly(typeof(ContentLoader));
var path = $"{typeof(ContentLoader).Namespace}.BlockedPage.html";
using (var stream = assembly.GetManifestResourceStream(path))
using (var reader = new StreamReader(stream))

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* 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 CefSharp;
using SafeExamBrowser.Browser.Content;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Browser.Handlers
{
internal class RenderProcessMessageHandler : IRenderProcessMessageHandler
{
private readonly AppConfig appConfig;
private readonly ContentLoader contentLoader;
private readonly ILogger logger;
private readonly IKeyGenerator generator;
internal RenderProcessMessageHandler(AppConfig appConfig, ILogger logger, IKeyGenerator generator, IText text)
{
this.appConfig = appConfig;
this.contentLoader = new ContentLoader(text);
this.logger = logger;
this.generator = generator;
}
public void OnContextCreated(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
{
var browserExamKey = generator.CalculateBrowserExamKeyHash(webBrowser.Address);
var configurationKey = generator.CalculateConfigurationKeyHash(webBrowser.Address);
var api = contentLoader.LoadApi(browserExamKey, configurationKey, appConfig.ProgramBuildVersion);
frame.ExecuteJavaScriptAsync(api);
}
public void OnContextReleased(IWebBrowser webBrowser, IBrowser browser, IFrame frame)
{
}
public void OnFocusedNodeChanged(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IDomNode node)
{
}
public void OnUncaughtException(IWebBrowser webBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
{
}
}
}

View file

@ -12,16 +12,15 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using CefSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SafeExamBrowser.Browser.Content;
using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Pages;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
@ -33,33 +32,34 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler
{
private SHA256Managed algorithm;
private AppConfig appConfig;
private string browserExamKey;
private readonly AppConfig appConfig;
private readonly ContentLoader contentLoader;
private readonly IRequestFilter filter;
private readonly IKeyGenerator keyGenerator;
private readonly ILogger logger;
private readonly BrowserSettings settings;
private readonly IText text;
private readonly WindowSettings windowSettings;
private IResourceHandler contentHandler;
private IRequestFilter filter;
private HtmlLoader htmlLoader;
private ILogger logger;
private IResourceHandler pageHandler;
private string sessionIdentifier;
private BrowserSettings settings;
private WindowSettings windowSettings;
private IText text;
internal event SessionIdentifierDetectedEventHandler SessionIdentifierDetected;
internal ResourceHandler(
AppConfig appConfig,
IRequestFilter filter,
IKeyGenerator keyGenerator,
ILogger logger,
BrowserSettings settings,
WindowSettings windowSettings,
IText text)
{
this.appConfig = appConfig;
this.algorithm = new SHA256Managed();
this.filter = filter;
this.htmlLoader = new HtmlLoader(text);
this.contentLoader = new ContentLoader(text);
this.keyGenerator = keyGenerator;
this.logger = logger;
this.settings = settings;
this.windowSettings = windowSettings;
@ -123,22 +123,15 @@ namespace SafeExamBrowser.Browser.Handlers
if (pageUrl?.Host?.Equals(requestUrl?.Host) == true)
{
var headers = new NameValueCollection(request.Headers);
var urlWithoutFragment = request.Url.Split('#')[0];
if (settings.SendConfigurationKey)
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + settings.ConfigurationKey));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
headers["X-SafeExamBrowser-ConfigKeyHash"] = key;
headers["X-SafeExamBrowser-ConfigKeyHash"] = keyGenerator.CalculateConfigurationKeyHash(request.Url);
}
if (settings.SendExamKey)
if (settings.SendBrowserExamKey)
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey())));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
headers["X-SafeExamBrowser-RequestHash"] = key;
headers["X-SafeExamBrowser-RequestHash"] = keyGenerator.CalculateBrowserExamKeyHash(request.Url);
}
request.Headers = headers;
@ -169,27 +162,6 @@ namespace SafeExamBrowser.Browser.Handlers
return block;
}
private string ComputeBrowserExamKey()
{
var salt = settings.ExamKeySalt;
if (salt == default(byte[]))
{
salt = new byte[0];
logger.Warn("The current configuration does not contain a salt value for the browser exam key!");
}
using (var algorithm = new HMACSHA256(salt))
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(appConfig.CodeSignatureHash + appConfig.ProgramBuildVersion + settings.ConfigurationKey));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
browserExamKey = key;
return browserExamKey;
}
}
private bool IsMailtoUrl(string url)
{
return url.StartsWith(Uri.UriSchemeMailto);
@ -234,12 +206,12 @@ namespace SafeExamBrowser.Browser.Handlers
{
if (contentHandler == default(IResourceHandler))
{
contentHandler = CefSharp.ResourceHandler.FromString(htmlLoader.LoadBlockedContent());
contentHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedContent());
}
if (pageHandler == default(IResourceHandler))
{
pageHandler = CefSharp.ResourceHandler.FromString(htmlLoader.LoadBlockedPage());
pageHandler = CefSharp.ResourceHandler.FromString(contentLoader.LoadBlockedPage());
}
switch (resourceType)

View file

@ -104,9 +104,10 @@
<Compile Include="Handlers\DownloadHandler.cs" />
<Compile Include="Handlers\KeyboardHandler.cs" />
<Compile Include="Handlers\LifeSpanHandler.cs" />
<Compile Include="Handlers\RenderProcessMessageHandler.cs" />
<Compile Include="Handlers\RequestHandler.cs" />
<Compile Include="Handlers\ResourceHandler.cs" />
<Compile Include="Pages\HtmlLoader.cs" />
<Compile Include="Content\ContentLoader.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@ -148,8 +149,8 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\BlockedContent.html" />
<EmbeddedResource Include="Pages\BlockedPage.html" />
<EmbeddedResource Include="Content\BlockedContent.html" />
<EmbeddedResource Include="Content\BlockedPage.html" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
@ -157,6 +158,9 @@
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Content\Api.js" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>

View file

@ -209,8 +209,19 @@ namespace SafeExamBrowser.Client
private IOperation BuildBrowserOperation()
{
var fileSystemDialog = BuildFileSystemDialog();
var keyGenerator = new KeyGenerator(context.AppConfig, ModuleLogger(nameof(KeyGenerator)), context.Settings);
var moduleLogger = ModuleLogger(nameof(BrowserApplication));
var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, fileSystemDialog, new HashAlgorithm(), nativeMethods, messageBox, moduleLogger, text, uiFactory);
var browser = new BrowserApplication(
context.AppConfig,
context.Settings.Browser,
fileSystemDialog,
new HashAlgorithm(),
keyGenerator,
nativeMethods,
messageBox,
moduleLogger,
text,
uiFactory);
var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskview, uiFactory);
context.Browser = browser;

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* 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.Contracts.Cryptography
{
/// <summary>
/// Provides funcionality to calculate keys for integrity checks.
/// </summary>
public interface IKeyGenerator
{
/// <summary>
/// Calculates the hash value of the browser exam key (BEK) for the given URL.
/// </summary>
string CalculateBrowserExamKeyHash(string url);
/// <summary>
/// Calculates the hash value of the configuration key (CK) for the given URL.
/// </summary>
string CalculateConfigurationKeyHash(string url);
}
}

View file

@ -59,6 +59,7 @@
<Compile Include="Cryptography\EncryptionParameters.cs" />
<Compile Include="Cryptography\ICertificateStore.cs" />
<Compile Include="Cryptography\IHashAlgorithm.cs" />
<Compile Include="Cryptography\IKeyGenerator.cs" />
<Compile Include="Cryptography\IPasswordEncryption.cs" />
<Compile Include="Cryptography\IPublicKeyEncryption.cs" />
<Compile Include="Cryptography\PasswordParameters.cs" />

View file

@ -346,7 +346,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is byte[] salt)
{
settings.Browser.ExamKeySalt = salt;
settings.Browser.BrowserExamKeySalt = salt;
}
}
@ -487,7 +487,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
if (value is bool send)
{
settings.Browser.SendConfigurationKey = send;
settings.Browser.SendExamKey = send;
settings.Browser.SendBrowserExamKey = send;
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
*
* 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 SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings;
namespace SafeExamBrowser.Configuration.Cryptography
{
public class KeyGenerator : IKeyGenerator
{
private readonly SHA256Managed algorithm;
private readonly AppConfig appConfig;
private readonly ILogger logger;
private readonly AppSettings settings;
private string browserExamKey;
public KeyGenerator(AppConfig appConfig, ILogger logger, AppSettings settings)
{
this.algorithm = new SHA256Managed();
this.appConfig = appConfig;
this.logger = logger;
this.settings = settings;
}
public string CalculateBrowserExamKeyHash(string url)
{
var urlWithoutFragment = url.Split('#')[0];
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey())));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
return key;
}
public string CalculateConfigurationKeyHash(string url)
{
var urlWithoutFragment = url.Split('#')[0];
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + settings.Browser.ConfigurationKey));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
return key;
}
private string ComputeBrowserExamKey()
{
var salt = settings.Browser.BrowserExamKeySalt;
if (salt == default(byte[]))
{
salt = new byte[0];
logger.Warn("The current configuration does not contain a salt value for the browser exam key!");
}
using (var algorithm = new HMACSHA256(salt))
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(appConfig.CodeSignatureHash + appConfig.ProgramBuildVersion + settings.Browser.ConfigurationKey));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
browserExamKey = key;
return browserExamKey;
}
}
}
}

View file

@ -74,6 +74,7 @@
<Compile Include="ConfigurationData\Keys.cs" />
<Compile Include="ConfigurationData\DataValues.cs" />
<Compile Include="Cryptography\CertificateStore.cs" />
<Compile Include="Cryptography\KeyGenerator.cs" />
<Compile Include="DataCompression\GZipCompressor.cs" />
<Compile Include="Cryptography\PasswordEncryption.cs" />
<Compile Include="Cryptography\PublicKeyEncryption.cs" />

View file

@ -66,6 +66,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary>
public bool AllowUploads { get; set; }
/// <summary>
/// The salt value for the calculation of the browser exam key which is used for integrity checks with server applications (see also <see cref="SendBrowserExamKey"/>).
/// </summary>
public byte[] BrowserExamKeySalt { get; set; }
/// <summary>
/// The configuration key used for integrity checks with server applications (see also <see cref="SendConfigurationKey"/>).
/// </summary>
@ -108,11 +113,6 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary>
public bool EnableBrowser { get; set; }
/// <summary>
/// The salt value for the calculation of the exam key which is used for integrity checks with server applications (see also <see cref="SendExamKey"/>).
/// </summary>
public byte[] ExamKeySalt { get; set; }
/// <summary>
/// The settings to be used for the browser request filter.
/// </summary>
@ -169,9 +169,9 @@ namespace SafeExamBrowser.Settings.Browser
public bool SendConfigurationKey { get; set; }
/// <summary>
/// Determines whether the exam key header is sent with every HTTP request (see also <see cref="ExamKeySalt"/>).
/// Determines whether the browser exam key header is sent with every HTTP request (see also <see cref="BrowserExamKeySalt"/>).
/// </summary>
public bool SendExamKey { get; set; }
public bool SendBrowserExamKey { get; set; }
/// <summary>
/// The URL with which the main browser window will be loaded.