SEBWIN-608: Finished app signature key implementation.

This commit is contained in:
Damian Büchel 2023-03-02 23:48:11 +01:00
parent 6c31ce0833
commit e743d4a564
14 changed files with 136 additions and 65 deletions

View file

@ -60,8 +60,8 @@ namespace SafeExamBrowser.Browser.UnitTests.Handlers
var request = new Mock<IRequest>();
browser.SetupGet(b => b.Address).Returns("http://www.host.org");
keyGenerator.Setup(g => g.CalculateBrowserExamKeyHash(It.IsAny<string>())).Returns(new Random().Next().ToString());
keyGenerator.Setup(g => g.CalculateConfigurationKeyHash(It.IsAny<string>())).Returns(new Random().Next().ToString());
keyGenerator.Setup(g => g.CalculateBrowserExamKeyHash(It.IsAny<string>(), It.IsAny<byte[]>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
keyGenerator.Setup(g => g.CalculateConfigurationKeyHash(It.IsAny<string>(), It.IsAny<string>())).Returns(new Random().Next().ToString());
request.SetupGet(r => r.Headers).Returns(new NameValueCollection());
request.SetupGet(r => r.Url).Returns("http://www.host.org");
request.SetupSet(r => r.Headers = It.IsAny<NameValueCollection>()).Callback<NameValueCollection>((h) => headers = h);

View file

@ -34,8 +34,8 @@ namespace SafeExamBrowser.Browser.Handlers
public void OnContextCreated(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
{
var browserExamKey = keyGenerator.CalculateBrowserExamKeyHash(frame.Url);
var configurationKey = keyGenerator.CalculateConfigurationKeyHash(frame.Url);
var browserExamKey = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, frame.Url);
var configurationKey = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, frame.Url);
var api = contentLoader.LoadApi(browserExamKey, configurationKey, appConfig.ProgramBuildVersion);
frame.ExecuteJavaScriptAsync(api);

View file

@ -124,12 +124,12 @@ namespace SafeExamBrowser.Browser.Handlers
if (settings.SendConfigurationKey)
{
headers["X-SafeExamBrowser-ConfigKeyHash"] = keyGenerator.CalculateConfigurationKeyHash(request.Url);
headers["X-SafeExamBrowser-ConfigKeyHash"] = keyGenerator.CalculateConfigurationKeyHash(settings.ConfigurationKey, request.Url);
}
if (settings.SendBrowserExamKey)
{
headers["X-SafeExamBrowser-RequestHash"] = keyGenerator.CalculateBrowserExamKeyHash(request.Url);
headers["X-SafeExamBrowser-RequestHash"] = keyGenerator.CalculateBrowserExamKeyHash(settings.ConfigurationKey, settings.BrowserExamKeySalt, request.Url);
}
request.Headers = headers;

View file

@ -223,7 +223,7 @@ namespace SafeExamBrowser.Client
private IOperation BuildBrowserOperation()
{
var fileSystemDialog = BuildFileSystemDialog();
var keyGenerator = new KeyGenerator(context.AppConfig, context.IntegrityModule, ModuleLogger(nameof(KeyGenerator)), context.Settings);
var keyGenerator = new KeyGenerator(context.AppConfig, context.IntegrityModule, ModuleLogger(nameof(KeyGenerator)));
var moduleLogger = ModuleLogger(nameof(BrowserApplication));
var browser = new BrowserApplication(
context.AppConfig,
@ -287,7 +287,8 @@ namespace SafeExamBrowser.Client
private IOperation BuildServerOperation()
{
var server = new ServerProxy(context.AppConfig, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo, powerSupply, networkAdapter);
var keyGenerator = new KeyGenerator(context.AppConfig, context.IntegrityModule, ModuleLogger(nameof(KeyGenerator)));
var server = new ServerProxy(context.AppConfig, keyGenerator, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo, powerSupply, networkAdapter);
var operation = new ServerOperation(context, logger, server);
context.Server = server;

View file

@ -13,14 +13,19 @@ namespace SafeExamBrowser.Configuration.Contracts.Cryptography
/// </summary>
public interface IKeyGenerator
{
/// <summary>
/// Calculates the encrypted value of the app signature key.
/// </summary>
string CalculateAppSignatureKey(string connectionToken, string salt);
/// <summary>
/// Calculates the hash value of the browser exam key (BEK) for the given URL.
/// </summary>
string CalculateBrowserExamKeyHash(string url);
string CalculateBrowserExamKeyHash(string configurationKey, byte[] salt, string url);
/// <summary>
/// Calculates the hash value of the configuration key (CK) for the given URL.
/// </summary>
string CalculateConfigurationKeyHash(string url);
string CalculateConfigurationKeyHash(string configurationKey, string url);
}
}

View file

@ -23,6 +23,11 @@ namespace SafeExamBrowser.Configuration.Contracts.Integrity
/// </summary>
void ClearSession(string configurationKey, string startUrl);
/// <summary>
/// Attempts to calculate the app signature key.
/// </summary>
bool TryCalculateAppSignatureKey(string connectionToken, string salt, out string appSignatureKey);
/// <summary>
/// Attempts to calculate the browser exam key.
/// </summary>

View file

@ -13,7 +13,6 @@ using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Configuration.Contracts.Integrity;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Settings;
namespace SafeExamBrowser.Configuration.Cryptography
{
@ -25,42 +24,51 @@ namespace SafeExamBrowser.Configuration.Cryptography
private readonly AppConfig appConfig;
private readonly IIntegrityModule integrityModule;
private readonly ILogger logger;
private readonly AppSettings settings;
private string browserExamKey;
public KeyGenerator(AppConfig appConfig, IIntegrityModule integrityModule, ILogger logger, AppSettings settings)
public KeyGenerator(AppConfig appConfig, IIntegrityModule integrityModule, ILogger logger)
{
this.algorithm = new SHA256Managed();
this.appConfig = appConfig;
this.integrityModule = integrityModule;
this.logger = logger;
this.settings = settings;
}
public string CalculateBrowserExamKeyHash(string url)
public string CalculateAppSignatureKey(string connectionToken, string salt)
{
if (integrityModule.TryCalculateAppSignatureKey(connectionToken, salt, out var appSignatureKey))
{
logger.Debug("Successfully calculated app signature key using integrity module.");
}
else
{
logger.Error("Failed to calculate app signature key using integrity module!");
}
return appSignatureKey;
}
public string CalculateBrowserExamKeyHash(string configurationKey, byte[] salt, string url)
{
var urlWithoutFragment = url.Split('#')[0];
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey())));
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey(configurationKey, salt))));
var key = ToString(hash);
return key;
}
public string CalculateConfigurationKeyHash(string url)
public string CalculateConfigurationKeyHash(string configurationKey, string url)
{
var urlWithoutFragment = url.Split('#')[0];
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + settings.Browser.ConfigurationKey));
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + configurationKey));
var key = ToString(hash);
return key;
}
private string ComputeBrowserExamKey()
private string ComputeBrowserExamKey(string configurationKey, byte[] salt)
{
var configurationKey = settings.Browser.ConfigurationKey;
var salt = settings.Browser.BrowserExamKeySalt;
lock (@lock)
{
if (browserExamKey == default)
@ -81,11 +89,11 @@ namespace SafeExamBrowser.Configuration.Cryptography
if (integrityModule.TryCalculateBrowserExamKey(configurationKey, ToString(salt), out browserExamKey))
{
logger.Debug("Successfully calculated BEK using integrity module.");
logger.Debug("Successfully calculated browser exam key using integrity module.");
}
else
{
logger.Warn("Failed to calculate BEK using integrity module! Falling back to simplified calculation...");
logger.Warn("Failed to calculate browser exam key using integrity module! Falling back to simplified calculation...");
using (var algorithm = new HMACSHA256(salt))
{

View file

@ -74,6 +74,26 @@ namespace SafeExamBrowser.Configuration.Integrity
}
}
public bool TryCalculateAppSignatureKey(string connectionToken, string salt, out string appSignatureKey)
{
appSignatureKey = default;
try
{
appSignatureKey = CalculateAppSignatureKey(connectionToken, salt);
}
catch (DllNotFoundException)
{
logger.Warn("Integrity module is not available!");
}
catch (Exception e)
{
logger.Error("Unexpected error while attempting to calculate app signature key!", e);
}
return appSignatureKey != default;
}
public bool TryCalculateBrowserExamKey(string configurationKey, string salt, out string browserExamKey)
{
browserExamKey = default;
@ -214,6 +234,10 @@ namespace SafeExamBrowser.Configuration.Integrity
return success;
}
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string CalculateAppSignatureKey(string connectionToken, string salt);
[DllImport(DLL_NAME, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string CalculateBrowserExamKey(string configurationKey, string salt);

View file

@ -80,6 +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<ServerSettings>())).Callback(() => initialize = ++counter);
server.Setup(s => s.GetConnectionInfo()).Returns(connection).Callback(() => getConnection = ++counter);
server.Setup(s => s.SendSelectedExam(It.IsAny<Exam>())).Returns(new ServerResponse(true));
server
.Setup(s => s.GetAvailableExams(It.IsAny<string>()))
.Returns(new ServerResponse<IEnumerable<Exam>>(true, default(IEnumerable<Exam>)))
@ -106,6 +107,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Verify(s => s.GetAvailableExams(It.IsAny<string>()), Times.Once);
server.Verify(s => s.GetConfigurationFor(It.Is<Exam>(e => e == exam)), Times.Once);
server.Verify(s => s.GetConnectionInfo(), Times.Once);
server.Verify(s => s.SendSelectedExam(It.Is<Exam>(e => e == exam)), Times.Once);
Assert.AreEqual(1, initialize);
Assert.AreEqual(2, connect);
@ -274,6 +276,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Setup(s => s.GetConnectionInfo()).Returns(connection);
server.Setup(s => s.GetAvailableExams(It.IsAny<string>())).Returns(new ServerResponse<IEnumerable<Exam>>(true, new[] { exam }));
server.Setup(s => s.GetConfigurationFor(It.IsAny<Exam>())).Returns(new ServerResponse<Uri>(true, new Uri("file:///configuration.seb")));
server.Setup(s => s.SendSelectedExam(It.IsAny<Exam>())).Returns(new ServerResponse(true));
sut.ActionRequired += (args) => Assert.Fail();
var result = sut.Perform();
@ -284,6 +287,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Verify(s => s.GetAvailableExams(It.IsAny<string>()), Times.Once);
server.Verify(s => s.GetConfigurationFor(It.Is<Exam>(e => e == exam)), Times.Once);
server.Verify(s => s.GetConnectionInfo(), Times.Once);
server.Verify(s => s.SendSelectedExam(It.Is<Exam>(e => e == exam)), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}
@ -332,6 +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<ServerSettings>())).Callback(() => initialize = ++counter);
server.Setup(s => s.GetConnectionInfo()).Returns(connection).Callback(() => getConnection = ++counter);
server.Setup(s => s.SendSelectedExam(It.IsAny<Exam>())).Returns(new ServerResponse(true));
server
.Setup(s => s.GetAvailableExams(It.IsAny<string>()))
.Returns(new ServerResponse<IEnumerable<Exam>>(true, default(IEnumerable<Exam>)))
@ -358,6 +363,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Verify(s => s.GetAvailableExams(It.IsAny<string>()), Times.Once);
server.Verify(s => s.GetConfigurationFor(It.Is<Exam>(e => e == exam)), Times.Once);
server.Verify(s => s.GetConnectionInfo(), Times.Once);
server.Verify(s => s.SendSelectedExam(It.Is<Exam>(e => e == exam)), Times.Once);
Assert.AreEqual(1, initialize);
Assert.AreEqual(2, connect);
@ -526,6 +532,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Setup(s => s.GetConnectionInfo()).Returns(connection);
server.Setup(s => s.GetAvailableExams(It.IsAny<string>())).Returns(new ServerResponse<IEnumerable<Exam>>(true, new[] { exam }));
server.Setup(s => s.GetConfigurationFor(It.IsAny<Exam>())).Returns(new ServerResponse<Uri>(true, new Uri("file:///configuration.seb")));
server.Setup(s => s.SendSelectedExam(It.IsAny<Exam>())).Returns(new ServerResponse(true));
sut.ActionRequired += (args) => Assert.Fail();
var result = sut.Repeat();
@ -536,6 +543,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
server.Verify(s => s.GetAvailableExams(It.IsAny<string>()), Times.Once);
server.Verify(s => s.GetConfigurationFor(It.Is<Exam>(e => e == exam)), Times.Once);
server.Verify(s => s.GetConnectionInfo(), Times.Once);
server.Verify(s => s.SendSelectedExam(It.Is<Exam>(e => e == exam)), Times.Once);
Assert.AreEqual(OperationResult.Success, result);
}

