/// ---------------------------------------------------------------------------------------- /// <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 IDictionary<string, object> GetPreferencesDictFromConfigData(byte[] sebData, GetPasswordMethod getPassword, bool forEditing) { IDictionary<string, object> sebPreferencesDict = null; try { // Get preferences dictionary from decrypted data sebPreferencesDict = (IDictionary<string, object>)PropertyList.readPlist(sebData); } catch(Exception readPlistException) { SebMessageBox.Show(SEBUIStrings.loadingSettingsFailed, SEBUIStrings.loadingSettingsFailedReason, MessageBoxImage.Error, MessageBoxButton.OK); 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 = SebInstance.Settings.Get<string>(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 = SebInstance.Settings.Get<string>(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, getPassword, forEditing)) { return null; } } } } // 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 IDictionary<string, object> DecryptDataWithPasswordForConfiguringClient(byte[] sebData, GetPasswordMethod getPassword, 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 var hashedAdminPassword = SebInstance.Settings.Get<string>(SebSettings.KeyHashedAdminPassword); if(hashedAdminPassword == null) { hashedAdminPassword = ""; } // We use always uppercase letters in the base16 hashed admin password used for encrypting hashedAdminPassword = hashedAdminPassword.ToUpper(); IDictionary<string, object> sebPreferencesDict; var 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 var gotPassword = getPassword(GetPasswordPurpose.ConfigureClient, p => { var hashedPassword = SebProtectionController.ComputePasswordHash(p); // we try to decrypt with the hashed password decryptedSebData = SebProtectionController.DecryptDataWithPassword(sebData, hashedPassword); return decryptedSebData != null; }, out password); // If cancel was pressed, abort if(!gotPassword.HasValue) { return null; } if(!gotPassword.Value) { //wrong password entered in 5th try: stop reading .seb file SebMessageBox.Show(SEBUIStrings.reconfiguringLocalSettingsFailed, SEBUIStrings.reconfiguringLocalSettingsFailedWrongPassword, MessageBoxImage.Error, MessageBoxButton.OK); 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 = (IDictionary<string, object>)PropertyList.readPlist(decryptedSebData); } catch(Exception readPlistException) { // Error when deserializing the decrypted configuration data // We abort reading the new settings here SebMessageBox.Show(SEBUIStrings.loadingSettingsFailed, SEBUIStrings.loadingSettingsFailedReason, MessageBoxImage.Error, MessageBoxButton.OK); Console.WriteLine(readPlistException.Message); return null; } // Get the admin password set in these settings var sebFileHashedAdminPassword = (string)SebInstance.Settings.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, getPassword, 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 var gotPassword = getPassword(GetPasswordPurpose.ConfigureLocalAdmin, p => { var hashedPassword = string.IsNullOrEmpty(p) ? string.Empty : SebProtectionController.ComputePasswordHash(p); return String.Compare(hashedPassword, hashedAdminPassword, StringComparison.OrdinalIgnoreCase) == 0; }, out password); if(!gotPassword.GetValueOrDefault()) { //wrong password entered in 5th try: stop reading .seb file SebMessageBox.Show(SEBUIStrings.reconfiguringLocalSettingsFailed, SEBUIStrings.reconfiguringLocalSettingsFailedWrongCurrentAdminPwd, MessageBoxImage.Error, MessageBoxButton.OK); 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; // Reading preferences was successful! return sebPreferencesDict; }
/// ---------------------------------------------------------------------------------------- /// <summary> /// Decrypt, parse and use new SEB settings /// </summary> /// ---------------------------------------------------------------------------------------- public static bool StoreDecryptedSEBSettings(byte[] sebData, GetPasswordMethod getPassword, SebSettings settings) { Logger.AddInformation("Reconfiguring"); string sebFilePassword = null; bool passwordIsHash = false; X509Certificate2 sebFileCertificateRef = null; var sebPreferencesDict = DecryptSEBSettings(sebData, getPassword, false, ref sebFilePassword, ref passwordIsHash, ref sebFileCertificateRef); if(sebPreferencesDict == null) { return false; //Decryption didn't work, we abort } // Store decrypted settings Logger.AddInformation("Attempting to StoreSebClientSettings"); settings.StoreSebClientSettings(sebPreferencesDict); Logger.AddInformation("Successfully StoreSebClientSettings"); return true; }
/// ---------------------------------------------------------------------------------------- /// <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, GetPasswordMethod getPassword, 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 string password; var gotPassword = getPassword(GetPasswordPurpose.LoadingSettings, p => { var hashedPassword = string.IsNullOrEmpty(p) ? string.Empty : SebProtectionController.ComputePasswordHash(p); return String.Compare(hashedPassword, sebFileHashedAdminPassword, StringComparison.OrdinalIgnoreCase) == 0; }, out password); if(!gotPassword.GetValueOrDefault()) { //wrong password entered in 5th try: stop reading .seb file SebMessageBox.Show(SEBUIStrings.loadingSettingsFailed, SEBUIStrings.loadingSettingsFailedWrongAdminPwd, MessageBoxImage.Error, MessageBoxButton.OK); return false; } // Right password entered return true; }
/// ---------------------------------------------------------------------------------------- /// <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 IDictionary<string, object> DecryptSEBSettings(byte[] sebData, GetPasswordMethod getPassword, bool forEditing, ref string sebFilePassword, ref bool passwordIsHash, ref X509Certificate2 sebFileCertificateRef) { // 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 // Prefix = pkhs ("Public Key Hash") ? if(String.CompareOrdinal(prefixString, PUBLIC_KEY_HASH_MODE) == 0) { // Decrypt with cryptographic identity/private key sebData = DecryptDataWithPublicKeyHashPrefix(sebData, forEditing, ref sebFileCertificateRef); //SEBMessageBox.Show(SEBUIStrings.errorDecryptingSettings, SEBUIStrings.certificateNotFoundInStore, MessageBoxIcon.Error, MessageBoxButtons.OK, neverShowTouchOptimized : forEditing); //SEBMessageBox.Show(SEBUIStrings.errorDecryptingSettings, SEBUIStrings.certificateDecryptingError + ex.Message, MessageBoxImage.Error, MessageBoxButton.OK); 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(String.CompareOrdinal(prefixString, PASSWORD_MODE) == 0) { // Decrypt with password // if the user enters the right one byte[] sebDataDecrypted = null; string password; var gotPassword = getPassword(GetPasswordPurpose.PasswordMode, p => { sebDataDecrypted = SebProtectionController.DecryptDataWithPassword(sebData, p); return sebDataDecrypted != null; }, out password); if(!gotPassword.HasValue) { return null; } if(!gotPassword.Value) { //wrong password entered in 5th try: stop reading .seb file SebMessageBox.Show(SEBUIStrings.decryptingSettingsFailed, SEBUIStrings.decryptingSettingsFailedReason, MessageBoxImage.Error, MessageBoxButton.OK); 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(String.CompareOrdinal(prefixString, PASSWORD_CONFIGURING_CLIENT_MODE) == 0) { // Decrypt with password and configure local client settings // and quit afterwards, returning if reading the .seb file was successfull var sebSettings = DecryptDataWithPasswordForConfiguringClient(sebData, getPassword, forEditing, ref sebFilePassword, ref passwordIsHash); return sebSettings; } else { // Prefix = plnd ("Plain Data") ? if(String.CompareOrdinal(prefixString, PLAIN_DATA_MODE) != 0) { // No valid 4-char prefix was found in the .seb file // Check if .seb file is unencrypted if(String.CompareOrdinal(prefixString, 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 SebMessageBox.Show(SEBUIStrings.settingsNotUsable, SEBUIStrings.settingsNotUsableReason, MessageBoxImage.Error, MessageBoxButton.OK); 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(String.CompareOrdinal(prefixString, UNENCRYPTED_MODE) != 0) { sebData = GZipByte.Decompress(sebData); } // Get preferences dictionary from decrypted data var sebPreferencesDict = GetPreferencesDictFromConfigData(sebData, getPassword, forEditing); // 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; // Reading preferences was successful! return sebPreferencesDict; }