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 internal class ResourceHandler : CefSharp.Handler.ResourceRequestHandler
{ {
private AppConfig appConfig;
private SHA256Managed algorithm; private SHA256Managed algorithm;
private BrowserSettings settings; private AppConfig appConfig;
private ILogger logger; private string browserExamKey;
private IRequestFilter filter;
private IResourceHandler contentHandler; private IResourceHandler contentHandler;
private IRequestFilter filter;
private ILogger logger;
private IResourceHandler pageHandler; private IResourceHandler pageHandler;
private BrowserSettings settings;
private IText text; private IText text;
internal ResourceHandler(AppConfig appConfig, BrowserSettings settings, IRequestFilter filter, ILogger logger, 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; return CefReturnValue.Cancel;
} }
// TODO: CEF does not yet support intercepting requests from service workers, thus the user agent must be statically set at browser AppendCustomHeaders(request);
// 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);
}
ReplaceSebScheme(request); ReplaceSebScheme(request);
return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback); return base.OnBeforeResourceLoad(webBrowser, browser, frame, request, callback);
@ -93,28 +85,32 @@ namespace SafeExamBrowser.Browser.Handlers
return abort; 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) private void AppendCustomHeaders(IRequest request)
{ {
var headers = new NameValueCollection(request.Headers); var headers = new NameValueCollection(request.Headers);
var urlWithoutFragment = request.Url.Split('#')[0]; var urlWithoutFragment = request.Url.Split('#')[0];
var configurationBytes = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + settings.HashValue)); var userAgent = request.Headers["User-Agent"];
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);
headers["X-SafeExamBrowser-ConfigKeyHash"] = configurationKey; // TODO: CEF does not yet support intercepting requests from service workers, thus the user agent must be statically set at browser
headers["X-SafeExamBrowser-RequestHash"] = requestHashKey; // 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; request.Headers = headers;
} }
@ -137,6 +133,16 @@ namespace SafeExamBrowser.Browser.Handlers
return block; 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) private bool IsMailtoUrl(string url)
{ {
return url.StartsWith(Uri.UriSchemeMailto); return url.StartsWith(Uri.UriSchemeMailto);

View file

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

View file

@ -6,6 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.Collections.Generic; using System.Collections.Generic;
using SafeExamBrowser.Settings; using SafeExamBrowser.Settings;
using SafeExamBrowser.Settings.Browser; using SafeExamBrowser.Settings.Browser;
@ -69,6 +70,9 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
case Keys.Browser.EnableBrowser: case Keys.Browser.EnableBrowser:
MapEnableBrowser(settings, value); MapEnableBrowser(settings, value);
break; break;
case Keys.Browser.ExamKeySalt:
MapExamKeySalt(settings, value);
break;
case Keys.Browser.Filter.FilterRules: case Keys.Browser.Filter.FilterRules:
MapFilterRules(settings, value); MapFilterRules(settings, value);
break; 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) private void MapMainWindowMode(AppSettings settings, object value)
{ {
const int FULLSCREEN = 1; const int FULLSCREEN = 1;
@ -338,7 +350,8 @@ namespace SafeExamBrowser.Configuration.ConfigurationData.DataMapping
{ {
if (value is bool send) 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(); writer.Flush();
stream.Seek(0, SeekOrigin.Begin); stream.Seek(0, SeekOrigin.Begin);
var hashBytes = algorithm.ComputeHash(stream); var hash = algorithm.ComputeHash(stream);
var hashValue = BitConverter.ToString(hashBytes).ToLower().Replace("-", string.Empty); 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 DownloadDirectory = "downloadDirectoryWin";
internal const string DownloadPdfFiles = "downloadPDFFiles"; internal const string DownloadPdfFiles = "downloadPDFFiles";
internal const string EnableBrowser = "enableSebBrowser"; internal const string EnableBrowser = "enableSebBrowser";
internal const string ExamKeySalt = "examKeySalt";
internal const string PopupPolicy = "newBrowserWindowByLinkPolicy"; internal const string PopupPolicy = "newBrowserWindowByLinkPolicy";
internal const string PopupBlockForeignHost = "newBrowserWindowByLinkBlockForeign"; internal const string PopupBlockForeignHost = "newBrowserWindowByLinkBlockForeign";
internal const string QuitUrl = "quitURL"; internal const string QuitUrl = "quitURL";

View file

@ -56,6 +56,11 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary> /// </summary>
public bool AllowUploads { get; set; } 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> /// <summary>
/// Determines whether the user needs to confirm the termination of SEB by <see cref="QuitUrl"/>. /// Determines whether the user needs to confirm the termination of SEB by <see cref="QuitUrl"/>.
/// </summary> /// </summary>
@ -76,16 +81,16 @@ namespace SafeExamBrowser.Settings.Browser
/// </summary> /// </summary>
public bool EnableBrowser { get; set; } 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> /// <summary>
/// The settings to be used for the browser request filter. /// The settings to be used for the browser request filter.
/// </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>
@ -107,9 +112,14 @@ namespace SafeExamBrowser.Settings.Browser
public string QuitUrl { get; set; } public string QuitUrl { get; set; }
/// <summary> /// <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> /// </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> /// <summary>
/// The URL with which the main browser window will be loaded. /// The URL with which the main browser window will be loaded.