SEBWIN-221: Extended loading algorithm with functionality required to save data as local client configuration.

This commit is contained in:
dbuechel 2018-12-14 09:50:10 +01:00
parent f9f2bb3257
commit 75f5994a3b
29 changed files with 572 additions and 280 deletions

View file

@ -11,6 +11,7 @@ using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq; using Moq;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.UnitTests namespace SafeExamBrowser.Configuration.UnitTests

View file

@ -10,8 +10,12 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Configuration.DataFormats; using SafeExamBrowser.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration.DataResources;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -29,7 +33,7 @@ namespace SafeExamBrowser.Configuration
private AppConfig appConfig; private AppConfig appConfig;
private IHashAlgorithm hashAlgorithm; private IHashAlgorithm hashAlgorithm;
private IList<IDataFormat> dataFormats; private IList<IDataFormat> dataFormats;
private IList<IResourceLoader> resourceLoaders; private IList<IDataResource> dataResources;
private ILogger logger; private ILogger logger;
public ConfigurationRepository( public ConfigurationRepository(
@ -41,7 +45,7 @@ namespace SafeExamBrowser.Configuration
string programVersion) string programVersion)
{ {
dataFormats = new List<IDataFormat>(); dataFormats = new List<IDataFormat>();
resourceLoaders = new List<IResourceLoader>(); dataResources = new List<IDataResource>();
this.hashAlgorithm = hashAlgorithm; this.hashAlgorithm = hashAlgorithm;
this.logger = logger; this.logger = logger;
@ -51,6 +55,35 @@ namespace SafeExamBrowser.Configuration
this.programVersion = programVersion ?? string.Empty; this.programVersion = programVersion ?? string.Empty;
} }
public void ConfigureClientWith(Uri resource, EncryptionParameters encryption = null)
{
logger.Info($"Attempting to configure local client settings from '{resource}'...");
try
{
TryLoadData(resource, out Stream stream);
using (stream)
{
// TODO:
//TryParseData(stream, encryption, out _, out _, out var data);
//HandleIdentityCertificates(data);
// Save configuration data as local client config under %APPDATA%!
// -> New key will determine whether to use default password or current settings password!
// -> "clientConfigEncryptUsingSettingsPassword"
// -> Default settings password for local client configuration appears to be string.Empty -> passwords.SettingsPassword
// -> Otherwise, the local client configuration must again be encrypted in the same way as the original file!!
}
logger.Info($"Successfully configured local client settings with '{resource}'.");
}
catch (Exception e)
{
logger.Error($"Unexpected error while trying to configure local client settings '{resource}'!", e);
}
}
public AppConfig InitializeAppConfig() public AppConfig InitializeAppConfig()
{ {
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser)); var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser));
@ -128,34 +161,38 @@ namespace SafeExamBrowser.Configuration
dataFormats.Add(dataFormat); dataFormats.Add(dataFormat);
} }
public void Register(IResourceLoader resourceLoader) public void Register(IDataResource dataResource)
{ {
resourceLoaders.Add(resourceLoader); dataResources.Add(dataResource);
} }
public LoadStatus TryLoadSettings(Uri resource, PasswordInfo passwordInfo, out Settings settings) public LoadStatus TryLoadSettings(Uri resource, PasswordParameters password, out EncryptionParameters encryption, out Format format, out Settings settings)
{ {
logger.Info($"Attempting to load '{resource}'..."); logger.Info($"Attempting to load '{resource}'...");
encryption = default(EncryptionParameters);
format = default(Format);
settings = LoadDefaultSettings(); settings = LoadDefaultSettings();
try try
{ {
var status = TryLoadData(resource, out Stream data); var status = TryLoadData(resource, out Stream stream);
using (data) using (stream)
{ {
if (status == LoadStatus.LoadWithBrowser)
{
return HandleBrowserResource(resource, settings);
}
if (status != LoadStatus.Success) if (status != LoadStatus.Success)
{ {
return status; return status;
} }
return TryParseData(data, passwordInfo, resource, settings); status = TryParseData(stream, password, out encryption, out format, out var data);
if (status == LoadStatus.Success)
{
data.MapTo(settings);
}
return status;
} }
} }
catch (Exception e) catch (Exception e)
@ -166,39 +203,65 @@ namespace SafeExamBrowser.Configuration
} }
} }
private void ExtractAndImportCertificates(IDictionary<string, object> data) public SaveStatus TrySaveSettings(Uri resource, Format format, Settings settings, EncryptionParameters encryption = null)
{ {
// TODO throw new NotImplementedException();
} }
private LoadStatus HandleBrowserResource(Uri resource, Settings settings) private void HandleIdentityCertificates(IDictionary<string, object> data)
{ {
settings.Browser.StartUrl = resource.AbsoluteUri; const int IDENTITY_CERTIFICATE = 1;
logger.Info($"The resource needs authentication or is HTML data, loaded default settings with '{resource}' as startup URL."); var hasCertificates = data.TryGetValue("embeddedCertificates", out object value);
return LoadStatus.Success; if (hasCertificates && value is IList<IDictionary<string, object>> certificates)
{
var toRemove = new List<IDictionary<string, object>>();
foreach (var certificate in certificates)
{
var isIdentity = certificate.TryGetValue("type", out object t) && t is int type && type == IDENTITY_CERTIFICATE;
var hasData = certificate.TryGetValue("certificateData", out value);
if (isIdentity && hasData && value is byte[] certificateData)
{
ImportIdentityCertificate(certificateData, new X509Store(StoreLocation.CurrentUser));
ImportIdentityCertificate(certificateData, new X509Store(StoreName.TrustedPeople, StoreLocation.LocalMachine));
toRemove.Add(certificate);
}
} }
private void HandleParseSuccess(ParseResult result, Settings settings, PasswordInfo passwordInfo, Uri resource) toRemove.ForEach(c => certificates.Remove(c));
{ }
var appDataFile = new Uri(Path.Combine(appConfig.AppDataFolder, appConfig.DefaultSettingsFileName)); }
var programDataFile = new Uri(Path.Combine(appConfig.ProgramDataFolder, appConfig.DefaultSettingsFileName));
var isAppDataFile = resource.AbsolutePath.Equals(appDataFile.AbsolutePath, StringComparison.OrdinalIgnoreCase);
var isProgramDataFile = resource.AbsolutePath.Equals(programDataFile.AbsolutePath, StringComparison.OrdinalIgnoreCase);
logger.Info("Mapping settings data..."); private void ImportIdentityCertificate(byte[] certificateData, X509Store store)
result.RawData.MapTo(settings);
if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient && !isAppDataFile && !isProgramDataFile)
{ {
result.Status = TryConfigureClient(result.RawData, settings, passwordInfo); try
{
var certificate = new X509Certificate2();
certificate.Import(certificateData, "Di𝈭l𝈖Ch𝈒ah𝉇t𝈁a𝉈Hai1972", X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.PersistKeySet);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
logger.Info($"Successfully imported identity certificate into {store.Location}.{store.Name}.");
}
catch (Exception e)
{
logger.Error($"Failed to import identity certificate into {store.Location}.{store.Name}!", e);
}
finally
{
store.Close();
} }
} }
private LoadStatus TryLoadData(Uri resource, out Stream data) private LoadStatus TryLoadData(Uri resource, out Stream data)
{ {
var status = LoadStatus.NotSupported; var status = LoadStatus.NotSupported;
var resourceLoader = resourceLoaders.FirstOrDefault(l => l.CanLoad(resource)); var resourceLoader = dataResources.FirstOrDefault(l => l.CanLoad(resource));
data = default(Stream); data = default(Stream);
@ -215,23 +278,25 @@ namespace SafeExamBrowser.Configuration
return status; return status;
} }
private LoadStatus TryParseData(Stream data, PasswordInfo passwordInfo, Uri resource, Settings settings) private LoadStatus TryParseData(Stream data, PasswordParameters password, out EncryptionParameters encryption, out Format format, out IDictionary<string, object> rawData)
{ {
var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data)); var dataFormat = dataFormats.FirstOrDefault(f => f.CanParse(data));
var status = LoadStatus.NotSupported; var status = LoadStatus.NotSupported;
encryption = default(EncryptionParameters);
format = default(Format);
rawData = default(Dictionary<string, object>);
if (dataFormat != null) if (dataFormat != null)
{ {
var result = dataFormat.TryParse(data, passwordInfo); var result = dataFormat.TryParse(data, password);
logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {result.Status}.");
if (result.Status == LoadStatus.Success || result.Status == LoadStatus.SuccessConfigureClient)
{
HandleParseSuccess(result, settings, passwordInfo, resource);
}
encryption = result.Encryption;
format = result.Format;
rawData = result.RawData;
status = result.Status; status = result.Status;
logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {status}.");
} }
else else
{ {
@ -241,44 +306,6 @@ namespace SafeExamBrowser.Configuration
return status; return status;
} }
private LoadStatus TryConfigureClient(IDictionary<string, object> data, Settings settings, PasswordInfo passwordInfo)
{
logger.Info("Attempting to configure local client settings...");
if (passwordInfo.AdminPasswordHash != null)
{
var adminPasswordHash = passwordInfo.AdminPassword != null ? hashAlgorithm.GenerateHashFor(passwordInfo.AdminPassword) : null;
var settingsPasswordHash = passwordInfo.SettingsPassword != null ? hashAlgorithm.GenerateHashFor(passwordInfo.SettingsPassword) : null;
var enteredCorrectPassword = passwordInfo.AdminPasswordHash.Equals(adminPasswordHash, StringComparison.OrdinalIgnoreCase);
var sameAdminPassword = passwordInfo.AdminPasswordHash.Equals(settings.AdminPasswordHash, StringComparison.OrdinalIgnoreCase);
var knowsAdminPassword = passwordInfo.AdminPasswordHash.Equals(settingsPasswordHash, StringComparison.OrdinalIgnoreCase);
if (sameAdminPassword || knowsAdminPassword || enteredCorrectPassword)
{
logger.Info("Authentication was successful.");
}
else
{
logger.Info("Authentication has failed!");
return LoadStatus.AdminPasswordNeeded;
}
}
else
{
logger.Info("Authentication is not required.");
}
// -> Certificates need to be imported and REMOVED from the settings before the data is saved!
ExtractAndImportCertificates(data);
// Save configuration data as local client config under %APPDATA%!
// -> Default settings password for local client configuration appears to be string.Empty
// -> Local client configuration needs to again be encrypted in the same way as the original file was!!
return LoadStatus.SuccessConfigureClient;
}
private void UpdateAppConfig() private void UpdateAppConfig()
{ {
appConfig.ClientId = Guid.NewGuid(); appConfig.ClientId = Guid.NewGuid();

View file

@ -10,9 +10,9 @@ using System;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.Cryptography;
namespace SafeExamBrowser.Configuration namespace SafeExamBrowser.Configuration.Cryptography
{ {
public class HashAlgorithm : IHashAlgorithm public class HashAlgorithm : IHashAlgorithm
{ {

View file

@ -12,7 +12,7 @@ using System.Security.Cryptography;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography namespace SafeExamBrowser.Configuration.Cryptography
{ {
internal class PasswordEncryption internal class PasswordEncryption
{ {
@ -37,7 +37,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
if (password == null) if (password == null)
{ {
return LoadStatus.SettingsPasswordNeeded; return LoadStatus.PasswordNeeded;
} }
var (version, options) = ParseHeader(data); var (version, options) = ParseHeader(data);
@ -112,7 +112,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
{ {
logger.Debug($"The authentication failed due to an invalid password or corrupted data!"); logger.Debug($"The authentication failed due to an invalid password or corrupted data!");
return LoadStatus.SettingsPasswordNeeded; return LoadStatus.PasswordNeeded;
} }
private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength) private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength)

View file

@ -13,7 +13,7 @@ using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography namespace SafeExamBrowser.Configuration.Cryptography
{ {
internal class PublicKeyHashEncryption internal class PublicKeyHashEncryption
{ {
@ -26,10 +26,10 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
this.logger = logger; this.logger = logger;
} }
internal virtual LoadStatus Decrypt(Stream data, out Stream decrypted) internal virtual LoadStatus Decrypt(Stream data, out Stream decrypted, out X509Certificate2 certificate)
{ {
var keyHash = ParsePublicKeyHash(data); var keyHash = ParsePublicKeyHash(data);
var found = TryGetCertificateWith(keyHash, out X509Certificate2 certificate); var found = TryGetCertificateWith(keyHash, out certificate);
decrypted = default(Stream); decrypted = default(Stream);

View file

@ -12,7 +12,7 @@ using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography namespace SafeExamBrowser.Configuration.Cryptography
{ {
internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption
{ {
@ -25,10 +25,10 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
this.passwordEncryption = passwordEncryption; this.passwordEncryption = passwordEncryption;
} }
internal override LoadStatus Decrypt(Stream data, out Stream decrypted) internal override LoadStatus Decrypt(Stream data, out Stream decrypted, out X509Certificate2 certificate)
{ {
var keyHash = ParsePublicKeyHash(data); var keyHash = ParsePublicKeyHash(data);
var found = TryGetCertificateWith(keyHash, out X509Certificate2 certificate); var found = TryGetCertificateWith(keyHash, out certificate);
decrypted = default(Stream); decrypted = default(Stream);

View file

@ -9,10 +9,10 @@
using System; using System;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration.DataCompression;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.Compression namespace SafeExamBrowser.Configuration.DataCompression
{ {
/// <summary> /// <summary>
/// Data compression using the GNU-Zip format (see https://en.wikipedia.org/wiki/Gzip). /// Data compression using the GNU-Zip format (see https://en.wikipedia.org/wiki/Gzip).

View file

@ -8,9 +8,13 @@
using System; using System;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
using SafeExamBrowser.Configuration.DataFormats.Cryptography; using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataCompression;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats namespace SafeExamBrowser.Configuration.DataFormats
@ -56,7 +60,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
return false; return false;
} }
public ParseResult TryParse(Stream data, PasswordInfo passwordInfo) public ParseResult TryParse(Stream data, PasswordParameters password)
{ {
var prefix = ParsePrefix(data); var prefix = ParsePrefix(data);
var success = TryDetermineFormat(prefix, out FormatType format); var success = TryDetermineFormat(prefix, out FormatType format);
@ -75,13 +79,13 @@ namespace SafeExamBrowser.Configuration.DataFormats
{ {
case FormatType.Password: case FormatType.Password:
case FormatType.PasswordConfigureClient: case FormatType.PasswordConfigureClient:
return ParsePasswordBlock(data, passwordInfo, format); return ParsePasswordBlock(data, format, password);
case FormatType.PlainData: case FormatType.PlainData:
return ParsePlainDataBlock(data, passwordInfo); return ParsePlainDataBlock(data);
case FormatType.PublicKeyHash: case FormatType.PublicKeyHash:
return ParsePublicKeyHashBlock(data, passwordInfo); return ParsePublicKeyHashBlock(data, password);
case FormatType.PublicKeyHashWithSymmetricKey: case FormatType.PublicKeyHashWithSymmetricKey:
return ParsePublicKeyHashWithSymmetricKeyBlock(data, passwordInfo); return ParsePublicKeyHashWithSymmetricKeyBlock(data, password);
} }
} }
@ -90,40 +94,35 @@ namespace SafeExamBrowser.Configuration.DataFormats
return new ParseResult { Status = LoadStatus.InvalidData }; return new ParseResult { Status = LoadStatus.InvalidData };
} }
private ParseResult ParsePasswordBlock(Stream data, PasswordInfo passwordInfo, FormatType format) private ParseResult ParsePasswordBlock(Stream data, FormatType format, PasswordParameters password)
{ {
var decrypted = default(Stream);
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption))); var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
var status = default(LoadStatus); var encryptionParams = new PasswordParameters();
var result = new ParseResult();
if (format == FormatType.PasswordConfigureClient) if (format == FormatType.PasswordConfigureClient)
{ {
status = encryption.Decrypt(data, string.Empty, out decrypted); encryptionParams.Password = password.IsHash ? password.Password : hashAlgorithm.GenerateHashFor(password.Password);
encryptionParams.IsHash = true;
if (status == LoadStatus.SettingsPasswordNeeded && passwordInfo.AdminPasswordHash != null)
{
status = encryption.Decrypt(data, passwordInfo.AdminPasswordHash, out decrypted);
}
if (status == LoadStatus.SettingsPasswordNeeded && passwordInfo.SettingsPassword != null)
{
status = encryption.Decrypt(data, hashAlgorithm.GenerateHashFor(passwordInfo.SettingsPassword), out decrypted);
}
} }
else else
{ {
status = encryption.Decrypt(data, passwordInfo.SettingsPassword, out decrypted); encryptionParams.Password = password.Password;
encryptionParams.IsHash = password.IsHash;
} }
if (status == LoadStatus.Success) result.Status = encryption.Decrypt(data, encryptionParams.Password, out var decrypted);
if (result.Status == LoadStatus.Success)
{ {
return ParsePlainDataBlock(decrypted, passwordInfo); result = ParsePlainDataBlock(decrypted);
result.Encryption = encryptionParams;
} }
return new ParseResult { Status = status }; return result;
} }
private ParseResult ParsePlainDataBlock(Stream data, PasswordInfo passwordInfo) private ParseResult ParsePlainDataBlock(Stream data)
{ {
var xmlFormat = new XmlFormat(logger.CloneFor(nameof(XmlFormat))); var xmlFormat = new XmlFormat(logger.CloneFor(nameof(XmlFormat)));
@ -132,35 +131,50 @@ namespace SafeExamBrowser.Configuration.DataFormats
data = compressor.Decompress(data); data = compressor.Decompress(data);
} }
return xmlFormat.TryParse(data, passwordInfo); return xmlFormat.TryParse(data);
} }
private ParseResult ParsePublicKeyHashBlock(Stream data, PasswordInfo passwordInfo) private ParseResult ParsePublicKeyHashBlock(Stream data, PasswordParameters password)
{ {
var encryption = new PublicKeyHashEncryption(logger.CloneFor(nameof(PublicKeyHashEncryption))); var encryption = new PublicKeyHashEncryption(logger.CloneFor(nameof(PublicKeyHashEncryption)));
var status = encryption.Decrypt(data, out Stream decrypted); var result = new ParseResult();
if (status == LoadStatus.Success) result.Status = encryption.Decrypt(data, out var decrypted, out var certificate);
if (result.Status == LoadStatus.Success)
{ {
return TryParse(decrypted, passwordInfo); result = TryParse(decrypted, password);
result.Encryption = new PublicKeyHashParameters
{
Certificate = certificate,
InnerEncryption = result.Encryption as PasswordParameters,
SymmetricEncryption = false
};
} }
return new ParseResult { Status = status }; return result;
} }
private ParseResult ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, PasswordInfo passwordInfo) private ParseResult ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, PasswordParameters password)
{ {
var logger = this.logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption));
var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption))); var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
var encryption = new PublicKeyHashWithSymmetricKeyEncryption(logger, passwordEncryption); var encryption = new PublicKeyHashWithSymmetricKeyEncryption(logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption)), passwordEncryption);
var status = encryption.Decrypt(data, out Stream decrypted); var result = new ParseResult();
if (status == LoadStatus.Success) result.Status = encryption.Decrypt(data, out Stream decrypted, out X509Certificate2 certificate);
if (result.Status == LoadStatus.Success)
{ {
return TryParse(decrypted, passwordInfo); result = TryParse(decrypted, password);
result.Encryption = new PublicKeyHashParameters
{
Certificate = certificate,
InnerEncryption = result.Encryption as PasswordParameters,
SymmetricEncryption = true
};
} }
return new ParseResult { Status = status }; return result;
} }
private string ParsePrefix(Stream data) private string ParsePrefix(Stream data)

