From ae3755df847b0d35b28fbb3c56b9e34a1f1b6c57 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Damian=20B=C3=BCchel?= <damian.buechel@let.ethz.ch>
Date: Fri, 24 Feb 2023 15:48:54 +0100
Subject: [PATCH] SEBWIN-608: Implemented basic mechanism for app signature key
 exchange.

---
 .../Operations/ServerOperation.cs             | 64 +++++++++++--------
 .../IServerProxy.cs                           |  5 ++
 SafeExamBrowser.Server/Parser.cs              | 21 ++++++
 SafeExamBrowser.Server/ServerProxy.cs         | 57 +++++++++++++++++
 4 files changed, 119 insertions(+), 28 deletions(-)

diff --git a/SafeExamBrowser.Runtime/Operations/ServerOperation.cs b/SafeExamBrowser.Runtime/Operations/ServerOperation.cs
index ccbec1db..b5fce4d6 100644
--- a/SafeExamBrowser.Runtime/Operations/ServerOperation.cs
+++ b/SafeExamBrowser.Runtime/Operations/ServerOperation.cs
@@ -51,6 +51,10 @@ namespace SafeExamBrowser.Runtime.Operations
 
 			if (Context.Next.Settings.SessionMode == SessionMode.Server)
 			{
+				var exam = default(Exam);
+				var exams = default(IEnumerable<Exam>);
+				var uri = default(Uri);
+
 				logger.Info("Initializing server...");
 				StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
 
@@ -60,22 +64,27 @@ namespace SafeExamBrowser.Runtime.Operations
 
 				if (success)
 				{
-					(abort, fallback, success) = TryPerformWithFallback(() => server.GetAvailableExams(Context.Next.Settings.Server.ExamId), out var exams);
+					(abort, fallback, success) = TryPerformWithFallback(() => server.GetAvailableExams(Context.Next.Settings.Server.ExamId), out exams);
+				}
 
-					if (success)
-					{
-						success = TrySelectExam(exams, out var exam);
+				if (success)
+				{
+					success = TrySelectExam(exams, out exam);
+				}
 
-						if (success)
-						{
-							(abort, fallback, success) = TryPerformWithFallback(() => server.GetConfigurationFor(exam), out var uri);
+				if (success)
+				{
+					(abort, fallback, success) = TryPerformWithFallback(() => server.GetConfigurationFor(exam), out uri);
+				}
 
-							if (success)
-							{
-								result = TryLoadServerSettings(exam, uri);
-							}
-						}
-					}
+				if (success)
+				{
+					result = TryLoadServerSettings(exam, uri);
+				}
+
+				if (success && result == OperationResult.Success)
+				{
+					(abort, fallback, success) = TryPerformWithFallback(() => server.SendSelectedExam(exam));
 				}
 
 				if (abort)
@@ -97,25 +106,22 @@ namespace SafeExamBrowser.Runtime.Operations
 
 		public override OperationResult Repeat()
 		{
-			if (Context.Current.Settings.SessionMode == SessionMode.Server)
-			{
-				if (Context.Next.Settings.SessionMode == SessionMode.Server)
-				{
-					ShowReconfigurationError();
+			var result = OperationResult.Success;
 
-					return OperationResult.Aborted;
-				}
-				else
-				{
-					return Revert();
-				}
+			if (Context.Current.Settings.SessionMode == SessionMode.Server && Context.Next.Settings.SessionMode == SessionMode.Server)
+			{
+				result = AbortServerReconfiguration();
+			}
+			else if (Context.Current.Settings.SessionMode == SessionMode.Server)
+			{
+				result = Revert();
 			}
 			else if (Context.Next.Settings.SessionMode == SessionMode.Server)
 			{
-				return Perform();
+				result = Perform();
 			}
 
-			return OperationResult.Success;
+			return result;
 		}
 
 		public override OperationResult Revert()
@@ -254,7 +260,7 @@ namespace SafeExamBrowser.Runtime.Operations
 			return success;
 		}
 
-		private void ShowReconfigurationError()
+		private OperationResult AbortServerReconfiguration()
 		{
 			var args = new MessageEventArgs
 			{
@@ -263,9 +269,11 @@ namespace SafeExamBrowser.Runtime.Operations
 				Message = TextKey.MessageBox_ServerReconfigurationWarning,
 				Title = TextKey.MessageBox_ServerReconfigurationWarningTitle
 			};
-			logger.Warn("Server reconfiguration requested but is not allowed.");
 
+			logger.Warn("Server reconfiguration is currently not supported, aborting...");
 			ActionRequired?.Invoke(args);
+
+			return OperationResult.Aborted;
 		}
 	}
 }
