seb-win-refactoring/SebWindowsConfig/Utilities/SEBConfigFileManager.cs

908 lines
37 KiB
C#

using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Windows.Forms;
using DictObj = System.Collections.Generic.Dictionary<string, object>;
//
// SEBConfigFileManager.cs
// SafeExamBrowser
//
// Copyright (c) 2010-2020 Daniel R. Schneider,
// ETH Zurich, Educational Development and Technology (LET),
// based on the original idea of Safe Exam Browser
// by Stefan Schneider, University of Giessen
// Project concept: Thomas Piendl, Daniel R. Schneider,
// Dirk Bauer, Kai Reuter, Tobias Halbherr, Karsten Burger, Marco Lehre,
// Brigitte Schmucki, Oliver Rahs. French localization: Nicolas Dunand
//
// ``The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is Safe Exam Browser for Windows.
//
// The Initial Developer of the Original Code is Daniel R. Schneider.
// Portions created by Daniel R. Schneider
// are Copyright (c) 2010-2020 Daniel R. Schneider,
// ETH Zurich, Educational Development and Technology (LET),
// based on the original idea of Safe Exam Browser
// by Stefan Schneider, University of Giessen. All Rights Reserved.
//
// Contributor(s): ______________________________________.
//
namespace SebWindowsConfig.Utilities
{
public class SEBConfigFileManager
{
public static SebPasswordDialogForm sebPasswordDialogForm;
// Prefixes
private const int PREFIX_LENGTH = 4;
private const int MULTIPART_LENGTH = 8;
private const int CUSTOMHEADER_LENGTH = 4;
private const string PUBLIC_KEY_HASH_MODE = "pkhs";
private const string PUBLIC_SYMMETRIC_KEY_MODE = "phsk";
private const string PASSWORD_MODE = "pswd";
private const string PLAIN_DATA_MODE = "plnd";
private const string PASSWORD_CONFIGURING_CLIENT_MODE = "pwcc";
private const string UNENCRYPTED_MODE = "<?xm";
private const string MULTIPART_MODE = "mphd";
private const string CUSTOM_HEADER_MODE = "cmhd";
// Public key hash identifier length
private const int PUBLIC_KEY_HASH_LENGTH = 20;
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Decrypt and deserialize SEB settings
/// When forEditing = true, then the decrypting password the user entered and/or
/// certificate reference found in the .seb file is returned
/// </summary>
/// ----------------------------------------------------------------------------------------
public static DictObj DecryptSEBSettings(byte[] sebData, bool forEditing, ref string sebFilePassword, ref bool passwordIsHash, ref X509Certificate2 sebFileCertificateRef, bool suppressFileFormatError = false)
{
// Ungzip the .seb (according to specification >= v14) source data
byte[] unzippedSebData = GZipByte.Decompress(sebData);
// if unzipped data is not null, then unzipping worked, we use unzipped data
// if unzipped data is null, then the source data may be an uncompressed .seb file, we proceed with it
if (unzippedSebData != null) sebData = unzippedSebData;
string prefixString;
// save the data including the first 4 bytes for the case that it's acutally an unencrypted XML plist
byte[] sebDataUnencrypted = sebData.Clone() as byte[];
// Get 4-char prefix
prefixString = GetPrefixStringFromData(ref sebData);
//// Check prefix identifying encryption modes
/// Check for new Multipart and Custom headers
// Multipart Config File: The first part containts the regular SEB key/value settings
// following parts can contain additional resources. An updated SEB version will be
// able to read and process those parts sequentially as a stream.
// Therefore potentially large additional resources won't have to be loaded into memory at once
if (prefixString.CompareTo(MULTIPART_MODE) == 0)
{
// Skip the Multipart Config File header
byte[] multipartConfigLengthData = GetPrefixDataFromData(ref sebData, MULTIPART_LENGTH);
long multipartConfigLength = BitConverter.ToInt64(multipartConfigLengthData, 0);
Logger.AddInformation("Multipart Config File, first part (settings) length: " + multipartConfigLength);
try
{
Logger.AddInformation("Cropping config file, as this SEB version cannot process additional parts of multipart config files.");
byte[] dataFirstPart = new byte[sebData.Length - multipartConfigLength];
Buffer.BlockCopy(sebData, 0, dataFirstPart, 0, dataFirstPart.Length);
sebData = dataFirstPart;
}
catch (Exception ex)
{
Logger.AddError("Error while cropping config file", null, ex, ex.Message);
}
}
// Custom Header: Containts a 32 bit value for the length of the header
// followed by the custom header information. After the header, regular
// SEB config file data follows
if (prefixString.CompareTo(CUSTOM_HEADER_MODE) == 0)
{
// Skip the Custom Header
byte[] customHeaderLengthData = GetPrefixDataFromData(ref sebData, CUSTOMHEADER_LENGTH);
int customHeaderLength = BitConverter.ToInt32(customHeaderLengthData, 0);
Logger.AddInformation("Custom Config File Header length: " + customHeaderLength);
try
{
Logger.AddInformation("Removing custom header from config file data. This SEB version cannot process this header type and will ignore it.");
byte[] customHeaderData = GetPrefixDataFromData(ref sebData, customHeaderLength);
Logger.AddInformation("Custom header data: " + customHeaderData);
}
catch (Exception ex)
{
Logger.AddError("Error while removing custom header from config file data", null, ex, ex.Message);
}
}
// Prefix = pksh ("Public-Symmetric Key Hash") ?
if (prefixString.CompareTo(PUBLIC_SYMMETRIC_KEY_MODE) == 0)
{
// Decrypt with cryptographic identity/private and symmetric key
sebData = DecryptDataWithPublicKeyHashPrefix(sebData, true, forEditing, ref sebFileCertificateRef);
if (sebData == null)
{
return null;
}
// Get 4-char prefix again
// and remaining data without prefix, which is either plain or still encoded with password
prefixString = GetPrefixStringFromData(ref sebData);
}
// Prefix = pkhs ("Public Key Hash") ?
if (prefixString.CompareTo(PUBLIC_KEY_HASH_MODE) == 0)
{
// Decrypt with cryptographic identity/private key
sebData = DecryptDataWithPublicKeyHashPrefix(sebData, false, forEditing, ref sebFileCertificateRef);
if (sebData == null)
{
return null;
}
// Get 4-char prefix again
// and remaining data without prefix, which is either plain or still encoded with password
prefixString = GetPrefixStringFromData(ref sebData);
}
// Prefix = pswd ("Password") ?
if (prefixString.CompareTo(PASSWORD_MODE) == 0)
{
// Decrypt with password
// if the user enters the right one
byte[] sebDataDecrypted = null;
string password;
// Allow up to 5 attempts for entering decoding password
string enterPasswordString = SEBUIStrings.enterPassword;
int i = 5;
do
{
i--;
// Prompt for password
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.loadingSettings, enterPasswordString);
if (password == null) return null;
//error = nil;
sebDataDecrypted = SEBProtectionController.DecryptDataWithPassword(sebData, password);
enterPasswordString = SEBUIStrings.enterPasswordAgain;
// in case we get an error we allow the user to try it again
} while ((sebDataDecrypted == null) && i > 0);
if (sebDataDecrypted == null)
{
//wrong password entered in 5th try: stop reading .seb file
MessageBox.Show(SEBUIStrings.decryptingSettingsFailedReason, SEBUIStrings.decryptingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
sebData = sebDataDecrypted;
// If these settings are being decrypted for editing, we return the decryption password
if (forEditing) sebFilePassword = password;
}
else
{
// Prefix = pwcc ("Password Configuring Client") ?
if (prefixString.CompareTo(PASSWORD_CONFIGURING_CLIENT_MODE) == 0)
{
// Decrypt with password and configure local client settings
// and quit afterwards, returning if reading the .seb file was successfull
DictObj sebSettings = DecryptDataWithPasswordForConfiguringClient(sebData, forEditing, ref sebFilePassword, ref passwordIsHash);
return sebSettings;
}
else
{
// Prefix = plnd ("Plain Data") ?
if (prefixString.CompareTo(PLAIN_DATA_MODE) != 0)
{
// No valid 4-char prefix was found in the .seb file
// Check if .seb file is unencrypted
if (prefixString.CompareTo(UNENCRYPTED_MODE) == 0)
{
// .seb file seems to be an unencrypted XML plist
// get the original data including the first 4 bytes
sebData = sebDataUnencrypted;
}
else
{
// No valid prefix and no unencrypted file with valid header
// cancel reading .seb file
if (!suppressFileFormatError)
{
MessageBox.Show(SEBUIStrings.settingsNotUsableReason, SEBUIStrings.settingsNotUsable, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return null;
}
}
}
}
// If we don't deal with an unencrypted seb file
// ungzip the .seb (according to specification >= v14) decrypted serialized XML plist data
if (prefixString.CompareTo(UNENCRYPTED_MODE) != 0)
{
sebData = GZipByte.Decompress(sebData);
}
// Get preferences dictionary from decrypted data
DictObj sebPreferencesDict = GetPreferencesDictFromConfigData(sebData, forEditing);
DictObj sebPreferencesDictOriginal = GetPreferencesDictFromConfigData(sebData, false);
// If we didn't get a preferences dict back, we abort reading settings
if (sebPreferencesDict == null) return null;
// We need to set the right value for the key sebConfigPurpose to know later where to store the new settings
sebPreferencesDict[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam;
sebPreferencesDictOriginal[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam;
SEBSettings.settingsCurrentOriginal = sebPreferencesDictOriginal;
// Reading preferences was successful!
return sebPreferencesDict;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method which decrypts the byte array using an empty password,
/// or the administrator password currently set in SEB
/// or asks for the password used for encrypting this SEB file
/// for configuring the client
/// </summary>
/// ----------------------------------------------------------------------------------------
private static DictObj DecryptDataWithPasswordForConfiguringClient(byte[] sebData, bool forEditing, ref string sebFilePassword, ref bool passwordIsHash)
{
passwordIsHash = false;
string password;
// First try to decrypt with the current admin password
// get admin password hash
string hashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(SEBSettings.settingsCurrent, SEBSettings.KeyHashedAdminPassword);
if (hashedAdminPassword == null)
{
hashedAdminPassword = "";
}
// We use always uppercase letters in the base16 hashed admin password used for encrypting
hashedAdminPassword = hashedAdminPassword.ToUpper();
DictObj sebPreferencesDict = null;
DictObj sebPreferencesDictOriginal = null;
byte[] decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, hashedAdminPassword);
if (decryptedSebData == null)
{
// If decryption with admin password didn't work, try it with an empty password
decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, "");
if (decryptedSebData == null)
{
// If decryption with empty and admin password didn't work, ask for the password the .seb file was encrypted with
// Allow up to 5 attempts for entering decoding password
int i = 5;
password = null;
string enterPasswordString = SEBUIStrings.enterEncryptionPassword;
do
{
i--;
// Prompt for password
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.reconfiguringLocalSettings, enterPasswordString);
// If cancel was pressed, abort
if (password == null) return null;
string hashedPassword = SEBProtectionController.ComputePasswordHash(password);
// we try to decrypt with the hashed password
decryptedSebData = SEBProtectionController.DecryptDataWithPassword(sebData, hashedPassword);
// in case we get an error we allow the user to try it again
enterPasswordString = SEBUIStrings.enterEncryptionPasswordAgain;
} while (decryptedSebData == null && i > 0);
if (decryptedSebData == null)
{
//wrong password entered in 5th try: stop reading .seb file
MessageBox.Show(SEBUIStrings.reconfiguringLocalSettingsFailedWrongPassword, SEBUIStrings.reconfiguringLocalSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
else
{
// Decrypting with entered password worked: We save it for returning it later
if (forEditing) sebFilePassword = password;
}
}
}
else
{
//decrypting with hashedAdminPassword worked: we save it for returning as decryption password
sebFilePassword = hashedAdminPassword;
// identify that password as hash
passwordIsHash = true;
}
/// Decryption worked
// Ungzip the .seb (according to specification >= v14) decrypted serialized XML plist data
decryptedSebData = GZipByte.Decompress(decryptedSebData);
// Check if the openend reconfiguring seb file has the same admin password inside like the current one
try
{
sebPreferencesDict = (DictObj)Plist.readPlist(decryptedSebData);
sebPreferencesDictOriginal = (DictObj)Plist.readPlist(decryptedSebData);
}
catch (Exception readPlistException)
{
// Error when deserializing the decrypted configuration data
// We abort reading the new settings here
MessageBox.Show(SEBUIStrings.loadingSettingsFailedReason, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
Console.WriteLine(readPlistException.Message);
return null;
}
// Get the admin password set in these settings
string sebFileHashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(sebPreferencesDict, SEBSettings.KeyHashedAdminPassword);
if (sebFileHashedAdminPassword == null)
{
sebFileHashedAdminPassword = "";
}
// Has the SEB config file the same admin password inside as the current settings have?
if (String.Compare(hashedAdminPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) != 0)
{
//No: The admin password inside the .seb file wasn't the same as the current one
if (forEditing)
{
// If the file is openend for editing (and not to reconfigure SEB)
// we have to ask the user for the admin password inside the file
if (!askForPasswordAndCompareToHashedPassword(sebFileHashedAdminPassword, forEditing))
{
// If the user didn't enter the right password we abort
return null;
}
}
else
{
// The file was actually opened for reconfiguring the SEB client:
// we have to ask for the current admin password and
// allow reconfiguring only if the user enters the right one
// We don't check this for the case the current admin password was used to encrypt the new settings
// In this case there can be a new admin pw defined in the new settings and users don't need to enter the old one
if (passwordIsHash == false && hashedAdminPassword.Length > 0)
{
// Allow up to 5 attempts for entering current admin password
int i = 5;
password = null;
string hashedPassword;
string enterPasswordString = SEBUIStrings.enterCurrentAdminPwdForReconfiguring;
bool passwordsMatch;
do
{
i--;
// Prompt for password
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.reconfiguringLocalSettings, enterPasswordString);
// If cancel was pressed, abort
if (password == null) return null;
if (password.Length == 0)
{
hashedPassword = "";
}
else
{
hashedPassword = SEBProtectionController.ComputePasswordHash(password);
}
passwordsMatch = (String.Compare(hashedPassword, hashedAdminPassword, StringComparison.OrdinalIgnoreCase) == 0);
// in case we get an error we allow the user to try it again
enterPasswordString = SEBUIStrings.enterCurrentAdminPwdForReconfiguringAgain;
} while (!passwordsMatch && i > 0);
if (!passwordsMatch)
{
//wrong password entered in 5th try: stop reading .seb file
MessageBox.Show(SEBUIStrings.reconfiguringLocalSettingsFailedWrongCurrentAdminPwd, SEBUIStrings.reconfiguringLocalSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
}
}
}
// We need to set the right value for the key sebConfigPurpose to know later where to store the new settings
sebPreferencesDict[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient;
sebPreferencesDictOriginal[SEBSettings.KeySebConfigPurpose] = (int)SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient;
SEBSettings.settingsCurrentOriginal = sebPreferencesDictOriginal;
// Reading preferences was successful!
return sebPreferencesDict;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method: Get preferences dictionary from decrypted data.
/// In editing mode, users have to enter the right SEB administrator password
/// before they can access the settings contents
/// and returns the decrypted bytes
/// </summary>
/// ----------------------------------------------------------------------------------------
private static DictObj GetPreferencesDictFromConfigData(byte[] sebData, bool forEditing)
{
DictObj sebPreferencesDict = null;
try
{
// Get preferences dictionary from decrypted data
sebPreferencesDict = (DictObj)Plist.readPlist(sebData);
}
catch (Exception readPlistException)
{
MessageBox.Show(SEBUIStrings.loadingSettingsFailedReason, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
Console.WriteLine(readPlistException.Message);
return null;
}
// In editing mode, the user has to enter the right SEB administrator password used in those settings before he can access their contents
if (forEditing)
{
// Get the admin password set in these settings
string sebFileHashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(sebPreferencesDict, SEBSettings.KeyHashedAdminPassword);
// If there was no or empty admin password set in these settings, the user can access them anyways
if (!String.IsNullOrEmpty(sebFileHashedAdminPassword))
{
// Get the current hashed admin password
string hashedAdminPassword = (string)SEBSettings.valueForDictionaryKey(SEBSettings.settingsCurrent, SEBSettings.KeyHashedAdminPassword);
if (hashedAdminPassword == null)
{
hashedAdminPassword = "";
}
// If the current hashed admin password is same as the hashed admin password from the settings file
// then the user is allowed to access the settings
if (String.Compare(hashedAdminPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) != 0)
{
// otherwise we have to ask for the SEB administrator password used in those settings and
// allow opening settings only if the user enters the right one
if (!askForPasswordAndCompareToHashedPassword(sebFileHashedAdminPassword, forEditing))
{
return null;
}
}
}
}
// Reading preferences was successful!
return sebPreferencesDict;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Ask user to enter password and compare it to the passed (hashed) password string
/// Returns true if correct password was entered
/// </summary>
/// ----------------------------------------------------------------------------------------
private static bool askForPasswordAndCompareToHashedPassword(string sebFileHashedAdminPassword, bool forEditing)
{
// Check if there wasn't a hashed password (= empty password)
if (sebFileHashedAdminPassword.Length == 0) return true;
// We have to ask for the SEB administrator password used in the settings
// and allow opening settings only if the user enters the right one
// Allow up to 5 attempts for entering admin password
int i = 5;
string password = null;
string hashedPassword;
string enterPasswordString = SEBUIStrings.enterAdminPasswordRequired;
bool passwordsMatch;
do
{
i--;
// Prompt for password
password = SebPasswordDialogForm.ShowPasswordDialogForm(SEBUIStrings.loadingSettings + (String.IsNullOrEmpty(SEBClientInfo.LoadingSettingsFileName) ? "" : ": " + SEBClientInfo.LoadingSettingsFileName), enterPasswordString);
// If cancel was pressed, abort
if (password == null) return false;
if (password.Length == 0)
{
hashedPassword = "";
}
else
{
hashedPassword = SEBProtectionController.ComputePasswordHash(password);
}
passwordsMatch = (String.Compare(hashedPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) == 0);
// in case we get an error we allow the user to try it again
enterPasswordString = SEBUIStrings.enterAdminPasswordRequiredAgain;
} while ((password == null || !passwordsMatch) && i > 0);
if (!passwordsMatch)
{
//wrong password entered in 5th try: stop reading .seb file
MessageBox.Show(SEBUIStrings.loadingSettingsFailedWrongAdminPwd, SEBUIStrings.loadingSettingsFailed, MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// Right password entered
return passwordsMatch;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method which fetches the public key hash from a byte array,
/// retrieves the according cryptographic identity from the certificate store
/// and returns the decrypted bytes
/// </summary>
/// ----------------------------------------------------------------------------------------
private static byte[] DecryptDataWithPublicKeyHashPrefix(byte[] sebData, bool usingSymmetricKey, bool forEditing, ref X509Certificate2 sebFileCertificateRef)
{
// Get 20 bytes public key hash prefix
// and remaining data with the prefix stripped
byte[] publicKeyHash = GetPrefixDataFromData(ref sebData, PUBLIC_KEY_HASH_LENGTH);
X509Certificate2 certificateRef = SEBProtectionController.GetCertificateFromStore(publicKeyHash);
if (certificateRef == null)
{
MessageBox.Show(SEBUIStrings.certificateNotFoundInStore, SEBUIStrings.errorDecryptingSettings, MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
// If these settings are being decrypted for editing, we will return the decryption certificate reference
// in the variable which was passed as reference when calling this method
if (forEditing) sebFileCertificateRef = certificateRef;
// Are we using the new identity certificate decryption with a symmetric key?
if (usingSymmetricKey)
{
// Get length of the encrypted symmetric key
Int32 encryptedSymmetricKeyLength = BitConverter.ToInt32(GetPrefixDataFromData(ref sebData, sizeof(Int32)), 0);
// Get encrypted symmetric key
byte[] encryptedSymmetricKey = GetPrefixDataFromData(ref sebData, encryptedSymmetricKeyLength);
// Decrypt symmetric key
byte[] symmetricKey = SEBProtectionController.DecryptDataWithCertificate(encryptedSymmetricKey, certificateRef);
if (symmetricKey == null)
{
return null;
}
string symmetricKeyString = Convert.ToBase64String(symmetricKey);
// Decrypt config file data using the symmetric key as password
sebData = SEBProtectionController.DecryptDataWithPassword(sebData, symmetricKeyString);
}
else
{
sebData = SEBProtectionController.DecryptDataWithCertificate(sebData, certificateRef);
}
return sebData;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method for returning a prefix string (of PREFIX_LENGTH, currently 4 chars)
/// from a data byte array which is returned without the stripped prefix
/// </summary>
/// ----------------------------------------------------------------------------------------
public static string GetPrefixStringFromData(ref byte[] data)
{
string decryptedDataString = Encoding.UTF8.GetString(GetPrefixDataFromData(ref data, PREFIX_LENGTH));
return decryptedDataString;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Helper method for stripping (and returning) a prefix byte array of prefixLength
/// from a data byte array which is returned without the stripped prefix
/// </summary>
/// ----------------------------------------------------------------------------------------
public static byte[] GetPrefixDataFromData(ref byte[] data, int prefixLength)
{
// Get prefix with indicated length
byte[] prefixData = new byte[prefixLength];
Buffer.BlockCopy(data, 0, prefixData, 0, prefixLength);
// Get data without the stripped prefix
byte[] dataStrippedKey = new byte[data.Length - prefixLength];
Buffer.BlockCopy(data, prefixLength, dataStrippedKey, 0, data.Length - prefixLength);
data = dataStrippedKey;
return prefixData;
}
///// ----------------------------------------------------------------------------------------
///// <summary>
///// Show SEB Password Dialog Form.
///// </summary>
///// ----------------------------------------------------------------------------------------
//public static string ShowPasswordDialogForm(string title, string passwordRequestText)
//{
// // Set the title of the dialog window
// sebPasswordDialogForm.Text = title;
// // Set the text of the dialog
// sebPasswordDialogForm.LabelText = passwordRequestText;
// sebPasswordDialogForm.txtSEBPassword.Focus();
// // If we are running in SebWindowsClient we need to activate it before showing the password dialog
// if (SEBClientInfo.SebWindowsClientForm != null) SebWindowsClientForm.SEBToForeground(); //SEBClientInfo.SebWindowsClientForm.Activate();
// // Show password dialog as a modal dialog and determine if DialogResult = OK.
// if (sebPasswordDialogForm.ShowDialog() == DialogResult.OK)
// {
// // Read the contents of testDialog's TextBox.
// string password = sebPasswordDialogForm.txtSEBPassword.Text;
// sebPasswordDialogForm.txtSEBPassword.Text = "";
// //sebPasswordDialogForm.txtSEBPassword.Focus();
// return password;
// }
// else
// {
// return null;
// }
//}
/// Generate Encrypted .seb Settings Data
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Read SEB settings from UserDefaults and encrypt them using provided security credentials
/// </summary>
/// ----------------------------------------------------------------------------------------
public static byte[] EncryptSEBSettingsWithCredentials(string settingsPassword, bool passwordIsHash, X509Certificate2 certificateRef, bool useAsymmetricOnlyEncryption, SEBSettings.sebConfigPurposes configPurpose, bool forEditing)
{
// Get current settings dictionary and clean it from empty arrays and dictionaries
//DictObj cleanedCurrentSettings = SEBSettings.CleanSettingsDictionary();
SEBSettings.settingsCurrentOriginal = SEBSettings.settingsCurrent;
// Serialize preferences dictionary to an XML string
string sebXML = Plist.writeXml(SEBSettings.settingsCurrent);
string cleanedSebXML = sebXML.Replace("<array />", "<array></array>");
cleanedSebXML = cleanedSebXML.Replace("<dict />", "<dict></dict>");
cleanedSebXML = cleanedSebXML.Replace("<data />", "<data></data>");
byte[] encryptedSebData = Encoding.UTF8.GetBytes(cleanedSebXML);
string encryptingPassword = null;
// Check for special case: .seb configures client, empty password
if (String.IsNullOrEmpty(settingsPassword) && configPurpose == SEBSettings.sebConfigPurposes.sebConfigPurposeConfiguringClient)
{
encryptingPassword = "";
}
else
{
// in all other cases:
// Check if no password entered and no identity selected
if (String.IsNullOrEmpty(settingsPassword) && certificateRef == null)
{
if (MessageBox.Show(SEBUIStrings.noEncryptionChosenSaveUnencrypted, SEBUIStrings.noEncryptionChosen, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
// OK: save .seb config data unencrypted
return encryptedSebData;
}
else
{
return null;
}
}
}
// gzip the serialized XML data
encryptedSebData = GZipByte.Compress(encryptedSebData);
// Check if password for encryption is provided and use it then
if (!String.IsNullOrEmpty(settingsPassword))
{
encryptingPassword = settingsPassword;
}
// So if password is empty (special case) or provided
if (!(encryptingPassword == null))
{
// encrypt with password
encryptedSebData = EncryptDataUsingPassword(encryptedSebData, encryptingPassword, passwordIsHash, configPurpose);
}
else
{
// Create byte array large enough to hold prefix and data
byte[] encryptedData = new byte[encryptedSebData.Length + PREFIX_LENGTH];
// if no encryption with password: Add a 4-char prefix identifying plain data
string prefixString = PLAIN_DATA_MODE;
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedData, 0, PREFIX_LENGTH);
// append plain data
Buffer.BlockCopy(encryptedSebData, 0, encryptedData, PREFIX_LENGTH, encryptedSebData.Length);
encryptedSebData = (byte[])encryptedData.Clone();
}
// Check if cryptographic identity for encryption is selected
if (certificateRef != null)
{
// Encrypt preferences using a cryptographic identity
encryptedSebData = EncryptDataUsingIdentity(encryptedSebData, certificateRef, useAsymmetricOnlyEncryption);
}
// gzip the encrypted data
encryptedSebData = GZipByte.Compress(encryptedSebData);
return encryptedSebData;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Encrypt preferences using a certificate
/// </summary>
/// ----------------------------------------------------------------------------------------
public static byte[] EncryptDataUsingIdentity(byte[] data, X509Certificate2 certificateRef, bool useAsymmetricOnlyEncryption)
{
// Get public key hash from selected identity's certificate
string prefixString;
byte[] publicKeyHash = SEBProtectionController.GetPublicKeyHashFromCertificate(certificateRef);
byte[] encryptedData;
byte[] encryptedKeyLengthBytes = new byte[0];
byte[] encryptedKey = new byte[0];
byte[] encryptedSEBConfigData;
if (!useAsymmetricOnlyEncryption)
{
prefixString = PUBLIC_SYMMETRIC_KEY_MODE;
// For new asymmetric/symmetric encryption create a random symmetric key
byte[] symmetricKey = AESThenHMAC.NewKey();
string symmetricKeyString = Convert.ToBase64String(symmetricKey);
// Encrypt the symmetric key using the identity certificate
encryptedKey = SEBProtectionController.EncryptDataWithCertificate(symmetricKey, certificateRef);
// Get length of the encrypted key
encryptedKeyLengthBytes = BitConverter.GetBytes(encryptedKey.Length);
//encrypt data using symmetric key
encryptedData = SEBProtectionController.EncryptDataWithPassword(data, symmetricKeyString);
}
else
{
prefixString = PUBLIC_KEY_HASH_MODE;
//encrypt data using public key
encryptedData = SEBProtectionController.EncryptDataWithCertificate(data, certificateRef);
}
// Create byte array large enough to hold prefix, public key hash, length of and encrypted symmetric key plus encrypted data
encryptedSEBConfigData = new byte[PREFIX_LENGTH + publicKeyHash.Length + encryptedKeyLengthBytes.Length + encryptedKey.Length + encryptedData.Length];
int destinationOffset = 0;
// Copy prefix indicating data has been encrypted with a public key identified by hash into out data
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedSEBConfigData, destinationOffset, PREFIX_LENGTH);
destinationOffset += PREFIX_LENGTH;
// Copy public key hash to out data
Buffer.BlockCopy(publicKeyHash, 0, encryptedSEBConfigData, destinationOffset, publicKeyHash.Length);
destinationOffset += publicKeyHash.Length;
// Copy length of encrypted symmetric key to out data
Buffer.BlockCopy(encryptedKeyLengthBytes, 0, encryptedSEBConfigData, destinationOffset, encryptedKeyLengthBytes.Length);
destinationOffset += encryptedKeyLengthBytes.Length;
// Copy encrypted symmetric key to out data
Buffer.BlockCopy(encryptedKey, 0, encryptedSEBConfigData, destinationOffset, encryptedKey.Length);
destinationOffset += encryptedKey.Length;
// Copy encrypted data to out data
Buffer.BlockCopy(encryptedData, 0, encryptedSEBConfigData, destinationOffset, encryptedData.Length);
return encryptedSEBConfigData;
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Encrypt preferences using a password
/// </summary>
/// ----------------------------------------------------------------------------------------
// Encrypt preferences using a password
public static byte[] EncryptDataUsingPassword(byte[] data, string password, bool passwordIsHash, SEBSettings.sebConfigPurposes configPurpose)
{
string prefixString;
// Check if .seb file should start exam or configure client
if (configPurpose == SEBSettings.sebConfigPurposes.sebConfigPurposeStartingExam)
{
// prefix string for starting exam: normal password will be prompted
prefixString = PASSWORD_MODE;
}
else
{
// prefix string for configuring client: configuring password will either be hashed admin pw on client
// or if no admin pw on client set: empty pw
prefixString = PASSWORD_CONFIGURING_CLIENT_MODE;
if (!String.IsNullOrEmpty(password) && !passwordIsHash)
{
//empty password means no admin pw on clients and should not be hashed
//or we got already a hashed admin pw as settings pw, then we don't hash again
password = SEBProtectionController.ComputePasswordHash(password);
}
}
byte[] encryptedData = SEBProtectionController.EncryptDataWithPassword(data, password);
// Create byte array large enough to hold prefix and data
byte[] encryptedSebData = new byte[encryptedData.Length + PREFIX_LENGTH];
Buffer.BlockCopy(Encoding.UTF8.GetBytes(prefixString), 0, encryptedSebData, 0, PREFIX_LENGTH);
Buffer.BlockCopy(encryptedData, 0, encryptedSebData, PREFIX_LENGTH, encryptedData.Length);
return encryptedSebData;
}
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Compressing and decompressing byte arrays using gzip
/// </summary>
/// ----------------------------------------------------------------------------------------
public static class GZipByte
{
public static byte[] Compress(byte[] input)
{
using (MemoryStream output = new MemoryStream())
{
using (GZipStream zip = new GZipStream(output, CompressionMode.Compress))
{
zip.Write(input, 0, input.Length);
}
return output.ToArray();
}
}
public static byte[] Decompress(byte[] input)
{
try
{
using (GZipStream stream = new GZipStream(new MemoryStream(input),
CompressionMode.Decompress))
{
const int size = 4096;
byte[] buffer = new byte[size];
using (MemoryStream output = new MemoryStream())
{
int count = 0;
do
{
count = stream.Read(buffer, 0, size);
if (count > 0)
{
output.Write(buffer, 0, count);
}
}
while (count > 0);
return output.ToArray();
}
}
}
catch (Exception)
{
return null;
}
}
/// ----------------------------------------------------------------------------------------
/// <summary>
/// Show SEB Password Dialog Form.
/// </summary>
/// ----------------------------------------------------------------------------------------
//public static string ShowPasswordDialogForm(string title, string passwordRequestText)
//{
// Thread sf= new Thread(new ThreadStart(SebPasswordDialogForm.ShowPasswordDialogForm);
// sf.Start();
//}
}
}