diff --git a/SafeExamBrowser.Configuration/Cryptography/PasswordEncryption.cs b/SafeExamBrowser.Configuration/Cryptography/PasswordEncryption.cs index be0df0bf..eee7dd9e 100644 --- a/SafeExamBrowser.Configuration/Cryptography/PasswordEncryption.cs +++ b/SafeExamBrowser.Configuration/Cryptography/PasswordEncryption.cs @@ -31,9 +31,9 @@ namespace SafeExamBrowser.Configuration.Cryptography this.logger = logger; } - internal LoadStatus Decrypt(Stream data, string password, out Stream decrypted) + internal LoadStatus Decrypt(Stream data, string password, out Stream decryptedData) { - decrypted = default(Stream); + decryptedData = default(Stream); if (password == null) { @@ -49,17 +49,17 @@ namespace SafeExamBrowser.Configuration.Cryptography return FailForInvalidHmac(); } - decrypted = Decrypt(data, encryptionKey, originalHmac.Length); + decryptedData = Decrypt(data, encryptionKey, originalHmac.Length); return LoadStatus.Success; } - internal SaveStatus Encrypt(Stream data, string password, out Stream encrypted) + internal SaveStatus Encrypt(Stream data, string password, out Stream encryptedData) { var (authKey, authSalt, encrKey, encrSalt) = GenerateKeysForEncryption(password); - encrypted = Encrypt(data, encrKey, out var initVector); - encrypted = WriteEncryptionParameters(authKey, authSalt, encrSalt, initVector, encrypted); + encryptedData = Encrypt(data, encrKey, out var initVector); + encryptedData = WriteEncryptionParameters(authKey, authSalt, encrSalt, initVector, encryptedData); return SaveStatus.Success; } diff --git a/SafeExamBrowser.Configuration/Cryptography/PublicKeyHashEncryption.cs b/SafeExamBrowser.Configuration/Cryptography/PublicKeyHashEncryption.cs index e6a94f60..a3602a03 100644 --- a/SafeExamBrowser.Configuration/Cryptography/PublicKeyHashEncryption.cs +++ b/SafeExamBrowser.Configuration/Cryptography/PublicKeyHashEncryption.cs @@ -6,7 +6,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -using System; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -27,28 +26,48 @@ namespace SafeExamBrowser.Configuration.Cryptography this.logger = logger; } - internal virtual LoadStatus Decrypt(Stream data, out Stream decrypted, out X509Certificate2 certificate) + internal virtual LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate) { - var keyHash = ParsePublicKeyHash(data); - var found = TryGetCertificateWith(keyHash, out certificate); + var publicKeyHash = ParsePublicKeyHash(data); + var found = TryGetCertificateWith(publicKeyHash, out certificate); - decrypted = default(Stream); + decryptedData = default(Stream); if (!found) { return FailForMissingCertificate(); } - decrypted = Decrypt(data, PUBLIC_KEY_HASH_SIZE, certificate); + decryptedData = Decrypt(data, PUBLIC_KEY_HASH_SIZE, certificate); return LoadStatus.Success; } - internal virtual SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encrypted) + internal virtual SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData) { - // TODO: Don't forget to write encryption parameters! + var publicKeyHash = GeneratePublicKeyHash(certificate); - throw new NotImplementedException(); + encryptedData = Encrypt(data, certificate); + encryptedData = WriteEncryptionParameters(encryptedData, publicKeyHash); + + return SaveStatus.Success; + } + + protected LoadStatus FailForMissingCertificate() + { + logger.Error($"Could not find certificate which matches the given public key hash!"); + + return LoadStatus.InvalidData; + } + + protected byte[] GeneratePublicKeyHash(X509Certificate2 certificate) + { + var publicKey = certificate.PublicKey.EncodedKeyValue.RawData; + + using (var sha = new SHA1CryptoServiceProvider()) + { + return sha.ComputeHash(publicKey); + } } protected byte[] ParsePublicKeyHash(Stream data) @@ -102,42 +121,84 @@ namespace SafeExamBrowser.Configuration.Cryptography return false; } - protected LoadStatus FailForMissingCertificate() - { - logger.Error($"Could not find certificate which matches the given public key hash!"); - - return LoadStatus.InvalidData; - } - protected MemoryStream Decrypt(Stream data, long offset, X509Certificate2 certificate) { var algorithm = certificate.PrivateKey as RSACryptoServiceProvider; var blockSize = algorithm.KeySize / 8; var blockCount = (data.Length - offset) / blockSize; + var decrypted = new MemoryStream(); + var decryptedBuffer = new byte[blockSize]; + var encryptedBuffer = new byte[blockSize]; var remainingBytes = data.Length - offset - (blockSize * blockCount); - var encrypted = new byte[blockSize]; - var decrypted = default(byte[]); - var decryptedData = new MemoryStream(); data.Seek(offset, SeekOrigin.Begin); logger.Debug("Decrypting data..."); - for (int i = 0; i < blockCount; i++) + using (algorithm) { - data.Read(encrypted, 0, encrypted.Length); - decrypted = algorithm.Decrypt(encrypted, false); - decryptedData.Write(decrypted, 0, decrypted.Length); + for (var block = 0; block < blockCount; block++) + { + data.Read(encryptedBuffer, 0, encryptedBuffer.Length); + decryptedBuffer = algorithm.Decrypt(encryptedBuffer, false); + decrypted.Write(decryptedBuffer, 0, decryptedBuffer.Length); + } + + if (remainingBytes > 0) + { + encryptedBuffer = new byte[remainingBytes]; + data.Read(encryptedBuffer, 0, encryptedBuffer.Length); + decryptedBuffer = algorithm.Decrypt(encryptedBuffer, false); + decrypted.Write(decryptedBuffer, 0, decryptedBuffer.Length); + } } - if (remainingBytes > 0) + return decrypted; + } + + protected Stream Encrypt(Stream data, X509Certificate2 certificate) + { + var algorithm = certificate.PublicKey.Key as RSACryptoServiceProvider; + var blockSize = (algorithm.KeySize / 8) - 32; + var blockCount = data.Length / blockSize; + var decryptedBuffer = new byte[blockSize]; + var encrypted = new MemoryStream(); + var encryptedBuffer = new byte[blockSize]; + var remainingBytes = data.Length - (blockCount * blockSize); + + data.Seek(0, SeekOrigin.Begin); + logger.Debug("Encrypting data..."); + + using (algorithm) { - encrypted = new byte[remainingBytes]; - data.Read(encrypted, 0, encrypted.Length); - decrypted = algorithm.Decrypt(encrypted, false); - decryptedData.Write(decrypted, 0, decrypted.Length); + for (var block = 0; block < blockCount; block++) + { + data.Read(decryptedBuffer, 0, decryptedBuffer.Length); + encryptedBuffer = algorithm.Encrypt(decryptedBuffer, false); + encrypted.Write(encryptedBuffer, 0, encryptedBuffer.Length); + } + + if (remainingBytes > 0) + { + decryptedBuffer = new byte[remainingBytes]; + data.Read(decryptedBuffer, 0, decryptedBuffer.Length); + encryptedBuffer = algorithm.Encrypt(decryptedBuffer, false); + encrypted.Write(encryptedBuffer, 0, encryptedBuffer.Length); + } } - return decryptedData; + return encrypted; + } + + private Stream WriteEncryptionParameters(Stream encryptedData, byte[] keyHash) + { + var data = new MemoryStream(); + + logger.Debug("Writing encryption parameters..."); + data.Write(keyHash, 0, keyHash.Length); + encryptedData.Seek(0, SeekOrigin.Begin); + encryptedData.CopyTo(data); + + return data; } } } diff --git a/SafeExamBrowser.Configuration/Cryptography/PublicKeyHashWithSymmetricKeyEncryption.cs b/SafeExamBrowser.Configuration/Cryptography/PublicKeyHashWithSymmetricKeyEncryption.cs index 7dbd8c62..692c232a 100644 --- a/SafeExamBrowser.Configuration/Cryptography/PublicKeyHashWithSymmetricKeyEncryption.cs +++ b/SafeExamBrowser.Configuration/Cryptography/PublicKeyHashWithSymmetricKeyEncryption.cs @@ -8,6 +8,7 @@ using System; using System.IO; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Logging; @@ -16,6 +17,7 @@ namespace SafeExamBrowser.Configuration.Cryptography { internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption { + private const int ENCRYPTION_KEY_LENGTH = 32; private const int KEY_LENGTH_SIZE = 4; private PasswordEncryption passwordEncryption; @@ -25,12 +27,12 @@ namespace SafeExamBrowser.Configuration.Cryptography this.passwordEncryption = passwordEncryption; } - internal override LoadStatus Decrypt(Stream data, out Stream decrypted, out X509Certificate2 certificate) + internal override LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate) { - var keyHash = ParsePublicKeyHash(data); - var found = TryGetCertificateWith(keyHash, out certificate); + var publicKeyHash = ParsePublicKeyHash(data); + var found = TryGetCertificateWith(publicKeyHash, out certificate); - decrypted = default(Stream); + decryptedData = default(Stream); if (!found) { @@ -39,16 +41,45 @@ namespace SafeExamBrowser.Configuration.Cryptography var symmetricKey = ParseSymmetricKey(data, certificate); var stream = new SubStream(data, data.Position, data.Length - data.Position); - var status = passwordEncryption.Decrypt(stream, symmetricKey, out decrypted); + var status = passwordEncryption.Decrypt(stream, symmetricKey, out decryptedData); return status; } - internal override SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encrypted) + internal override SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData) { - // TODO: Don't forget to write encryption parameters! + var publicKeyHash = GeneratePublicKeyHash(certificate); + var symmetricKey = GenerateSymmetricKey(); + var symmetricKeyString = Convert.ToBase64String(symmetricKey); + var status = passwordEncryption.Encrypt(data, symmetricKeyString, out encryptedData); - throw new NotImplementedException(); + if (status != SaveStatus.Success) + { + return FailForUnsuccessfulPasswordEncryption(status); + } + + encryptedData = WriteEncryptionParameters(encryptedData, certificate, publicKeyHash, symmetricKey); + + return SaveStatus.Success; + } + + private SaveStatus FailForUnsuccessfulPasswordEncryption(SaveStatus status) + { + logger.Error($"Password encryption has failed with status '{status}'!"); + + return SaveStatus.UnexpectedError; + } + + private byte[] GenerateSymmetricKey() + { + var key = new byte[ENCRYPTION_KEY_LENGTH]; + + using (var generator = RandomNumberGenerator.Create()) + { + generator.GetBytes(key); + } + + return key; } private string ParseSymmetricKey(Stream data, X509Certificate2 certificate) @@ -60,16 +91,38 @@ namespace SafeExamBrowser.Configuration.Cryptography data.Seek(PUBLIC_KEY_HASH_SIZE, SeekOrigin.Begin); data.Read(keyLengthData, 0, keyLengthData.Length); - var keyLength = BitConverter.ToInt32(keyLengthData, 0); - var encryptedKey = new byte[keyLength]; + var encryptedKeyLength = BitConverter.ToInt32(keyLengthData, 0); + var encryptedKey = new byte[encryptedKeyLength]; data.Read(encryptedKey, 0, encryptedKey.Length); - var stream = new SubStream(data, PUBLIC_KEY_HASH_SIZE + KEY_LENGTH_SIZE, keyLength); + var stream = new SubStream(data, PUBLIC_KEY_HASH_SIZE + KEY_LENGTH_SIZE, encryptedKeyLength); var decryptedKey = Decrypt(stream, 0, certificate); var symmetricKey = Convert.ToBase64String(decryptedKey.ToArray()); return symmetricKey; } + + private Stream WriteEncryptionParameters(Stream encryptedData, X509Certificate2 certificate, byte[] publicKeyHash, byte[] symmetricKey) + { + var data = new MemoryStream(); + var symmetricKeyData = new MemoryStream(symmetricKey); + var encryptedKey = Encrypt(symmetricKeyData, certificate); + // IMPORTANT: The key length must be exactly 4 Bytes, thus the cast to integer! + var encryptedKeyLength = BitConverter.GetBytes((int) encryptedKey.Length); + + logger.Debug("Writing encryption parameters..."); + + data.Write(publicKeyHash, 0, publicKeyHash.Length); + data.Write(encryptedKeyLength, 0, encryptedKeyLength.Length); + + encryptedKey.Seek(0, SeekOrigin.Begin); + encryptedKey.CopyTo(data); + + encryptedData.Seek(0, SeekOrigin.Begin); + encryptedData.CopyTo(data); + + return data; + } } } diff --git a/SafeExamBrowser.Configuration/DataFormats/BinaryParser.cs b/SafeExamBrowser.Configuration/DataFormats/BinaryParser.cs index c081f781..be3f590c 100644 --- a/SafeExamBrowser.Configuration/DataFormats/BinaryParser.cs +++ b/SafeExamBrowser.Configuration/DataFormats/BinaryParser.cs @@ -46,7 +46,7 @@ namespace SafeExamBrowser.Configuration.DataFormats var prefix = ReadPrefix(data); var isValid = IsValid(prefix); - logger.Debug($"'{data}' starting with '{prefix}' does {(isValid ? string.Empty : "not ")}match the {FormatType.Binary} format."); + logger.Debug($"'{data}' starting with '{prefix}' {(isValid ? "matches" : "does not match")} the {FormatType.Binary} format."); return isValid; } diff --git a/SafeExamBrowser.Configuration/DataFormats/BinarySerializer.cs b/SafeExamBrowser.Configuration/DataFormats/BinarySerializer.cs index 23e40631..29539c51 100644 --- a/SafeExamBrowser.Configuration/DataFormats/BinarySerializer.cs +++ b/SafeExamBrowser.Configuration/DataFormats/BinarySerializer.cs @@ -105,25 +105,20 @@ namespace SafeExamBrowser.Configuration.DataFormats private SerializeResult SerializePublicKeyHashBlock(IDictionary data, PublicKeyHashParameters parameters) { - var result = SerializePlainDataBlock(data); + var result = SerializePublicKeyHashInnerBlock(data, parameters); if (result.Status == SaveStatus.Success) { - result = SerializePublicKeyHashInnerBlock(data, parameters); + var encryption = DetermineEncryptionForPublicKeyHashBlock(parameters); + var prefix = parameters.SymmetricEncryption ? BinaryBlock.PublicKeyHashWithSymmetricKey : BinaryBlock.PublicKeyHash; - if (result.Status == SaveStatus.Success) + logger.Debug("Attempting to serialize public key hash block..."); + + var status = encryption.Encrypt(result.Data, parameters.Certificate, out var encrypted); + + if (status == SaveStatus.Success) { - var encryption = DetermineEncryptionForPublicKeyHashBlock(parameters); - var prefix = parameters.SymmetricEncryption ? BinaryBlock.PublicKeyHashWithSymmetricKey : BinaryBlock.PublicKeyHash; - - logger.Debug("Attempting to serialize public key hash block..."); - - var status = encryption.Encrypt(result.Data, parameters.Certificate, out var encrypted); - - if (status == SaveStatus.Success) - { - result.Data = WritePrefix(prefix, encrypted); - } + result.Data = WritePrefix(prefix, encrypted); } } diff --git a/SafeExamBrowser.Configuration/DataFormats/XmlParser.cs b/SafeExamBrowser.Configuration/DataFormats/XmlParser.cs index da3039c9..a6d9c277 100644 --- a/SafeExamBrowser.Configuration/DataFormats/XmlParser.cs +++ b/SafeExamBrowser.Configuration/DataFormats/XmlParser.cs @@ -43,11 +43,11 @@ namespace SafeExamBrowser.Configuration.DataFormats data.Read(prefixData, 0, prefixData.Length); var prefix = Encoding.UTF8.GetString(prefixData); - var success = prefix == XML_PREFIX; + var isXml = prefix == XML_PREFIX; - logger.Debug($"'{data}' starting with '{prefix}' does {(success ? string.Empty : "not ")}match the {FormatType.Xml} format."); + logger.Debug($"'{data}' starting with '{prefix}' {(isXml ? "matches" : "does not match")} the {FormatType.Xml} format."); - return success; + return isXml; } logger.Debug($"'{data}' is not long enough ({data.Length} bytes) to match the {FormatType.Xml} format.");