SEBWIN-221: Implemented public key hash encryption.
This commit is contained in:
parent
ee166796a5
commit
fc179fe47d
6 changed files with 173 additions and 64 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -105,25 +105,20 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.");
|
||||
|
|
Loading…
Reference in a new issue