SEBWIN-221: Implemented basic certificate decryption.

This commit is contained in:
dbuechel 2018-11-29 15:00:10 +01:00
parent 243d32879e
commit 483ba7ad4a
5 changed files with 214 additions and 34 deletions

View file

@ -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())

View file

@ -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)

View file

@ -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);

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}