SEBWIN-221: Implemented basic certificate decryption.
This commit is contained in:
parent
243d32879e
commit
483ba7ad4a
5 changed files with 214 additions and 34 deletions
|
@ -81,8 +81,6 @@ namespace SafeExamBrowser.Configuration.Compression
|
||||||
var cm = data.ReadByte();
|
var cm = data.ReadByte();
|
||||||
var compressed = id1 == ID1 && id2 == ID2 && cm == CM;
|
var compressed = id1 == ID1 && id2 == ID2 && cm == CM;
|
||||||
|
|
||||||
logger.Debug($"'{data}' is {(compressed ? string.Empty : "not ")}a gzip-compressed stream.");
|
|
||||||
|
|
||||||
return compressed;
|
return compressed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +98,6 @@ namespace SafeExamBrowser.Configuration.Compression
|
||||||
{
|
{
|
||||||
var stream = new GZipStream(data, CompressionMode.Decompress);
|
var stream = new GZipStream(data, CompressionMode.Decompress);
|
||||||
|
|
||||||
logger.Debug($"Peeking {count} bytes from '{data}'...");
|
|
||||||
data.Seek(0, SeekOrigin.Begin);
|
data.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
using (var decompressed = new MemoryStream())
|
using (var decompressed = new MemoryStream())
|
||||||
|
|
|
@ -95,16 +95,15 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
|
|
||||||
private LoadStatus ParsePasswordBlock(Stream data, FormatType format, out Settings settings, string password, bool passwordIsHash)
|
private LoadStatus ParsePasswordBlock(Stream data, FormatType format, out Settings settings, string password, bool passwordIsHash)
|
||||||
{
|
{
|
||||||
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
|
||||||
|
|
||||||
settings = default(Settings);
|
settings = default(Settings);
|
||||||
|
|
||||||
// TODO: Check whether the hashing (bool passwordIsHash) can be extracted and moved to ConfigurationOperation!
|
|
||||||
if (format == FormatType.PasswordConfigureClient && !passwordIsHash)
|
if (format == FormatType.PasswordConfigureClient && !passwordIsHash)
|
||||||
{
|
{
|
||||||
password = hashAlgorithm.GenerateHashFor(password);
|
password = hashAlgorithm.GenerateHashFor(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
||||||
var status = encryption.Decrypt(data, out Stream decrypted, password);
|
var status = encryption.Decrypt(data, out Stream decrypted, password);
|
||||||
|
|
||||||
if (status == LoadStatus.Success)
|
if (status == LoadStatus.Success)
|
||||||
|
@ -129,12 +128,34 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
||||||
|
|
||||||
private LoadStatus ParsePublicKeyHashBlock(Stream data, out Settings settings, string password, bool passwordIsHash)
|
private LoadStatus ParsePublicKeyHashBlock(Stream data, out Settings settings, string password, bool passwordIsHash)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var encryption = new PublicKeyHashEncryption(logger.CloneFor(nameof(PublicKeyHashEncryption)));
|
||||||
|
var status = encryption.Decrypt(data, out Stream decrypted);
|
||||||
|
|
||||||
|
settings = default(Settings);
|
||||||
|
|
||||||
|
if (status == LoadStatus.Success)
|
||||||
|
{
|
||||||
|
return TryParse(decrypted, out settings, password, passwordIsHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoadStatus ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, out Settings settings, string password, bool passwordIsHash)
|
private LoadStatus ParsePublicKeyHashWithSymmetricKeyBlock(Stream data, out Settings settings, string password, bool passwordIsHash)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var logger = this.logger.CloneFor(nameof(PublicKeyHashWithSymmetricKeyEncryption));
|
||||||
|
var passwordEncryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
||||||
|
var encryption = new PublicKeyHashWithSymmetricKeyEncryption(logger, passwordEncryption);
|
||||||
|
var status = encryption.Decrypt(data, out Stream decrypted);
|
||||||
|
|
||||||
|
settings = default(Settings);
|
||||||
|
|
||||||
|
if (status == LoadStatus.Success)
|
||||||
|
{
|
||||||
|
return TryParse(decrypted, out settings, password, passwordIsHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ParsePrefix(Stream data)
|
private string ParsePrefix(Stream data)
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -32,11 +31,6 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Stream Encrypt(Stream data, string password)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
internal LoadStatus Decrypt(Stream data, out Stream decrypted, string password)
|
internal LoadStatus Decrypt(Stream data, out Stream decrypted, string password)
|
||||||
{
|
{
|
||||||
decrypted = default(Stream);
|
decrypted = default(Stream);
|
||||||
|
@ -50,9 +44,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
|
|
||||||
if (version != VERSION || options != OPTIONS)
|
if (version != VERSION || options != OPTIONS)
|
||||||
{
|
{
|
||||||
logger.Error($"Invalid encryption header! Expected: [{VERSION},{OPTIONS},...] - Actual: [{version},{options},...]");
|
return FailForInvalidHeader(version, options);
|
||||||
|
|
||||||
return LoadStatus.InvalidData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (authenticationKey, encryptionKey) = GenerateKeys(data, password);
|
var (authenticationKey, encryptionKey) = GenerateKeys(data, password);
|
||||||
|
@ -60,9 +52,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
|
|
||||||
if (!computedHmac.SequenceEqual(originalHmac))
|
if (!computedHmac.SequenceEqual(originalHmac))
|
||||||
{
|
{
|
||||||
logger.Warn($"The authentication failed due to an invalid password or corrupted data!");
|
return FailForInvalidHmac();
|
||||||
|
|
||||||
return LoadStatus.PasswordNeeded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decrypted = Decrypt(data, encryptionKey, originalHmac.Length);
|
decrypted = Decrypt(data, encryptionKey, originalHmac.Length);
|
||||||
|
@ -73,6 +63,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
private (int version, int options) ParseHeader(Stream data)
|
private (int version, int options) ParseHeader(Stream data)
|
||||||
{
|
{
|
||||||
data.Seek(0, SeekOrigin.Begin);
|
data.Seek(0, SeekOrigin.Begin);
|
||||||
|
logger.Debug("Parsing encryption header...");
|
||||||
|
|
||||||
var version = data.ReadByte();
|
var version = data.ReadByte();
|
||||||
var options = data.ReadByte();
|
var options = data.ReadByte();
|
||||||
|
@ -80,11 +71,20 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
return (version, options);
|
return (version, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LoadStatus FailForInvalidHeader(int version, int options)
|
||||||
|
{
|
||||||
|
logger.Error($"Invalid encryption header! Expected: [{VERSION},{OPTIONS},...] - Actual: [{version},{options},...]");
|
||||||
|
|
||||||
|
return LoadStatus.InvalidData;
|
||||||
|
}
|
||||||
|
|
||||||
private (byte[] authenticationKey, byte[] encryptionKey) GenerateKeys(Stream data, string password)
|
private (byte[] authenticationKey, byte[] encryptionKey) GenerateKeys(Stream data, string password)
|
||||||
{
|
{
|
||||||
var authenticationSalt = new byte[SALT_SIZE];
|
var authenticationSalt = new byte[SALT_SIZE];
|
||||||
var encryptionSalt = new byte[SALT_SIZE];
|
var encryptionSalt = new byte[SALT_SIZE];
|
||||||
|
|
||||||
|
logger.Debug("Generating keys for authentication and decryption...");
|
||||||
|
|
||||||
data.Seek(HEADER_SIZE, SeekOrigin.Begin);
|
data.Seek(HEADER_SIZE, SeekOrigin.Begin);
|
||||||
data.Read(encryptionSalt, 0, SALT_SIZE);
|
data.Read(encryptionSalt, 0, SALT_SIZE);
|
||||||
data.Read(authenticationSalt, 0, SALT_SIZE);
|
data.Read(authenticationSalt, 0, SALT_SIZE);
|
||||||
|
@ -101,11 +101,13 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
|
|
||||||
private (byte[] originalHmac, byte[] computedHmac) GenerateHmac(Stream data, byte[] authenticationKey)
|
private (byte[] originalHmac, byte[] computedHmac) GenerateHmac(Stream data, byte[] authenticationKey)
|
||||||
{
|
{
|
||||||
using (var hmac = new HMACSHA256(authenticationKey))
|
logger.Debug("Generating HMACs for authentication...");
|
||||||
|
|
||||||
|
using (var algorithm = new HMACSHA256(authenticationKey))
|
||||||
{
|
{
|
||||||
var originalHmac = new byte[hmac.HashSize / 8];
|
var originalHmac = new byte[algorithm.HashSize / 8];
|
||||||
var hashStream = new SubStream(data, 0, data.Length - originalHmac.Length);
|
var hashStream = new SubStream(data, 0, data.Length - originalHmac.Length);
|
||||||
var computedHmac = hmac.ComputeHash(hashStream);
|
var computedHmac = algorithm.ComputeHash(hashStream);
|
||||||
|
|
||||||
data.Seek(originalHmac.Length, SeekOrigin.End);
|
data.Seek(originalHmac.Length, SeekOrigin.End);
|
||||||
data.Read(originalHmac, 0, originalHmac.Length);
|
data.Read(originalHmac, 0, originalHmac.Length);
|
||||||
|
@ -114,6 +116,13 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LoadStatus FailForInvalidHmac()
|
||||||
|
{
|
||||||
|
logger.Warn($"The authentication failed due to an invalid password or corrupted data!");
|
||||||
|
|
||||||
|
return LoadStatus.PasswordNeeded;
|
||||||
|
}
|
||||||
|
|
||||||
private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength)
|
private Stream Decrypt(Stream data, byte[] encryptionKey, int hmacLength)
|
||||||
{
|
{
|
||||||
var initializationVector = new byte[BLOCK_SIZE];
|
var initializationVector = new byte[BLOCK_SIZE];
|
||||||
|
@ -124,8 +133,10 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
var decryptedData = new MemoryStream();
|
var decryptedData = new MemoryStream();
|
||||||
var encryptedData = new SubStream(data, data.Position, data.Length - data.Position - hmacLength);
|
var encryptedData = new SubStream(data, data.Position, data.Length - data.Position - hmacLength);
|
||||||
|
|
||||||
using (var aes = new AesManaged { KeySize = KEY_SIZE * 8, BlockSize = BLOCK_SIZE * 8, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
|
logger.Debug("Decrypting data...");
|
||||||
using (var decryptor = aes.CreateDecryptor(encryptionKey, initializationVector))
|
|
||||||
|
using (var algorithm = new AesManaged { KeySize = KEY_SIZE * 8, BlockSize = BLOCK_SIZE * 8, Mode = CipherMode.CBC, Padding = PaddingMode.PKCS7 })
|
||||||
|
using (var decryptor = algorithm.CreateDecryptor(encryptionKey, initializationVector))
|
||||||
using (var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
|
using (var cryptoStream = new CryptoStream(encryptedData, decryptor, CryptoStreamMode.Read))
|
||||||
{
|
{
|
||||||
cryptoStream.CopyTo(decryptedData);
|
cryptoStream.CopyTo(decryptedData);
|
||||||
|
|
|
@ -6,21 +6,130 @@
|
||||||
* 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.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using SafeExamBrowser.Contracts.Configuration;
|
||||||
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
{
|
{
|
||||||
internal class PublicKeyHashEncryption
|
internal class PublicKeyHashEncryption
|
||||||
{
|
{
|
||||||
internal Stream Encrypt(Stream data, string password)
|
protected const int PUBLIC_KEY_HASH_SIZE = 20;
|
||||||
|
|
||||||
|
protected ILogger logger;
|
||||||
|
|
||||||
|
internal PublicKeyHashEncryption(ILogger logger)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Stream Decrypt(Stream data, string password)
|
internal virtual LoadStatus Decrypt(Stream data, out Stream decrypted)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var keyHash = ParsePublicKeyHash(data);
|
||||||
|
var found = TryGetCertificateWith(keyHash, out X509Certificate2 certificate);
|
||||||
|
|
||||||
|
decrypted = default(Stream);
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
return FailForMissingCertificate();
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypted = Decrypt(data, PUBLIC_KEY_HASH_SIZE, certificate);
|
||||||
|
|
||||||
|
return LoadStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] ParsePublicKeyHash(Stream data)
|
||||||
|
{
|
||||||
|
var keyHash = new byte[PUBLIC_KEY_HASH_SIZE];
|
||||||
|
|
||||||
|
logger.Debug("Parsing public key hash...");
|
||||||
|
|
||||||
|
data.Seek(0, SeekOrigin.Begin);
|
||||||
|
data.Read(keyHash, 0, keyHash.Length);
|
||||||
|
|
||||||
|
return keyHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool TryGetCertificateWith(byte[] keyHash, out X509Certificate2 certificate)
|
||||||
|
{
|
||||||
|
var storesToSearch = new[]
|
||||||
|
{
|
||||||
|
new X509Store(StoreLocation.CurrentUser),
|
||||||
|
new X509Store(StoreLocation.LocalMachine),
|
||||||
|
new X509Store(StoreName.TrustedPeople)
|
||||||
|
};
|
||||||
|
|
||||||
|
certificate = default(X509Certificate2);
|
||||||
|
logger.Debug("Searching certificate for decryption...");
|
||||||
|
|
||||||
|
using (var algorithm = new SHA1CryptoServiceProvider())
|
||||||
|
{
|
||||||
|
foreach (var store in storesToSearch)
|
||||||
|
{
|
||||||
|
store.Open(OpenFlags.ReadOnly);
|
||||||
|
|
||||||
|
foreach (var current in store.Certificates)
|
||||||
|
{
|
||||||
|
var publicKey = current.PublicKey.EncodedKeyValue.RawData;
|
||||||
|
var publicKeyHash = algorithm.ComputeHash(publicKey);
|
||||||
|
|
||||||
|
if (publicKeyHash.SequenceEqual(keyHash))
|
||||||
|
{
|
||||||
|
certificate = current;
|
||||||
|
store.Close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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++)
|
||||||
|
{
|
||||||
|
data.Read(encrypted, 0, encrypted.Length);
|
||||||
|
decrypted = algorithm.Decrypt(encrypted, false);
|
||||||
|
decryptedData.Write(decrypted, 0, decrypted.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainingBytes > 0)
|
||||||
|
{
|
||||||
|
encrypted = new byte[remainingBytes];
|
||||||
|
data.Read(encrypted, 0, encrypted.Length);
|
||||||
|
decrypted = algorithm.Decrypt(encrypted, false);
|
||||||
|
decryptedData.Write(decrypted, 0, decrypted.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptedData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,61 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using SafeExamBrowser.Contracts.Configuration;
|
||||||
|
using SafeExamBrowser.Contracts.Logging;
|
||||||
|
|
||||||
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
||||||
{
|
{
|
||||||
internal class PublicKeyHashWithSymmetricKeyEncryption
|
internal class PublicKeyHashWithSymmetricKeyEncryption : PublicKeyHashEncryption
|
||||||
{
|
{
|
||||||
internal Stream Encrypt(Stream data, string password)
|
private const int KEY_LENGTH_SIZE = 4;
|
||||||
|
|
||||||
|
private PasswordEncryption passwordEncryption;
|
||||||
|
|
||||||
|
internal PublicKeyHashWithSymmetricKeyEncryption(ILogger logger, PasswordEncryption passwordEncryption) : base(logger)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
this.passwordEncryption = passwordEncryption;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Stream Decrypt(Stream data, string password)
|
internal override LoadStatus Decrypt(Stream data, out Stream decrypted)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var keyHash = ParsePublicKeyHash(data);
|
||||||
|
var found = TryGetCertificateWith(keyHash, out X509Certificate2 certificate);
|
||||||
|
|
||||||
|
decrypted = default(Stream);
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
return FailForMissingCertificate();
|
||||||
|
}
|
||||||
|
|
||||||
|
var symmetricKey = ParseSymmetricKey(data, certificate);
|
||||||
|
var stream = new SubStream(data, data.Position, data.Length - data.Position);
|
||||||
|
var status = passwordEncryption.Decrypt(stream, out decrypted, symmetricKey);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ParseSymmetricKey(Stream data, X509Certificate2 certificate)
|
||||||
|
{
|
||||||
|
var keyLengthData = new byte[KEY_LENGTH_SIZE];
|
||||||
|
|
||||||
|
logger.Debug("Parsing symmetric key...");
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
data.Read(encryptedKey, 0, encryptedKey.Length);
|
||||||
|
|
||||||
|
var stream = new SubStream(data, PUBLIC_KEY_HASH_SIZE + KEY_LENGTH_SIZE, keyLength);
|
||||||
|
var decryptedKey = Decrypt(stream, 0, certificate);
|
||||||
|
var symmetricKey = Convert.ToBase64String(decryptedKey.ToArray());
|
||||||
|
|
||||||
|
return symmetricKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue