SEBWIN-296: Decoupled cryptographic implementations from data formats to be able to unit test the latter.

This commit is contained in:
dbuechel 2019-02-19 15:54:11 +01:00
parent f817f31f2d
commit cf7d6c4d3e
20 changed files with 392 additions and 81 deletions

View file

@ -21,13 +21,13 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.UnitTests.Cryptography namespace SafeExamBrowser.Configuration.UnitTests.Cryptography
{ {
[TestClass] [TestClass]
public class PublicKeyHashEncryptionTests public class PublicKeyEncryptionTests
{ {
private Mock<ILogger> logger; private Mock<ILogger> logger;
private Mock<ICertificateStore> store; private Mock<ICertificateStore> store;
private X509Certificate2 certificate; private X509Certificate2 certificate;
private PublicKeyHashEncryption sut; private PublicKeyEncryption sut;
[TestInitialize] [TestInitialize]
public void Initialize() public void Initialize()
@ -38,7 +38,7 @@ namespace SafeExamBrowser.Configuration.UnitTests.Cryptography
LoadCertificate(); LoadCertificate();
store.Setup(s => s.TryGetCertificateWith(It.IsAny<byte[]>(), out certificate)).Returns(true); store.Setup(s => s.TryGetCertificateWith(It.IsAny<byte[]>(), out certificate)).Returns(true);
sut = new PublicKeyHashEncryption(store.Object, logger.Object); sut = new PublicKeyEncryption(store.Object, logger.Object);
} }
[TestMethod] [TestMethod]

View file

@ -21,13 +21,13 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.UnitTests.Cryptography namespace SafeExamBrowser.Configuration.UnitTests.Cryptography
{ {
[TestClass] [TestClass]
public class PublicKeyHashWithSymmetricKeyEncryptionTests public class PublicKeySymmetricEncryptionTests
{ {
private Mock<ILogger> logger; private Mock<ILogger> logger;
private PasswordEncryption passwordEncryption; private PasswordEncryption passwordEncryption;
private Mock<ICertificateStore> store; private Mock<ICertificateStore> store;
private PublicKeyHashWithSymmetricKeyEncryption sut; private PublicKeySymmetricEncryption sut;
private X509Certificate2 certificate; private X509Certificate2 certificate;
[TestInitialize] [TestInitialize]
@ -40,7 +40,7 @@ namespace SafeExamBrowser.Configuration.UnitTests.Cryptography
LoadCertificate(); LoadCertificate();
store.Setup(s => s.TryGetCertificateWith(It.IsAny<byte[]>(), out certificate)).Returns(true); store.Setup(s => s.TryGetCertificateWith(It.IsAny<byte[]>(), out certificate)).Returns(true);
sut = new PublicKeyHashWithSymmetricKeyEncryption(store.Object, logger.Object, passwordEncryption); sut = new PublicKeySymmetricEncryption(store.Object, logger.Object, passwordEncryption);
} }
[TestMethod] [TestMethod]

View file

@ -0,0 +1,168 @@
/*
* Copyright (c) 2019 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.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using SafeExamBrowser.Configuration.DataFormats;
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.UnitTests.DataFormats
{
[TestClass]
public class BinaryParserTests
{
private Mock<IDataCompressor> compressor;
private Mock<IHashAlgorithm> hashAlgorithm;
private Mock<ILogger> logger;
private Mock<IPasswordEncryption> passwordEncryption;
private Mock<IPublicKeyEncryption> publicKeyEncryption;
private Mock<IPublicKeyEncryption> symmetricEncryption;
private Mock<IDataParser> xmlParser;
private BinaryParser sut;
[TestInitialize]
public void Initialize()
{
compressor = new Mock<IDataCompressor>();
hashAlgorithm = new Mock<IHashAlgorithm>();
logger = new Mock<ILogger>();
passwordEncryption = new Mock<IPasswordEncryption>();
publicKeyEncryption = new Mock<IPublicKeyEncryption>();
symmetricEncryption = new Mock<IPublicKeyEncryption>();
xmlParser = new Mock<IDataParser>();
xmlParser.Setup(p => p.TryParse(It.IsAny<Stream>(), It.IsAny<PasswordParameters>())).Returns(new ParseResult { Status = LoadStatus.Success });
sut = new BinaryParser(compressor.Object, hashAlgorithm.Object, logger.Object, passwordEncryption.Object, publicKeyEncryption.Object, symmetricEncryption.Object, xmlParser.Object);
}
[TestMethod]
public void MustCorrectlyDetectValidPrefixes()
{
var data = new byte[123];
var pswd = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.Password).Concat(data).ToArray());
var pwcc = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PasswordConfigureClient).Concat(data).ToArray());
var plnd = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PlainData).Concat(data).ToArray());
var pkhs = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PublicKey).Concat(data).ToArray());
var phsk = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PublicKeySymmetric).Concat(data).ToArray());
Assert.IsFalse(sut.CanParse(null));
Assert.IsFalse(sut.CanParse(new MemoryStream()));
Assert.IsFalse(sut.CanParse(new MemoryStream(data)));
Assert.IsTrue(sut.CanParse(pswd));
Assert.IsTrue(sut.CanParse(pwcc));
Assert.IsTrue(sut.CanParse(plnd));
Assert.IsTrue(sut.CanParse(pkhs));
Assert.IsTrue(sut.CanParse(phsk));
}
[TestMethod]
public void MustCorrectlyParsePasswordBlock()
{
var data = new byte[123];
var decrypted = default(Stream);
var pswd = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.Password).Concat(data).ToArray()) as Stream;
passwordEncryption.Setup(p => p.Decrypt(It.IsAny<Stream>(), It.Is<string>(s => s == "wrong"), out decrypted)).Returns(LoadStatus.PasswordNeeded);
passwordEncryption.Setup(p => p.Decrypt(It.IsAny<Stream>(), It.Is<string>(s => s == "correct"), out decrypted)).Returns(LoadStatus.Success);
var result = sut.TryParse(pswd);
Assert.AreEqual(LoadStatus.PasswordNeeded, result.Status);
result = sut.TryParse(pswd, new PasswordParameters { Password = "wrong" });
Assert.AreEqual(LoadStatus.PasswordNeeded, result.Status);
result = sut.TryParse(pswd, new PasswordParameters { Password = "correct" });
passwordEncryption.Verify(p => p.Decrypt(It.IsAny<Stream>(), It.IsAny<string>(), out decrypted), Times.AtLeastOnce);
xmlParser.Verify(p => p.TryParse(It.Is<Stream>(s => s == decrypted), It.IsAny<PasswordParameters>()), Times.Once);
publicKeyEncryption.VerifyNoOtherCalls();
symmetricEncryption.VerifyNoOtherCalls();
}
[TestMethod]
public void MustCorrectlyParsePlainDataBlock()
{
var data = new byte[123];
var plnd = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PlainData).Concat(data).ToArray());
compressor.Setup(c => c.Decompress(It.IsAny<Stream>())).Returns(plnd);
compressor.Setup(c => c.Peek(It.IsAny<Stream>(), It.IsAny<int>())).Returns(Encoding.UTF8.GetBytes(BinaryBlock.PlainData));
compressor.Setup(c => c.IsCompressed(It.IsAny<Stream>())).Returns(true);
var result = sut.TryParse(plnd);
compressor.Verify(c => c.IsCompressed(It.IsAny<Stream>()), Times.AtLeastOnce);
compressor.Verify(c => c.Decompress(It.IsAny<Stream>()), Times.AtLeastOnce);
xmlParser.Verify(x => x.TryParse(It.IsAny<Stream>(), It.IsAny<PasswordParameters>()), Times.Once);
passwordEncryption.VerifyNoOtherCalls();
publicKeyEncryption.VerifyNoOtherCalls();
symmetricEncryption.VerifyNoOtherCalls();
Assert.AreEqual(LoadStatus.Success, result.Status);
}
[TestMethod]
public void MustCorrectlyParsePublicKeyBlock()
{
var data = new byte[123];
var certificate = default(X509Certificate2);
var decrypted = default(Stream);
var pswd = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.Password).Concat(data).ToArray()) as Stream;
var pkhs = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PublicKey).Concat(data).ToArray());
passwordEncryption.Setup(p => p.Decrypt(It.IsAny<Stream>(), It.IsAny<string>(), out decrypted)).Returns(LoadStatus.Success);
publicKeyEncryption.Setup(p => p.Decrypt(It.IsAny<Stream>(), out pswd, out certificate)).Returns(LoadStatus.Success);
var result = sut.TryParse(pkhs, new PasswordParameters { Password = "blubb" });
publicKeyEncryption.Verify(p => p.Decrypt(It.IsAny<Stream>(), out decrypted, out certificate), Times.Once);
passwordEncryption.Verify(p => p.Decrypt(It.IsAny<Stream>(), It.Is<string>(s => s == "blubb"), out decrypted), Times.Once);
symmetricEncryption.VerifyNoOtherCalls();
Assert.AreEqual(LoadStatus.Success, result.Status);
}
[TestMethod]
public void MustOnlyParseIfFormatSupported()
{
var data = new byte[123];
var certificate = default(X509Certificate2);
var decrypted = default(Stream);
var pswd = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.Password).Concat(data).ToArray()) as Stream;
var pwcc = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PasswordConfigureClient).Concat(data).ToArray());
var plnd = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PlainData).Concat(data).ToArray());
var pkhs = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PublicKey).Concat(data).ToArray());
var phsk = new MemoryStream(Encoding.UTF8.GetBytes(BinaryBlock.PublicKeySymmetric).Concat(data).ToArray());
passwordEncryption.Setup(p => p.Decrypt(It.IsAny<Stream>(), It.IsAny<string>(), out decrypted)).Returns(LoadStatus.Success);
publicKeyEncryption.Setup(p => p.Decrypt(It.IsAny<Stream>(), out pswd, out certificate)).Returns(LoadStatus.Success);
symmetricEncryption.Setup(p => p.Decrypt(It.IsAny<Stream>(), out pswd, out certificate)).Returns(LoadStatus.Success);
Assert.AreEqual(LoadStatus.InvalidData, sut.TryParse(new MemoryStream(data)).Status);
Assert.AreEqual(LoadStatus.Success, sut.TryParse(pswd, new PasswordParameters { Password = "blubb" })?.Status);
Assert.AreEqual(LoadStatus.Success, sut.TryParse(pwcc, new PasswordParameters { Password = "blubb" })?.Status);
Assert.AreEqual(LoadStatus.Success, sut.TryParse(plnd).Status);
Assert.AreEqual(LoadStatus.Success, sut.TryParse(pkhs, new PasswordParameters { Password = "blubb" }).Status);
Assert.AreEqual(LoadStatus.Success, sut.TryParse(phsk, new PasswordParameters { Password = "blubb" }).Status);
}
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2019 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 Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SafeExamBrowser.Configuration.UnitTests.DataFormats
{
[TestClass]
public class BinarySerializerTests
{
[TestInitialize]
public void Initialize()
{
}
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2019 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 Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SafeExamBrowser.Configuration.UnitTests.DataFormats
{
[TestClass]
public class XmlParserTests
{
[TestInitialize]
public void Initialize()
{
}
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2019 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 Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SafeExamBrowser.Configuration.UnitTests.DataFormats
{
[TestClass]
public class XmlSerializerTests
{
[TestInitialize]
public void Initialize()
{
}
}
}

View file

@ -86,9 +86,13 @@
<Compile Include="ConfigurationRepositoryTests.cs" /> <Compile Include="ConfigurationRepositoryTests.cs" />
<Compile Include="Cryptography\HashAlgorithmTests.cs" /> <Compile Include="Cryptography\HashAlgorithmTests.cs" />
<Compile Include="Cryptography\PasswordEncryptionTests.cs" /> <Compile Include="Cryptography\PasswordEncryptionTests.cs" />
<Compile Include="Cryptography\PublicKeyHashEncryptionTests.cs" /> <Compile Include="Cryptography\PublicKeyEncryptionTests.cs" />
<Compile Include="Cryptography\PublicKeyHashWithSymmetricKeyEncryptionTests.cs" /> <Compile Include="Cryptography\PublicKeySymmetricEncryptionTests.cs" />
<Compile Include="DataCompression\GZipCompressorTests.cs" /> <Compile Include="DataCompression\GZipCompressorTests.cs" />
<Compile Include="DataFormats\BinaryParserTests.cs" />
<Compile Include="DataFormats\BinarySerializerTests.cs" />
<Compile Include="DataFormats\XmlParserTests.cs" />
<Compile Include="DataFormats\XmlSerializerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -13,7 +13,7 @@ using SafeExamBrowser.Contracts.Configuration.Cryptography;
namespace SafeExamBrowser.Configuration.Cryptography namespace SafeExamBrowser.Configuration.Cryptography
{ {
internal class CertificateStore : ICertificateStore public class CertificateStore : ICertificateStore
{ {
private readonly X509Store[] stores = new[] private readonly X509Store[] stores = new[]
{ {

View file

@ -10,11 +10,12 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.Cryptography namespace SafeExamBrowser.Configuration.Cryptography
{ {
internal class PasswordEncryption public class PasswordEncryption : IPasswordEncryption
{ {
private const int BLOCK_SIZE = 16; private const int BLOCK_SIZE = 16;
private const int HEADER_SIZE = 2; private const int HEADER_SIZE = 2;
@ -26,14 +27,14 @@ namespace SafeExamBrowser.Configuration.Cryptography
private ILogger logger; private ILogger logger;
internal PasswordEncryption(ILogger logger) public PasswordEncryption(ILogger logger)
{ {
this.logger = logger; this.logger = logger;
} }
internal LoadStatus Decrypt(Stream data, string password, out Stream decryptedData) public LoadStatus Decrypt(Stream data, string password, out Stream decrypted)
{ {
decryptedData = default(Stream); decrypted = default(Stream);
if (password == null) if (password == null)
{ {
@ -49,17 +50,17 @@ namespace SafeExamBrowser.Configuration.Cryptography
return FailForInvalidHmac(); return FailForInvalidHmac();
} }
decryptedData = Decrypt(data, encryptionKey, originalHmac.Length); decrypted = Decrypt(data, encryptionKey, originalHmac.Length);
return LoadStatus.Success; return LoadStatus.Success;
} }
internal SaveStatus Encrypt(Stream data, string password, out Stream encryptedData) public SaveStatus Encrypt(Stream data, string password, out Stream encrypted)
{ {
var (authKey, authSalt, encrKey, encrSalt) = GenerateKeysForEncryption(password); var (authKey, authSalt, encrKey, encrSalt) = GenerateKeysForEncryption(password);
encryptedData = Encrypt(data, encrKey, out var initVector); encrypted = Encrypt(data, encrKey, out var initVector);
encryptedData = WriteEncryptionParameters(authKey, authSalt, encrSalt, initVector, encryptedData); encrypted = WriteEncryptionParameters(authKey, authSalt, encrSalt, initVector, encrypted);
return SaveStatus.Success; return SaveStatus.Success;
} }

View file

@ -15,20 +15,20 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.Cryptography namespace SafeExamBrowser.Configuration.Cryptography
{ {
internal class PublicKeyHashEncryption public class PublicKeyEncryption : IPublicKeyEncryption
{ {
protected const int PUBLIC_KEY_HASH_SIZE = 20; protected const int PUBLIC_KEY_HASH_SIZE = 20;
protected ICertificateStore store; protected ICertificateStore store;
protected ILogger logger; protected ILogger logger;
internal PublicKeyHashEncryption(ICertificateStore store, ILogger logger) public PublicKeyEncryption(ICertificateStore store, ILogger logger)
{ {
this.logger = logger; this.logger = logger;
this.store = store; this.store = store;
} }
internal virtual LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate) public virtual LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate)
{ {
var publicKeyHash = ParsePublicKeyHash(data); var publicKeyHash = ParsePublicKeyHash(data);
var found = store.TryGetCertificateWith(publicKeyHash, out certificate); var found = store.TryGetCertificateWith(publicKeyHash, out certificate);
@ -45,7 +45,7 @@ namespace SafeExamBrowser.Configuration.Cryptography
return LoadStatus.Success; return LoadStatus.Success;
} }
internal virtual SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData) public virtual SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData)
{ {
var publicKeyHash = GeneratePublicKeyHash(certificate); var publicKeyHash = GeneratePublicKeyHash(certificate);

View file

@ -16,19 +16,19 @@ using SafeExamBrowser.Contracts.Logging;
namespace SafeExamBrowser.Configuration.Cryptography namespace SafeExamBrowser.Configuration.Cryptography
{ {
internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption public class PublicKeySymmetricEncryption : PublicKeyEncryption
{ {
private const int ENCRYPTION_KEY_LENGTH = 32; private const int ENCRYPTION_KEY_LENGTH = 32;
private const int KEY_LENGTH_SIZE = 4; private const int KEY_LENGTH_SIZE = 4;
private PasswordEncryption passwordEncryption; private PasswordEncryption passwordEncryption;
internal PublicKeyHashWithSymmetricKeyEncryption(ICertificateStore store, ILogger logger, PasswordEncryption passwordEncryption) : base(store, logger) public PublicKeySymmetricEncryption(ICertificateStore store, ILogger logger, PasswordEncryption passwordEncryption) : base(store, logger)
{ {
this.passwordEncryption = passwordEncryption; this.passwordEncryption = passwordEncryption;
} }
internal override LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate) public override LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate)
{ {
var publicKeyHash = ParsePublicKeyHash(data); var publicKeyHash = ParsePublicKeyHash(data);
var found = store.TryGetCertificateWith(publicKeyHash, out certificate); var found = store.TryGetCertificateWith(publicKeyHash, out certificate);
@ -47,7 +47,7 @@ namespace SafeExamBrowser.Configuration.Cryptography
return status; return status;
} }
internal override SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData) public override SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData)
{ {
var publicKeyHash = GeneratePublicKeyHash(certificate); var publicKeyHash = GeneratePublicKeyHash(certificate);
var symmetricKey = GenerateSymmetricKey(); var symmetricKey = GenerateSymmetricKey();

View file

@ -13,7 +13,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
internal const string Password = "pswd"; internal const string Password = "pswd";
internal const string PasswordConfigureClient = "pwcc"; internal const string PasswordConfigureClient = "pwcc";
internal const string PlainData = "plnd"; internal const string PlainData = "plnd";
internal const string PublicKeyHash = "pkhs"; internal const string PublicKey = "pkhs";
internal const string PublicKeyHashWithSymmetricKey = "phsk"; internal const string PublicKeySymmetric = "phsk";
} }
} }

View file

@ -11,7 +11,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Configuration.Cryptography; using SafeExamBrowser.Contracts.Configuration.Cryptography;
using SafeExamBrowser.Contracts.Configuration.DataCompression; using SafeExamBrowser.Contracts.Configuration.DataCompression;
@ -26,13 +25,28 @@ namespace SafeExamBrowser.Configuration.DataFormats
private IDataCompressor compressor; private IDataCompressor compressor;
private IHashAlgorithm hashAlgorithm; private IHashAlgorithm hashAlgorithm;
private IModuleLogger logger; private ILogger logger;
private IPasswordEncryption passwordEncryption;
private IPublicKeyEncryption publicKeyEncryption;
private IPublicKeyEncryption publicKeySymmetricEncryption;
private readonly IDataParser xmlParser;
public BinaryParser(IDataCompressor compressor, IHashAlgorithm hashAlgorithm, IModuleLogger logger) public BinaryParser(
IDataCompressor compressor,
IHashAlgorithm hashAlgorithm,
ILogger logger,
IPasswordEncryption passwordEncryption,
IPublicKeyEncryption publicKeyEncryption,
IPublicKeyEncryption publicKeySymmetricEncryption,
IDataParser xmlParser)
{ {
this.compressor = compressor; this.compressor = compressor;
this.hashAlgorithm = hashAlgorithm; this.hashAlgorithm = hashAlgorithm;
this.logger = logger; this.logger = logger;
this.passwordEncryption = passwordEncryption;
this.publicKeyEncryption = publicKeyEncryption;
this.publicKeySymmetricEncryption = publicKeySymmetricEncryption;
this.xmlParser = xmlParser;
} }
public bool CanParse(Stream data) public bool CanParse(Stream data)
@ -55,7 +69,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
} }
catch (Exception e) catch (Exception e)
{ {
logger.Error($"Failed to determine whether '{data}' with {data.Length / 1000.0} KB data matches the {FormatType.Binary} format!", e); logger.Error($"Failed to determine whether '{data}' with {data?.Length / 1000.0} KB data matches the {FormatType.Binary} format!", e);
} }
return false; return false;
@ -78,9 +92,9 @@ namespace SafeExamBrowser.Configuration.DataFormats
return ParsePasswordBlock(data, prefix, password); return ParsePasswordBlock(data, prefix, password);
case BinaryBlock.PlainData: case BinaryBlock.PlainData:
return ParsePlainDataBlock(data); return ParsePlainDataBlock(data);
case BinaryBlock.PublicKeyHash: case BinaryBlock.PublicKey:
case BinaryBlock.PublicKeyHashWithSymmetricKey: case BinaryBlock.PublicKeySymmetric:
return ParsePublicKeyHashBlock(data, prefix, password); return ParsePublicKeyBlock(data, prefix, password);
} }
} }
@ -91,20 +105,19 @@ namespace SafeExamBrowser.Configuration.DataFormats
private ParseResult ParsePasswordBlock(Stream data, string prefix, PasswordParameters password = null) private ParseResult ParsePasswordBlock(Stream data, string prefix, PasswordParameters password = null)
{ {
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
var result = new ParseResult(); var result = new ParseResult();
if (password != null) if (password != null)
{ {
var encryptionParameters = DetermineEncryptionParametersFor(prefix, password); var parameters = DetermineEncryptionParametersFor(prefix, password);
logger.Debug($"Attempting to parse password block with prefix '{prefix}'..."); logger.Debug($"Attempting to parse password block with prefix '{prefix}'...");
result.Status = encryption.Decrypt(data, encryptionParameters.Password, out var decrypted); result.Status = passwordEncryption.Decrypt(data, parameters.Password, out var decrypted);
if (result.Status == LoadStatus.Success) if (result.Status == LoadStatus.Success)
{ {
result = ParsePlainDataBlock(decrypted); result = ParsePlainDataBlock(decrypted);
result.Encryption = encryptionParameters; result.Encryption = parameters;
} }
} }
else else
@ -117,20 +130,18 @@ namespace SafeExamBrowser.Configuration.DataFormats
private ParseResult ParsePlainDataBlock(Stream data) private ParseResult ParsePlainDataBlock(Stream data)
{ {
var xmlFormat = new XmlParser(logger.CloneFor(nameof(XmlParser)));
data = compressor.IsCompressed(data) ? compressor.Decompress(data) : data; data = compressor.IsCompressed(data) ? compressor.Decompress(data) : data;
logger.Debug("Attempting to parse plain data block..."); logger.Debug("Attempting to parse plain data block...");
var result = xmlFormat.TryParse(data); var result = xmlParser.TryParse(data);
result.Format = FormatType.Binary; result.Format = FormatType.Binary;
return result; return result;
} }
private ParseResult ParsePublicKeyHashBlock(Stream data, string prefix, PasswordParameters password = null) private ParseResult ParsePublicKeyBlock(Stream data, string prefix, PasswordParameters password = null)
{ {
var encryption = DetermineEncryptionForPublicKeyHashBlock(prefix); var encryption = prefix == BinaryBlock.PublicKey ? publicKeyEncryption : publicKeySymmetricEncryption;
var result = new ParseResult(); var result = new ParseResult();
logger.Debug($"Attempting to parse public key hash block with prefix '{prefix}'..."); logger.Debug($"Attempting to parse public key hash block with prefix '{prefix}'...");
@ -139,29 +150,17 @@ namespace SafeExamBrowser.Configuration.DataFormats
if (result.Status == LoadStatus.Success) if (result.Status == LoadStatus.Success)
{ {
result = TryParse(decrypted, password); result = TryParse(decrypted, password);
result.Encryption = new PublicKeyHashParameters result.Encryption = new PublicKeyParameters
{ {
Certificate = certificate, Certificate = certificate,
InnerEncryption = result.Encryption as PasswordParameters, InnerEncryption = result.Encryption as PasswordParameters,
SymmetricEncryption = prefix == BinaryBlock.PublicKeyHashWithSymmetricKey SymmetricEncryption = prefix == BinaryBlock.PublicKeySymmetric
}; };
} }
return result; return result;
} }
private PublicKeyHashEncryption DetermineEncryptionForPublicKeyHashBlock(string prefix)
{
var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
if (prefix == BinaryBlock.PublicKeyHash)
{
return new PublicKeyHashEncryption(new CertificateStore(), logger.CloneFor(nameof(PublicKeyHashEncryption)));
}
return new PublicKeyHashWithSymmetricKeyEncryption(new CertificateStore(), logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption)), passwordEncryption);
}
private PasswordParameters DetermineEncryptionParametersFor(string prefix, PasswordParameters password) private PasswordParameters DetermineEncryptionParametersFor(string prefix, PasswordParameters password)
{ {
var parameters = new PasswordParameters(); var parameters = new PasswordParameters();

View file

@ -43,7 +43,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
case PasswordParameters p: case PasswordParameters p:
result = SerializePasswordBlock(data, p); result = SerializePasswordBlock(data, p);
break; break;
case PublicKeyHashParameters p: case PublicKeyParameters p:
result = SerializePublicKeyHashBlock(data, p); result = SerializePublicKeyHashBlock(data, p);
break; break;
default: default:
@ -103,14 +103,14 @@ namespace SafeExamBrowser.Configuration.DataFormats
return result; return result;
} }
private SerializeResult SerializePublicKeyHashBlock(IDictionary<string, object> data, PublicKeyHashParameters parameters) private SerializeResult SerializePublicKeyHashBlock(IDictionary<string, object> data, PublicKeyParameters parameters)
{ {
var result = SerializePublicKeyHashInnerBlock(data, parameters); var result = SerializePublicKeyHashInnerBlock(data, parameters);
if (result.Status == SaveStatus.Success) if (result.Status == SaveStatus.Success)
{ {
var encryption = DetermineEncryptionForPublicKeyHashBlock(parameters); var encryption = DetermineEncryptionForPublicKeyHashBlock(parameters);
var prefix = parameters.SymmetricEncryption ? BinaryBlock.PublicKeyHashWithSymmetricKey : BinaryBlock.PublicKeyHash; var prefix = parameters.SymmetricEncryption ? BinaryBlock.PublicKeySymmetric : BinaryBlock.PublicKey;
logger.Debug("Attempting to serialize public key hash block..."); logger.Debug("Attempting to serialize public key hash block...");
@ -125,7 +125,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
return result; return result;
} }
private SerializeResult SerializePublicKeyHashInnerBlock(IDictionary<string, object> data, PublicKeyHashParameters parameters) private SerializeResult SerializePublicKeyHashInnerBlock(IDictionary<string, object> data, PublicKeyParameters parameters)
{ {
if (parameters.InnerEncryption is PasswordParameters password) if (parameters.InnerEncryption is PasswordParameters password)
{ {
@ -135,16 +135,16 @@ namespace SafeExamBrowser.Configuration.DataFormats
return SerializePlainDataBlock(data, true); return SerializePlainDataBlock(data, true);
} }
private PublicKeyHashEncryption DetermineEncryptionForPublicKeyHashBlock(PublicKeyHashParameters parameters) private PublicKeyEncryption DetermineEncryptionForPublicKeyHashBlock(PublicKeyParameters parameters)
{ {
var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption))); var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
if (parameters.SymmetricEncryption) if (parameters.SymmetricEncryption)
{ {
return new PublicKeyHashWithSymmetricKeyEncryption(new CertificateStore(), logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption)), passwordEncryption); return new PublicKeySymmetricEncryption(new CertificateStore(), logger.CloneFor(nameof(PublicKeySymmetricEncryption)), passwordEncryption);
} }
return new PublicKeyHashEncryption(new CertificateStore(), logger.CloneFor(nameof(PublicKeyHashEncryption))); return new PublicKeyEncryption(new CertificateStore(), logger.CloneFor(nameof(PublicKeyEncryption)));
} }
private Stream WritePrefix(string prefix, Stream data) private Stream WritePrefix(string prefix, Stream data)

View file

@ -64,8 +64,8 @@
<Compile Include="Cryptography\CertificateStore.cs" /> <Compile Include="Cryptography\CertificateStore.cs" />
<Compile Include="DataCompression\GZipCompressor.cs" /> <Compile Include="DataCompression\GZipCompressor.cs" />
<Compile Include="Cryptography\PasswordEncryption.cs" /> <Compile Include="Cryptography\PasswordEncryption.cs" />
<Compile Include="Cryptography\PublicKeyHashEncryption.cs" /> <Compile Include="Cryptography\PublicKeyEncryption.cs" />
<Compile Include="Cryptography\PublicKeyHashWithSymmetricKeyEncryption.cs" /> <Compile Include="Cryptography\PublicKeySymmetricEncryption.cs" />
<Compile Include="DataFormats\BinaryParser.cs" /> <Compile Include="DataFormats\BinaryParser.cs" />
<Compile Include="DataFormats\BinarySerializer.cs" /> <Compile Include="DataFormats\BinarySerializer.cs" />
<Compile Include="DataFormats\BinaryBlock.cs" /> <Compile Include="DataFormats\BinaryBlock.cs" />

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2019 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.IO;
namespace SafeExamBrowser.Contracts.Configuration.Cryptography
{
/// <summary>
/// Encrypts and decrypts data with a password.
/// </summary>
public interface IPasswordEncryption
{
/// <summary>
/// Attempts to decrypt the given data. The decrypted data stream can only be considered valid if <see cref="LoadStatus.Success"/>
/// is returned!
/// </summary>
LoadStatus Decrypt(Stream data, string password, out Stream decrypted);
/// <summary>
/// Attempts to encrypt the given data. The encrypted data stream can only be considered valid if <see cref="SaveStatus.Success"/>
/// is returned.
/// </summary>
SaveStatus Encrypt(Stream data, string password, out Stream encrypted);
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2019 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.IO;
using System.Security.Cryptography.X509Certificates;
namespace SafeExamBrowser.Contracts.Configuration.Cryptography
{
/// <summary>
/// Encrypts and decrypts data with a certificate.
/// </summary>
public interface IPublicKeyEncryption
{
/// <summary>
/// Attempts to decrypt the given data. The decrypted data stream and the certificate can only be considered valid if
/// <see cref="LoadStatus.Success"/> is returned!
/// </summary>
LoadStatus Decrypt(Stream data, out Stream decrypted, out X509Certificate2 certificate);
/// <summary>
/// Attempts to encrypt the given data. The encrypted data stream can only be considered valid if <see cref="SaveStatus.Success"/>
/// is returned.
/// </summary>
SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encrypted);
}
}

View file

@ -13,7 +13,7 @@ namespace SafeExamBrowser.Contracts.Configuration.Cryptography
/// <summary> /// <summary>
/// Holds all parameters for data encryption by certificate. /// Holds all parameters for data encryption by certificate.
/// </summary> /// </summary>
public class PublicKeyHashParameters : EncryptionParameters public class PublicKeyParameters : EncryptionParameters
{ {
/// <summary> /// <summary>
/// The certificate holding the public key used for encryption. /// The certificate holding the public key used for encryption.

View file

@ -59,8 +59,10 @@
<Compile Include="Communication\Events\MessageBoxRequestEventArgs.cs" /> <Compile Include="Communication\Events\MessageBoxRequestEventArgs.cs" />
<Compile Include="Configuration\Cryptography\EncryptionParameters.cs" /> <Compile Include="Configuration\Cryptography\EncryptionParameters.cs" />
<Compile Include="Configuration\Cryptography\ICertificateStore.cs" /> <Compile Include="Configuration\Cryptography\ICertificateStore.cs" />
<Compile Include="Configuration\Cryptography\IPasswordEncryption.cs" />
<Compile Include="Configuration\Cryptography\IPublicKeyEncryption.cs" />
<Compile Include="Configuration\Cryptography\PasswordParameters.cs" /> <Compile Include="Configuration\Cryptography\PasswordParameters.cs" />
<Compile Include="Configuration\Cryptography\PublicKeyHashParameters.cs" /> <Compile Include="Configuration\Cryptography\PublicKeyParameters.cs" />
<Compile Include="Configuration\DataFormats\FormatType.cs" /> <Compile Include="Configuration\DataFormats\FormatType.cs" />
<Compile Include="Configuration\DataCompression\IDataCompressor.cs" /> <Compile Include="Configuration\DataCompression\IDataCompressor.cs" />
<Compile Include="Configuration\Cryptography\IHashAlgorithm.cs" /> <Compile Include="Configuration\Cryptography\IHashAlgorithm.cs" />

View file

@ -62,12 +62,12 @@ namespace SafeExamBrowser.Runtime
InitializeText(); InitializeText();
var messageBox = new MessageBox(text); var messageBox = new MessageBox(text);
var desktopFactory = new DesktopFactory(new ModuleLogger(logger, nameof(DesktopFactory))); var desktopFactory = new DesktopFactory(ModuleLogger(nameof(DesktopFactory)));
var explorerShell = new ExplorerShell(new ModuleLogger(logger, nameof(ExplorerShell)), nativeMethods); var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var processFactory = new ProcessFactory(new ModuleLogger(logger, nameof(ProcessFactory))); var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger); var proxyFactory = new ProxyFactory(new ProxyObjectFactory(), logger);
var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), new ModuleLogger(logger, nameof(RuntimeHost)), FIVE_SECONDS); var runtimeHost = new RuntimeHost(appConfig.RuntimeAddress, new HostObjectFactory(), ModuleLogger(nameof(RuntimeHost)), FIVE_SECONDS);
var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(ServiceProxy))); var serviceProxy = new ServiceProxy(appConfig.ServiceAddress, new ProxyObjectFactory(), ModuleLogger(nameof(ServiceProxy)));
var sessionContext = new SessionContext(); var sessionContext = new SessionContext();
var uiFactory = new UserInterfaceFactory(text); var uiFactory = new UserInterfaceFactory(text);
@ -116,18 +116,23 @@ namespace SafeExamBrowser.Runtime
var programCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright; var programCopyright = executable.GetCustomAttribute<AssemblyCopyrightAttribute>().Copyright;
var programTitle = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title; var programTitle = executable.GetCustomAttribute<AssemblyTitleAttribute>().Title;
var programVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion; var programVersion = executable.GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
var compressor = new GZipCompressor(new ModuleLogger(logger, nameof(GZipCompressor))); var compressor = new GZipCompressor(ModuleLogger(nameof(GZipCompressor)));
var repositoryLogger = new ModuleLogger(logger, nameof(ConfigurationRepository)); var passwordEncryption = new PasswordEncryption(ModuleLogger(nameof(PasswordEncryption)));
var publicKeyEncryption = new PublicKeyEncryption(new CertificateStore(), ModuleLogger(nameof(PublicKeyEncryption)));
var symmetricInnerEncryption = new PasswordEncryption(ModuleLogger(nameof(PasswordEncryption)));
var symmetricEncryption = new PublicKeySymmetricEncryption(new CertificateStore(), ModuleLogger(nameof(PublicKeySymmetricEncryption)), symmetricInnerEncryption);
var repositoryLogger = ModuleLogger(nameof(ConfigurationRepository));
var xmlParser = new XmlParser(ModuleLogger(nameof(XmlParser)));
configuration = new ConfigurationRepository(new HashAlgorithm(), repositoryLogger, executable.Location, programCopyright, programTitle, programVersion); configuration = new ConfigurationRepository(new HashAlgorithm(), repositoryLogger, executable.Location, programCopyright, programTitle, programVersion);
appConfig = configuration.InitializeAppConfig(); appConfig = configuration.InitializeAppConfig();
configuration.Register(new BinaryParser(compressor, new HashAlgorithm(), new ModuleLogger(logger, nameof(BinaryParser)))); configuration.Register(new BinaryParser(compressor, new HashAlgorithm(), ModuleLogger(nameof(BinaryParser)), passwordEncryption, publicKeyEncryption, symmetricEncryption, xmlParser));
configuration.Register(new BinarySerializer(compressor, new ModuleLogger(logger, nameof(BinarySerializer)))); configuration.Register(new BinarySerializer(compressor, ModuleLogger(nameof(BinarySerializer))));
configuration.Register(new XmlParser(new ModuleLogger(logger, nameof(XmlParser)))); configuration.Register(new XmlParser(ModuleLogger(nameof(XmlParser))));
configuration.Register(new XmlSerializer(new ModuleLogger(logger, nameof(XmlSerializer)))); configuration.Register(new XmlSerializer(ModuleLogger(nameof(XmlSerializer))));
configuration.Register(new FileResourceLoader(new ModuleLogger(logger, nameof(FileResourceLoader)))); configuration.Register(new FileResourceLoader(ModuleLogger(nameof(FileResourceLoader))));
configuration.Register(new FileResourceSaver(new ModuleLogger(logger, nameof(FileResourceSaver)))); configuration.Register(new FileResourceSaver(ModuleLogger(nameof(FileResourceSaver))));
configuration.Register(new NetworkResourceLoader(appConfig, new ModuleLogger(logger, nameof(NetworkResourceLoader)))); configuration.Register(new NetworkResourceLoader(appConfig, new ModuleLogger(logger, nameof(NetworkResourceLoader))));
} }
@ -148,5 +153,10 @@ namespace SafeExamBrowser.Runtime
text = new Text(logger); text = new Text(logger);
textResource = new XmlTextResource(path); textResource = new XmlTextResource(path);
} }
private IModuleLogger ModuleLogger(string moduleInfo)
{
return new ModuleLogger(logger, moduleInfo);
}
} }
} }