diff --git a/SafeExamBrowser.Browser/BrowserApplication.cs b/SafeExamBrowser.Browser/BrowserApplication.cs
index 942a5760..f5ef88b9 100644
--- a/SafeExamBrowser.Browser/BrowserApplication.cs
+++ b/SafeExamBrowser.Browser/BrowserApplication.cs
@@ -115,16 +115,18 @@ namespace SafeExamBrowser.Browser
if (success)
{
- if (settings.UseTemporaryDownAndUploadDirectory)
- {
- CreateTemporaryDownAndUploadDirectory();
- }
+ InitializeIntegrityKeys();
if (settings.DeleteCookiesOnStartup)
{
DeleteCookies();
}
+ if (settings.UseTemporaryDownAndUploadDirectory)
+ {
+ CreateTemporaryDownAndUploadDirectory();
+ }
+
logger.Info("Initialized browser.");
}
else
@@ -362,6 +364,22 @@ namespace SafeExamBrowser.Browser
return cefSettings;
}
+ private void InitializeIntegrityKeys()
+ {
+ logger.Debug($"Browser Exam Key (BEK) transmission is {(settings.SendBrowserExamKey ? "enabled" : "disabled")}.");
+ logger.Debug($"Configuration Key (CK) transmission is {(settings.SendConfigurationKey ? "enabled" : "disabled")}.");
+
+ if (settings.CustomBrowserExamKey != default)
+ {
+ keyGenerator.UseCustomBrowserExamKey(settings.CustomBrowserExamKey);
+ logger.Debug($"The browser application will be using a custom browser exam key.");
+ }
+ else
+ {
+ logger.Debug($"The browser application will be using the default browser exam key.");
+ }
+ }
+
private void InitializeProxySettings(CefSettings cefSettings)
{
if (settings.Proxy.Policy == ProxyPolicy.Custom)
diff --git a/SafeExamBrowser.Configuration.Contracts/Cryptography/IKeyGenerator.cs b/SafeExamBrowser.Configuration.Contracts/Cryptography/IKeyGenerator.cs
index ad8242bc..be5e9ee3 100644
--- a/SafeExamBrowser.Configuration.Contracts/Cryptography/IKeyGenerator.cs
+++ b/SafeExamBrowser.Configuration.Contracts/Cryptography/IKeyGenerator.cs
@@ -27,5 +27,10 @@ namespace SafeExamBrowser.Configuration.Contracts.Cryptography
/// Calculates the hash value of the configuration key (CK) for the given URL.
///
string CalculateConfigurationKeyHash(string configurationKey, string url);
+
+ ///
+ /// Specifies that a custom browser exam key (BEK) should be used.
+ ///
+ void UseCustomBrowserExamKey(string browserExamKey);
}
}
diff --git a/SafeExamBrowser.Configuration/Cryptography/KeyGenerator.cs b/SafeExamBrowser.Configuration/Cryptography/KeyGenerator.cs
index 1fc2657e..36a65d02 100644
--- a/SafeExamBrowser.Configuration/Cryptography/KeyGenerator.cs
+++ b/SafeExamBrowser.Configuration/Cryptography/KeyGenerator.cs
@@ -67,6 +67,15 @@ namespace SafeExamBrowser.Configuration.Cryptography
return key;
}
+ public void UseCustomBrowserExamKey(string browserExamKey)
+ {
+ if (browserExamKey != default)
+ {
+ this.browserExamKey = browserExamKey;
+ logger.Debug("Initialized custom browser exam key.");
+ }
+ }
+
private string ComputeBrowserExamKey(string configurationKey, byte[] salt)
{
lock (@lock)
diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/ServerOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/ServerOperationTests.cs
index e77690cd..7ef4a093 100644
--- a/SafeExamBrowser.Runtime.UnitTests/Operations/ServerOperationTests.cs
+++ b/SafeExamBrowser.Runtime.UnitTests/Operations/ServerOperationTests.cs
@@ -80,7 +80,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Setup(s => s.Connect()).Returns(new ServerResponse(true)).Callback(() => connect = ++counter);
server.Setup(s => s.Initialize(It.IsAny())).Callback(() => initialize = ++counter);
server.Setup(s => s.GetConnectionInfo()).Returns(connection).Callback(() => getConnection = ++counter);
- server.Setup(s => s.SendSelectedExam(It.IsAny())).Returns(new ServerResponse(true));
+ server.Setup(s => s.SendSelectedExam(It.IsAny())).Returns(new ServerResponse(true, default));
server
.Setup(s => s.GetAvailableExams(It.IsAny()))
.Returns(new ServerResponse>(true, default(IEnumerable)))
@@ -276,7 +276,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Setup(s => s.GetConnectionInfo()).Returns(connection);
server.Setup(s => s.GetAvailableExams(It.IsAny())).Returns(new ServerResponse>(true, new[] { exam }));
server.Setup(s => s.GetConfigurationFor(It.IsAny())).Returns(new ServerResponse(true, new Uri("file:///configuration.seb")));
- server.Setup(s => s.SendSelectedExam(It.IsAny())).Returns(new ServerResponse(true));
+ server.Setup(s => s.SendSelectedExam(It.IsAny())).Returns(new ServerResponse(true, default));
sut.ActionRequired += (args) => Assert.Fail();
var result = sut.Perform();
@@ -336,7 +336,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Setup(s => s.Connect()).Returns(new ServerResponse(true)).Callback(() => connect = ++counter);
server.Setup(s => s.Initialize(It.IsAny())).Callback(() => initialize = ++counter);
server.Setup(s => s.GetConnectionInfo()).Returns(connection).Callback(() => getConnection = ++counter);
- server.Setup(s => s.SendSelectedExam(It.IsAny())).Returns(new ServerResponse(true));
+ server.Setup(s => s.SendSelectedExam(It.IsAny())).Returns(new ServerResponse(true, default));
server
.Setup(s => s.GetAvailableExams(It.IsAny()))
.Returns(new ServerResponse>(true, default(IEnumerable)))
@@ -532,7 +532,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Setup(s => s.GetConnectionInfo()).Returns(connection);
server.Setup(s => s.GetAvailableExams(It.IsAny())).Returns(new ServerResponse>(true, new[] { exam }));
server.Setup(s => s.GetConfigurationFor(It.IsAny())).Returns(new ServerResponse(true, new Uri("file:///configuration.seb")));
- server.Setup(s => s.SendSelectedExam(It.IsAny())).Returns(new ServerResponse(true));
+ server.Setup(s => s.SendSelectedExam(It.IsAny())).Returns(new ServerResponse(true, default));
sut.ActionRequired += (args) => Assert.Fail();
var result = sut.Repeat();
diff --git a/SafeExamBrowser.Runtime/Operations/ServerOperation.cs b/SafeExamBrowser.Runtime/Operations/ServerOperation.cs
index 92dc14b3..5a07a129 100644
--- a/SafeExamBrowser.Runtime/Operations/ServerOperation.cs
+++ b/SafeExamBrowser.Runtime/Operations/ServerOperation.cs
@@ -51,6 +51,7 @@ namespace SafeExamBrowser.Runtime.Operations
if (Context.Next.Settings.SessionMode == SessionMode.Server)
{
+ var browserExamKey = default(string);
var exam = default(Exam);
var exams = default(IEnumerable);
var uri = default(Uri);
@@ -84,7 +85,12 @@ namespace SafeExamBrowser.Runtime.Operations
if (success && result == OperationResult.Success)
{
- (abort, fallback, success) = TryPerformWithFallback(() => server.SendSelectedExam(exam));
+ (abort, fallback, success) = TryPerformWithFallback(() => server.SendSelectedExam(exam), out browserExamKey);
+ }
+
+ if (browserExamKey != default)
+ {
+ Context.Next.Settings.Browser.CustomBrowserExamKey = browserExamKey;
}
if (abort)
diff --git a/SafeExamBrowser.Server.Contracts/IServerProxy.cs b/SafeExamBrowser.Server.Contracts/IServerProxy.cs
index 385101a4..fc227b25 100644
--- a/SafeExamBrowser.Server.Contracts/IServerProxy.cs
+++ b/SafeExamBrowser.Server.Contracts/IServerProxy.cs
@@ -105,9 +105,9 @@ namespace SafeExamBrowser.Server.Contracts
ServerResponse RaiseHand(string message = default);
///
- /// Sends the selected exam to the server.
+ /// Sends the selected exam to the server. Optionally returns a custom browser exam key to be used for the active session.
///
- ServerResponse SendSelectedExam(Exam exam);
+ ServerResponse SendSelectedExam(Exam exam);
///
/// Sends the given user session identifier of a LMS and thus establishes a connection with the server.
diff --git a/SafeExamBrowser.Server/Parser.cs b/SafeExamBrowser.Server/Parser.cs
index 3a90451b..b8219a5e 100644
--- a/SafeExamBrowser.Server/Parser.cs
+++ b/SafeExamBrowser.Server/Parser.cs
@@ -127,6 +127,27 @@ namespace SafeExamBrowser.Server
return salt != default;
}
+ internal bool TryParseBrowserExamKey(HttpResponseMessage response, out string browserExamKey)
+ {
+ browserExamKey = default;
+
+ try
+ {
+ var hasHeader = response.Headers.TryGetValues(Header.BROWSER_EXAM_KEY, out var values);
+
+ if (hasHeader)
+ {
+ browserExamKey = values.First();
+ }
+ }
+ catch (Exception e)
+ {
+ logger.Error("Failed to parse browser exam key!", e);
+ }
+
+ return browserExamKey != default;
+ }
+
internal bool TryParseConnectionToken(HttpResponseMessage response, out string connectionToken)
{
connectionToken = default;
diff --git a/SafeExamBrowser.Server/Requests/Header.cs b/SafeExamBrowser.Server/Requests/Header.cs
index f8eb8f27..381b3548 100644
--- a/SafeExamBrowser.Server/Requests/Header.cs
+++ b/SafeExamBrowser.Server/Requests/Header.cs
@@ -13,6 +13,7 @@ namespace SafeExamBrowser.Server.Requests
internal const string ACCEPT = "Accept";
internal const string APP_SIGNATURE_KEY_SALT = "SEBExamSalt";
internal const string AUTHORIZATION = "Authorization";
+ internal const string BROWSER_EXAM_KEY = "SEBServerBEK";
internal const string CONNECTION_TOKEN = "SEBConnectionToken";
}
}
diff --git a/SafeExamBrowser.Server/Requests/SelectExamRequest.cs b/SafeExamBrowser.Server/Requests/SelectExamRequest.cs
index f0989d56..ec138d41 100644
--- a/SafeExamBrowser.Server/Requests/SelectExamRequest.cs
+++ b/SafeExamBrowser.Server/Requests/SelectExamRequest.cs
@@ -20,18 +20,20 @@ namespace SafeExamBrowser.Server.Requests
{
}
- internal bool TryExecute(Exam exam, out string message, out string salt)
+ internal bool TryExecute(Exam exam, out string message, out string appSignatureKeySalt, out string browserExamKey)
{
var content = $"examId={exam.Id}";
var method = new HttpMethod("PATCH");
var success = TryExecute(method, api.HandshakeEndpoint, out var response, content, ContentType.URL_ENCODED, Authorization, Token);
+ appSignatureKeySalt = default;
+ browserExamKey = default;
message = response.ToLogString();
- salt = default;
if (success)
{
- parser.TryParseAppSignatureKeySalt(response, out salt);
+ parser.TryParseAppSignatureKeySalt(response, out appSignatureKeySalt);
+ parser.TryParseBrowserExamKey(response, out browserExamKey);
}
return success;
diff --git a/SafeExamBrowser.Server/ServerProxy.cs b/SafeExamBrowser.Server/ServerProxy.cs
index c224f9b2..58369c77 100644
--- a/SafeExamBrowser.Server/ServerProxy.cs
+++ b/SafeExamBrowser.Server/ServerProxy.cs
@@ -278,10 +278,10 @@ namespace SafeExamBrowser.Server
return new ServerResponse(success, message);
}
- public ServerResponse SendSelectedExam(Exam exam)
+ public ServerResponse SendSelectedExam(Exam exam)
{
var request = new SelectExamRequest(api, httpClient, logger, parser, settings);
- var success = request.TryExecute(exam, out var message, out var salt);
+ var success = request.TryExecute(exam, out var message, out var appSignatureKeySalt, out var browserExamKey);
if (success)
{
@@ -292,17 +292,22 @@ namespace SafeExamBrowser.Server
logger.Error("Failed to send selected exam!");
}
- if (success && salt != default)
+ if (success && appSignatureKeySalt != default)
{
logger.Info("App signature key salt detected, performing key exchange...");
- success = TrySendAppSignatureKey(salt, out message);
+ success = TrySendAppSignatureKey(appSignatureKeySalt, out message);
}
else
{
logger.Info("No app signature key salt detected, skipping key exchange.");
}
- return new ServerResponse(success, message);
+ if (browserExamKey != default)
+ {
+ logger.Info("Custom browser exam key detected.");
+ }
+
+ return new ServerResponse(success, browserExamKey, message);
}
public ServerResponse SendSessionIdentifier(string identifier)
diff --git a/SafeExamBrowser.Settings/Browser/BrowserSettings.cs b/SafeExamBrowser.Settings/Browser/BrowserSettings.cs
index ab196c19..ec401b86 100644
--- a/SafeExamBrowser.Settings/Browser/BrowserSettings.cs
+++ b/SafeExamBrowser.Settings/Browser/BrowserSettings.cs
@@ -86,6 +86,11 @@ namespace SafeExamBrowser.Settings.Browser
///
public bool ConfirmQuitUrl { get; set; }
+ ///
+ /// An optional, custom browser exam key used for integrity checks with server applications (see also ).
+ ///
+ public string CustomBrowserExamKey { get; set; }
+
///
/// The custom user agent to optionally be used for all requests.
///
@@ -174,7 +179,7 @@ namespace SafeExamBrowser.Settings.Browser
public bool SendConfigurationKey { get; set; }
///
- /// Determines whether the browser exam key header is sent with every HTTP request (see also ).
+ /// Determines whether the browser exam key header is sent with every HTTP request (see also and ).
///
public bool SendBrowserExamKey { get; set; }