SEBWIN-221: Implemented public key hash encryption.

This commit is contained in:
dbuechel 2019-01-08 14:10:45 +01:00
parent ee166796a5
commit fc179fe47d
6 changed files with 173 additions and 64 deletions

View file

@ -31,9 +31,9 @@ namespace SafeExamBrowser.Configuration.Cryptography
this.logger = logger; 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) if (password == null)
{ {
@ -49,17 +49,17 @@ namespace SafeExamBrowser.Configuration.Cryptography
return FailForInvalidHmac(); return FailForInvalidHmac();
} }
decrypted = Decrypt(data, encryptionKey, originalHmac.Length); decryptedData = Decrypt(data, encryptionKey, originalHmac.Length);
return LoadStatus.Success; 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); var (authKey, authSalt, encrKey, encrSalt) = GenerateKeysForEncryption(password);
encrypted = Encrypt(data, encrKey, out var initVector); encryptedData = Encrypt(data, encrKey, out var initVector);
encrypted = WriteEncryptionParameters(authKey, authSalt, encrSalt, initVector, encrypted); encryptedData = WriteEncryptionParameters(authKey, authSalt, encrSalt, initVector, encryptedData);
return SaveStatus.Success; return SaveStatus.Success;
} }

View file

@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -27,28 +26,48 @@ namespace SafeExamBrowser.Configuration.Cryptography
this.logger = logger; 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 publicKeyHash = ParsePublicKeyHash(data);
var found = TryGetCertificateWith(keyHash, out certificate); var found = TryGetCertificateWith(publicKeyHash, out certificate);
decrypted = default(Stream); decryptedData = default(Stream);
if (!found) if (!found)
{ {
return FailForMissingCertificate(); return FailForMissingCertificate();
} }
decrypted = Decrypt(data, PUBLIC_KEY_HASH_SIZE, certificate); decryptedData = Decrypt(data, PUBLIC_KEY_HASH_SIZE, certificate);
return LoadStatus.Success; 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) protected byte[] ParsePublicKeyHash(Stream data)
@ -102,42 +121,84 @@ namespace SafeExamBrowser.Configuration.Cryptography
return false; 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) protected MemoryStream Decrypt(Stream data, long offset, X509Certificate2 certificate)
{ {
var algorithm = certificate.PrivateKey as RSACryptoServiceProvider; var algorithm = certificate.PrivateKey as RSACryptoServiceProvider;
var blockSize = algorithm.KeySize / 8; var blockSize = algorithm.KeySize / 8;
var blockCount = (data.Length - offset) / blockSize; 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 remainingBytes = data.Length - offset - (blockSize * blockCount);
var encrypted = new byte[blockSize];
var decrypted = default(byte[]);
var decryptedData = new MemoryStream();
data.Seek(offset, SeekOrigin.Begin); data.Seek(offset, SeekOrigin.Begin);
logger.Debug("Decrypting data..."); logger.Debug("Decrypting data...");
for (int i = 0; i < blockCount; i++) using (algorithm)
{ {
data.Read(encrypted, 0, encrypted.Length); for (var block = 0; block < blockCount; block++)
decrypted = algorithm.Decrypt(encrypted, false); {
decryptedData.Write(decrypted, 0, decrypted.Length); data.Read(encryptedBuffer, 0, encryptedBuffer.Length);
decryptedBuffer = algorithm.Decrypt(encryptedBuffer, false);
decrypted.Write(decryptedBuffer, 0, decryptedBuffer.Length);
} }
if (remainingBytes > 0) if (remainingBytes > 0)
{ {
encrypted = new byte[remainingBytes]; encryptedBuffer = new byte[remainingBytes];
data.Read(encrypted, 0, encrypted.Length); data.Read(encryptedBuffer, 0, encryptedBuffer.Length);
decrypted = algorithm.Decrypt(encrypted, false); decryptedBuffer = algorithm.Decrypt(encryptedBuffer, false);
decryptedData.Write(decrypted, 0, decrypted.Length); decrypted.Write(decryptedBuffer, 0, decryptedBuffer.Length);
}
} }
return decryptedData; 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)
{
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 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;
} }
} }
} }

