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 compressed = id1 == ID1 && id2 == ID2 && cm == CM;
|
||||
|
||||
logger.Debug($"'{data}' is {(compressed ? string.Empty : "not ")}a gzip-compressed stream.");
|
||||
|
||||
return compressed;
|
||||
}
|
||||
|
||||
|
@ -100,7 +98,6 @@ namespace SafeExamBrowser.Configuration.Compression
|
|||
{
|
||||
var stream = new GZipStream(data, CompressionMode.Decompress);
|
||||
|
||||
logger.Debug($"Peeking {count} bytes from '{data}'...");
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
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)
|
||||
{
|
||||
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
||||
|
||||
settings = default(Settings);
|
||||
|
||||
// TODO: Check whether the hashing (bool passwordIsHash) can be extracted and moved to ConfigurationOperation!
|
||||
if (format == FormatType.PasswordConfigureClient && !passwordIsHash)
|
||||
{
|
||||
password = hashAlgorithm.GenerateHashFor(password);
|
||||
}
|
||||
|
||||
var encryption = new PasswordEncryption(logger.CloneFor(nameof(PasswordEncryption)));
|
||||
var status = encryption.Decrypt(data, out Stream decrypted, password);
|
||||
|
||||
if (status == LoadStatus.Success)
|
||||
|
@ -129,12 +128,34 @@ namespace SafeExamBrowser.Configuration.DataFormats
|
|||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
|
|
@ -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;
|
||||
|
@ -32,11 +31,6 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
this.logger = logger;
|
||||
}
|
||||
|
||||
internal Stream Encrypt(Stream data, string password)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
internal LoadStatus Decrypt(Stream data, out Stream decrypted, string password)
|
||||
{
|
||||
decrypted = default(Stream);
|
||||
|
@ -50,9 +44,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
|
||||
if (version != VERSION || options != OPTIONS)
|
||||
{
|
||||
logger.Error($"Invalid encryption header! Expected: [{VERSION},{OPTIONS},...] - Actual: [{version},{options},...]");
|
||||
|
||||
return LoadStatus.InvalidData;
|
||||
return FailForInvalidHeader(version, options);
|
||||
}
|
||||
|
||||
var (authenticationKey, encryptionKey) = GenerateKeys(data, password);
|
||||
|
@ -60,9 +52,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
|
||||
if (!computedHmac.SequenceEqual(originalHmac))
|
||||
{
|
||||
logger.Warn($"The authentication failed due to an invalid password or corrupted data!");
|
||||
|
||||
return LoadStatus.PasswordNeeded;
|
||||
return FailForInvalidHmac();
|
||||
}
|
||||
|
||||
decrypted = Decrypt(data, encryptionKey, originalHmac.Length);
|
||||
|
@ -73,6 +63,7 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
private (int version, int options) ParseHeader(Stream data)
|
||||
{
|
||||
data.Seek(0, SeekOrigin.Begin);
|
||||
logger.Debug("Parsing encryption header...");
|
||||
|
||||
var version = data.ReadByte();
|
||||
var options = data.ReadByte();
|
||||
|
@ -80,11 +71,20 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
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)
|
||||
{
|
||||
var authenticationSalt = 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.Read(encryptionSalt, 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)
|
||||
{
|
||||
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 computedHmac = hmac.ComputeHash(hashStream);
|
||||
var computedHmac = algorithm.ComputeHash(hashStream);
|
||||
|
||||
data.Seek(originalHmac.Length, SeekOrigin.End);
|
||||
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)
|
||||
{
|
||||
var initializationVector = new byte[BLOCK_SIZE];
|
||||
|
@ -124,8 +133,10 @@ namespace SafeExamBrowser.Configuration.DataFormats.Cryptography
|
|||
var decryptedData = new MemoryStream();
|
||||
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 })
|
||||
using (var decryptor = aes.CreateDecryptor(encryptionKey, initializationVector))
|
||||
logger.Debug("Decrypting data...");
|
||||
|
||||
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))
|
||||
{
|
||||
cryptoStream.CopyTo(decryptedData);
|
||||
|
|
|
@ -6,21 +6,130 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
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
|
||||
{
|
||||
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.IO;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using SafeExamBrowser.Contracts.Configuration;
|
||||
using SafeExamBrowser.Contracts.Logging;
|
||||
|
||||
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