View file

@ -71,6 +71,7 @@ namespace SafeExamBrowser.Runtime
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var fileSystem = new FileSystem();
var keyGenerator = new KeyGenerator(appConfig, integrityModule, ModuleLogger(nameof(KeyGenerator)));
var messageBox = new MessageBoxFactory(text);
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), ModuleLogger(nameof(ProxyFactory)));
@ -78,7 +79,7 @@ namespace SafeExamBrowser.Runtime
var remoteSessionDetector = new RemoteSessionDetector(ModuleLogger(nameof(RemoteSessionDetector)));
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS);
var runtimeWindow = uiFactory.CreateRuntimeWindow(appConfig);
var server = new ServerProxy(appConfig, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo);
var server = new ServerProxy(appConfig, keyGenerator, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo);
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)), Interlocutor.Runtime);
var sessionContext = new SessionContext();
var splashScreen = uiFactory.CreateSplashScreen(appConfig);

View file

@ -24,9 +24,9 @@ namespace SafeExamBrowser.Server.Requests
{
}
internal bool TryExecute(out string message)
internal bool TryExecute(string appSignatureKey, out string message)
{
var content = $"seb_signature_key={"WINDOWS-TEST-ASK-1234"}";
var content = $"seb_signature_key={appSignatureKey}";
var success = TryExecute(new HttpMethod("PATCH"), api.HandshakeEndpoint, out var response, content, ContentType.URL_ENCODED, Authorization, Token);
message = response.ToLogString();

View file

@ -13,6 +13,7 @@ using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Data;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.Settings.Server;
using SafeExamBrowser.SystemComponents.Contracts.Network;
namespace SafeExamBrowser.Server.Requests
{
@ -27,22 +28,24 @@ namespace SafeExamBrowser.Server.Requests
{
}
internal bool TryExecute(string text, int? value = default)
internal bool TryExecute(IWirelessNetwork network, out string message)
{
var json = new JObject
{
["text"] = text,
["text"] = network != default ? $"<wlan> {network.Name}: {network.Status}, {network.SignalStrength}%" : "<wlan> not connected",
["timestamp"] = DateTime.Now.ToUnixTimestamp(),
["type"] = LogLevel.Info.ToLogType()
};
if (value != default)
if (network != default)
{
json["numericValue"] = value.Value;
json["numericValue"] = network.SignalStrength;
}
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, json.ToString(), ContentType.JSON, Authorization, Token);
message = response.ToLogString();
return success;
}
}

