diff --git a/SafeExamBrowser.Browser/Handlers/ResourceHandler.cs b/SafeExamBrowser.Browser/Handlers/ResourceHandler.cs
index 368608b1..b483477d 100644
--- a/SafeExamBrowser.Browser/Handlers/ResourceHandler.cs
+++ b/SafeExamBrowser.Browser/Handlers/ResourceHandler.cs
@@ -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);
diff --git a/SafeExamBrowser.Configuration.UnitTests/ConfigurationData/DataProcessorTests.cs b/SafeExamBrowser.Configuration.UnitTests/ConfigurationData/DataProcessorTests.cs
index e06ca6ae..3a996fff 100644
--- a/SafeExamBrowser.Configuration.UnitTests/ConfigurationData/DataProcessorTests.cs
+++ b/SafeExamBrowser.Configuration.UnitTests/ConfigurationData/DataProcessorTests.cs
@@ -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);
}
}
}
diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/BrowserDataMapper.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/BrowserDataMapper.cs
index 175104d1..24b3fc79 100644
--- a/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/BrowserDataMapper.cs
+++ b/SafeExamBrowser.Configuration/ConfigurationData/DataMapping/BrowserDataMapper.cs
@@ -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;
}
}
diff --git a/SafeExamBrowser.Configuration/ConfigurationData/DataProcessor.cs b/SafeExamBrowser.Configuration/ConfigurationData/DataProcessor.cs
index 1a4c0a6d..db0af698 100644
--- a/SafeExamBrowser.Configuration/ConfigurationData/DataProcessor.cs
+++ b/SafeExamBrowser.Configuration/ConfigurationData/DataProcessor.cs
@@ -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;
}
}
diff --git a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs
index ddd99bc2..2607beeb 100644
--- a/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs
+++ b/SafeExamBrowser.Configuration/ConfigurationData/Keys.cs
@@ -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";
diff --git a/SafeExamBrowser.Settings/Browser/BrowserSettings.cs b/SafeExamBrowser.Settings/Browser/BrowserSettings.cs
index ca435303..440d083c 100644
--- a/SafeExamBrowser.Settings/Browser/BrowserSettings.cs
+++ b/SafeExamBrowser.Settings/Browser/BrowserSettings.cs
@@ -56,6 +56,11 @@ namespace SafeExamBrowser.Settings.Browser
///
public bool AllowUploads { get; set; }
+ ///
+ /// The configuration key used for integrity checks with server applications (see also ).
+ ///
+ public string ConfigurationKey { get; set; }
+
///
/// Determines whether the user needs to confirm the termination of SEB by .
///
@@ -76,16 +81,16 @@ namespace SafeExamBrowser.Settings.Browser
///
public bool EnableBrowser { get; set; }
+ ///
+ /// The salt value for the calculation of the exam key which is used for integrity checks with server applications (see also ).
+ ///
+ public string ExamKeySalt { get; set; }
+
///
/// The settings to be used for the browser request filter.
///
public FilterSettings Filter { get; set; }
- ///
- /// The hash value of the raw settings data, used for integrity checks with server applications (see also ).
- ///
- public string HashValue { get; set; }
-
///
/// The settings to be used for the main browser window.
///
@@ -107,9 +112,14 @@ namespace SafeExamBrowser.Settings.Browser
public string QuitUrl { get; set; }
///
- /// 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 ).
///
- public bool SendCustomHeaders { get; set; }
+ public bool SendConfigurationKey { get; set; }
+
+ ///
+ /// Determines whether the exam key header is sent with every HTTP request (see also ).
+ ///
+ public bool SendExamKey { get; set; }
///
/// The URL with which the main browser window will be loaded.