129 lines
4.1 KiB
C#
129 lines
4.1 KiB
C#
/*
|
|
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using SafeExamBrowser.Configuration.Contracts;
|
|
using SafeExamBrowser.Configuration.Contracts.Cryptography;
|
|
using SafeExamBrowser.Logging.Contracts;
|
|
|
|
namespace SafeExamBrowser.Configuration.Cryptography
|
|
{
|
|
public class PublicKeySymmetricEncryption : PublicKeyEncryption
|
|
{
|
|
private const int ENCRYPTION_KEY_LENGTH = 32;
|
|
private const int KEY_LENGTH_SIZE = 4;
|
|
|
|
private PasswordEncryption passwordEncryption;
|
|
|
|
public PublicKeySymmetricEncryption(ICertificateStore store, ILogger logger, PasswordEncryption passwordEncryption) : base(store, logger)
|
|
{
|
|
this.passwordEncryption = passwordEncryption;
|
|
}
|
|
|
|
public override LoadStatus Decrypt(Stream data, out Stream decryptedData, out X509Certificate2 certificate)
|
|
{
|
|
var publicKeyHash = ParsePublicKeyHash(data);
|
|
var found = store.TryGetCertificateWith(publicKeyHash, out certificate);
|
|
|
|
decryptedData = 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, symmetricKey, out decryptedData);
|
|
|
|
return status;
|
|
}
|
|
|
|
public override SaveStatus Encrypt(Stream data, X509Certificate2 certificate, out Stream encryptedData)
|
|
{
|
|
var publicKeyHash = GeneratePublicKeyHash(certificate);
|
|
var symmetricKey = GenerateSymmetricKey();
|
|
var symmetricKeyString = Convert.ToBase64String(symmetricKey);
|
|
var status = passwordEncryption.Encrypt(data, symmetricKeyString, out encryptedData);
|
|
|
|
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)
|
|
{
|
|
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 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, 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;
|
|
}
|
|
}
|
|
}
|