View file

@ -13,6 +13,7 @@ using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Data;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.Settings.Server;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
namespace SafeExamBrowser.Server.Requests
{
@ -27,8 +28,24 @@ namespace SafeExamBrowser.Server.Requests
{
}
internal bool TryExecute(string text, int value)
internal bool TryExecute(IPowerSupplyStatus status, bool previouslyConnected, int previousValue, out string message)
{
var connected = status.IsOnline;
var text = default(string);
var value = Convert.ToInt32(status.BatteryCharge * 100);
if (value != previousValue)
{
var chargeInfo = $"{status.BatteryChargeStatus} at {value}%";
var gridInfo = $"{(connected ? "connected to" : "disconnected from")} the power grid";
text = $"<battery> {chargeInfo}, {status.BatteryTimeRemaining} remaining, {gridInfo}";
}
else if (connected != previouslyConnected)
{
text = $"<battery> Device has been {(connected ? "connected to" : "disconnected from")} power grid";
}
var json = new JObject
{
["numericValue"] = value,
@ -37,7 +54,9 @@ namespace SafeExamBrowser.Server.Requests
["type"] = LogLevel.Info.ToLogType()
};
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out _, json.ToString(), ContentType.JSON, Authorization, Token);
var success = TryExecute(HttpMethod.Post, api.LogEndpoint, out var response, json.ToString(), ContentType.JSON, Authorization, Token);
message = response.ToLogString();
return success;
}