View file

@ -13,6 +13,8 @@ using System.Text;
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.DataFormats namespace SafeExamBrowser.Configuration.DataFormats
@ -58,7 +60,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
return false; return false;
} }
public ParseResult TryParse(Stream data, PasswordInfo passwordInfo) public ParseResult TryParse(Stream data, PasswordParameters password = null)
{ {
var result = new ParseResult { Status = LoadStatus.InvalidData }; var result = new ParseResult { Status = LoadStatus.InvalidData };
var xmlSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }; var xmlSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
@ -218,6 +220,11 @@ namespace SafeExamBrowser.Configuration.DataFormats
{ {
value = null; value = null;
if (element.IsEmpty)
{
return LoadStatus.Success;
}
switch (element.Name.LocalName) switch (element.Name.LocalName)
{ {
case DataTypes.DATA: case DataTypes.DATA:

View file

@ -9,15 +9,16 @@
using System; using System;
using System.IO; using System.IO;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.DataResources;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.ResourceLoaders namespace SafeExamBrowser.Configuration.DataResources
{ {
public class FileResourceLoader : IResourceLoader public class FileResource: IDataResource
{ {
private ILogger logger; private ILogger logger;
public FileResourceLoader(ILogger logger) public FileResource(ILogger logger)
{ {
this.logger = logger; this.logger = logger;
} }
@ -46,5 +47,10 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
return LoadStatus.Success; return LoadStatus.Success;
} }
public SaveStatus TrySave(Uri resource, Stream data)
{
throw new NotImplementedException();
}
} }
} }

View file

@ -14,11 +14,12 @@ using System.Net.Http;
using System.Net.Mime; using System.Net.Mime;
using System.Threading.Tasks; using System.Threading.Tasks;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.DataResources;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.ResourceLoaders namespace SafeExamBrowser.Configuration.DataResources
{ {
public class NetworkResourceLoader : IResourceLoader public class NetworkResource : IDataResource
{ {
private AppConfig appConfig; private AppConfig appConfig;
private ILogger logger; private ILogger logger;
@ -40,7 +41,7 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
Uri.UriSchemeHttps Uri.UriSchemeHttps
}; };
public NetworkResourceLoader(AppConfig appConfig, ILogger logger) public NetworkResource(AppConfig appConfig, ILogger logger)
{ {
this.appConfig = appConfig; this.appConfig = appConfig;
this.logger = logger; this.logger = logger;
@ -85,6 +86,11 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
return LoadStatus.Success; return LoadStatus.Success;
} }
public SaveStatus TrySave(Uri resource, Stream data)
{
throw new NotImplementedException();
}
private Uri BuildUriFor(Uri resource) private Uri BuildUriFor(Uri resource)
{ {
var scheme = GetSchemeFor(resource); var scheme = GetSchemeFor(resource);

View file

@ -58,18 +58,18 @@
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Compression\GZipCompressor.cs" /> <Compile Include="DataCompression\GZipCompressor.cs" />
<Compile Include="DataFormats\BinaryFormat.cs" /> <Compile Include="DataFormats\BinaryFormat.cs" />
<Compile Include="DataFormats\Cryptography\PasswordEncryption.cs" /> <Compile Include="Cryptography\PasswordEncryption.cs" />
<Compile Include="DataFormats\Cryptography\PublicKeyHashEncryption.cs" /> <Compile Include="Cryptography\PublicKeyHashEncryption.cs" />
<Compile Include="DataFormats\Cryptography\PublicKeyHashWithSymmetricKeyEncryption.cs" /> <Compile Include="Cryptography\PublicKeyHashWithSymmetricKeyEncryption.cs" />
<Compile Include="DataFormats\XmlFormat.cs" /> <Compile Include="DataFormats\XmlFormat.cs" />
<Compile Include="DataFormats\DataMapper.cs" /> <Compile Include="DataFormats\DataMapper.cs" />
<Compile Include="HashAlgorithm.cs" /> <Compile Include="Cryptography\HashAlgorithm.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ConfigurationRepository.cs" /> <Compile Include="ConfigurationRepository.cs" />
<Compile Include="ResourceLoaders\FileResourceLoader.cs" /> <Compile Include="DataResources\FileResource.cs" />
<Compile Include="ResourceLoaders\NetworkResourceLoader.cs" /> <Compile Include="DataResources\NetworkResource.cs" />
<Compile Include="SessionConfiguration.cs" /> <Compile Include="SessionConfiguration.cs" />
<Compile Include="SubStream.cs" /> <Compile Include="SubStream.cs" />
<Compile Include="SystemInfo.cs" /> <Compile Include="SystemInfo.cs" />

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Configuration.Cryptography
{
/// <summary>
/// Holds the cryptographic parameters used to encrypt configuration data.
/// </summary>
public abstract class EncryptionParameters
{
}
}

View file

@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
namespace SafeExamBrowser.Contracts.Configuration namespace SafeExamBrowser.Contracts.Configuration.Cryptography
{ {
/// <summary> /// <summary>
/// Provides functionality to calculate hash codes of different objects. /// Provides functionality to calculate hash codes of different objects.

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Configuration.Cryptography
{
/// <summary>
/// Holds all parameters for data encryption by password.
/// </summary>
public class PasswordParameters : EncryptionParameters
{
/// <summary>
/// The password in plain text.
/// </summary>
public string Password { get; set; }
/// <summary>
/// Indicates whether the password is a hash code.
/// </summary>
public bool IsHash { get; set; }
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System.Security.Cryptography.X509Certificates;
namespace SafeExamBrowser.Contracts.Configuration.Cryptography
{
/// <summary>
/// Holds all parameters for data encryption by certificate.
/// </summary>
public class PublicKeyHashParameters : EncryptionParameters
{
/// <summary>
/// The certificate holding the public key used for encryption.
/// </summary>
public X509Certificate2 Certificate { get; set; }
/// <summary>
/// The encryption parameters of the inner data, if available.
/// </summary>
public PasswordParameters InnerEncryption { get; set; }
/// <summary>
/// Determines the usage of symmetric vs. asymmetric encryption.
/// </summary>
public bool SymmetricEncryption { get; set; }
}
}

View file

@ -8,7 +8,7 @@
using System.IO; using System.IO;
namespace SafeExamBrowser.Contracts.Configuration namespace SafeExamBrowser.Contracts.Configuration.DataCompression
{ {
/// <summary> /// <summary>
/// Defines the functionality for data compression and decompression. /// Defines the functionality for data compression and decompression.

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Configuration.DataFormats
{
/// <summary>
/// Defines all supported data formats.
/// </summary>
public enum Format
{
Binary = 1,
Xml
}
}

View file

@ -7,8 +7,9 @@
*/ */
using System.IO; using System.IO;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
namespace SafeExamBrowser.Contracts.Configuration namespace SafeExamBrowser.Contracts.Configuration.DataFormats
{ {
/// <summary> /// <summary>
/// Provides functionality to parse configuration data with a particular format. /// Provides functionality to parse configuration data with a particular format.
@ -23,6 +24,6 @@ namespace SafeExamBrowser.Contracts.Configuration
/// <summary> /// <summary>
/// Tries to parse the given data. /// Tries to parse the given data.
/// </summary> /// </summary>
ParseResult TryParse(Stream data, PasswordInfo passwordInfo); ParseResult TryParse(Stream data, PasswordParameters password = null);
} }
} }

View file

@ -7,14 +7,25 @@
*/ */
using System.Collections.Generic; using System.Collections.Generic;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
namespace SafeExamBrowser.Contracts.Configuration namespace SafeExamBrowser.Contracts.Configuration.DataFormats
{ {
/// <summary> /// <summary>
/// Defines the result of a data parsing operation by an <see cref="IDataFormat"/>. /// Defines the result of a data parsing operation by an <see cref="IDataFormat"/>.
/// </summary> /// </summary>
public class ParseResult public class ParseResult
{ {
/// <summary>
/// The encryption parameters which were used to decrypt the data, or <c>null</c> if it was not encrypted.
/// </summary>
public EncryptionParameters Encryption { get; set; }
/// <summary>
/// The original format of the data.
/// </summary>
public Format Format { get; set; }
/// <summary> /// <summary>
/// The parsed settings data. Might be <c>null</c> or in an undefinable state, depending on <see cref="Status"/>. /// The parsed settings data. Might be <c>null</c> or in an undefinable state, depending on <see cref="Status"/>.
/// </summary> /// </summary>

View file

@ -9,15 +9,15 @@
using System; using System;
using System.IO; using System.IO;
namespace SafeExamBrowser.Contracts.Configuration namespace SafeExamBrowser.Contracts.Configuration.DataResources
{ {
/// <summary> /// <summary>
/// Provides functionality to load configuration data from a particular resource. /// Provides functionality to load and save configuration data from / as a particular resource.
/// </summary> /// </summary>
public interface IResourceLoader public interface IDataResource
{ {
/// <summary> /// <summary>
/// Indicates whether the resource loader is able to load data from the specified resource. /// Indicates whether data can be loaded from the specified resource.
/// </summary> /// </summary>
bool CanLoad(Uri resource); bool CanLoad(Uri resource);
@ -25,5 +25,10 @@ namespace SafeExamBrowser.Contracts.Configuration
/// Tries to load the configuration data from the specified resource. /// Tries to load the configuration data from the specified resource.
/// </summary> /// </summary>
LoadStatus TryLoad(Uri resource, out Stream data); LoadStatus TryLoad(Uri resource, out Stream data);
/// <summary>
/// Tries to save the given configuration data as the specified resource.
/// </summary>
SaveStatus TrySave(Uri resource, Stream data);
} }
} }

View file

@ -7,6 +7,9 @@
*/ */
using System; using System;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration.DataResources;
namespace SafeExamBrowser.Contracts.Configuration namespace SafeExamBrowser.Contracts.Configuration
{ {
@ -15,6 +18,11 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary> /// </summary>
public interface IConfigurationRepository public interface IConfigurationRepository
{ {
/// <summary>
/// Saves the given resource as local client configuration.
/// </summary>
void ConfigureClientWith(Uri resource, EncryptionParameters encryption = null);
/// <summary> /// <summary>
/// Initializes the global configuration information for the currently running application instance. /// Initializes the global configuration information for the currently running application instance.
/// </summary> /// </summary>
@ -31,19 +39,23 @@ namespace SafeExamBrowser.Contracts.Configuration
Settings.Settings LoadDefaultSettings(); Settings.Settings LoadDefaultSettings();
/// <summary> /// <summary>
/// Registers the specified <see cref="IDataFormat"/> as option to parse data from a configuration resource. /// Registers the specified <see cref="IDataFormat"/> to be used when loading or saving configuration data.
/// </summary> /// </summary>
void Register(IDataFormat dataFormat); void Register(IDataFormat dataFormat);
/// <summary> /// <summary>
/// Registers the specified <see cref="IResourceLoader"/> as option to load data from a configuration resource. /// Registers the specified <see cref="IDataResource"/> to be used when loading or saving configuration data.
/// </summary> /// </summary>
void Register(IResourceLoader resourceLoader); void Register(IDataResource dataResource);
/// <summary> /// <summary>
/// Attempts to load settings from the specified resource. As long as the result is not <see cref="LoadStatus.Success"/>, /// Attempts to load settings from the specified resource.
/// the referenced settings may be <c>null</c> or in an undefinable state!
/// </summary> /// </summary>
LoadStatus TryLoadSettings(Uri resource, PasswordInfo passwordInfo, out Settings.Settings settings); LoadStatus TryLoadSettings(Uri resource, PasswordParameters password, out EncryptionParameters encryption, out Format format, out Settings.Settings settings);
/// <summary>
/// Attempts to save settings according to the specified parameters.
/// </summary>
SaveStatus TrySaveSettings(Uri resource, Format format, Settings.Settings settings, EncryptionParameters encryption = null);
} }
} }

View file

@ -13,11 +13,6 @@ namespace SafeExamBrowser.Contracts.Configuration
/// </summary> /// </summary>
public enum LoadStatus public enum LoadStatus
{ {
/// <summary>
/// Indicates that the current administrator password is needed to be allowed to configure the local client.
/// </summary>
AdminPasswordNeeded = 1,
/// <summary> /// <summary>
/// Indicates that a resource contains invalid data. /// Indicates that a resource contains invalid data.
/// </summary> /// </summary>
@ -34,20 +29,15 @@ namespace SafeExamBrowser.Contracts.Configuration
NotSupported, NotSupported,
/// <summary> /// <summary>
/// Indicates that the settings password is needed in order to decrypt the settings. /// Indicates that a password is needed in order to decrypt the settings.
/// </summary> /// </summary>
SettingsPasswordNeeded, PasswordNeeded,
/// <summary> /// <summary>
/// The settings were loaded successfully. /// The settings were loaded successfully.
/// </summary> /// </summary>
Success, Success,
/// <summary>
/// The settings were loaded and the local client configuration was performed successfully.
/// </summary>
SuccessConfigureClient,
/// <summary> /// <summary>
/// An unexpected error occurred while trying to load the settings. /// An unexpected error occurred while trying to load the settings.
/// </summary> /// </summary>

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Configuration
{
/// <summary>
/// Holds all password data necessary to load an application configuration.
/// </summary>
public class PasswordInfo
{
/// <summary>
/// The current administrator password in plain text.
/// </summary>
public string AdminPassword { get; set; }
/// <summary>
/// The hash code of the current administrator password.
/// </summary>
public string AdminPasswordHash { get; set; }
/// <summary>
/// The settings password of the configuration in plain text.
/// </summary>
public string SettingsPassword { get; set; }
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright (c) 2018 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace SafeExamBrowser.Contracts.Configuration
{
/// <summary>
/// Defines all possible results of an attempt to save an application configuration.
/// </summary>
public enum SaveStatus
{
}
}

View file

@ -53,11 +53,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" /> <Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" />
<Compile Include="Configuration\IDataCompressor.cs" /> <Compile Include="Configuration\Cryptography\EncryptionParameters.cs" />
<Compile Include="Configuration\IDataFormat.cs" /> <Compile Include="Configuration\Cryptography\PasswordParameters.cs" />
<Compile Include="Configuration\IHashAlgorithm.cs" /> <Compile Include="Configuration\Cryptography\PublicKeyHashParameters.cs" />
<Compile Include="Configuration\ParseResult.cs" /> <Compile Include="Configuration\DataFormats\Format.cs" />
<Compile Include="Configuration\PasswordInfo.cs" /> <Compile Include="Configuration\DataCompression\IDataCompressor.cs" />
<Compile Include="Configuration\DataFormats\IDataFormat.cs" />
<Compile Include="Configuration\Cryptography\IHashAlgorithm.cs" />
<Compile Include="Configuration\DataFormats\ParseResult.cs" />
<Compile Include="Configuration\SaveStatus.cs" />
<Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" /> <Compile Include="Core\Events\InstanceTerminatedEventHandler.cs" />
<Compile Include="Core\Events\NameChangedEventHandler.cs" /> <Compile Include="Core\Events\NameChangedEventHandler.cs" />
<Compile Include="Core\IApplicationController.cs" /> <Compile Include="Core\IApplicationController.cs" />
@ -112,7 +116,7 @@
<Compile Include="Communication\Data\SimpleResponsePurport.cs" /> <Compile Include="Communication\Data\SimpleResponsePurport.cs" />
<Compile Include="Communication\Data\SimpleResponse.cs" /> <Compile Include="Communication\Data\SimpleResponse.cs" />
<Compile Include="Configuration\ClientConfiguration.cs" /> <Compile Include="Configuration\ClientConfiguration.cs" />
<Compile Include="Configuration\IResourceLoader.cs" /> <Compile Include="Configuration\DataResources\IDataResource.cs" />
<Compile Include="Configuration\LoadStatus.cs" /> <Compile Include="Configuration\LoadStatus.cs" />
<Compile Include="Configuration\AppConfig.cs" /> <Compile Include="Configuration\AppConfig.cs" />
<Compile Include="Configuration\ISessionConfiguration.cs" /> <Compile Include="Configuration\ISessionConfiguration.cs" />

View file

@ -13,9 +13,10 @@ using System.Reflection;
using SafeExamBrowser.Communication.Hosts; using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Communication.Proxies; using SafeExamBrowser.Communication.Proxies;
using SafeExamBrowser.Configuration; using SafeExamBrowser.Configuration;
using SafeExamBrowser.Configuration.Compression; using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Configuration.DataCompression;
using SafeExamBrowser.Configuration.DataFormats; using SafeExamBrowser.Configuration.DataFormats;
using SafeExamBrowser.Configuration.ResourceLoaders; using SafeExamBrowser.Configuration.DataResources;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Core; using SafeExamBrowser.Contracts.Core;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
@ -75,7 +76,7 @@ namespace SafeExamBrowser.Runtime
bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger)); bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger));
sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext)); sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext));
sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, logger, sessionContext)); sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, new HashAlgorithm(), logger, sessionContext));
sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, FIFTEEN_SECONDS)); sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, FIFTEEN_SECONDS));
sessionOperations.Enqueue(new KioskModeTerminationOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext)); sessionOperations.Enqueue(new KioskModeTerminationOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext));
sessionOperations.Enqueue(new ServiceOperation(logger, serviceProxy, sessionContext)); sessionOperations.Enqueue(new ServiceOperation(logger, serviceProxy, sessionContext));
@ -121,8 +122,8 @@ namespace SafeExamBrowser.Runtime
configuration.Register(new BinaryFormat(compressor, new HashAlgorithm(), new ModuleLogger(logger, nameof(BinaryFormat)))); configuration.Register(new BinaryFormat(compressor, new HashAlgorithm(), new ModuleLogger(logger, nameof(BinaryFormat))));
configuration.Register(new XmlFormat(new ModuleLogger(logger, nameof(XmlFormat)))); configuration.Register(new XmlFormat(new ModuleLogger(logger, nameof(XmlFormat))));
configuration.Register(new FileResourceLoader(new ModuleLogger(logger, nameof(FileResourceLoader)))); configuration.Register(new FileResource(new ModuleLogger(logger, nameof(FileResource))));
configuration.Register(new NetworkResourceLoader(appConfig, new ModuleLogger(logger, nameof(NetworkResourceLoader)))); configuration.Register(new NetworkResource(appConfig, new ModuleLogger(logger, nameof(NetworkResource))));
} }
private void InitializeLogging() private void InitializeLogging()