View file

@ -8,6 +8,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using SafeExamBrowser.Contracts.Configuration; using SafeExamBrowser.Contracts.Configuration;
using SafeExamBrowser.Contracts.Logging; using SafeExamBrowser.Contracts.Logging;
@ -16,6 +17,7 @@ namespace SafeExamBrowser.Configuration.Cryptography
{ {
internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption
{ {
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;
@ -25,12 +27,12 @@ namespace SafeExamBrowser.Configuration.Cryptography
this.passwordEncryption = passwordEncryption; 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 publicKeyHash = ParsePublicKeyHash(data);
var found = TryGetCertificateWith(keyHash, out certificate); var found = TryGetCertificateWith(publicKeyHash, out certificate);
decrypted = default(Stream); decryptedData = default(Stream);
if (!found) if (!found)
{ {
@ -39,16 +41,45 @@ namespace SafeExamBrowser.Configuration.Cryptography
var symmetricKey = ParseSymmetricKey(data, certificate); var symmetricKey = ParseSymmetricKey(data, certificate);
var stream = new SubStream(data, data.Position, data.Length - data.Position); 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; 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) private string ParseSymmetricKey(Stream data, X509Certificate2 certificate)
@ -60,16 +91,38 @@ namespace SafeExamBrowser.Configuration.Cryptography
data.Seek(PUBLIC_KEY_HASH_SIZE, SeekOrigin.Begin); data.Seek(PUBLIC_KEY_HASH_SIZE, SeekOrigin.Begin);
data.Read(keyLengthData, 0, keyLengthData.Length); data.Read(keyLengthData, 0, keyLengthData.Length);
var keyLength = BitConverter.ToInt32(keyLengthData, 0); var encryptedKeyLength = BitConverter.ToInt32(keyLengthData, 0);
var encryptedKey = new byte[keyLength]; var encryptedKey = new byte[encryptedKeyLength];
data.Read(encryptedKey, 0, encryptedKey.Length); 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 decryptedKey = Decrypt(stream, 0, certificate);
var symmetricKey = Convert.ToBase64String(decryptedKey.ToArray()); var symmetricKey = Convert.ToBase64String(decryptedKey.ToArray());
return symmetricKey; 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;
}
} }
} }

View file

@ -46,7 +46,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
var prefix = ReadPrefix(data); var prefix = ReadPrefix(data);
var isValid = IsValid(prefix); 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; return isValid;
} }

View file

@ -105,11 +105,7 @@ namespace SafeExamBrowser.Configuration.DataFormats
private SerializeResult SerializePublicKeyHashBlock(IDictionary<string, object> data, PublicKeyHashParameters parameters) private SerializeResult SerializePublicKeyHashBlock(IDictionary<string, object> data, PublicKeyHashParameters parameters)
{ {
var result = SerializePlainDataBlock(data); var result = SerializePublicKeyHashInnerBlock(data, parameters);
if (result.Status == SaveStatus.Success)
{
result = SerializePublicKeyHashInnerBlock(data, parameters);
if (result.Status == SaveStatus.Success) if (result.Status == SaveStatus.Success)
{ {
@ -125,7 +121,6 @@ namespace SafeExamBrowser.Configuration.DataFormats
result.Data = WritePrefix(prefix, encrypted); result.Data = WritePrefix(prefix, encrypted);
} }
} }
}
return result; return result;
} }

View file

@ -43,11 +43,11 @@ namespace SafeExamBrowser.Configuration.DataFormats
data.Read(prefixData, 0, prefixData.Length); data.Read(prefixData, 0, prefixData.Length);
var prefix = Encoding.UTF8.GetString(prefixData); 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."); logger.Debug($"'{data}' is not long enough ({data.Length} bytes) to match the {FormatType.Xml} format.");