View file

@ -15,6 +15,7 @@ using System.Threading.Tasks;
using System.Timers;
using Newtonsoft.Json;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Server.Contracts.Data;
@ -34,6 +35,7 @@ namespace SafeExamBrowser.Server
private readonly AppConfig appConfig;
private readonly FileSystem fileSystem;
private readonly ConcurrentQueue<string> instructionConfirmations;
private readonly IKeyGenerator keyGenerator;
private readonly ILogger logger;
private readonly ConcurrentQueue<ILogContent> logContent;
private readonly Timer logTimer;
@ -45,16 +47,16 @@ namespace SafeExamBrowser.Server
private readonly INetworkAdapter networkAdapter;
private ApiVersion1 api;
private bool connectedToPowergrid;
private int currentHandId;
private int currentLockScreenId;
private int currentPowerSupplyValue;
private int currentWlanValue;
private string examId;
private HttpClient httpClient;
private int notificationId;
private int pingNumber;
private bool powerSupplyConnected;
private int powerSupplyValue;
private ServerSettings settings;
private int wirelessNetworkValue;
public event ServerEventHandler HandConfirmed;
public event ServerEventHandler LockScreenConfirmed;
@ -65,6 +67,7 @@ namespace SafeExamBrowser.Server
public ServerProxy(
AppConfig appConfig,
IKeyGenerator keyGenerator,
ILogger logger,
ISystemInfo systemInfo,
IUserInfo userInfo,
@ -73,6 +76,7 @@ namespace SafeExamBrowser.Server
{
this.api = new ApiVersion1();
this.appConfig = appConfig;
this.keyGenerator = keyGenerator;
this.fileSystem = new FileSystem(appConfig, logger);
this.instructionConfirmations = new ConcurrentQueue<string>();
this.logger = logger;
@ -291,7 +295,7 @@ namespace SafeExamBrowser.Server
if (success && salt != default)
{
logger.Info("App signature key salt detected, performing key exchange...");
success = TrySendAppSignatureKey(out message);
success = TrySendAppSignatureKey(salt, out message);
}
else
{
@ -425,23 +429,22 @@ namespace SafeExamBrowser.Server
{
var connected = status.IsOnline;
var value = Convert.ToInt32(status.BatteryCharge * 100);
var text = default(string);
if (value != currentPowerSupplyValue)
if (powerSupplyConnected != connected || powerSupplyValue != value)
{
var chargeInfo = $"{status.BatteryChargeStatus} at {value}%";
var gridInfo = $"{(status.IsOnline ? "connected to" : "disconnected from")} the power grid";
var request = new PowerSupplyRequest(api, httpClient, logger, parser, settings);
var success = request.TryExecute(status, powerSupplyConnected, powerSupplyValue, out var message);
currentPowerSupplyValue = value;
text = $"<battery> {chargeInfo}, {status.BatteryTimeRemaining} remaining, {gridInfo}";
if (success)
{
powerSupplyConnected = connected;
powerSupplyValue = value;
}
else
{
logger.Warn($"Failed to send power supply status! {message}");
}
}
else if (connected != connectedToPowergrid)
{
connectedToPowergrid = connected;
text = $"<battery> Device has been {(connected ? "connected to" : "disconnected from")} power grid";
}
new PowerSupplyRequest(api, httpClient, logger, parser, settings).TryExecute(text, value);
}
catch (Exception e)
{
@ -457,23 +460,19 @@ namespace SafeExamBrowser.Server
{
var network = networkAdapter.GetWirelessNetworks().FirstOrDefault(n => n.Status == ConnectionStatus.Connected);
if (network?.SignalStrength != currentWlanValue)
if (network?.SignalStrength != wirelessNetworkValue)
{
var text = default(string);
var value = default(int?);
var request = new NetworkAdapterRequest(api, httpClient, logger, parser, settings);
var success = request.TryExecute(network, out var message);
if (network != default(IWirelessNetwork))
if (success)
{
text = $"<wlan> {network.Name}: {network.Status}, {network.SignalStrength}%";
value = network.SignalStrength;
wirelessNetworkValue = network?.SignalStrength ?? NOT_CONNECTED;
}
else
{
text = "<wlan> not connected";
logger.Warn($"Failed to send wireless status! {message}");
}
new NetworkAdapterRequest(api, httpClient, logger, parser, settings).TryExecute(text, value);
currentWlanValue = network?.SignalStrength ?? NOT_CONNECTED;
}
}
catch (Exception e)
@ -507,13 +506,11 @@ namespace SafeExamBrowser.Server
}
}
private bool TrySendAppSignatureKey(out string message)
private bool TrySendAppSignatureKey(string salt, out string message)
{
// TODO:
// keyGenerator.CalculateAppSignatureKey(configurationKey, server.AppSignatureKeySalt)
var appSignatureKey = keyGenerator.CalculateAppSignatureKey(BaseRequest.ConnectionToken, salt);
var request = new AppSignatureKeyRequest(api, httpClient, logger, parser, settings);
var success = request.TryExecute(out message);
var success = request.TryExecute(appSignatureKey, out message);
if (success)
{