diff --git a/TameMyCerts.Tests/XMLPolicyTests.cs b/TameMyCerts.Tests/XMLPolicyTests.cs index f943362..9f7bda0 100644 --- a/TameMyCerts.Tests/XMLPolicyTests.cs +++ b/TameMyCerts.Tests/XMLPolicyTests.cs @@ -70,7 +70,7 @@ public void Test_Unknown_XML_Element() Assert.Empty(cacheEntry.ErrorMessage); Assert.Equal(2, _listener.Events.Count); Assert.Equal(92, _listener.Events[0].EventId); - + output.WriteLine(_listener.Events[0].Message); File.Delete(filename); } @@ -96,4 +96,53 @@ public void Test_Unknown_XML_Element2() File.Delete(filename); } + [Fact] + public void Test_Yubikey_Policies() + { + var filename = Path.GetTempFileName(); + + string sampleXML = @" + + + Allow + + 9A + + + + + +"; + File.WriteAllText(filename, sampleXML); + _listener.ClearEvents(); + + CertificateRequestPolicyCacheEntry cacheEntry = new CertificateRequestPolicyCacheEntry(filename); + + //Assert.Empty(cacheEntry.ErrorMessage); + //Assert.Equal(2, _listener.Events.Count); + Assert.DoesNotContain(92, _listener.Events.Select(e => e.EventId)); + File.Delete(filename); + } + + + [Fact] + public void Broken_XML_Policies() + { + var filename = Path.GetTempFileName(); + + string sampleXML = @" + +"; + File.WriteAllText(filename, sampleXML); + _listener.ClearEvents(); + + CertificateRequestPolicyCacheEntry cacheEntry = new CertificateRequestPolicyCacheEntry(filename); + + output.WriteLine(cacheEntry.ErrorMessage); + Assert.Contains(94, _listener.Events.Select(e => e.EventId)); + File.Delete(filename); + } + } \ No newline at end of file diff --git a/TameMyCerts.Tests/YubikeyValidatorTests.cs b/TameMyCerts.Tests/YubikeyValidatorTests.cs index b12dbf5..a342348 100644 --- a/TameMyCerts.Tests/YubikeyValidatorTests.cs +++ b/TameMyCerts.Tests/YubikeyValidatorTests.cs @@ -576,7 +576,7 @@ public void Rewrite_Subject_to_slot_10014() } ); - result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, 10014); + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, dbRow.RequestID); result = _CCvalidator.VerifyRequest(result, policy, _yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_dbRow, null, _caConfig, yubikeyInfo); PrintResult(result); @@ -637,7 +637,7 @@ public void Validate_Accutial_Attestions_certificate_wrong_public_key_10015() result = _YKvalidator.ExtractAttestion(result, _policy, dbrow, out var yubikey); CertificateRequestPolicy policy = _policy; - result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10015); + result = _YKvalidator.VerifyRequest(result, policy, yubikey, dbrow.RequestID); PrintResult(result); Assert.Contains(4207, _listener.Events.Select(e => e.EventId)); @@ -655,12 +655,126 @@ public void Include_the_AttestionData_in_Certificate_10016() CertificateRequestPolicy policy = _policy; policy.YubikeyPolicy[0].IncludeAttestationInCertificate = true; - result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, 10016); + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, dbRow.RequestID); PrintResult(result); Assert.False(result.DeniedForIssuance); Assert.True(result.CertificateExtensions.ContainsKey(YubikeyX509Extensions.ATTESTION_DEVICE)); + + } + + [Fact] + public void Validate_Slot_Allow_policy_10017() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10017); + + // Allow if Slot is in an allow Policy + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].Slot = new List { "9a" }; + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikeyInfo); + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, dbRow.RequestID); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + + } + + [Fact] + public void Validate_Slot_Deny_policy_10018() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10018); + + // Deny if Slot is in a deny Policy + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].Slot = new List { "9a" }; + policy.YubikeyPolicy[0].Action = YubikeyPolicyAction.Deny; + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikeyInfo); + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, dbRow.RequestID); + + PrintResult(result); + + Assert.True(result.DeniedForIssuance); + + } + + [Fact] + public void Validate_Slot_Missing_in_Allow_policy_10019() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10019); + CertificateRequestPolicy policy = _policy; + var result = new CertificateRequestValidationResult(dbRow); + + // Test if the slot is not in the only allow policy + policy = _policy; + policy.YubikeyPolicy[0].Slot = new List { "9e" }; + result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikeyInfo); + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, dbRow.RequestID); + Assert.True(result.DeniedForIssuance); + + PrintResult(result); + + } + + [Fact] + public void Validate_Slot_Allow_if_Wrong_slot_is_denied_10020() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10020); + CertificateRequestPolicy policy = _policy; + var result = new CertificateRequestValidationResult(dbRow); + + // Allow if the Deny does not match this slot + policy = _policy; + policy.YubikeyPolicy.Add(new YubikeyPolicy()); + policy.YubikeyPolicy[0].Action = YubikeyPolicyAction.Deny; + policy.YubikeyPolicy[0].Slot = new List { "9e" }; + result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikeyInfo); + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, dbRow.RequestID); + Assert.False(result.DeniedForIssuance); + PrintResult(result); + + output.WriteLine(policy.SaveToString()); + } + [Fact] + public void Validate_Slot_with_0x_10021() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10020); + CertificateRequestPolicy policy = _policy; + var result = new CertificateRequestValidationResult(dbRow); + + // Required slot 0x9a, which needs to match 9a + policy = _policy; + policy.YubikeyPolicy[0].Slot = new List { "0x9a" }; + result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikeyInfo); + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, dbRow.RequestID); + Assert.False(result.DeniedForIssuance); + PrintResult(result); + + output.WriteLine(policy.SaveToString()); + } + [Fact] + public void Validate_Slot_incorrect_with_0x_10022() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10020); + CertificateRequestPolicy policy = _policy; + var result = new CertificateRequestValidationResult(dbRow); + + // Should not match the csr which is 9A + policy = _policy; + policy.YubikeyPolicy[0].Slot = new List { "0x9e" }; + result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikeyInfo); + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, dbRow.RequestID); + Assert.True(result.DeniedForIssuance); + PrintResult(result); + + output.WriteLine(policy.SaveToString()); } } } \ No newline at end of file diff --git a/TameMyCerts/EWTLogger.cs b/TameMyCerts/EWTLogger.cs index 8d4a20d..9a048fe 100644 --- a/TameMyCerts/EWTLogger.cs +++ b/TameMyCerts/EWTLogger.cs @@ -107,6 +107,14 @@ public void TMC_93_Policy_Unknown_XML_Attribute(string attributeName, string att WriteEvent(93, attributeName, attributeValue, lineNumber, linePosition); } } + [Event(94, Level = EventLevel.Critical, Channel = EventChannel.Admin, Task = Tasks.XMLParser, Keywords = EventKeywords.None)] + public void TMC_94_XML_Parsing_error(string filename, string error) + { + if (IsEnabled()) + { + WriteEvent(94, filename, error); + } + } #endregion diff --git a/TameMyCerts/LocalizedStrings.Designer.cs b/TameMyCerts/LocalizedStrings.Designer.cs index 39bc951..9c0da97 100644 --- a/TameMyCerts/LocalizedStrings.Designer.cs +++ b/TameMyCerts/LocalizedStrings.Designer.cs @@ -420,7 +420,7 @@ internal static string event_TMC_91_Policy_Read { } /// - /// Looks up a localized string similar to XML policy unknown Element: {0} at line {1}, position {2}. + /// Looks up a localized string similar to XML policy unknown Element: {0} at line {1}, position {2}, Elements are case sensitive.. /// internal static string event_TMC_92_Policy_Unknown_XML_Element { get { @@ -437,6 +437,15 @@ internal static string event_TMC_93_Policy_Unknown_XML_Attribute { } } + /// + /// Looks up a localized string similar to Unable to parse '{0}', {1}.. + /// + internal static string event_TMC_94_XML_Parsing_error { + get { + return ResourceManager.GetString("event_TMC_94_XML_Parsing_error", resourceCulture); + } + } + /// /// Looks up a localized string similar to The request {1} was rejected due to policy: \r\n{0}. /// diff --git a/TameMyCerts/LocalizedStrings.resx b/TameMyCerts/LocalizedStrings.resx index 330ef8a..82b9c6c 100644 --- a/TameMyCerts/LocalizedStrings.resx +++ b/TameMyCerts/LocalizedStrings.resx @@ -436,7 +436,7 @@ with the content: {1} - XML policy unknown Element: {0} at line {1}, position {2} + XML policy unknown Element: {0} at line {1}, position {2}, Elements are case sensitive. Unknown Attribute: {0}='{1}' at line {2}, position {3} @@ -459,4 +459,7 @@ with the content: Unable to determine the msds-TokenGroupNames attribute for {0}. Note that this attribute is only available on Windows Server 2016 and newer Domain Controllers. + + Unable to parse '{0}', {1}. + \ No newline at end of file diff --git a/TameMyCerts/Models/CertificateRequestPolicy.cs b/TameMyCerts/Models/CertificateRequestPolicy.cs index b7e35a4..a614474 100644 --- a/TameMyCerts/Models/CertificateRequestPolicy.cs +++ b/TameMyCerts/Models/CertificateRequestPolicy.cs @@ -84,8 +84,9 @@ public class CertificateRequestPolicy [XmlElement(ElementName = "DirectoryServicesMapping")] public DirectoryServicesMapping DirectoryServicesMapping { get; set; } - [XmlElement(ElementName = "YubikeyPolicy")] - public List YubikeyPolicy { get; set; } + [XmlArray(ElementName = "YubiKeyPolicies")] + [XmlArrayItem(ElementName = "YubiKeyPolicy")] + public List YubikeyPolicy { get; set; } = new(); [XmlElement(ElementName = "SupplementDnsNames")] public bool SupplementDnsNames { get; set; } @@ -164,7 +165,7 @@ public string SaveToString() } private static void UnknownElementHandler(object sender, XmlElementEventArgs e) { - ETWLogger.Log.TMC_92_Policy_Unknown_XML_Element(e.Element.Name, e.LineNumber, e.LinePosition); + ETWLogger.Log.TMC_92_Policy_Unknown_XML_Element(e.Element.Name, e.LineNumber, e.LinePosition); } // Event handler for unknown attributes diff --git a/TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs b/TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs index 8810a84..55afe89 100644 --- a/TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs +++ b/TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs @@ -30,6 +30,7 @@ public CertificateRequestPolicyCacheEntry(string fileName) ErrorMessage = ex.InnerException != null ? $"{ex.Message} {ex.InnerException.Message}" : ex.Message; + ETWLogger.Log.TMC_94_XML_Parsing_error(fileName, ErrorMessage); } LastUpdate = DateTimeOffset.Now; diff --git a/TameMyCerts/Models/YubikeyPolicy.cs b/TameMyCerts/Models/YubikeyPolicy.cs index cc630ac..c6da6db 100644 --- a/TameMyCerts/Models/YubikeyPolicy.cs +++ b/TameMyCerts/Models/YubikeyPolicy.cs @@ -26,7 +26,7 @@ namespace TameMyCerts.Models { // Must be public due to XML serialization, otherwise 0x80131509 / System.InvalidOperationException - [XmlRoot(ElementName = "YubikeyPolicy")] + [XmlRoot(ElementName = "YubiKeyPolicy")] public class YubikeyPolicy { [XmlElement(ElementName = "Action")] @@ -50,6 +50,9 @@ public class YubikeyPolicy [XmlArray(ElementName = "Edition")] [XmlArrayItem(ElementName = "string")] public List Edition { get; set; } = new List(); + [XmlArray(ElementName = "Slot")] + [XmlArrayItem(ElementName = "string")] + public List Slot { get; set; } = new List(); [XmlArray(ElementName = "KeyAlgorithm")] [XmlArrayItem(ElementName = "string")] public List KeyAlgorithmFamilies { get; set; } = new List(); diff --git a/TameMyCerts/Validators/YubikeyValidator.cs b/TameMyCerts/Validators/YubikeyValidator.cs index fb864bb..025acda 100644 --- a/TameMyCerts/Validators/YubikeyValidator.cs +++ b/TameMyCerts/Validators/YubikeyValidator.cs @@ -185,6 +185,15 @@ private bool ObjectMatchesPolicy(YubikeyPolicy policy, YubikeyObject yubikey) } #endregion + #region Slot + // Look if the slot is in the policy, if not, say that we arent matching + // Look for both 0xXX and XX + if (policy.Slot.Any() && !(policy.Slot.Any(s => s.Equals(yubikey.Slot, StringComparison.OrdinalIgnoreCase)) || policy.Slot.Any(s => s.Equals($"0x{yubikey.Slot}", StringComparison.OrdinalIgnoreCase)))) + { + return false; + } + #endregion + if (policy.KeyAlgorithmFamilies.Any() && ! policy.KeyAlgorithmFamilies.Contains(yubikey.keyAlgorithm)) { return false; diff --git a/TameMyCerts/install.ps1 b/TameMyCerts/install.ps1 index a472b89..2c21c20 100644 --- a/TameMyCerts/install.ps1 +++ b/TameMyCerts/install.ps1 @@ -257,11 +257,15 @@ if (-not $Uninstall.IsPresent) { New-Item -Path $AppInstallDirectory -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null New-Item -Path "$AppInstallDirectory\\runtimes\win\lib\net8.0" -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null - $FileList | ForEach-Object -Process { + # Only copy the files if .\install.ps1 is run from another folder than the AppInstallDirectory + if ($BaseDirectory -ne $AppInstallDirectory) + { + $FileList | ForEach-Object -Process { - Write-Verbose -Message "Copying $_ to $AppInstallDirectory." + Write-Verbose -Message "Copying $_ to $AppInstallDirectory." - Copy-Item -Path "$BaseDirectory\$_" -Destination "$AppInstallDirectory\$_" -Force + Copy-Item -Path "$BaseDirectory\$_" -Destination "$AppInstallDirectory\$_" -Force + } } Write-Verbose -Message "Registering $PolicyModuleName policy module COM Object" diff --git a/examples/Sample_Online_Yubikey_Verification_and_Rewrite.xml b/examples/Sample_Online_Yubikey_Verification_and_Rewrite.xml index 0e1e00f..7fb453a 100644 --- a/examples/Sample_Online_Yubikey_Verification_and_Rewrite.xml +++ b/examples/Sample_Online_Yubikey_Verification_and_Rewrite.xml @@ -9,18 +9,20 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema"> - - 5.6.9 - ECC - Deny - - - - Always - Once - - Allow - + + + 5.6.9 + ECC + Deny + + + + Always + Once + + Allow + + commonName