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.