View file

@ -10,6 +10,8 @@ using System;
using System.IO; using System.IO;
using SafeExamBrowser.Contracts.Communication.Data; using SafeExamBrowser.Contracts.Communication.Data;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataFormats;
using SafeExamBrowser.Contracts.Configuration.Settings; using SafeExamBrowser.Contracts.Configuration.Settings;
using SafeExamBrowser.Contracts.Core.OperationModel; using SafeExamBrowser.Contracts.Core.OperationModel;
using SafeExamBrowser.Contracts.Core.OperationModel.Events; using SafeExamBrowser.Contracts.Core.OperationModel.Events;
@ -23,19 +25,32 @@ namespace SafeExamBrowser.Runtime.Operations
{ {
private string[] commandLineArgs; private string[] commandLineArgs;
private IConfigurationRepository configuration; private IConfigurationRepository configuration;
private IHashAlgorithm hashAlgorithm;
private ILogger logger; private ILogger logger;
private string AppDataFile
{
get { return Path.Combine(Context.Next.AppConfig.AppDataFolder, Context.Next.AppConfig.DefaultSettingsFileName); }
}
private string ProgramDataFile
{
get { return Path.Combine(Context.Next.AppConfig.ProgramDataFolder, Context.Next.AppConfig.DefaultSettingsFileName); }
}
public override event ActionRequiredEventHandler ActionRequired; public override event ActionRequiredEventHandler ActionRequired;
public override event StatusChangedEventHandler StatusChanged; public override event StatusChangedEventHandler StatusChanged;
public ConfigurationOperation( public ConfigurationOperation(
string[] commandLineArgs, string[] commandLineArgs,
IConfigurationRepository configuration, IConfigurationRepository configuration,
IHashAlgorithm hashAlgorithm,
ILogger logger, ILogger logger,
SessionContext sessionContext) : base(sessionContext) SessionContext sessionContext) : base(sessionContext)
{ {
this.commandLineArgs = commandLineArgs; this.commandLineArgs = commandLineArgs;
this.configuration = configuration; this.configuration = configuration;
this.hashAlgorithm = hashAlgorithm;
this.logger = logger; this.logger = logger;
} }
@ -98,53 +113,191 @@ namespace SafeExamBrowser.Runtime.Operations
private OperationResult LoadSettings(Uri uri) private OperationResult LoadSettings(Uri uri)
{ {
var settings = default(Settings); var passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
var status = default(LoadStatus); var status = configuration.TryLoadSettings(uri, passwordParams, out var encryption, out var format, out var settings);
var passwordInfo = new PasswordInfo { AdminPasswordHash = Context.Current?.Settings?.AdminPasswordHash };
for (int adminAttempts = 0, settingsAttempts = 0; adminAttempts < 5 && settingsAttempts < 5;) if (status == LoadStatus.PasswordNeeded && Context.Current?.Settings.AdminPasswordHash != null)
{ {
status = configuration.TryLoadSettings(uri, passwordInfo, out settings); passwordParams.Password = Context.Current.Settings.AdminPasswordHash;
passwordParams.IsHash = true;
if (status != LoadStatus.AdminPasswordNeeded && status != LoadStatus.SettingsPasswordNeeded) status = configuration.TryLoadSettings(uri, passwordParams, out encryption, out format, out settings);
{
break;
} }
var success = TryGetPassword(status, passwordInfo); for (int attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
{
var success = TryGetPassword(PasswordRequestPurpose.Settings, out var password);
if (!success) if (success)
{
passwordParams.Password = password;
passwordParams.IsHash = false;
}
else
{ {
return OperationResult.Aborted; return OperationResult.Aborted;
} }
adminAttempts += status == LoadStatus.AdminPasswordNeeded ? 1 : 0; status = configuration.TryLoadSettings(uri, passwordParams, out encryption, out format, out settings);
settingsAttempts += status == LoadStatus.SettingsPasswordNeeded ? 1 : 0;
} }
Context.Next.Settings = settings; Context.Next.Settings = settings;
return HandleLoadResult(uri, settings, status, passwordParams, encryption, format);
}
private OperationResult HandleLoadResult(Uri uri, Settings settings, LoadStatus status, PasswordParameters password, EncryptionParameters encryption, Format format)
{
if (status == LoadStatus.LoadWithBrowser)
{
return HandleBrowserResource(uri);
}
if (status == LoadStatus.Success && settings.ConfigurationMode == ConfigurationMode.ConfigureClient)
{
return HandleClientConfiguration(uri, password, encryption, format);
}
if (status == LoadStatus.Success) if (status == LoadStatus.Success)
{ {
return OperationResult.Success; return OperationResult.Success;
} }
if (status == LoadStatus.SuccessConfigureClient)
{
return HandleClientConfiguration();
}
ShowFailureMessage(status, uri); ShowFailureMessage(status, uri);
return OperationResult.Failed; return OperationResult.Failed;
} }
private OperationResult HandleBrowserResource(Uri uri)
{
Context.Next.Settings.Browser.StartUrl = uri.AbsoluteUri;
logger.Info($"The configuration resource needs authentication or is a webpage, using '{uri}' as startup URL for the browser.");
return OperationResult.Success;
}
private OperationResult HandleClientConfiguration(Uri resource, PasswordParameters password, EncryptionParameters encryption, Format format)
{
var isAppDataFile = Path.GetFullPath(resource.AbsolutePath).Equals(AppDataFile, StringComparison.OrdinalIgnoreCase);
var isProgramDataFile = Path.GetFullPath(resource.AbsolutePath).Equals(ProgramDataFile, StringComparison.OrdinalIgnoreCase);
if (!isAppDataFile && !isProgramDataFile)
{
var isFirstSession = Context.Current == null;
var requiresAuthentication = IsAuthenticationRequiredForClientConfiguration(password);
logger.Info("Starting client configuration...");
if (requiresAuthentication)
{
var result = HandleClientConfigurationAuthentication();
if (result != OperationResult.Success)
{
return result;
}
}
else
{
logger.Info("Authentication is not required.");
}
configuration.ConfigureClientWith(resource, encryption);
if (isFirstSession)
{
var result = HandleClientConfigurationSuccess();
if (result != OperationResult.Success)
{
return result;
}
}
}
return OperationResult.Success;
}
private bool IsAuthenticationRequiredForClientConfiguration(PasswordParameters password)
{
var requiresAuthentication = Context.Current?.Settings.AdminPasswordHash != null;
if (requiresAuthentication)
{
var currentPassword = Context.Current.Settings.AdminPasswordHash;
var nextPassword = Context.Next.Settings.AdminPasswordHash;
var hasSettingsPassword = password.Password != null;
var sameAdminPassword = currentPassword.Equals(nextPassword, StringComparison.OrdinalIgnoreCase);
requiresAuthentication = !sameAdminPassword;
if (requiresAuthentication && hasSettingsPassword)
{
var settingsPassword = password.IsHash ? password.Password : hashAlgorithm.GenerateHashFor(password.Password);
var knowsAdminPassword = currentPassword.Equals(settingsPassword, StringComparison.OrdinalIgnoreCase);
requiresAuthentication = !knowsAdminPassword;
}
}
return requiresAuthentication;
}
private OperationResult HandleClientConfigurationAuthentication()
{
var currentPassword = Context.Current.Settings.AdminPasswordHash;
var isSamePassword = false;
for (int attempts = 0; attempts < 5 && !isSamePassword; attempts++)
{
var success = TryGetPassword(PasswordRequestPurpose.Administrator, out var password);
if (success)
{
isSamePassword = currentPassword.Equals(hashAlgorithm.GenerateHashFor(password), StringComparison.OrdinalIgnoreCase);
}
else
{
logger.Info("Authentication was aborted.");
return OperationResult.Aborted;
}
}
if (isSamePassword)
{
logger.Info("Authentication was successful.");
return OperationResult.Success;
}
else
{
logger.Info("Authentication has failed!");
return OperationResult.Failed;
}
}
private OperationResult HandleClientConfigurationSuccess()
{
var args = new ConfigurationCompletedEventArgs();
ActionRequired?.Invoke(args);
logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} after successful client configuration.");
if (args.AbortStartup)
{
return OperationResult.Aborted;
}
return OperationResult.Success;
}
private void ShowFailureMessage(LoadStatus status, Uri uri) private void ShowFailureMessage(LoadStatus status, Uri uri)
{ {
switch (status) switch (status)
{ {
case LoadStatus.AdminPasswordNeeded: case LoadStatus.PasswordNeeded:
case LoadStatus.SettingsPasswordNeeded:
ActionRequired?.Invoke(new InvalidPasswordMessageArgs()); ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
break; break;
case LoadStatus.InvalidData: case LoadStatus.InvalidData:
@ -159,31 +312,20 @@ namespace SafeExamBrowser.Runtime.Operations
} }
} }
private bool TryGetPassword(LoadStatus status, PasswordInfo passwordInfo) private bool TryGetPassword(PasswordRequestPurpose purpose, out string password)
{ {
var purpose = status == LoadStatus.AdminPasswordNeeded ? PasswordRequestPurpose.Administrator : PasswordRequestPurpose.Settings;
var args = new PasswordRequiredEventArgs { Purpose = purpose }; var args = new PasswordRequiredEventArgs { Purpose = purpose };
ActionRequired?.Invoke(args); ActionRequired?.Invoke(args);
password = args.Password;
if (purpose == PasswordRequestPurpose.Administrator)
{
passwordInfo.AdminPassword = args.Password;
}
else
{
passwordInfo.SettingsPassword = args.Password;
}
return args.Success; return args.Success;
} }
private bool TryInitializeSettingsUri(out Uri uri) private bool TryInitializeSettingsUri(out Uri uri)
{ {
var path = string.Empty; var path = default(string);
var isValidUri = false; var isValidUri = false;
var programDataSettings = Path.Combine(Context.Next.AppConfig.ProgramDataFolder, Context.Next.AppConfig.DefaultSettingsFileName);
var appDataSettings = Path.Combine(Context.Next.AppConfig.AppDataFolder, Context.Next.AppConfig.DefaultSettingsFileName);
uri = null; uri = null;
@ -194,16 +336,16 @@ namespace SafeExamBrowser.Runtime.Operations
logger.Info($"Found command-line argument for configuration resource: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}."); logger.Info($"Found command-line argument for configuration resource: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}.");
} }
if (!isValidUri && File.Exists(programDataSettings)) if (!isValidUri && File.Exists(ProgramDataFile))
{ {
path = programDataSettings; path = ProgramDataFile;
isValidUri = Uri.TryCreate(path, UriKind.Absolute, out uri); isValidUri = Uri.TryCreate(path, UriKind.Absolute, out uri);
logger.Info($"Found configuration file in PROGRAMDATA: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}."); logger.Info($"Found configuration file in PROGRAMDATA: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}.");
} }
if (!isValidUri && File.Exists(appDataSettings)) if (!isValidUri && File.Exists(AppDataFile))
{ {
path = appDataSettings; path = AppDataFile;
isValidUri = Uri.TryCreate(path, UriKind.Absolute, out uri); isValidUri = Uri.TryCreate(path, UriKind.Absolute, out uri);
logger.Info($"Found configuration file in APPDATA: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}."); logger.Info($"Found configuration file in APPDATA: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}.");
} }
@ -221,32 +363,6 @@ namespace SafeExamBrowser.Runtime.Operations
return isValidUri; return isValidUri;
} }
private OperationResult HandleClientConfiguration()
{
var firstSession = Context.Current == null;
if (firstSession)
{
var args = new ConfigurationCompletedEventArgs();
ActionRequired?.Invoke(args);
logger.Info($"The user chose to {(args.AbortStartup ? "abort" : "continue")} after successful client configuration.");
if (args.AbortStartup)
{
return OperationResult.Aborted;
}
}
else
{
// TODO: If the client configuration happens while the application is already running, the new configuration should first
// be loaded and then the user should have the option to terminate!
// -> Introduce flag in Context, e.g. AskForTermination?
}
return OperationResult.Success;
}
private void LogOperationResult(OperationResult result) private void LogOperationResult(OperationResult result)
{ {
switch (result) switch (result)

View file

@ -398,6 +398,8 @@ namespace SafeExamBrowser.Runtime
private void ShowMessageBox(MessageEventArgs args) private void ShowMessageBox(MessageEventArgs args)
{ {
var isStartup = !SessionIsRunning;
var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.KioskMode == KioskMode.DisableExplorerShell;
var message = text.Get(args.Message); var message = text.Get(args.Message);
var title = text.Get(args.Title); var title = text.Get(args.Title);
@ -411,8 +413,15 @@ namespace SafeExamBrowser.Runtime
title = title.Replace(placeholder.Key, placeholder.Value); title = title.Replace(placeholder.Key, placeholder.Value);
} }
if (isStartup || isRunningOnDefaultDesktop)
{
messageBox.Show(message, title, MessageBoxAction.Confirm, args.Icon, runtimeWindow); messageBox.Show(message, title, MessageBoxAction.Confirm, args.Icon, runtimeWindow);
} }
else
{
// TODO
}
}
private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args) private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args)
{ {