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;
|
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
Loading…
Add table
Reference in a new issue