SEBWIN-309: Corrected implementation of configuration and browser exam key.

This commit is contained in:
dbuechel 2020-02-13 11:01:07 +01:00
parent ad023853d4
commit 9f8920b410
6 changed files with 76 additions and 46 deletions

View file

@ -26,13 +26,14 @@ namespace SafeExamBrowser.Browser.Handlers
{
internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler
{
private AppConfig appConfig;
private SHA256Managed algorithm;
private BrowserSettings settings;
private ILogger logger;
private IRequestFilter filter;
private AppConfig appConfig;
private string browserExamKey;
private IResourceHandler contentHandler;
private IRequestFilter filter;
private ILogger logger;
private IResourceHandler pageHandler;
private BrowserSettings settings;
private IText text;
internal ResourceHandler(AppConfig appConfig, BrowserSettings settings, IRequestFilter filter, ILogger logger, IText text)
@ -62,16 +63,7 @@ namespace SafeExamBrowser.Browser.Handlers
return CefReturnValue.Cancel;
}
// 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
// reactivated. See https://bitbucket.org/chromiumembedded/cef/issues/2622 for the current status of development.
// AppendCustomUserAgent(request);
if (settings.SendCustomHeaders)
{
AppendCustomHeaders(request);
}
AppendCustomHeaders(request);
ReplaceSebScheme(request);
return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback);
@ -93,28 +85,32 @@ namespace SafeExamBrowser.Browser.Handlers
return abort;
}
private void AppendCustomUserAgent(IRequest request)
{
var headers = new NameValueCollection(request.Headers);
var userAgent = request.Headers["User-Agent"];
headers["User-Agent"] = $"{userAgent} SEB/{appConfig.ProgramInformationalVersion}";
request.Headers = headers;
}
private void AppendCustomHeaders(IRequest request)
{
var headers = new NameValueCollection(request.Headers);
var urlWithoutFragment = request.Url.Split('#')[0];
var configurationBytes = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + settings.HashValue));
var configurationKey = BitConverter.ToString(configurationBytes).ToLower().Replace("-", string.Empty);
var browserExamBytes = algorithm.ComputeHash(Encoding.UTF8.GetBytes(appConfig.CodeSignatureHash + appConfig.ProgramBuildVersion + configurationKey));
var browserExamKey = BitConverter.ToString(browserExamBytes).ToLower().Replace("-", string.Empty);
var requestHashBytes = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + browserExamKey));
var requestHashKey = BitConverter.ToString(requestHashBytes).ToLower().Replace("-", string.Empty);
var userAgent = request.Headers["User-Agent"];
headers["X-SafeExamBrowser-ConfigKeyHash"] = configurationKey;
headers["X-SafeExamBrowser-RequestHash"] = requestHashKey;
// 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
// reactivated. See https://bitbucket.org/chromiumembedded/cef/issues/2622 for the current status of development.
// headers["User-Agent"] = $"{userAgent} SEB/{appConfig.ProgramInformationalVersion}";
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;
}
if (settings.SendExamKey)
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey())));
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
headers["X-SafeExamBrowser-RequestHash"] = key;
}
request.Headers = headers;
}
@ -137,6 +133,16 @@ namespace SafeExamBrowser.Browser.Handlers
return block;
}
private string ComputeBrowserExamKey()
{
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(settings.ExamKeySalt + 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);

View file

@ -27,7 +27,7 @@ namespace SafeExamBrowser.Configuration.UnitTests.ConfigurationData
}
[TestMethod]
public void MustCalculateCorrectHashValue()
public void MustCalculateCorrectConfigurationKey()
{
var formatter = new BinaryFormatter();
var path1 = $"{nameof(SafeExamBrowser)}.{nameof(Configuration)}.{nameof(UnitTests)}.{nameof(ConfigurationData)}.TestDictionary1.bin";
@ -47,9 +47,9 @@ namespace SafeExamBrowser.Configuration.UnitTests.ConfigurationData
sut.Process(data2, settings2);
sut.Process(data3, settings3);
Assert.AreEqual("6063c3351ed1ac878c05072598d5079e30ca763c957d8e04bd45131c08f88d1a", settings1.Browser.HashValue);
Assert.AreEqual("4fc002d2ae4faf994a14bede54d95ac58a1a2cb9b59bc5b4277ff29559b46e3d", settings2.Browser.HashValue);
Assert.AreEqual("ab426e25b795c917f1fb40f7ef8e5757ef97d7c7ad6792e655c4421d47329d7a", settings3.Browser.HashValue);
Assert.AreEqual("6063c3351ed1ac878c05072598d5079e30ca763c957d8e04bd45131c08f88d1a", settings1.Browser.ConfigurationKey);
Assert.AreEqual("4fc002d2ae4faf994a14bede54d95ac58a1a2cb9b59bc5b4277ff29559b46e3d", settings2.Browser.ConfigurationKey);
Assert.AreEqual("ab426e25b795c917f1fb40f7ef8e5757ef97d7c7ad6792e655c4421d47329d7a", settings3.Browser.ConfigurationKey);
}
}
}

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.Collections.Generic;
using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Browser;
@ -69,6 +70,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.Browser.EnableBrowser:
MapEnableBrowser(settings, value);
break;
case Keys.Browser.ExamKeySalt:
MapExamKeySalt(settings, value);
break;
case Keys.Browser.Filter.FilterRules:
MapFilterRules(settings, value);
break;
@ -251,6 +255,14 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
}
}
private void MapExamKeySalt(AppSettings settings, object value)
{
if (value is byte[] salt)
{
settings.Browser.ExamKeySalt = BitConverter.ToString(salt).ToLower().Replace("-", string.Empty);
}
}
private void MapMainWindowMode(AppSettings settings, object value)
{
const int FULLSCREEN = 1;
@ -338,7 +350,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{
if (value is bool send)
{
settings.Browser.SendCustomHeaders = send;
settings.Browser.SendConfigurationKey = send;
settings.Browser.SendExamKey = send;
}
}

View file

@ -34,10 +34,10 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
writer.Flush();
stream.Seek(0, SeekOrigin.Begin);
var hashBytes = algorithm.ComputeHash(stream);
var hashValue = BitConverter.ToString(hashBytes).ToLower().Replace("-", string.Empty);
var hash = algorithm.ComputeHash(stream);
var key = BitConverter.ToString(hash).ToLower().Replace("-", string.Empty);
settings.Browser.HashValue = hashValue;
settings.Browser.ConfigurationKey = key;
}
}

