SEBWIN-221: Extended loading algorithm with functionality required to save data as local client configuration.
This commit is contained in:
parent
f9f2bb3257
commit
75f5994a3b
29 changed files with 572 additions and 280 deletions
|
@ -11,6 +11,7 @@ using System.Reflection;
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.UnitTests
|
||||
|
|
|
@ -10,8 +10,12 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using SafeExamBrowser.Configuration.DataFormats;
|
||||
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.Logging;
|
||||
|
||||
|
@ -29,7 +33,7 @@ namespace SafeExamBrowser.Configuration
|
|||
private AppConfig appConfig;
|
||||
private IHashAlgorithm hashAlgorithm;
|
||||
private IList<IDataFormat> dataFormats;
|
||||
private IList<IResourceLoader> resourceLoaders;
|
||||
private IList<IDataResource> dataResources;
|
||||
private ILogger logger;
|
||||
|
||||
public ConfigurationRepository(
|
||||
|
@ -41,7 +45,7 @@ namespace SafeExamBrowser.Configuration
|
|||
string programVersion)
|
||||
{
|
||||
dataFormats = new List<IDataFormat>();
|
||||
resourceLoaders = new List<IResourceLoader>();
|
||||
dataResources = new List<IDataResource>();
|
||||
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.logger = logger;
|
||||
|
@ -51,6 +55,35 @@ namespace SafeExamBrowser.Configuration
|
|||
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()
|
||||
{
|
||||
var appDataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), nameof(SafeExamBrowser));
|
||||
|
@ -128,34 +161,38 @@ namespace SafeExamBrowser.Configuration
|
|||
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}'...");
|
||||
|
||||
encryption = default(EncryptionParameters);
|
||||
format = default(Format);
|
||||
settings = LoadDefaultSettings();
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
@ -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;
|
||||
logger.Info($"The resource needs authentication or is HTML data, loaded default settings with '{resource}' as startup URL.");
|
||||
const int IDENTITY_CERTIFICATE = 1;
|
||||
var hasCertificates = data.TryGetValue("embeddedCertificates", out object value);
|
||||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
private void HandleParseSuccess(ParseResult result, Settings settings, PasswordInfo passwordInfo, Uri resource)
|
||||
{
|
||||
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...");
|
||||
result.RawData.MapTo(settings);
|
||||
|
||||
if (settings.ConfigurationMode == ConfigurationMode.ConfigureClient && !isAppDataFile && !isProgramDataFile)
|
||||
if (hasCertificates && value is IList<IDictionary<string, object>> certificates)
|
||||
{
|
||||
result.Status = TryConfigureClient(result.RawData, settings, passwordInfo);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
toRemove.ForEach(c => certificates.Remove(c));
|
||||
}
|
||||
}
|
||||
|
||||
private void ImportIdentityCertificate(byte[] certificateData, X509Store store)
|
||||
{
|
||||
try
|
||||
{
|
||||
var certificate = new X509Certificate2();
|
||||
|
||||
certificate.Import(certificateData, "Di𝈭l𝈖Ch𝈒aht𝈁aHai1972", 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)
|
||||
{
|
||||
var status = LoadStatus.NotSupported;
|
||||
var resourceLoader = resourceLoaders.FirstOrDefault(l => l.CanLoad(resource));
|
||||
var resourceLoader = dataResources.FirstOrDefault(l => l.CanLoad(resource));
|
||||
|
||||
data = default(Stream);
|
||||
|
||||
|
@ -215,23 +278,25 @@ namespace SafeExamBrowser.Configuration
|
|||
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 status = LoadStatus.NotSupported;
|
||||
|
||||
encryption = default(EncryptionParameters);
|
||||
format = default(Format);
|
||||
rawData = default(Dictionary<string, object>);
|
||||
|
||||
if (dataFormat != null)
|
||||
{
|
||||
var result = dataFormat.TryParse(data, passwordInfo);
|
||||
|
||||
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);
|
||||
}
|
||||
var result = dataFormat.TryParse(data, password);
|
||||
|
||||
encryption = result.Encryption;
|
||||
format = result.Format;
|
||||
rawData = result.RawData;
|
||||
status = result.Status;
|
||||
|
||||
logger.Info($"Tried to parse data from '{data}' using {dataFormat.GetType().Name} -> Result: {status}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -241,44 +306,6 @@ namespace SafeExamBrowser.Configuration
|
|||
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()
|
||||
{
|
||||
appConfig.ClientId = Guid.NewGuid();
|
||||
|
|
|
@ -10,9 +10,9 @@ using System;
|
|||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Cryptography;
|
||||
|
||||
namespace SafeExamBrowser.Configuration
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
public class HashAlgorithm : IHashAlgorithm
|
||||
{
|
|
@ -12,7 +12,7 @@ using System.Security.Cryptography;
|
|||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
internal class PasswordEncryption
|
||||
{
|
||||
|
@ -37,7 +37,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
|
||||
if (password == null)
|
||||
{
|
||||
return LoadStatus.SettingsPasswordNeeded;
|
||||
return LoadStatus.PasswordNeeded;
|
||||
}
|
||||
|
||||
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!");
|
||||
|
||||
return LoadStatus.SettingsPasswordNeeded;
|
||||
return LoadStatus.PasswordNeeded;
|
||||
}
|
||||
|
||||
private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength)
|
|
@ -13,7 +13,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
internal class PublicKeyHashEncryption
|
||||
{
|
||||
|
@ -26,10 +26,10 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
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 found = TryGetCertificateWith(keyHash, out X509Certificate2 certificate);
|
||||
var found = TryGetCertificateWith(keyHash, out certificate);
|
||||
|
||||
decrypted = default(Stream);
|
||||
|
|
@ -12,7 +12,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||
namespace SafeExamBrowser.Configuration.Cryptography
|
||||
{
|
||||
internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption
|
||||
{
|
||||
|
@ -25,10 +25,10 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
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 found = TryGetCertificateWith(keyHash, out X509Certificate2 certificate);
|
||||
var found = TryGetCertificateWith(keyHash, out certificate);
|
||||
|
||||
decrypted = default(Stream);
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataCompression;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.Compression
|
||||
namespace SafeExamBrowser.Configuration.DataCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Data compression using the GNU-Zip format (see https://en.wikipedia.org/wiki/Gzip).
|
|
@ -8,9 +8,13 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Text;
|
||||
using SafeExamBrowser.Configuration.DataFormats.Cryptography;
|
||||
using SafeExamBrowser.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataCompression;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
|
@ -56,7 +60,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
return false;
|
||||
}
|
||||
|
||||
public ParseResult TryParse(Stream data, PasswordInfo passwordInfo)
|
||||
public ParseResult TryParse(Stream data, PasswordParameters password)
|
||||
{
|
||||
var prefix = ParsePrefix(data);
|
||||
var success = TryDetermineFormat(prefix, out FormatType format);
|
||||
|
@ -75,13 +79,13 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
{
|
||||
case FormatType.Password:
|
||||
case FormatType.PasswordConfigureClient:
|
||||
return ParsePasswordBlock(data, passwordInfo, format);
|
||||
return ParsePasswordBlock(data, format, password);
|
||||
case FormatType.PlainData:
|
||||
return ParsePlainDataBlock(data, passwordInfo);
|
||||
return ParsePlainDataBlock(data);
|
||||
case FormatType.PublicKeyHash:
|
||||
return ParsePublicKeyHashBlock(data, passwordInfo);
|
||||
return ParsePublicKeyHashBlock(data, password);
|
||||
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 };
|
||||
}
|
||||
|
||||
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 status = default(LoadStatus);
|
||||
var encryptionParams = new PasswordParameters();
|
||||
var result = new ParseResult();
|
||||
|
||||
if (format == FormatType.PasswordConfigureClient)
|
||||
{
|
||||
status = encryption.Decrypt(data, string.Empty, out decrypted);
|
||||
|
||||
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);
|
||||
}
|
||||
encryptionParams.Password = password.IsHash ? password.Password : hashAlgorithm.GenerateHashFor(password.Password);
|
||||
encryptionParams.IsHash = true;
|
||||
}
|
||||
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)));
|
||||
|
||||
|
@ -132,35 +131,50 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
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 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 encryption = new PublicKeyHashWithSymmetricKeyEncryption(logger, passwordEncryption);
|
||||
var status = encryption.Decrypt(data, out Stream decrypted);
|
||||
var encryption = new PublicKeyHashWithSymmetricKeyEncryption(logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption)), passwordEncryption);
|
||||
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)
|
||||
|
|
|
@ -13,6 +13,8 @@ using System.Text;
|
|||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.DataFormats
|
||||
|
@ -58,7 +60,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
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 xmlSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore };
|
||||
|
@ -218,6 +220,11 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
{
|
||||
value = null;
|
||||
|
||||
if (element.IsEmpty)
|
||||
{
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
switch (element.Name.LocalName)
|
||||
{
|
||||
case DataTypes.DATA:
|
||||
|
|
|
@ -9,15 +9,16 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataResources;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ResourceLoaders
|
||||
namespace SafeExamBrowser.Configuration.DataResources
|
||||
{
|
||||
public class FileResourceLoader : IResourceLoader
|
||||
public class FileResource: IDataResource
|
||||
{
|
||||
private ILogger logger;
|
||||
|
||||
public FileResourceLoader(ILogger logger)
|
||||
public FileResource(ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
@ -46,5 +47,10 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
|
|||
|
||||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
public SaveStatus TrySave(Uri resource, Stream data)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,11 +14,12 @@ using System.Net.Http;
|
|||
using System.Net.Mime;
|
||||
using System.Threading.Tasks;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataResources;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
namespace SafeExamBrowser.Configuration.ResourceLoaders
|
||||
namespace SafeExamBrowser.Configuration.DataResources
|
||||
{
|
||||
public class NetworkResourceLoader : IResourceLoader
|
||||
public class NetworkResource : IDataResource
|
||||
{
|
||||
private AppConfig appConfig;
|
||||
private ILogger logger;
|
||||
|
@ -40,7 +41,7 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
|
|||
Uri.UriSchemeHttps
|
||||
};
|
||||
|
||||
public NetworkResourceLoader(AppConfig appConfig, ILogger logger)
|
||||
public NetworkResource(AppConfig appConfig, ILogger logger)
|
||||
{
|
||||
this.appConfig = appConfig;
|
||||
this.logger = logger;
|
||||
|
@ -85,6 +86,11 @@ namespace SafeExamBrowser.Configuration.ResourceLoaders
|
|||
return LoadStatus.Success;
|
||||
}
|
||||
|
||||
public SaveStatus TrySave(Uri resource, Stream data)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Uri BuildUriFor(Uri resource)
|
||||
{
|
||||
var scheme = GetSchemeFor(resource);
|
|
@ -58,18 +58,18 @@
|
|||
<Reference Include="System.Xml.Linq" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Compression\GZipCompressor.cs" />
|
||||
<Compile Include="DataCompression\GZipCompressor.cs" />
|
||||
<Compile Include="DataFormats\BinaryFormat.cs" />
|
||||
<Compile Include="DataFormats\Cryptography\PasswordEncryption.cs" />
|
||||
<Compile Include="DataFormats\Cryptography\PublicKeyHashEncryption.cs" />
|
||||
<Compile Include="DataFormats\Cryptography\PublicKeyHashWithSymmetricKeyEncryption.cs" />
|
||||
<Compile Include="Cryptography\PasswordEncryption.cs" />
|
||||
<Compile Include="Cryptography\PublicKeyHashEncryption.cs" />
|
||||
<Compile Include="Cryptography\PublicKeyHashWithSymmetricKeyEncryption.cs" />
|
||||
<Compile Include="DataFormats\XmlFormat.cs" />
|
||||
<Compile Include="DataFormats\DataMapper.cs" />
|
||||
<Compile Include="HashAlgorithm.cs" />
|
||||
<Compile Include="Cryptography\HashAlgorithm.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ConfigurationRepository.cs" />
|
||||
<Compile Include="ResourceLoaders\FileResourceLoader.cs" />
|
||||
<Compile Include="ResourceLoaders\NetworkResourceLoader.cs" />
|
||||
<Compile Include="DataResources\FileResource.cs" />
|
||||
<Compile Include="DataResources\NetworkResource.cs" />
|
||||
<Compile Include="SessionConfiguration.cs" />
|
||||
<Compile Include="SubStream.cs" />
|
||||
<Compile Include="SystemInfo.cs" />
|
||||
|
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
namespace SafeExamBrowser.Contracts.Configuration.Cryptography
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functionality to calculate hash codes of different objects.
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
using System.IO;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
namespace SafeExamBrowser.Contracts.Configuration.DataCompression
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the functionality for data compression and decompression.
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -7,8 +7,9 @@
|
|||
*/
|
||||
|
||||
using System.IO;
|
||||
using SafeExamBrowser.Contracts.Configuration.Cryptography;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
namespace SafeExamBrowser.Contracts.Configuration.DataFormats
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functionality to parse configuration data with a particular format.
|
||||
|
@ -23,6 +24,6 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// <summary>
|
||||
/// Tries to parse the given data.
|
||||
/// </summary>
|
||||
ParseResult TryParse(Stream data, PasswordInfo passwordInfo);
|
||||
ParseResult TryParse(Stream data, PasswordParameters password = null);
|
||||
}
|
||||
}
|
|
@ -7,14 +7,25 @@
|
|||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Contracts.Configuration.Cryptography;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
namespace SafeExamBrowser.Contracts.Configuration.DataFormats
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the result of a data parsing operation by an <see cref="IDataFormat"/>.
|
||||
/// </summary>
|
||||
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>
|
||||
/// The parsed settings data. Might be <c>null</c> or in an undefinable state, depending on <see cref="Status"/>.
|
||||
/// </summary>
|
|
@ -9,15 +9,15 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
namespace SafeExamBrowser.Contracts.Configuration.DataResources
|
||||
{
|
||||
/// <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>
|
||||
public interface IResourceLoader
|
||||
public interface IDataResource
|
||||
{
|
||||
/// <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>
|
||||
bool CanLoad(Uri resource);
|
||||
|
||||
|
@ -25,5 +25,10 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// Tries to load the configuration data from the specified resource.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,9 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using SafeExamBrowser.Contracts.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataResources;
|
||||
|
||||
namespace SafeExamBrowser.Contracts.Configuration
|
||||
{
|
||||
|
@ -15,6 +18,11 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// </summary>
|
||||
public interface IConfigurationRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves the given resource as local client configuration.
|
||||
/// </summary>
|
||||
void ConfigureClientWith(Uri resource, EncryptionParameters encryption = null);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the global configuration information for the currently running application instance.
|
||||
/// </summary>
|
||||
|
@ -31,19 +39,23 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
Settings.Settings LoadDefaultSettings();
|
||||
|
||||
/// <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>
|
||||
void Register(IDataFormat dataFormat);
|
||||
|
||||
/// <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>
|
||||
void Register(IResourceLoader resourceLoader);
|
||||
void Register(IDataResource dataResource);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to load settings from the specified resource. As long as the result is not <see cref="LoadStatus.Success"/>,
|
||||
/// the referenced settings may be <c>null</c> or in an undefinable state!
|
||||
/// Attempts to load settings from the specified resource.
|
||||
/// </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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,11 +13,6 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
/// </summary>
|
||||
public enum LoadStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the current administrator password is needed to be allowed to configure the local client.
|
||||
/// </summary>
|
||||
AdminPasswordNeeded = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a resource contains invalid data.
|
||||
/// </summary>
|
||||
|
@ -34,20 +29,15 @@ namespace SafeExamBrowser.Contracts.Configuration
|
|||
NotSupported,
|
||||
|
||||
/// <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>
|
||||
SettingsPasswordNeeded,
|
||||
PasswordNeeded,
|
||||
|
||||
/// <summary>
|
||||
/// The settings were loaded successfully.
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// The settings were loaded and the local client configuration was performed successfully.
|
||||
/// </summary>
|
||||
SuccessConfigureClient,
|
||||
|
||||
/// <summary>
|
||||
/// An unexpected error occurred while trying to load the settings.
|
||||
/// </summary>
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
18
SafeExamBrowser.Contracts/Configuration/SaveStatus.cs
Normal file
18
SafeExamBrowser.Contracts/Configuration/SaveStatus.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -53,11 +53,15 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Communication\Events\ClientConfigurationEventArgs.cs" />
|
||||
<Compile Include="Configuration\IDataCompressor.cs" />
|
||||
<Compile Include="Configuration\IDataFormat.cs" />
|
||||
<Compile Include="Configuration\IHashAlgorithm.cs" />
|
||||
<Compile Include="Configuration\ParseResult.cs" />
|
||||
<Compile Include="Configuration\PasswordInfo.cs" />
|
||||
<Compile Include="Configuration\Cryptography\EncryptionParameters.cs" />
|
||||
<Compile Include="Configuration\Cryptography\PasswordParameters.cs" />
|
||||
<Compile Include="Configuration\Cryptography\PublicKeyHashParameters.cs" />
|
||||
<Compile Include="Configuration\DataFormats\Format.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\NameChangedEventHandler.cs" />
|
||||
<Compile Include="Core\IApplicationController.cs" />
|
||||
|
@ -112,7 +116,7 @@
|
|||
<Compile Include="Communication\Data\SimpleResponsePurport.cs" />
|
||||
<Compile Include="Communication\Data\SimpleResponse.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\AppConfig.cs" />
|
||||
<Compile Include="Configuration\ISessionConfiguration.cs" />
|
||||
|
|
|
@ -13,9 +13,10 @@ using System.Reflection;
|
|||
using SafeExamBrowser.Communication.Hosts;
|
||||
using SafeExamBrowser.Communication.Proxies;
|
||||
using SafeExamBrowser.Configuration;
|
||||
using SafeExamBrowser.Configuration.Compression;
|
||||
using SafeExamBrowser.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Configuration.DataCompression;
|
||||
using SafeExamBrowser.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Configuration.ResourceLoaders;
|
||||
using SafeExamBrowser.Configuration.DataResources;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Core;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
|
@ -75,7 +76,7 @@ namespace SafeExamBrowser.Runtime
|
|||
bootstrapOperations.Enqueue(new CommunicationHostOperation(runtimeHost, logger));
|
||||
|
||||
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 KioskModeTerminationOperation(desktopFactory, explorerShell, logger, processFactory, 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 XmlFormat(new ModuleLogger(logger, nameof(XmlFormat))));
|
||||
configuration.Register(new FileResourceLoader(new ModuleLogger(logger, nameof(FileResourceLoader))));
|
||||
configuration.Register(new NetworkResourceLoader(appConfig, new ModuleLogger(logger, nameof(NetworkResourceLoader))));
|
||||
configuration.Register(new FileResource(new ModuleLogger(logger, nameof(FileResource))));
|
||||
configuration.Register(new NetworkResource(appConfig, new ModuleLogger(logger, nameof(NetworkResource))));
|
||||
}
|
||||
|
||||
private void InitializeLogging()
|
||||
|
|
|
@ -10,6 +10,8 @@ using System;
|
|||
using System.IO;
|
||||
using SafeExamBrowser.Contracts.Communication.Data;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Configuration.Cryptography;
|
||||
using SafeExamBrowser.Contracts.Configuration.DataFormats;
|
||||
using SafeExamBrowser.Contracts.Configuration.Settings;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel;
|
||||
using SafeExamBrowser.Contracts.Core.OperationModel.Events;
|
||||
|
@ -23,19 +25,32 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
{
|
||||
private string[] commandLineArgs;
|
||||
private IConfigurationRepository configuration;
|
||||
private IHashAlgorithm hashAlgorithm;
|
||||
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 StatusChangedEventHandler StatusChanged;
|
||||
|
||||
public ConfigurationOperation(
|
||||
string[] commandLineArgs,
|
||||
IConfigurationRepository configuration,
|
||||
IHashAlgorithm hashAlgorithm,
|
||||
ILogger logger,
|
||||
SessionContext sessionContext) : base(sessionContext)
|
||||
{
|
||||
this.commandLineArgs = commandLineArgs;
|
||||
this.configuration = configuration;
|
||||
this.hashAlgorithm = hashAlgorithm;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
@ -98,53 +113,191 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
|
||||
private OperationResult LoadSettings(Uri uri)
|
||||
{
|
||||
var settings = default(Settings);
|
||||
var status = default(LoadStatus);
|
||||
var passwordInfo = new PasswordInfo { AdminPasswordHash = Context.Current?.Settings?.AdminPasswordHash };
|
||||
var passwordParams = new PasswordParameters { Password = string.Empty, IsHash = true };
|
||||
var status = configuration.TryLoadSettings(uri, passwordParams, out var encryption, out var format, out var settings);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
for (int attempts = 0; attempts < 5 && status == LoadStatus.PasswordNeeded; attempts++)
|
||||
{
|
||||
var success = TryGetPassword(PasswordRequestPurpose.Settings, out var password);
|
||||
|
||||
if (success)
|
||||
{
|
||||
break;
|
||||
passwordParams.Password = password;
|
||||
passwordParams.IsHash = false;
|
||||
}
|
||||
|
||||
var success = TryGetPassword(status, passwordInfo);
|
||||
|
||||
if (!success)
|
||||
else
|
||||
{
|
||||
return OperationResult.Aborted;
|
||||
}
|
||||
|
||||
adminAttempts += status == LoadStatus.AdminPasswordNeeded ? 1 : 0;
|
||||
settingsAttempts += status == LoadStatus.SettingsPasswordNeeded ? 1 : 0;
|
||||
status = configuration.TryLoadSettings(uri, passwordParams, out encryption, out format, out 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)
|
||||
{
|
||||
return OperationResult.Success;
|
||||
}
|
||||
|
||||
if (status == LoadStatus.SuccessConfigureClient)
|
||||
{
|
||||
return HandleClientConfiguration();
|
||||
}
|
||||
|
||||
ShowFailureMessage(status, uri);
|
||||
|
||||
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)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case LoadStatus.AdminPasswordNeeded:
|
||||
case LoadStatus.SettingsPasswordNeeded:
|
||||
case LoadStatus.PasswordNeeded:
|
||||
ActionRequired?.Invoke(new InvalidPasswordMessageArgs());
|
||||
break;
|
||||
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 };
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
if (purpose == PasswordRequestPurpose.Administrator)
|
||||
{
|
||||
passwordInfo.AdminPassword = args.Password;
|
||||
}
|
||||
else
|
||||
{
|
||||
passwordInfo.SettingsPassword = args.Password;
|
||||
}
|
||||
password = args.Password;
|
||||
|
||||
return args.Success;
|
||||
}
|
||||
|
||||
private bool TryInitializeSettingsUri(out Uri uri)
|
||||
{
|
||||
var path = string.Empty;
|
||||
var path = default(string);
|
||||
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;
|
||||
|
||||
|
@ -194,16 +336,16 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
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);
|
||||
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);
|
||||
logger.Info($"Found configuration file in APPDATA: '{path}', the URI is {(isValidUri ? "valid" : "invalid")}.");
|
||||
}
|
||||
|
@ -221,32 +363,6 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
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)
|
||||
{
|
||||
switch (result)
|
||||
|
|
|
@ -398,6 +398,8 @@ namespace SafeExamBrowser.Runtime
|
|||
|
||||
private void ShowMessageBox(MessageEventArgs args)
|
||||
{
|
||||
var isStartup = !SessionIsRunning;
|
||||
var isRunningOnDefaultDesktop = SessionIsRunning && Session.Settings.KioskMode == KioskMode.DisableExplorerShell;
|
||||
var message = text.Get(args.Message);
|
||||
var title = text.Get(args.Title);
|
||||
|
||||
|
@ -411,7 +413,14 @@ namespace SafeExamBrowser.Runtime
|
|||
title = title.Replace(placeholder.Key, placeholder.Value);
|
||||
}
|
||||
|
||||
messageBox.Show(message, title, MessageBoxAction.Confirm, args.Icon, runtimeWindow);
|
||||
if (isStartup || isRunningOnDefaultDesktop)
|
||||
{
|
||||
messageBox.Show(message, title, MessageBoxAction.Confirm, args.Icon, runtimeWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
private void TryGetPasswordViaDialog(PasswordRequiredEventArgs args)
|
||||
|
|
Loading…
Reference in a new issue