SEBWIN-309: Implemented scaffolding for Configuration Key.

This commit is contained in:
dbuechel 2020-01-10 08:54:10 +01:00
parent 45e1b001e3
commit 5e131289b0
8 changed files with 188 additions and 8 deletions

View file

@ -33,7 +33,7 @@ namespace SafeExamBrowser.Browser.Handlers
this.filter = filter; this.filter = filter;
this.logger = logger; this.logger = logger;
this.settings = settings; this.settings = settings;
this.resourceHandler = new ResourceHandler(appConfig, settings.Filter, filter, logger, text); this.resourceHandler = new ResourceHandler(appConfig, settings, filter, logger, text);
} }
protected override bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) protected override bool GetAuthCredentials(IWebBrowser webBrowser, IBrowser browser, string originUrl, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)

View file

@ -10,30 +10,34 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using CefSharp; using CefSharp;
using SafeExamBrowser.Browser.Contracts.Filters; using SafeExamBrowser.Browser.Contracts.Filters;
using SafeExamBrowser.Browser.Filters; using SafeExamBrowser.Browser.Filters;
using SafeExamBrowser.Configuration.Contracts; using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.I18n.Contracts; using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings.Browser;
using SafeExamBrowser.Settings.Browser.Filter; using SafeExamBrowser.Settings.Browser.Filter;
using BrowserSettings = SafeExamBrowser.Settings.Browser.BrowserSettings;
namespace SafeExamBrowser.Browser.Handlers namespace SafeExamBrowser.Browser.Handlers
{ {
internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler
{ {
private AppConfig appConfig; private AppConfig appConfig;
private FilterSettings settings; private SHA256Managed algorithm;
private BrowserSettings settings;
private ILogger logger; private ILogger logger;
private IRequestFilter filter; private IRequestFilter filter;
private IResourceHandler contentHandler; private IResourceHandler contentHandler;
private IResourceHandler pageHandler; private IResourceHandler pageHandler;
private IText text; private IText text;
internal ResourceHandler(AppConfig appConfig, FilterSettings settings, IRequestFilter filter, ILogger logger, IText text) internal ResourceHandler(AppConfig appConfig, BrowserSettings settings, IRequestFilter filter, ILogger logger, IText text)
{ {
this.appConfig = appConfig; this.appConfig = appConfig;
this.algorithm = new SHA256Managed();
this.filter = filter; this.filter = filter;
this.logger = logger; this.logger = logger;
this.settings = settings; this.settings = settings;
@ -52,14 +56,19 @@ namespace SafeExamBrowser.Browser.Handlers
protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) protected override CefReturnValue OnBeforeResourceLoad(IWebBrowser webBrowser, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
{ {
if (IsMailtoUrl(request.Url))
{
return CefReturnValue.Cancel;
}
// TODO: CEF does not yet support intercepting requests from service workers, thus the user agent must be statically set at browser // TODO: CEF does not yet support intercepting requests from service workers, thus the user agent must be statically set at browser
// startup for now. Once CEF has full support of service workers, the static user agent should be removed and the method below // startup for now. Once CEF has full support of service workers, the static user agent should be removed and the method below
// reactivated. See https://bitbucket.org/chromiumembedded/cef/issues/2622 for the current status of development. // reactivated. See https://bitbucket.org/chromiumembedded/cef/issues/2622 for the current status of development.
// AppendCustomUserAgent(request); // AppendCustomUserAgent(request);
if (IsMailtoUrl(request.Url)) if (settings.SendCustomHeaders)
{ {
return CefReturnValue.Cancel; AppendCustomHeaders(request);
} }
ReplaceSebScheme(request); ReplaceSebScheme(request);
@ -76,9 +85,22 @@ namespace SafeExamBrowser.Browser.Handlers
request.Headers = headers; request.Headers = headers;
} }
private void AppendCustomHeaders(IRequest request)
{
var headers = new NameValueCollection(request.Headers);
var urlWithoutFragment = request.Url.Split('#')[0];
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + settings.HashValue));
var configurationKey = BitConverter.ToString(hash).Replace("-", string.Empty);
// TODO: Implement Browser Exam Key calculation.
// headers["X-SafeExamBrowser-RequestHash"] = ...;
headers["X-SafeExamBrowser-ConfigKeyHash"] = configurationKey;
request.Headers = headers;
}
private bool Block(IRequest request) private bool Block(IRequest request)
{ {
if (settings.ProcessContentRequests) if (settings.Filter.ProcessContentRequests)
{ {
var result = filter.Process(new Request { Url = request.Url }); var result = filter.Process(new Request { Url = request.Url });
var block = result == FilterResult.Block; var block = result == FilterResult.Block;

View file

@ -96,6 +96,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.Browser.ShowToolbar: case Keys.Browser.ShowToolbar:
MapShowToolbar(settings, value); MapShowToolbar(settings, value);
break; break;
case Keys.Browser.SendCustomHeaders:
MapSendCustomHeaders(settings, value);
break;
case Keys.Browser.StartUrl: case Keys.Browser.StartUrl:
MapStartUrl(settings, value); MapStartUrl(settings, value);
break; break;
@ -275,6 +278,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
} }
} }
private void MapSendCustomHeaders(AppSettings settings, object value)
{
if (value is bool send)
{
settings.Browser.SendCustomHeaders = send;
}
}
private void MapStartUrl(AppSettings settings, object value) private void MapStartUrl(AppSettings settings, object value)
{ {
if (value is string url) if (value is string url)

View file

@ -0,0 +1,131 @@
/*
* Copyright (c) 2020 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.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using SafeExamBrowser.Settings;
namespace SafeExamBrowser.Configuration.ConfigurationData
{
internal class DataProcessor
{
internal void Process(IDictionary<string, object> rawData, AppSettings settings)
{
CalculateHashValue(rawData, settings);
}
private void CalculateHashValue(IDictionary<string, object> rawData, AppSettings settings)
{
using (var algorithm = new SHA256Managed())
using (var stream = new MemoryStream())
using (var writer = new StreamWriter(stream, Encoding.UTF8))
{
Serialize(rawData, writer);
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
var hash = algorithm.ComputeHash(stream);
var hashString = BitConverter.ToString(hash).Replace("-", string.Empty);
settings.Browser.HashValue = hashString;
}
}
private void Serialize(IDictionary<string, object> dictionary, StreamWriter stream)
{
var orderedByKey = dictionary.OrderBy(d => d.Key, StringComparer.OrdinalIgnoreCase).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 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 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;
}
}
}
}

View file

@ -52,6 +52,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string QuitUrl = "quitURL"; internal const string QuitUrl = "quitURL";
internal const string QuitUrlConfirmation = "quitURLConfirm"; internal const string QuitUrlConfirmation = "quitURLConfirm";
internal const string ShowToolbar = "enableBrowserWindowToolbar"; internal const string ShowToolbar = "enableBrowserWindowToolbar";
internal const string SendCustomHeaders = "sendBrowserExamKey";
internal const string StartUrl = "startURL"; internal const string StartUrl = "startURL";
internal const string UserAgentModeDesktop = "browserUserAgentWinDesktopMode"; internal const string UserAgentModeDesktop = "browserUserAgentWinDesktopMode";
internal const string UserAgentModeMobile = "browserUserAgentWinTouchMode"; internal const string UserAgentModeMobile = "browserUserAgentWinTouchMode";
@ -152,6 +153,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
{ {
internal const string AllowApplicationLog = "allowApplicationLog"; internal const string AllowApplicationLog = "allowApplicationLog";
internal const string LogLevel = "logLevel"; internal const string LogLevel = "logLevel";
internal const string OriginatorVersion = "originatorVersion";
} }
internal static class Keyboard internal static class Keyboard

View file

@ -15,8 +15,8 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography; using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Configuration.Contracts.DataFormats; using SafeExamBrowser.Configuration.Contracts.DataFormats;
using SafeExamBrowser.Configuration.Contracts.DataResources; using SafeExamBrowser.Configuration.Contracts.DataResources;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings;
namespace SafeExamBrowser.Configuration namespace SafeExamBrowser.Configuration
{ {
@ -26,6 +26,7 @@ namespace SafeExamBrowser.Configuration
private IList<IDataParser> dataParsers; private IList<IDataParser> dataParsers;
private IList<IDataSerializer> dataSerializers; private IList<IDataSerializer> dataSerializers;
private DataMapper dataMapper; private DataMapper dataMapper;
private DataProcessor dataProcessor;
private DataValues dataValues; private DataValues dataValues;
private IHashAlgorithm hashAlgorithm; private IHashAlgorithm hashAlgorithm;
private ILogger logger; private ILogger logger;
@ -49,6 +50,7 @@ namespace SafeExamBrowser.Configuration
dataParsers = new List<IDataParser>(); dataParsers = new List<IDataParser>();
dataSerializers = new List<IDataSerializer>(); dataSerializers = new List<IDataSerializer>();
dataMapper = new DataMapper(); dataMapper = new DataMapper();
dataProcessor = new DataProcessor();
dataValues = new DataValues(executablePath, programBuild, programCopyright, programTitle, programVersion); dataValues = new DataValues(executablePath, programBuild, programCopyright, programTitle, programVersion);
resourceLoaders = new List<IResourceLoader>(); resourceLoaders = new List<IResourceLoader>();
resourceSavers = new List<IResourceSaver>(); resourceSavers = new List<IResourceSaver>();
@ -144,6 +146,7 @@ namespace SafeExamBrowser.Configuration
if (status == LoadStatus.Success) if (status == LoadStatus.Success)
{ {
dataMapper.MapRawDataToSettings(data, settings); dataMapper.MapRawDataToSettings(data, settings);
dataProcessor.Process(data, settings);
} }
} }

View file

@ -65,6 +65,7 @@
<Compile Include="ConfigurationData\DataMapping\InputDataMapper.cs" /> <Compile Include="ConfigurationData\DataMapping\InputDataMapper.cs" />
<Compile Include="ConfigurationData\DataMapping\SecurityDataMapper.cs" /> <Compile Include="ConfigurationData\DataMapping\SecurityDataMapper.cs" />
<Compile Include="ConfigurationData\DataMapping\UserInterfaceDataMapper.cs" /> <Compile Include="ConfigurationData\DataMapping\UserInterfaceDataMapper.cs" />
<Compile Include="ConfigurationData\DataProcessor.cs" />
<Compile Include="ConfigurationData\Keys.cs" /> <Compile Include="ConfigurationData\Keys.cs" />
<Compile Include="ConfigurationData\DataValues.cs" /> <Compile Include="ConfigurationData\DataValues.cs" />
<Compile Include="Cryptography\CertificateStore.cs" /> <Compile Include="Cryptography\CertificateStore.cs" />

View file

@ -51,6 +51,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary> /// </summary>
public FilterSettings Filter { get; set; } public FilterSettings Filter { get; set; }
/// <summary>
/// The hash value of the raw settings data, used for integrity checks with server applications (see also <see cref="SendCustomHeaders"/>).
/// </summary>
public string HashValue { get; set; }
/// <summary> /// <summary>
/// The settings to be used for the main browser window. /// The settings to be used for the main browser window.
/// </summary> /// </summary>
@ -71,6 +76,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary> /// </summary>
public string QuitUrl { get; set; } public string QuitUrl { get; set; }
/// <summary>
/// Determines whether custom request headers (e.g. for integrity checks) are sent with every HTTP request.
/// </summary>
public bool SendCustomHeaders { get; set; }
/// <summary> /// <summary>
/// The URL with which the main browser window will be loaded. /// The URL with which the main browser window will be loaded.
/// </summary> /// </summary>