View file

@ -52,6 +52,7 @@ namespace SafeExamBrowser.Configuration.ConfigurationData
internal const string DownloadDirectory = "downloadDirectoryWin";
internal const string DownloadPdfFiles = "downloadPDFFiles";
internal const string EnableBrowser = "enableSebBrowser";
internal const string ExamKeySalt = "examKeySalt";
internal const string PopupPolicy = "newBrowserWindowByLinkPolicy";
internal const string PopupBlockForeignHost = "newBrowserWindowByLinkBlockForeign";
internal const string QuitUrl = "quitURL";

View file

@ -56,6 +56,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary>
public bool AllowUploads { get; set; }
/// <summary>
/// The configuration key used for integrity checks with server applications (see also <see cref="SendConfigurationKey"/>).
/// </summary>
public string ConfigurationKey { get; set; }
/// <summary>
/// Determines whether the user needs to confirm the termination of SEB by <see cref="QuitUrl"/>.
/// </summary>
@ -76,16 +81,16 @@ 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 string ExamKeySalt { get; set; }
/// <summary>
/// The settings to be used for the browser request filter.
/// </summary>
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>
/// The settings to be used for the main browser window.
/// </summary>
@ -107,9 +112,14 @@ namespace SafeExamBrowser.Settings.Browser
public string QuitUrl { get; set; }
/// <summary>
/// Determines whether custom request headers (e.g. for integrity checks) are sent with every HTTP request.
/// Determines whether the configuration key header is sent with every HTTP request (see also <see cref="ConfigurationKey"/>).
/// </summary>
public bool SendCustomHeaders { get; set; }
public bool SendConfigurationKey { get; set; }
/// <summary>
/// Determines whether the exam key header is sent with every HTTP request (see also <see cref="ExamKeySalt"/>).
/// </summary>
public bool SendExamKey { get; set; }
/// <summary>
/// The URL with which the main browser window will be loaded.