Skip to content

Commit

Permalink
YubiKeyPolicies
Browse files Browse the repository at this point in the history
Rename to YubiKeyPolicies, YubiKeyPolicy
Add posibility of Slot, with support for 0x
Added ETW warning for broken XML
Added more xunit tests
  • Loading branch information
virot authored Jan 17, 2025
1 parent c10cd17 commit ffe16d2
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 25 deletions.
51 changes: 50 additions & 1 deletion TameMyCerts.Tests/XMLPolicyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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 = @"<CertificateRequestPolicy xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<YubiKeyPolicies>
<YubiKeyPolicy>
<Action>Allow</Action>
<Slot>
<string>9A</string>
</Slot>
</YubiKeyPolicy>
</YubiKeyPolicies>
</CertificateRequestPolicy>
";
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 = @"<CertificateRequestPolicy xmlns:xsi=""""http://www.w3.org/2001/XMLSchema-instance""""
xmlns:xsd=""""http://www.w3.org/2001/XMLSchema"""">
</CertificateRequestPolicy>
";
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);
}

}
120 changes: 117 additions & 3 deletions TameMyCerts.Tests/YubikeyValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand All @@ -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<string> { "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<string> { "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<string> { "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<string> { "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<string> { "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<string> { "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());
}
}
}
8 changes: 8 additions & 0 deletions TameMyCerts/EWTLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 10 additions & 1 deletion TameMyCerts/LocalizedStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion TameMyCerts/LocalizedStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ with the content:
{1} </value>
</data>
<data name="event_TMC_92_Policy_Unknown_XML_Element" xml:space="preserve">
<value>XML policy unknown Element: {0} at line {1}, position {2}</value>
<value>XML policy unknown Element: {0} at line {1}, position {2}, Elements are case sensitive.</value>
</data>
<data name="event_TMC_93_Policy_Unknown_XML_Attribute" xml:space="preserve">
<value>Unknown Attribute: {0}='{1}' at line {2}, position {3}</value>
Expand All @@ -459,4 +459,7 @@ with the content:
<data name="DirVal_TokenGroupNames_Failed" xml:space="preserve">
<value>Unable to determine the msds-TokenGroupNames attribute for {0}. Note that this attribute is only available on Windows Server 2016 and newer Domain Controllers.</value>
</data>
<data name="event_TMC_94_XML_Parsing_error" xml:space="preserve">
<value>Unable to parse '{0}', {1}.</value>
</data>
</root>
7 changes: 4 additions & 3 deletions TameMyCerts/Models/CertificateRequestPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ public class CertificateRequestPolicy
[XmlElement(ElementName = "DirectoryServicesMapping")]
public DirectoryServicesMapping DirectoryServicesMapping { get; set; }

[XmlElement(ElementName = "YubikeyPolicy")]
public List<YubikeyPolicy> YubikeyPolicy { get; set; }
[XmlArray(ElementName = "YubiKeyPolicies")]
[XmlArrayItem(ElementName = "YubiKeyPolicy")]
public List<YubikeyPolicy> YubikeyPolicy { get; set; } = new();

[XmlElement(ElementName = "SupplementDnsNames")]
public bool SupplementDnsNames { get; set; }
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion TameMyCerts/Models/YubikeyPolicy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -50,6 +50,9 @@ public class YubikeyPolicy
[XmlArray(ElementName = "Edition")]
[XmlArrayItem(ElementName = "string")]
public List<YubikeyEdition> Edition { get; set; } = new List<YubikeyEdition>();
[XmlArray(ElementName = "Slot")]
[XmlArrayItem(ElementName = "string")]
public List<string> Slot { get; set; } = new List<string>();
[XmlArray(ElementName = "KeyAlgorithm")]
[XmlArrayItem(ElementName = "string")]
public List<KeyAlgorithmFamily> KeyAlgorithmFamilies { get; set; } = new List<KeyAlgorithmFamily>();
Expand Down
9 changes: 9 additions & 0 deletions TameMyCerts/Validators/YubikeyValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 7 additions & 3 deletions TameMyCerts/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
26 changes: 14 additions & 12 deletions examples/Sample_Online_Yubikey_Verification_and_Rewrite.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,20 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<DirectoryServicesMapping>
</DirectoryServicesMapping>
<YubikeyPolicy>
<MaximumFirmwareVersion>5.6.9</MaximumFirmwareVersion>
<KeyAlgorithm><string>ECC</string></KeyAlgorithm>
<Action>Deny</Action>
</YubikeyPolicy>
<YubikeyPolicy>
<PinPolicy>
<string>Always</string>
<string>Once</string>
</PinPolicy>
<Action>Allow</Action>
</YubikeyPolicy>
<YubiKeyPolicies>
<YubiKeyPolicy>
<MaximumFirmwareVersion>5.6.9</MaximumFirmwareVersion>
<KeyAlgorithm><string>ECC</string></KeyAlgorithm>
<Action>Deny</Action>
</YubiKeyPolicy>
<YubiKeyPolicy>
<PinPolicy>
<string>Always</string>
<string>Once</string>
</PinPolicy>
<Action>Allow</Action>
</YubiKeyPolicy>
</YubiKeyPolicies>
<OutboundSubject>
<OutboundSubjectRule>
<Field>commonName</Field>
Expand Down

0 comments on commit ffe16d2

Please sign in to comment.