Skip to content

Commit

Permalink
Implemented more guardrails for signed scenarios (#492)
Browse files Browse the repository at this point in the history
The app no longer allows the wrong certificate or common name to be used during signed policy deployment, re-deployment or removal. Such possible user accidents are caught very early on and communicated to the user with proper and clear messages so user can fix the mistake quickly. The goal is to never let AppControl Manager to be used even intentionally to cause boot failure when dealing with signed policies.

Deployment of signed policies is very much recommended over unsigned ones, check this article to see why: https://github.com/HotCakeX/Harden-Windows-Security/wiki/The-Strength-of-Signed-App-Control-Policies

AppControl Manager is the only app that's currently available that makes it the safest way to interact with signed policies and it keeps getting better quickly.

The content dialogs that ask for user input for signing scenarios have better visuals now, and the focus is by default on the Verify button, which makes it easier and clearer what needs to be done. It also means you can press the enter key on the keyboard quickly to confirm the actions without using mouse.

Improved DataGrid experience when removing items in MDE Advanced Hunting and Event Logs pages.

Bumped version from 1.8.0.0 to 1.8.1.0
  • Loading branch information
HotCakeX authored Jan 1, 2025
1 parent 9c4a5c8 commit 7f603d5
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 78 deletions.
2 changes: 1 addition & 1 deletion AppControl Manager/AppControl Manager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
<AssemblyName>AppControlManager</AssemblyName>
<PublishAot>False</PublishAot>
<ErrorReport>send</ErrorReport>
<FileVersion>1.8.0.0</FileVersion>
<FileVersion>1.8.1.0</FileVersion>
<AssemblyVersion>$(FileVersion)</AssemblyVersion>
<NeutralLanguage>en-US</NeutralLanguage>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
Expand Down
53 changes: 52 additions & 1 deletion AppControl Manager/CustomUIElements/SigningDetailsDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AppControlManager.Logic.IntelGathering;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;

namespace AppControlManager.CustomUIElements;

Expand All @@ -24,13 +26,17 @@ internal sealed partial class SigningDetailsDialog : ContentDialog
// To store the selectable Certificate common names
private HashSet<string> CertCommonNames = [];

// When this argument is provided, the verify button will check the input fields with the policy's details
private readonly SiPolicy.SiPolicy? policyObjectToVerify;

internal SigningDetailsDialog()
internal SigningDetailsDialog(SiPolicy.SiPolicy? policyObject = null)
{
this.InitializeComponent();

XamlRoot = App.MainWindow?.Content.XamlRoot;

policyObjectToVerify = policyObject;

// Populate the AutoSuggestBox with possible certificate common names available on the system
FetchLatestCertificateCNs();

Expand All @@ -46,6 +52,15 @@ internal SigningDetailsDialog()
CertificatePath = currentUserConfigs.CertificatePath;
CertificateCommonName = currentUserConfigs.CertificateCommonName;
SignToolPath = currentUserConfigs.SignToolCustomPath;


// Set the focus on the Verify button when the Content Dialog opens
// And highlight it
VerifyButton.Loaded += async (sender, e) =>
{
_ = await FocusManager.TryFocusAsync(VerifyButton, FocusState.Keyboard);
};

}


Expand Down Expand Up @@ -246,6 +261,42 @@ private async void VerifyButton_Click(object sender, RoutedEventArgs e)
}


#region Verify the certificate's detail is available in the XML policy as UpdatePolicySigner

string certCN = CertificateCommonNameAutoSuggestBox.Text;
string certPath = CertFilePathTextBox.Text;

if (policyObjectToVerify is not null)
{
bool isValid = false;

await Task.Run(() =>
{
isValid = CertificatePresence.InferCertificatePresence(policyObjectToVerify, certPath, certCN);
});


if (!isValid)
{
ShowTeachingTip("The selected certificate is not present in the XML policy file as an UpdatePolicySigner or the selected common name does not match the selected certificate's common name, thus it cannot be used to re-sign the policy. Please select the correct certificate.");
return;
};

}

// If cert object is not passed, only ensure cert CN and cert file match
else
{
if (!CertificatePresence.VerifyCertAndCNMatch(certPath, certCN))
{
ShowTeachingTip("The selected certificate file and common name don't match.");
return;
}
}

#endregion


// Verify the SignTool
if (!AutoAcquireSignTool.IsOn)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AppControlManager.Logic.IntelGathering;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;

namespace AppControlManager.CustomUIElements;

Expand Down Expand Up @@ -55,6 +57,14 @@ internal SigningDetailsDialogForRemoval(List<string?> currentlyDeployedBasePolic
CertificatePath = currentUserConfigs.CertificatePath;
CertificateCommonName = currentUserConfigs.CertificateCommonName;
SignToolPath = currentUserConfigs.SignToolCustomPath;


// Set the focus on the Verify button when the Content Dialog opens
// And highlight it
VerifyButton.Loaded += async (sender, e) =>
{
_ = await FocusManager.TryFocusAsync(VerifyButton, FocusState.Keyboard);
};
}