diff --git a/SafeExamBrowser.Server.Contracts/IServerProxy.cs b/SafeExamBrowser.Server.Contracts/IServerProxy.cs
index 4f320610..c4959337 100644
--- a/SafeExamBrowser.Server.Contracts/IServerProxy.cs
+++ b/SafeExamBrowser.Server.Contracts/IServerProxy.cs
@@ -99,6 +99,11 @@ namespace SafeExamBrowser.Server.Contracts
 		/// </summary>
 		ServerResponse LowerHand();
 
+		/// <summary>
+		/// Sends the selected exam to the server.
+		/// </summary>
+		ServerResponse SendSelectedExam(Exam exam);
+
 		/// <summary>
 		/// Sends the given user session identifier of a LMS and thus establishes a connection with the server.
 		/// </summary>
diff --git a/SafeExamBrowser.Server/Parser.cs b/SafeExamBrowser.Server/Parser.cs
index 51b430fa..67a6d7f4 100644
--- a/SafeExamBrowser.Server/Parser.cs
+++ b/SafeExamBrowser.Server/Parser.cs
@@ -105,6 +105,27 @@ namespace SafeExamBrowser.Server
 			return success;
 		}
 
+		internal bool TryParseAppSignatureKeySalt(HttpResponseMessage response, out string salt)
+		{
+			salt = default;
+
+			try
+			{
+				var hasHeader = response.Headers.TryGetValues("SEBExamSalt", out var values);
+
+				if (hasHeader)
+				{
+					salt = values.First();
+				}
+			}
+			catch (Exception e)
+			{
+				logger.Error("Failed to parse app signature key salt!", e);
+			}
+
+			return salt != default;
+		}
+
 		internal bool TryParseConnectionToken(HttpResponseMessage response, out string connectionToken)
 		{
 			connectionToken = default;
diff --git a/SafeExamBrowser.Server/ServerProxy.cs b/SafeExamBrowser.Server/ServerProxy.cs
index abf49e3c..43921a77 100644
--- a/SafeExamBrowser.Server/ServerProxy.cs
+++ b/SafeExamBrowser.Server/ServerProxy.cs
@@ -348,6 +348,38 @@ namespace SafeExamBrowser.Server
 			return new ServerResponse(success, response.ToLogString());
 		}
 
+		public ServerResponse SendSelectedExam(Exam exam)
+		{
+			var authorization = ("Authorization", $"Bearer {oauth2Token}");
+			var content = $"examId={exam.Id}";
+			var contentType = "application/x-www-form-urlencoded";
+			var token = ("SEBConnectionToken", connectionToken);
+
+			var success = TryExecute(new HttpMethod("PATCH"), api.HandshakeEndpoint, out var response, content, contentType, authorization, token);
+			var message = response.ToLogString();
+
+			if (success)
+			{
+				logger.Info("Successfully sent selected exam.");
+			}
+			else
+			{
+				logger.Error("Failed to send selected exam!");
+			}
+
+			if (parser.TryParseAppSignatureKeySalt(response, out var salt))
+			{
+				logger.Info("App signature key salt detected, performing key exchange...");
+				success = TrySendAppSignatureKey(out message);
+			}
+			else
+			{
+				logger.Info("No app signature key salt detected, skipping key exchange.");
+			}
+
+			return new ServerResponse(success, message);
+		}
+
 		public ServerResponse SendSessionIdentifier(string identifier)
 		{
 			var authorization = ("Authorization", $"Bearer {oauth2Token}");
@@ -614,6 +646,31 @@ namespace SafeExamBrowser.Server
 			return success;
 		}
 
+		private bool TrySendAppSignatureKey(out string message)
+		{
+			// TODO:
+			// keyGenerator.CalculateAppSignatureKey(configurationKey, server.AppSignatureKeySalt)
+
+			var authorization = ("Authorization", $"Bearer {oauth2Token}");
+			var content = $"seb_signature_key={"WINDOWS-TEST-ASK-1234"}";
+			var contentType = "application/x-www-form-urlencoded";
+			var token = ("SEBConnectionToken", connectionToken);
+			var success = TryExecute(new HttpMethod("PATCH"), api.HandshakeEndpoint, out var response, content, contentType, authorization, token);
+
+			message = response.ToLogString();
+
+			if (success)
+			{
+				logger.Info("Successfully sent app signature key.");
+			}
+			else
+			{
+				logger.Error("Failed to send app signature key!");
+			}
+
+			return success;
+		}
+
 		private bool TryExecute(
 			HttpMethod method,
 			string url,