Expand Down Expand Up @@ -323,6 +333,27 @@ await Task.Run(() =>
#endregion


#region Verify the certificate's detail is available in the XML policy as UpdatePolicySigner

string certFilePath = CertFilePathTextBox.Text;
string certCN = CertificateCommonNameAutoSuggestBox.Text;

bool certIsUpdatePolicySigner = false;

await Task.Run(() =>
{
certIsUpdatePolicySigner = CertificatePresence.InferCertificatePresence(policyObject, certFilePath, certCN);
});

if (!certIsUpdatePolicySigner)
{
ShowTeachingTip("The selected certificate is not present in the XML policy file as an UpdatePolicySigner or the selected common name does not match the selected certificate's common name, thus it cannot be used to re-sign the policy for the removal process. Please select the correct certificate.");
return;
}

#endregion


#region Verify the SignTool


Expand Down Expand Up @@ -385,6 +416,16 @@ await Task.Run(() =>

// Set certificate details that were verified to the user configurations
_ = UserConfiguration.Set(CertificateCommonName: CertificateCommonNameAutoSuggestBox.Text, CertificatePath: CertFilePathTextBox.Text);


// Set the focus on the Primary button after verification has been successful
Button? primaryButton = this.GetTemplateChild("PrimaryButton") as Button;
if (primaryButton is not null)
{
// Set focus on the primary button
_ = await FocusManager.TryFocusAsync(primaryButton, FocusState.Keyboard);
}

}
finally
{
Expand Down
109 changes: 109 additions & 0 deletions AppControl Manager/Logic/IntelGathering/InferCertificatePresence.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using AppControlManager.Logging;
using AppControlManager.SiPolicy;

namespace AppControlManager.Logic.IntelGathering;

internal static class CertificatePresence
{

/// <summary>
/// Takes in a policy object and certificate .cer file path and ensures the certificate's details is added to the policy as UpdatePolicySigner
/// It also checks to see whether user selected certificate matches the user selected certificate common name.
/// The reason we don't need to check signature of the deployed signed cip files in the EFI partition is because
/// The user-selected XML policy's ID is already checked against the deployed signed policies and that provides the necessary signing details in the XML.
/// </summary>
/// <param name="policyObject"></param>
/// <param name="certificatePath"></param>
/// <param name="certCN"></param>
/// <returns></returns>
internal static bool InferCertificatePresence(SiPolicy.SiPolicy policyObject, string certificatePath, string certCN)
{

// Create a certificate object from the .cer file
X509Certificate2 CertObject = X509CertificateLoader.LoadCertificateFromFile(certificatePath);

// Get the TBS of the certificate
string CertTBS = CertificateHelper.GetTBSCertificate(CertObject);

// Get the Common Name of the certificate
string CertCommonName = CryptoAPI.GetNameString(CertObject.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, false);


// Make sure the certificate that user selected matches the user-selected certificate Common Name
if (!string.Equals(certCN, CertCommonName, StringComparison.OrdinalIgnoreCase))
{
Logger.Write($"The selected common name is {certCN} but the common name of the certificate you selected is {CertCommonName} which doesn't match it.");
return false;
}

// Get the ID of all of the UpdatePolicySigners elements
IEnumerable<string> updatePolicySignerIDs = policyObject.UpdatePolicySigners.Select(x => x.SignerId);

// Get all of the <Signer> elements from the policy
Dictionary<string, Signer> signerDictionary = [];
foreach (Signer signer in policyObject.Signers)
{
_ = signerDictionary.TryAdd(signer.ID, signer);
}


// Loop over each updatePolicySignerID in the policy
foreach (string updatePolicySigner in updatePolicySignerIDs)
{
// Try to find a signer that is for UpdatePolicySigners
if (signerDictionary.TryGetValue(updatePolicySigner, out Signer? signerForUpdateSigner))
{
// If signer is TBS Signer
if (signerForUpdateSigner.CertRoot.Type is CertEnumType.TBS)
{
// Get the string value of the CertRoot which is the TBS Hash
string certRootTBS = Convert.ToHexString(signerForUpdateSigner.CertRoot.Value);

// Compare the selected certificate's TBS hash with the TBS hash of the signer which is the cert Root value
// Also compare the Signer's name with the selected certificate's Common Name
if (string.Equals(CertTBS, certRootTBS, StringComparison.OrdinalIgnoreCase) && string.Equals(CertCommonName, signerForUpdateSigner.Name, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
}

Logger.Write("No UpdatePolicySigner found with the same TBS hash and Common Name as the selected certificate in the policy XML file you selected.");

return false;
}



/// <summary>
/// Gets the path to a .cer certificate file and a certificate common name
/// Makes sure the common name belongs to the certificate file
/// </summary>
/// <param name="certificatePath"></param>
/// <param name="certCN"></param>
/// <returns></returns>
internal static bool VerifyCertAndCNMatch(string certificatePath, string certCN)
{
// Create a certificate object from the .cer file
X509Certificate2 CertObject = X509CertificateLoader.LoadCertificateFromFile(certificatePath);

// Get the Common Name of the certificate
string CertCommonName = CryptoAPI.GetNameString(CertObject.Handle, CryptoAPI.CERT_NAME_SIMPLE_DISPLAY_TYPE, null, false);

// Make sure the certificate that user selected matches the user-selected certificate Common Name
if (!string.Equals(certCN, CertCommonName, StringComparison.OrdinalIgnoreCase))
{
Logger.Write($"The selected common name is {certCN} but the common name of the certificate you selected is {CertCommonName} which doesn't match it.");
return false;
}

return true;
}

}

Loading

0 comments on commit 7f603d5

Please sign in to comment.