From 89b7a5f258c692fa45a62050b721271fa5cb57f4 Mon Sep 17 00:00:00 2001 From: Oscar Virot Date: Sat, 30 Nov 2024 17:54:50 +0100 Subject: [PATCH] Initial Yubikey support (#35) * Initial Yubikey support * Rebuilt the Yubikey Policy to allow multiple policies Added more testcases 5.7.1 is now available. * Allow same version for Min and Max firmware * Revert b36a062a488a34cfb3ccb946e343fd092a494ee5 Fixed Firmware checks Added FIPS yubikey check * Update to xUnit To be able to use KeyAlgorithms in XML they need to be public * Fix nunit output change from 1=>2 Update .any() to Assert.Contains Regresion in CCV, error for missing substition should have what module it came from * Add EWT logging Add Program to generate Manifest Add some fixes and logging and more EWT tests for Yubikeys * Copy the EWT dll/man to the output Add checks for XML * Basic MSI * Rename support projects to support. Add another XML Policy test * fix after changing to support. * Update GenerateEWTManifest to net8.0-windows7.0 * Update install.ps1 to install the Manifest for EWT Logging * Remove MSI installer as that was.. to big for this stage * Also checks if the provider is registered with Get-WinEvent before unregistering * Make Tasks static --- Support.GenerateEWTManifest/Program.cs | 15 + .../Support.GenerateEWTManifest.csproj | 44 ++ TameMyCerts.Tests/EWTLoggerListener.cs | 22 + TameMyCerts.Tests/TameMyCerts.Tests.csproj | 2 +- TameMyCerts.Tests/XMLPolicyTests.cs | 99 +++ TameMyCerts.Tests/YubikeyValidatorTests.cs | 639 ++++++++++++++++++ TameMyCerts.sln | 25 +- TameMyCerts/EWTLogger.cs | 177 +++++ TameMyCerts/Enums/KeyAlgorithmFamily.cs | 2 +- TameMyCerts/Enums/Yubikey.cs | 78 +++ TameMyCerts/LocalizedStrings.Designer.cs | 264 +++++++- TameMyCerts/LocalizedStrings.resx | 96 ++- TameMyCerts/Models/CertificateDatabaseRow.cs | 36 +- .../Models/CertificateRequestPolicy.cs | 34 +- .../CertificateRequestPolicyCacheEntry.cs | 1 + TameMyCerts/Models/YubikeyObject.cs | 170 +++++ TameMyCerts/Models/YubikeyPolicy.cs | 108 +++ TameMyCerts/Policy.cs | 15 +- TameMyCerts/TameMyCerts.csproj | 5 + .../Validators/CertificateContentValidator.cs | 15 +- TameMyCerts/Validators/YubikeyValidator.cs | 172 +++++ TameMyCerts/install.ps1 | 21 + 22 files changed, 2025 insertions(+), 15 deletions(-) create mode 100644 Support.GenerateEWTManifest/Program.cs create mode 100644 Support.GenerateEWTManifest/Support.GenerateEWTManifest.csproj create mode 100644 TameMyCerts.Tests/EWTLoggerListener.cs create mode 100644 TameMyCerts.Tests/XMLPolicyTests.cs create mode 100644 TameMyCerts.Tests/YubikeyValidatorTests.cs create mode 100644 TameMyCerts/EWTLogger.cs create mode 100644 TameMyCerts/Enums/Yubikey.cs create mode 100644 TameMyCerts/Models/YubikeyObject.cs create mode 100644 TameMyCerts/Models/YubikeyPolicy.cs create mode 100644 TameMyCerts/Validators/YubikeyValidator.cs diff --git a/Support.GenerateEWTManifest/Program.cs b/Support.GenerateEWTManifest/Program.cs new file mode 100644 index 0000000..a8c44da --- /dev/null +++ b/Support.GenerateEWTManifest/Program.cs @@ -0,0 +1,15 @@ +using System.Diagnostics.Tracing; +using TameMyCerts; + + // Generate the manifest + string? manifest = EventSource.GenerateManifest(typeof(EWTLogger), "TameMyCerts.Events.dll"); + // Save the manifest to a file + if (manifest is not null) + { + File.WriteAllText("TameMyCerts.Events.man", manifest); + Console.WriteLine("Manifest generated and saved to TameMyCerts.Events.man"); + } + else + { + Console.WriteLine("Failed to generate manifest. The manifest content is null."); + } \ No newline at end of file diff --git a/Support.GenerateEWTManifest/Support.GenerateEWTManifest.csproj b/Support.GenerateEWTManifest/Support.GenerateEWTManifest.csproj new file mode 100644 index 0000000..dcedb77 --- /dev/null +++ b/Support.GenerateEWTManifest/Support.GenerateEWTManifest.csproj @@ -0,0 +1,44 @@ + + + + WinExe + net8.0-windows7.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TameMyCerts.Tests/EWTLoggerListener.cs b/TameMyCerts.Tests/EWTLoggerListener.cs new file mode 100644 index 0000000..e635a35 --- /dev/null +++ b/TameMyCerts.Tests/EWTLoggerListener.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using TameMyCerts; + +namespace TameMyCerts.Tests +{ + public class EWTLoggerListener : EventListener + { + private readonly List events = new List(); + protected override void OnEventWritten(EventWrittenEventArgs eventData) { events.Add(eventData); } + public List Events => events; + public void ClearEvents() { events.Clear(); } + + protected override void OnEventSourceCreated(EventSource eventSource) + { + if (eventSource.Name == "TameMyCerts") + { + EnableEvents(eventSource, EventLevel.LogAlways, (EventKeywords)(-1)); + } + } + } +} diff --git a/TameMyCerts.Tests/TameMyCerts.Tests.csproj b/TameMyCerts.Tests/TameMyCerts.Tests.csproj index 916b2f2..193eae9 100644 --- a/TameMyCerts.Tests/TameMyCerts.Tests.csproj +++ b/TameMyCerts.Tests/TameMyCerts.Tests.csproj @@ -15,7 +15,7 @@ 1701;1702;CA1416 - + diff --git a/TameMyCerts.Tests/XMLPolicyTests.cs b/TameMyCerts.Tests/XMLPolicyTests.cs new file mode 100644 index 0000000..2898bd1 --- /dev/null +++ b/TameMyCerts.Tests/XMLPolicyTests.cs @@ -0,0 +1,99 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestPlatform.Utilities; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Linq; +using TameMyCerts.Enums; +using TameMyCerts.Models; +using TameMyCerts.Validators; +using Xunit; +using Xunit.Abstractions; +using static System.Net.Mime.MediaTypeNames; + +namespace TameMyCerts.Tests; + +public class XMLPolicyTests +{ + private EWTLoggerListener _listener; + private readonly ITestOutputHelper output; + + public XMLPolicyTests(ITestOutputHelper output) + { + this.output = output; + this._listener = new EWTLoggerListener(); + } + + internal void PrintResult(CertificateRequestValidationResult result) + { + output.WriteLine("0x{0:X} ({0}) {1}.", result.StatusCode, + new Win32Exception(result.StatusCode).Message); + output.WriteLine(string.Join("\n", result.Description)); + } + + [Fact] + public void Test_reading_compliant_XML() + { + var filename = Path.GetTempFileName(); + + string sampleXML = @" + false +"; + File.WriteAllText(filename, sampleXML); + + CertificateRequestPolicyCacheEntry cacheEntry = new CertificateRequestPolicyCacheEntry(filename); + + Assert.False(cacheEntry.CertificateRequestPolicy.AuditOnly); + Assert.Empty(cacheEntry.ErrorMessage); + + File.Delete(filename); + } + + [Fact] + public void Test_Unknown_XML_Element() + { + var filename = Path.GetTempFileName(); + + string sampleXML = @" + false + +"; + File.WriteAllText(filename, sampleXML); + _listener.ClearEvents(); + + CertificateRequestPolicyCacheEntry cacheEntry = new CertificateRequestPolicyCacheEntry(filename); + + Assert.Empty(cacheEntry.ErrorMessage); + Assert.Equal(2, _listener.Events.Count); + Assert.Equal(92, _listener.Events[0].EventId); + + File.Delete(filename); + } + + [Fact] + public void Test_Unknown_XML_Element2() + { + var filename = Path.GetTempFileName(); + + string sampleXML = @" + This should fault + +"; + File.WriteAllText(filename, sampleXML); + _listener.ClearEvents(); + + CertificateRequestPolicyCacheEntry cacheEntry = new CertificateRequestPolicyCacheEntry(filename); + + Assert.Empty(cacheEntry.ErrorMessage); + Assert.Equal(2, _listener.Events.Count); + Assert.Equal(92, _listener.Events[0].EventId); + + File.Delete(filename); + } + +} \ No newline at end of file diff --git a/TameMyCerts.Tests/YubikeyValidatorTests.cs b/TameMyCerts.Tests/YubikeyValidatorTests.cs new file mode 100644 index 0000000..824e10e --- /dev/null +++ b/TameMyCerts.Tests/YubikeyValidatorTests.cs @@ -0,0 +1,639 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Security.Principal; +using Xunit; +using TameMyCerts.Enums; +using TameMyCerts.Models; +using TameMyCerts.Validators; +using Xunit.Abstractions; +using System.ComponentModel.DataAnnotations; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace TameMyCerts.Tests +{ + public class YubikeyValidatorTests + { + private string _yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR; + private string _yubikey_valid_5_4_3_Once_Cached_UsbAKeychain_9a_FIPS_RSA_2048_CSR; + private string _yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_CSR; + private string _yubikey_valid_5_4_3_Always_Never_UsbAKeychain_9a_Normal_ECC_384_CSR; + private readonly CertificateDatabaseRow _yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_dbRow; + private readonly CertificateDatabaseRow _yubikey_valid_5_4_3_Once_Cached_UsbAKeychain_9a_FIPS_RSA_2048_dbRow; + private readonly CertificateDatabaseRow _yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_dbRow; + private readonly CertificateRequestPolicy _policy; + private readonly YubikeyValidator _YKvalidator = new YubikeyValidator(); + private readonly CertificateContentValidator _CCvalidator = new CertificateContentValidator(); + private readonly CertificateAuthorityConfiguration _caConfig; + + private readonly ITestOutputHelper output; + + private EWTLoggerListener _listener; + + public YubikeyValidatorTests(ITestOutputHelper output) + { + + // Sample CSR from a Yubikey with attestion included + _yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR = + "-----BEGIN CERTIFICATE REQUEST-----\n" + +"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\n" + +"Oq0a7fphvaY3PqAsU4JOFVw55ukrXnUSof+z\n" + +"-----END CERTIFICATE REQUEST-----\n"; + + _yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_CSR = "-----BEGIN CERTIFICATE REQUEST-----\n" + +"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\n" + +"S2tYi7825f8=\n" + +"-----END CERTIFICATE REQUEST-----\n"; + _yubikey_valid_5_4_3_Once_Cached_UsbAKeychain_9a_FIPS_RSA_2048_CSR = "-----BEGIN CERTIFICATE REQUEST-----\n" + + "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\n" + + "4gDDulQ=\n" + + "-----END CERTIFICATE REQUEST-----\n"; + + this._yubikey_valid_5_4_3_Always_Never_UsbAKeychain_9a_Normal_ECC_384_CSR = "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIIGaTCCBhACAQAwFjEUMBIGA1UEAxMLVGFtZU15Q2VydHMwWTATBgcqhkjOPQIB\n" + + "BggqhkjOPQMBBwNCAASa4hAbXsJat9RysMXDryp5eatzJhXtxpgyTwNgXXZUAoLg\n" + + "38xR0UIYHrM40ai7z527LiK5YUpzbFVPUCarGYRboIIFljCCBZIGCSqGSIb3DQEJ\n" + + "DjGCBYMwggV/MIICaQYKKwYBBAGCxAoDCwSCAlkwggJVMIIBPaADAgECAhABoGQP\n" + + "roLjaTWRZ0ShE3t+MA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMMFll1YmljbyBQ\n" + + "SVYgQXR0ZXN0YXRpb24wIBcNMTYwMzE0MDAwMDAwWhgPMjA1MjA0MTcwMDAwMDBa\n" + + "MCUxIzAhBgNVBAMMGll1YmlLZXkgUElWIEF0dGVzdGF0aW9uIDlhMFkwEwYHKoZI\n" + + "zj0CAQYIKoZIzj0DAQcDQgAEmuIQG17CWrfUcrDFw68qeXmrcyYV7caYMk8DYF12\n" + + "VAKC4N/MUdFCGB6zONGou8+duy4iuWFKc2xVT1AmqxmEW6NOMEwwEQYKKwYBBAGC\n" + + "xAoDAwQDBQQDMBQGCisGAQQBgsQKAwcEBgIEASwDdzAQBgorBgEEAYLECgMIBAID\n" + + "ATAPBgorBgEEAYLECgMJBAEBMA0GCSqGSIb3DQEBCwUAA4IBAQBQiEJ8rtn5AKCA\n" + + "SX8bqyYKoDS+h/PfFqBhRSY+y5nmVCqSwtZ7lm9/2bRtWwyGJ/xIRqBe8H1maUGl\n" + + "7x3ZEjCtZ48MJiAzrF0t2icB9N334XSUiHKranMWhjYhS7FY+kvQSMmxh0igGbB5\n" + + "kQWnO1QPzcgy1eEL6XLHuQxwZyOOTGI0C0oLJRpWxS262jRtWEQ7OdG7IgLrhsJL\n" + + "TrG0upuMKv9fL21cRT9cMfFlLSSIeiUXXOAOgMJ4NTuGuoNXpn0D7AkrlH0HaMBl\n" + + "D5NqwGwEML+inBuVkIzAc2KxWZllBu8BQzdX85kTgsP4cWbLXdiht7Rub2A68reR\n" + + "79IxVENoMIIDDgYKKwYBBAGCxAoDAgSCAv4wggL6MIIB4qADAgECAgkA6MPdeZ5D\n" + + "O2IwDQYJKoZIhvcNAQELBQAwKzEpMCcGA1UEAwwgWXViaWNvIFBJViBSb290IENB\n" + + "IFNlcmlhbCAyNjM3NTEwIBcNMTYwMzE0MDAwMDAwWhgPMjA1MjA0MTcwMDAwMDBa\n" + + "MCExHzAdBgNVBAMMFll1YmljbyBQSVYgQXR0ZXN0YXRpb24wggEiMA0GCSqGSIb3\n" + + "DQEBAQUAA4IBDwAwggEKAoIBAQC9PT4n9BHqypwVUo2qvOyQUG96nZZpArJfgc/t\n" + + "As8/Ylk2brMQjHIi0B8faIRbjrSsOS6vVk6ZX+P/cX1tR1a2hKZ+hbaUuC6wETPQ\n" + + "WA5LzWm/PqFx/b6Zbwp6B29moNtEjY45d3e217QPjwlrwPjHTmmPZ8xZh7x/lirc\n" + + "GO+ezkC2VXJDlQElCzTMVYE10M89Nicm3DZDhmfylkwchFfgVMulfzUYDaGnkelo\n" + + "IthlXpP4XVNgy65Nxgdiy48cr8oTLr1VLhS3bmjTZ06lj13SYCOF7fvAkLyemfwu\n" + + "P4820G+O/a3s1PXZpLxcbskP1YsaOr6+Fg8ISt0d5MTcJ673AgMBAAGjKTAnMBEG\n" + + "CisGAQQBgsQKAwMEAwUEAzASBgNVHRMBAf8ECDAGAQH/AgEAMA0GCSqGSIb3DQEB\n" + + "CwUAA4IBAQBbhnk9HZqNtSeqgfVfwyYcJmdd+wD0zQSrNBH4V9JKt3/Y37vlGLNv\n" + + "YWsGhz++9yrbFjlIDaFCurab7DY7vgP1GwH1Jy1Ffc64bFUqBBTRLTIaoxdelVI1\n" + + "PnZHIIvzzjqObjQ7ee57g/Ym1hnpNHuNZRim5UUlmeqGtdWwtD4OJMTjpgzHrWb1\n" + + "CqGe0ITdmNNdvb92wit83v8Hod/x94R00WjmfhwKPiwXm/N+UGxryl68ceUsw2y9\n" + + "WUwixxSMR8uQcym6a13qmttwzGnLJrE1db5lY7GP5eNpkyWsmr0BKxvdB+4EyJgg\n" + + "2MHFTwGtp1BYuNnL7G2sFJ0DNSIj9pg/MAoGCCqGSM49BAMCA0cAMEQCIHoAN963\n" + + "Jwhzf7EkenWe2R2m9slB2OWIBRB5TMUBWSDiAiAkwSNRUjDbWJIP3gNmmnrpJuaj\n" + + "nmObx+OfPQaCUoMxZw==\n" + + "-----END CERTIFICATE REQUEST-----\n"; + + _policy = new CertificateRequestPolicy { + YubikeyPolicy = new List + { + new YubikeyPolicy + { + } + } + }; + + _yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10); + _yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_dbRow = new CertificateDatabaseRow(_yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_CSR, CertCli.CR_IN_PKCS10); + _yubikey_valid_5_4_3_Once_Cached_UsbAKeychain_9a_FIPS_RSA_2048_dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Cached_UsbAKeychain_9a_FIPS_RSA_2048_CSR, CertCli.CR_IN_PKCS10); + + this.output = output; + this._listener = new EWTLoggerListener(); + } + + internal void PrintResult(CertificateRequestValidationResult result) + { + output.WriteLine("0x{0:X} ({0}) {1}.", result.StatusCode, + new Win32Exception(result.StatusCode).Message); + output.WriteLine(string.Join("\n", result.Description)); + } + + [Fact] + public void Extract_Genuine_Yubikey_Attestion_10001() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10001); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + Assert.True(yubikey.TouchPolicy == YubikeyTouchPolicy.Never); + Assert.True(yubikey.PinPolicy == YubikeyPinPolicy.Once); + Assert.True(yubikey.FirmwareVersion == new Version(5, 4, 3)); + Assert.True(yubikey.FormFactor == YubikeyFormFactor.UsbAKeychain); + Assert.True(yubikey.Slot == "9a"); + + PrintResult(result); + + } + + [Fact] + public void Validate_Policy_MinimumFirmware_5_7_1_should_Reject_10002() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10002); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].MinimumFirmwareString = "5.7.1"; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10002); + + PrintResult(result); + + Assert.True(result.DeniedForIssuance); + } + + [Fact] + public void Validate_Policy_MinimumFirmware_5_7_1_should_Allow_10003() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_CSR, CertCli.CR_IN_PKCS10, null, 10003); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].MinimumFirmwareString = "5.7.1"; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10003); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + } + + [Fact] + public void Validate_PIN_Policy_Once_should_Allow_10004() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10004); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].PinPolicies.Add(YubikeyPinPolicy.Once); + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10004); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + } + [Fact] + public void Validate_PIN_Policy_Deny_Never_should_Allow_10005() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10005); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].PinPolicies.Add(YubikeyPinPolicy.Never); + policy.YubikeyPolicy[0].Action = YubikeyPolicyAction.Deny; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10005); + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + } + [Fact] + public void Validate_PIN_Policy_Deny_Once_should_Deny_10006() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10006); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].PinPolicies.Add(YubikeyPinPolicy.Once); + policy.YubikeyPolicy[0].Action = YubikeyPolicyAction.Deny; + + _listener.ClearEvents(); + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10006); + + PrintResult(result); + + Assert.Single(_listener.Events); // Ensure one event was logged + Assert.Equal(4201, _listener.Events[0].EventId); + Assert.True(result.DeniedForIssuance); + } + [Fact] + public void Validate_FIPS_Edition_Should_Deny_10007() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10007); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].Edition.Add(YubikeyEdition.FIPS); + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10007); + PrintResult(result); + + Assert.True(result.DeniedForIssuance); + } + [Fact] + public void Validate_FIPS_Edition_Should_Allow_10008() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Cached_UsbAKeychain_9a_FIPS_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10008); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].Edition.Add(YubikeyEdition.FIPS); + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10008); + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + } + [Fact] + public void Validate_PIN_Policy_VerifyAll_10009() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10009); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].PinPolicies.Add(YubikeyPinPolicy.Once); + policy.YubikeyPolicy[0].TouchPolicies.Add(YubikeyTouchPolicy.Never); + policy.YubikeyPolicy[0].MinimumFirmwareString = "5.4.0"; + policy.YubikeyPolicy[0].MaximumFirmwareString = "5.7.0"; + policy.YubikeyPolicy[0].Formfactor.Add(YubikeyFormFactor.UsbAKeychain); + policy.YubikeyPolicy[0].Edition.Add(YubikeyEdition.Normal); + policy.YubikeyPolicy[0].KeyAlgorithmFamilies.Add(KeyAlgorithmFamily.RSA); + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10009); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + } + + [Fact] + public void Validate_Touch_Policy_Allow_Never_should_Allow_10010() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10010); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].TouchPolicies.Add(YubikeyTouchPolicy.Never); + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10010); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + } + [Fact] + public void Validate_Touch_Policy_Deny_Never_should_Deny_10011() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10011); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].TouchPolicies.Add(YubikeyTouchPolicy.Never); + policy.YubikeyPolicy[0].Action = YubikeyPolicyAction.Deny; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10011); + + PrintResult(result); + + Assert.True(result.DeniedForIssuance); + } + + [Fact] + public void Validate_Touch_Policy_Allowed_Always_should_Deny_10012() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10012); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].TouchPolicies.Add(YubikeyTouchPolicy.Always); + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10012); + + PrintResult(result); + + Assert.True(result.DeniedForIssuance); + } + + [Fact] + public void Validate_Require_Firemware_Above_5_7_1_to_allow_ECC_should_allow_10013() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_CSR, CertCli.CR_IN_PKCS10, null, 10013); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].KeyAlgorithmFamilies.Add(KeyAlgorithmFamily.ECC); + policy.YubikeyPolicy[0].MinimumFirmwareString = "5.7.1"; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10013); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + } + [Fact] + public void Validate_Require_Firemware_Above_5_7_1_to_allow_ECC_should_deny_10016() + { + _listener.ClearEvents(); + + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Always_Never_UsbAKeychain_9a_Normal_ECC_384_CSR, CertCli.CR_IN_PKCS10, null, 10016); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].KeyAlgorithmFamilies.Add(KeyAlgorithmFamily.ECC); + policy.YubikeyPolicy[0].MinimumFirmwareString = "5.7.1"; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, dbRow.RequestID); + + PrintResult(result); + + Assert.Contains(4203, _listener.Events.Select(e => e.EventId)); + Assert.True(result.DeniedForIssuance); + } + [Fact] + public void Validate_Deny_Firemware_Below_5_6_9_with_ECC_should_deny_10017() + { + _listener.ClearEvents(); + + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Always_Never_UsbAKeychain_9a_Normal_ECC_384_CSR, CertCli.CR_IN_PKCS10, null, 10017); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].KeyAlgorithmFamilies.Add(KeyAlgorithmFamily.ECC); + policy.YubikeyPolicy[0].MaximumFirmwareString = "5.6.9"; + policy.YubikeyPolicy[0].Action = YubikeyPolicyAction.Deny; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, dbRow.RequestID); + + PrintResult(result); + + Assert.Contains(4201, _listener.Events.Select(e => e.EventId)); + Assert.True(result.DeniedForIssuance); + } + [Fact] + public void Validate_Deny_Firemware_Below_5_6_9_with_ECC_should_allow_10018() + { + _listener.ClearEvents(); + + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_CSR, CertCli.CR_IN_PKCS10, null, 10018); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikey); + + CertificateRequestPolicy policy = _policy; + policy.YubikeyPolicy[0].KeyAlgorithmFamilies.Add(KeyAlgorithmFamily.ECC); + policy.YubikeyPolicy[0].MaximumFirmwareString = "5.6.9"; + policy.YubikeyPolicy[0].Action = YubikeyPolicyAction.Deny; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, dbRow.RequestID); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + } + [Fact] + public void Set_Subject_RDN_to_Yubbikey_Slot_10019() + { + _listener.ClearEvents(); + + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_7_1_Always_Always_UsbCKeychain_9c_Normal_ECC_384_CSR, CertCli.CR_IN_PKCS10, null, 10019); + CertificateRequestPolicy policy = _policy; + policy.OutboundSubject.Add(new OutboundSubjectRule + { + Field = RdnTypes.CommonName, + Value = "{yk:slot}", + Mandatory = true, + Force = true + }); + + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, policy, dbRow, out var yubikey); + result = _CCvalidator.VerifyRequest(result, policy, dbRow, null, _caConfig, yubikey); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + Assert.Contains("9c", result.CertificateProperties.Where(x => x.Key.Equals(RdnTypes.NameProperty[RdnTypes.CommonName])).Select(x => x.Value)); + } + + [Fact] + public void Rewrite_Subject_to_slot_10014() + { + CertificateDatabaseRow dbRow = new CertificateDatabaseRow(_yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_CSR, CertCli.CR_IN_PKCS10, null, 10014); + var result = new CertificateRequestValidationResult(dbRow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbRow, out var yubikeyInfo); + + CertificateRequestPolicy policy = _policy; + policy.OutboundSubject.Add(new OutboundSubjectRule + { + Field = RdnTypes.CommonName, + Value = "{yk:slot}", + Mandatory = true, + Force = true + } + ); + + result = _YKvalidator.VerifyRequest(result, policy, yubikeyInfo, 10014); + result = _CCvalidator.VerifyRequest(result, policy, _yubikey_valid_5_4_3_Once_Never_UsbAKeychain_9a_Normal_RSA_2048_dbRow, null, _caConfig, yubikeyInfo); + + PrintResult(result); + + Assert.False(result.DeniedForIssuance); + Assert.Contains("9a", result.CertificateProperties + .Where(x => x.Key.Equals(RdnTypes.NameProperty[RdnTypes.CommonName])) + .Select(x => x.Value)); + } + + [Fact] + public void Validate_Accutial_Attestions_certificate_wrong_public_key_10015() + { + #region CSR + string csr = "-----BEGIN CERTIFICATE REQUEST-----\n" + + "MIIGhjCCBi0CAQAwFjEUMBIGA1UEAxMLVGFtZU15Q2VydHMwWTATBgcqhkjOPQIB\n" + + "BggqhkjOPQMBBwNCAAQL7vD+BWCz9dE3w4LWyHynw26z+QjK6Mm6wKkGvnpAYY2R\n" + + "di8Dt01qG5X5art1njoS9gOvRbFnKHvHUwnPSOKooIIFszCCBa8GCSqGSIb3DQEJ\n" + + "DjGCBaAwggWcMIIChgYKKwYBBAGCxAoDCwSCAnYwggJyMIIBWqADAgECAhAB8pev\n" + + "E7/utfqJv2eZc/edMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNVBAMMFll1YmljbyBQ\n" + + "SVYgQXR0ZXN0YXRpb24wIBcNMTYwMzE0MDAwMDAwWhgPMjA1MjA0MTcwMDAwMDBa\n" + + "MCUxIzAhBgNVBAMMGll1YmlLZXkgUElWIEF0dGVzdGF0aW9uIDlhMHYwEAYHKoZI\n" + + "zj0CAQYFK4EEACIDYgAEi8t88XhiXfaC1S7+rfkmLIQMm4HS8wa4O8Su4JvIxEoK\n" + + "rQd5C3JvRFa4wiLn72OD3VDuNB22RfxBQxBJn9dl/VtPZbhf1uMVIBQeo2V66KvW\n" + + "44TUKRdCyMJ+nveIPO5to04wTDARBgorBgEEAYLECgMDBAMFBwEwFAYKKwYBBAGC\n" + + "xAoDBwQGAgQBymbqMBAGCisGAQQBgsQKAwgEAgMCMA8GCisGAQQBgsQKAwkEAQMw\n" + + "DQYJKoZIhvcNAQELBQADggEBAAtzgqRrrmrLIrS8+ZAj9DXOakG1Z+1y+FRNxCfK\n" + + "KG7ukpB9p4Nl1mJGy/39/af4tRrG5PfUB67FhTasRADBCpm9MiQOxTgpydLrco6U\n" + + "3t2g+0vw+4ic0BtqRgy+IZWW3nIaLhnRAtM80jjSsAwH10hQc0kiFgkZ1znlrV2M\n" + + "HX1BU4IesaOI6dT/tqpAqljdp75IR5wc4drbHlxjfSDxg7GkZq9iZrhEb0XK4oTq\n" + + "/Enl6R2dO/W6YXCEFHlgaqmuByF4ICG6ajN34gnQYnbkqQ483Gz5TZ1QPP4f4KxV\n" + + "3S8I2t1g0rbA17AKSEwxjEan2ke0C7uermbeRiMai56UzcUwggMOBgorBgEEAYLE\n" + + "CgMCBIIC/jCCAvowggHioAMCAQICCQCW07/1/bKfejANBgkqhkiG9w0BAQsFADAr\n" + + "MSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0EgU2VyaWFsIDI2Mzc1MTAgFw0x\n" + + "NjAzMTQwMDAwMDBaGA8yMDUyMDQxNzAwMDAwMFowITEfMB0GA1UEAwwWWXViaWNv\n" + + "IFBJViBBdHRlc3RhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n" + + "ANn3GakWu9K02zQtp32VGaRejTBDa9G+n8bJRMwwPN6AUqj3JKBpsn/lsxob3Z/G\n" + + "+gRnH5qaUb+Psl8k9ANmK9TQ02xt99ZUs2dkc9AF5eVmTWsndXUCWtlebxYujfrT\n" + + "+sGtC+lJxwOsTl0kdwsPHTFprbeRfj3/cSyui29qasFZieq6cTtfP7jyGliTAbyA\n" + + "k4hV9uO8Zi66CUjqdNFIbK3oajo9OgwJed6dA9JQHVoJuSVA5/ew5z3+/xAZlfxW\n" + + "EO5/TBrNwuxqKZV7UvlbAGUZY8d2wv0vHBIgAF2vDpxtc5LtI7M88bGrrcliAEPF\n" + + "3PCd2tKz2tvA8sbbnGLggQMCAwEAAaMpMCcwEQYKKwYBBAGCxAoDAwQDBQcBMBIG\n" + + "A1UdEwEB/wQIMAYBAf8CAQAwDQYJKoZIhvcNAQELBQADggEBAKzmuDPAmjsO/wpP\n" + + "x6eoE1wibCzN71buOaoiwz7j+w4e7iufkCXzDgw7MdXkrL9a/ulIKQlRMypJoEF/\n" + + "B6+8JrJuquk3K972hektlwYnIv4+WTN+lnOSmjdMv7gSxlMtNtd/53ES8WaERsJL\n" + + "AyUfuuwqTAuWGAN0qG0x583PmbvP0AtmnJ7s+9ilPDG6MG6wlKvPL0UvbB+f3ABb\n" + + "/1lVRc+6FOHpETUUi/p/BjgdYuy3XczPFM1Zm8fG2shmyW8BUj+BUuLJK5n2HzG4\n" + + "HfJNIDHjxxZceg2CaWu5U1tXCTY3Q2KY6Ks5oK9cYFWwG+71mODskNL9HgSUUCSn\n" + + "1/BkKNEwCgYIKoZIzj0EAwIDRwAwRAIgJi8zizb0MmkYFW3FHfU2RngAIK+kS+uw\n" + + "iIv3qbQubxYCIFOtZseiiicTiReB+Tsh7RnkJx72zx71VE0XdbhroWl+\n" + + "-----END CERTIFICATE REQUEST-----\n"; + + _listener.ClearEvents(); + + CertificateDatabaseRow dbrow = new CertificateDatabaseRow(csr, CertCli.CR_IN_PKCS10, null, 10015); + #endregion + var result = new CertificateRequestValidationResult(dbrow); + result = _YKvalidator.ExtractAttestion(result, _policy, dbrow, out var yubikey); + CertificateRequestPolicy policy = _policy; + + result = _YKvalidator.VerifyRequest(result, policy, yubikey, 10015); + + PrintResult(result); + Assert.Contains(4207, _listener.Events.Select(e => e.EventId)); + Assert.True(result.DeniedForIssuance); + } + + } +} \ No newline at end of file diff --git a/TameMyCerts.sln b/TameMyCerts.sln index 1e0d392..06166eb 100644 --- a/TameMyCerts.sln +++ b/TameMyCerts.sln @@ -3,24 +3,43 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.3.32922.545 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TameMyCerts", "TameMyCerts\TameMyCerts.csproj", "{BB35A67E-8E22-48C3-B3F8-E852161ACB59}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TameMyCerts", "TameMyCerts\TameMyCerts.csproj", "{BB35A67E-8E22-48C3-B3F8-E852161ACB59}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TameMyCerts.Tests", "TameMyCerts.Tests\TameMyCerts.Tests.csproj", "{98F86AA9-4B95-4300-9A23-512C620D075C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TameMyCerts.Tests", "TameMyCerts.Tests\TameMyCerts.Tests.csproj", "{98F86AA9-4B95-4300-9A23-512C620D075C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Support.GenerateEWTManifest", "Support.GenerateEWTManifest\Support.GenerateEWTManifest.csproj", "{6ED9736E-8C10-44A3-B80E-5FE83F2FE632}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {BB35A67E-8E22-48C3-B3F8-E852161ACB59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BB35A67E-8E22-48C3-B3F8-E852161ACB59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB35A67E-8E22-48C3-B3F8-E852161ACB59}.Debug|x64.ActiveCfg = Debug|Any CPU + {BB35A67E-8E22-48C3-B3F8-E852161ACB59}.Debug|x64.Build.0 = Debug|Any CPU {BB35A67E-8E22-48C3-B3F8-E852161ACB59}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB35A67E-8E22-48C3-B3F8-E852161ACB59}.Release|Any CPU.Build.0 = Release|Any CPU + {BB35A67E-8E22-48C3-B3F8-E852161ACB59}.Release|x64.ActiveCfg = Release|Any CPU + {BB35A67E-8E22-48C3-B3F8-E852161ACB59}.Release|x64.Build.0 = Release|Any CPU {98F86AA9-4B95-4300-9A23-512C620D075C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {98F86AA9-4B95-4300-9A23-512C620D075C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98F86AA9-4B95-4300-9A23-512C620D075C}.Debug|x64.ActiveCfg = Debug|Any CPU + {98F86AA9-4B95-4300-9A23-512C620D075C}.Debug|x64.Build.0 = Debug|Any CPU {98F86AA9-4B95-4300-9A23-512C620D075C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {98F86AA9-4B95-4300-9A23-512C620D075C}.Release|Any CPU.Build.0 = Release|Any CPU + {98F86AA9-4B95-4300-9A23-512C620D075C}.Release|x64.ActiveCfg = Release|Any CPU + {98F86AA9-4B95-4300-9A23-512C620D075C}.Release|x64.Build.0 = Release|Any CPU + {6ED9736E-8C10-44A3-B80E-5FE83F2FE632}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6ED9736E-8C10-44A3-B80E-5FE83F2FE632}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6ED9736E-8C10-44A3-B80E-5FE83F2FE632}.Debug|x64.ActiveCfg = Debug|Any CPU + {6ED9736E-8C10-44A3-B80E-5FE83F2FE632}.Debug|x64.Build.0 = Debug|Any CPU + {6ED9736E-8C10-44A3-B80E-5FE83F2FE632}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6ED9736E-8C10-44A3-B80E-5FE83F2FE632}.Release|Any CPU.Build.0 = Release|Any CPU + {6ED9736E-8C10-44A3-B80E-5FE83F2FE632}.Release|x64.ActiveCfg = Release|Any CPU + {6ED9736E-8C10-44A3-B80E-5FE83F2FE632}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TameMyCerts/EWTLogger.cs b/TameMyCerts/EWTLogger.cs new file mode 100644 index 0000000..392357b --- /dev/null +++ b/TameMyCerts/EWTLogger.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Tracing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TameMyCerts.Enums; +using TameMyCerts.Models; +using TameMyCerts.Validators; + +namespace TameMyCerts +{ + [EventSource(Name = "TameMyCerts", LocalizationResources = "TameMyCerts.LocalizedStrings")] + public sealed class EWTLogger : EventSource + { + public static EWTLogger Log = new EWTLogger(); + + public static class Tasks + { + public const EventTask None = (EventTask)1; + public const EventTask TameMyCerts = (EventTask)2; + public const EventTask YubikeyValidator = (EventTask)10; + public const EventTask XMLParser = (EventTask)11; + } + + #region Tame My Certs + [Event(1, Level = EventLevel.Informational, Channel = EventChannel.Admin, Task = Tasks.TameMyCerts, Keywords = EventKeywords.None)] + public void TMC_1_PolicyModule_Success_Initiated(string policyModule, string version) + { + if (IsEnabled()) + { + WriteEvent(1, policyModule, version); + } + } + [Event(2, Level = EventLevel.Error, Channel = EventChannel.Admin, Task = Tasks.TameMyCerts, Keywords = EventKeywords.None)] + public void TMC_2_PolicyModule_Failed_Initiated(string exception) + { + if (IsEnabled()) + { + WriteEvent(2, exception); + } + } + [Event(4, Level = EventLevel.Error, Channel = EventChannel.Admin, Task = Tasks.TameMyCerts, Keywords = EventKeywords.None)] + public void TMC_4_PolicyModule_Default_Shutdown_Failed(string exception) + { + if (IsEnabled()) + { + WriteEvent(4, exception); + } + } + [Event(5, Level = EventLevel.Informational, Channel = EventChannel.Analytic, Task = Tasks.TameMyCerts, Keywords = EventKeywords.None)] + public void TMC_5_Analytical_Audit_only_Deny(int requestID, string template, string reason) + { + if (IsEnabled()) + { + WriteEvent(5, requestID, template, reason); + } + } + [Event(6, Level = EventLevel.Informational, Channel = EventChannel.Admin, Task = Tasks.TameMyCerts, Keywords = EventKeywords.None)] + public void TMC_6_Deny_Issuing_Request(int requestID, string template, string reason) + { + if (IsEnabled()) + { + WriteEvent(6, requestID, template, reason); + } + } + [Event(12, Level = EventLevel.Informational, Channel = EventChannel.Admin, Task = Tasks.TameMyCerts, Keywords = EventKeywords.None)] + public void TMC_12_Success_Issued(int requestID, string template) + { + if (IsEnabled()) + { + WriteEvent(12, requestID, template); + } + } + [Event(13, Level = EventLevel.Informational, Channel = EventChannel.Admin, Task = Tasks.TameMyCerts, Keywords = EventKeywords.None)] + public void TMC_13_Success_Pending(int requestID, string template) + { + if (IsEnabled()) + { + WriteEvent(13, requestID, template); + } + } + [Event(91, Level = EventLevel.Informational, Channel = EventChannel.Debug, Task = Tasks.XMLParser, Keywords = EventKeywords.None)] + public void TMC_91_Policy_Read(string templateName, string policy) + { + if (IsEnabled()) + { + WriteEvent(91, templateName, policy); + } + } + [Event(92, Level = EventLevel.Critical, Channel = EventChannel.Admin, Task = Tasks.XMLParser, Keywords = EventKeywords.None)] + public void TMC_92_Policy_Unknown_XML_Element(string elementName, int lineNumber, int linePosition) + { + if (IsEnabled()) + { + WriteEvent(92, elementName, lineNumber, linePosition); + } + } + [Event(93, Level = EventLevel.Critical, Channel = EventChannel.Admin, Task = Tasks.XMLParser, Keywords = EventKeywords.None)] + public void TMC_93_Policy_Unknown_XML_Attribute(string attributeName, string attributeValue, int lineNumber, int linePosition) + { + if (IsEnabled()) + { + WriteEvent(93, attributeName, attributeValue, lineNumber, linePosition); + } + } + + #endregion + + #region Yubico Validator events 4201-4399 + [Event(4201, Level = EventLevel.Warning, Channel = EventChannel.Operational, Task = Tasks.YubikeyValidator, Keywords = EventKeywords.None)] + public void YKVal_4201_Denied_by_Policy(string denyingPolicy, int requestID) + { + if (IsEnabled()) + { + WriteEvent(4201, denyingPolicy, requestID); + } + } + [Event(4202, Level = EventLevel.Warning, Channel = EventChannel.Operational, Task = Tasks.YubikeyValidator, Keywords = EventKeywords.None)] + public void YKVal_4202_Denied_by_Policy(int requestID) + { + if (IsEnabled()) + { + WriteEvent(4202, requestID); + } + } + [Event(4203, Level = EventLevel.Warning, Channel = EventChannel.Operational, Task = Tasks.YubikeyValidator, Keywords = EventKeywords.None)] + public void YKVal_4203_Denied_due_to_no_matching_policy_default_deny(int requestID) + { + if (IsEnabled()) + { + WriteEvent(4203, requestID); + } + } + [Event(4204, Level = EventLevel.Verbose, Channel = EventChannel.Operational, Task = Tasks.YubikeyValidator, Keywords = EventKeywords.None)] + public void YKVal_4204_Matching_policy(string policy, int requestID) + { + if (IsEnabled()) + { + WriteEvent(4204, policy, requestID); + } + } + [Event(4205, Level = EventLevel.Error, Channel = EventChannel.Operational, Task = Tasks.YubikeyValidator, Keywords = EventKeywords.None)] + public void YKVal_4205_Failed_to_extract_Yubikey_Attestion(int requestID) + { + if (IsEnabled()) + { + WriteEvent(4205, requestID); + } + } + [Event(4206, Level = EventLevel.Warning, Channel = EventChannel.Debug, Task = Tasks.YubikeyValidator, Keywords = EventKeywords.None)] + public void YKVal_4206_Debug_failed_to_match_policy(int requestID, string policy) + { + if (IsEnabled()) + { + WriteEvent(4206, requestID, policy); + } + } + [Event(4207, Level = EventLevel.Error, Channel = EventChannel.Operational, Task = Tasks.YubikeyValidator, Keywords = EventKeywords.None)] + public void YKVal_4207_Yubikey_Attestion_Missmatch_with_CSR(int requestID) + { + if (IsEnabled()) + { + WriteEvent(4207, requestID); + } + } + [Event(4208, Level = EventLevel.Error, Channel = EventChannel.Operational, Task = Tasks.YubikeyValidator, Keywords = EventKeywords.None)] + public void YKVal_4208_Yubikey_Attestion_Failed_to_build(int requestID) + { + if (IsEnabled()) + { + WriteEvent(4208, requestID); + } + } + #endregion + } +} diff --git a/TameMyCerts/Enums/KeyAlgorithmFamily.cs b/TameMyCerts/Enums/KeyAlgorithmFamily.cs index 11f5e22..d280fd3 100644 --- a/TameMyCerts/Enums/KeyAlgorithmFamily.cs +++ b/TameMyCerts/Enums/KeyAlgorithmFamily.cs @@ -14,7 +14,7 @@ namespace TameMyCerts.Enums; -internal enum KeyAlgorithmFamily +public enum KeyAlgorithmFamily { UNKNOWN = 0, RSA = 1, diff --git a/TameMyCerts/Enums/Yubikey.cs b/TameMyCerts/Enums/Yubikey.cs new file mode 100644 index 0000000..03bb51e --- /dev/null +++ b/TameMyCerts/Enums/Yubikey.cs @@ -0,0 +1,78 @@ +// Copyright 2021-2023 Uwe Gradenegger + +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace TameMyCerts.Enums +{ + /// + /// Constants from Yubico + /// https://developers.yubico.com/PIV/Introduction/PIV_attestation.html + /// + internal static class YubikeyX509Extensions + { + public const string FIRMWARE = "1.3.6.1.4.1.41482.3.3"; + public const string SERIALNUMBER = "1.3.6.1.4.1.41482.3.7"; + public const string PIN_TOUCH_POLICY = "1.3.6.1.4.1.41482.3.8"; + public const string FORMFACTOR = "1.3.6.1.4.1.41482.3.9"; + public const string FIPS_CERTIFIED = "1.3.6.1.4.1.41482.3.10"; + public const string CPSN_CERTIFIED = "1.3.6.1.4.1.41482.3.11"; + public const string ATTESTION_INTERMEDIATE = "1.3.6.1.4.1.41482.3.2"; + public const string ATTESTION_DEVICE = "1.3.6.1.4.1.41482.3.11"; + } + + public enum YubikeyFormFactor + { + Unknown = 0, + UsbAKeychain = 1, + UsbANano = 2, + UsbCKeychain = 3, + UsbCNano = 4, + UsbCLightning = 5, + UsbABiometricKeychain = 6, + UsbCBiometricKeychain = 7, + } + + public enum YubikeyTouchPolicy + { + None = 0, + Never = 1, + Always = 2, + Cached = 3, + Default = 32, + } + + public enum YubikeyPinPolicy + { + None = 0, + Never = 1, + Once = 2, + Always = 3, + MatchOnce = 4, + MatchAlways = 5, + Default = 32, + } + + public enum YubikeyPolicyAction + { + Allow, + Deny, + } + + public enum YubikeyEdition + { + Normal, + FIPS, + CSPN, + } + +} \ No newline at end of file diff --git a/TameMyCerts/LocalizedStrings.Designer.cs b/TameMyCerts/LocalizedStrings.Designer.cs index b7c98d7..70ec47a 100644 --- a/TameMyCerts/LocalizedStrings.Designer.cs +++ b/TameMyCerts/LocalizedStrings.Designer.cs @@ -294,6 +294,178 @@ internal static string DirVal_San_Invalid_Field { } } + /// + /// Looks up a localized string similar to {0} policy module version {1} is ready to process incoming certificate requests.. + /// + internal static string event_TMC_1_PolicyModule_Success_Initiated { + get { + return ResourceManager.GetString("event_TMC_1_PolicyModule_Success_Initiated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request {0} for {1} will get issued.. + /// + internal static string event_TMC_12_Success_Issued { + get { + return ResourceManager.GetString("event_TMC_12_Success_Issued", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request {0} for {1} will be put into pending state.. + /// + internal static string event_TMC_13_Success_Pending { + get { + return ResourceManager.GetString("event_TMC_13_Success_Pending", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error initializing Windows Default policy module: + ///{0}. + /// + internal static string event_TMC_2_PolicyModule_Failed_Initiated { + get { + return ResourceManager.GetString("event_TMC_2_PolicyModule_Failed_Initiated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shutting down Windows Default policy module failed: + ///{0}. + /// + internal static string event_TMC_4_PolicyModule_Default_Shutdown_Failed { + get { + return ResourceManager.GetString("event_TMC_4_PolicyModule_Default_Shutdown_Failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Audit mode is enabled for {1}. Request {0} would get denied because: + ///{2}. + /// + internal static string event_TMC_5_Analytical_Audit_only_Deny { + get { + return ResourceManager.GetString("event_TMC_5_Analytical_Audit_only_Deny", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request {0} for {1} was denied because: + ///{2}. + /// + internal static string event_TMC_6_Deny_Issuing_Request { + get { + return ResourceManager.GetString("event_TMC_6_Deny_Issuing_Request", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TameMyCerts read the policy from file: + ///{0} + /// + ///with the content: + ///{1} . + /// + internal static string event_TMC_91_Policy_Read { + get { + return ResourceManager.GetString("event_TMC_91_Policy_Read", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to XML policy unknown Element: {0} at line {1}, position {2}. + /// + internal static string event_TMC_92_Policy_Unknown_XML_Element { + get { + return ResourceManager.GetString("event_TMC_92_Policy_Unknown_XML_Element", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown Attribute: {0}='{1}' at line {2}, position {3}. + /// + internal static string event_TMC_93_Policy_Unknown_XML_Attribute { + get { + return ResourceManager.GetString("event_TMC_93_Policy_Unknown_XML_Attribute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request {1} was rejected due to policy: \r\n{0}. + /// + internal static string event_YKVal_4201_Denied_by_Policy { + get { + return ResourceManager.GetString("event_YKVal_4201_Denied_by_Policy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request {0} was rejected as the Yubikey Attestion failed for the embedded attestion.. + /// + internal static string event_YKVal_4202_Denied_by_Policy { + get { + return ResourceManager.GetString("event_YKVal_4202_Denied_by_Policy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request {0} was denied due to not meeting any Grant policy in the Yubikey Validator.. + /// + internal static string event_YKVal_4203_Denied_due_to_no_matching_policy_default_deny { + get { + return ResourceManager.GetString("event_YKVal_4203_Denied_due_to_no_matching_policy_default_deny", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request {1} is matching policy: + ///{0}. + /// + internal static string event_YKVal_4204_Matching_policy { + get { + return ResourceManager.GetString("event_YKVal_4204_Matching_policy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request {0} contained an embedded Yubikey Attestion, but it failed to be extracted.. + /// + internal static string event_YKVal_4205_Failed_to_extract_Yubikey_Attestion { + get { + return ResourceManager.GetString("event_YKVal_4205_Failed_to_extract_Yubikey_Attestion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request {0} failed to match the Yubikey Policy: + ///{1}. + /// + internal static string event_YKVal_4206_Debug_failed_to_match_policy { + get { + return ResourceManager.GetString("event_YKVal_4206_Debug_failed_to_match_policy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request {0}, attestion certificates public key missmatch with the certificate request.. + /// + internal static string event_YKVal_4207_Yubikey_Attestion_Missmatch_with_CSR { + get { + return ResourceManager.GetString("event_YKVal_4207_Yubikey_Attestion_Missmatch_with_CSR", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request {0}, yubikey attestion failed to build trust chain to Yubikey CA.. + /// + internal static string event_YKVal_4208_Yubikey_Attestion_Failed_to_build { + get { + return ResourceManager.GetString("event_YKVal_4208_Yubikey_Attestion_Failed_to_build", resourceCulture); + } + } + /// /// Looks up a localized string similar to The {0} policy module currently does not support standalone certification authorities.. /// @@ -725,12 +897,102 @@ internal static string San_unable_to_add { } /// - /// Looks up a localized string similar to The "{0}" token for the construction of a Subject Relative Distinguished Name is unknown. Ensure that Directory Service Mapping is enabled if it is an AD attribute, and that the originating certificate request contains the token, if it is a request field.. + /// Looks up a localized string similar to The "{0}:{1}" token for the construction of a Subject Relative Distinguished Name is unknown. Ensure that Directory Service Mapping is enabled if it is an AD attribute, and that the originating certificate request contains the token, if it is a request field.. /// internal static string Token_invalid { get { return ResourceManager.GetString("Token_invalid", resourceCulture); } } + + /// + /// Looks up a localized string similar to This Yubikey's firmware "{0}", is not on the list of allowed firmwares.. + /// + internal static string YKVal_Allowed_Firmware_Version { + get { + return ResourceManager.GetString("YKVal_Allowed_Firmware_Version", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This Yubikey's PIN policy "{0}", is not on the list of allowed PIN policies.. + /// + internal static string YKVal_Allowed_PIN_Policy { + get { + return ResourceManager.GetString("YKVal_Allowed_PIN_Policy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This Yubikey's touch policy "{0}", is not on the list of allowed touch policies.. + /// + internal static string YKVal_Allowed_Touch_Policy { + get { + return ResourceManager.GetString("YKVal_Allowed_Touch_Policy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Yubikey's firmware "{0}" is actively being denied.. + /// + internal static string YKVal_Disallowed_Firmware_Version { + get { + return ResourceManager.GetString("YKVal_Disallowed_Firmware_Version", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Yubikey's PIN Policy "{0}" is actively being denied.. + /// + internal static string YKVal_Disallowed_PIN_Policy { + get { + return ResourceManager.GetString("YKVal_Disallowed_PIN_Policy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Yubikey's Touch Policy "{0}" is actively being denied.. + /// + internal static string YKVal_Disallowed_Touch_Policy { + get { + return ResourceManager.GetString("YKVal_Disallowed_Touch_Policy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The certificate request has been denied due to incorrect certificate request.. + /// + internal static string YKVal_Invalid_Attestion_with_YubikeyPolicy { + get { + return ResourceManager.GetString("YKVal_Invalid_Attestion_with_YubikeyPolicy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request failed to find a matching yubikey policy, defaulting to deny.. + /// + internal static string YKVal_No_Matching_Policy_Found { + get { + return ResourceManager.GetString("YKVal_No_Matching_Policy_Found", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request matched a deny yubikey policy: {0}. + /// + internal static string YKVal_Policy_Matches_with_Reject { + get { + return ResourceManager.GetString("YKVal_Policy_Matches_with_Reject", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The module was unable to read the embedded certificates. Actual error: {0}. + /// + internal static string YKVal_Unable_to_read_embedded_certificates { + get { + return ResourceManager.GetString("YKVal_Unable_to_read_embedded_certificates", resourceCulture); + } + } } } diff --git a/TameMyCerts/LocalizedStrings.resx b/TameMyCerts/LocalizedStrings.resx index 99826fd..8b497cf 100644 --- a/TameMyCerts/LocalizedStrings.resx +++ b/TameMyCerts/LocalizedStrings.resx @@ -338,7 +338,7 @@ Unable to add entry of type "{0}" with value "{1}" to the Subject Alternative Name certificate extension. Either type or value is invalid. - The "{0}" token for the construction of a Subject Relative Distinguished Name is unknown. Ensure that Directory Service Mapping is enabled if it is an AD attribute, and that the originating certificate request contains the token, if it is a request field. + The "{0}:{1}" token for the construction of a Subject Relative Distinguished Name is unknown. Ensure that Directory Service Mapping is enabled if it is an AD attribute, and that the originating certificate request contains the token, if it is a request field. " @@ -347,4 +347,98 @@ Request {0} for {1} will be put into pending state. + + This Yubikey's firmware "{0}", is not on the list of allowed firmwares. + + + This Yubikey's PIN policy "{0}", is not on the list of allowed PIN policies. + + + This Yubikey's touch policy "{0}", is not on the list of allowed touch policies. + + + The Yubikey's firmware "{0}" is actively being denied. + + + The Yubikey's PIN Policy "{0}" is actively being denied. + + + The Yubikey's Touch Policy "{0}" is actively being denied. + + + The module was unable to read the embedded certificates. Actual error: {0} + + + The request failed to find a matching yubikey policy, defaulting to deny. + + + The request matched a deny yubikey policy: {0} + + + The certificate request has been denied due to incorrect certificate request. + + + The request {1} was rejected due to policy: \r\n{0} + + + The request {0} was rejected as the Yubikey Attestion failed for the embedded attestion. + + + The request {0} was denied due to not meeting any Grant policy in the Yubikey Validator. + + + The request {1} is matching policy: +{0} + + + The request {0} contained an embedded Yubikey Attestion, but it failed to be extracted. + + + The request {0} failed to match the Yubikey Policy: +{1} + + + The request {0}, attestion certificates public key missmatch with the certificate request. + + + The request {0}, yubikey attestion failed to build trust chain to Yubikey CA. + + + {0} policy module version {1} is ready to process incoming certificate requests. + + + Error initializing Windows Default policy module: +{0} + + + Request {0} for {1} will get issued. + + + Request {0} for {1} will be put into pending state. + + + Shutting down Windows Default policy module failed: +{0} + + + Audit mode is enabled for {1}. Request {0} would get denied because: +{2} + + + Request {0} for {1} was denied because: +{2} + + + TameMyCerts read the policy from file: +{0} + +with the content: +{1} + + + XML policy unknown Element: {0} at line {1}, position {2} + + + Unknown Attribute: {0}='{1}' at line {2}, position {3} + \ No newline at end of file diff --git a/TameMyCerts/Models/CertificateDatabaseRow.cs b/TameMyCerts/Models/CertificateDatabaseRow.cs index d5aa300..c5e809c 100644 --- a/TameMyCerts/Models/CertificateDatabaseRow.cs +++ b/TameMyCerts/Models/CertificateDatabaseRow.cs @@ -1,4 +1,18 @@ -using System; +// Copyright 2021-2024 Uwe Gradenegger + +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -18,7 +32,9 @@ public CertificateDatabaseRow(CCertServerPolicy serverPolicy) NotBefore = serverPolicy.GetDateCertificatePropertyOrDefault("NotBefore"); NotAfter = serverPolicy.GetDateCertificatePropertyOrDefault("NotAfter"); KeyLength = serverPolicy.GetLongCertificatePropertyOrDefault("PublicKeyLength"); + PublicKey = serverPolicy.GetBinaryCertificatePropertyOrDefault("RawPublicKey"); RawRequest = serverPolicy.GetBinaryRequestPropertyOrDefault("RawRequest"); + RequestID = serverPolicy.GetLongRequestPropertyOrDefault("RequestID"); RequestType = serverPolicy.GetLongRequestPropertyOrDefault("RequestType") ^ CertCli.CR_IN_FULLRESPONSE; Upn = serverPolicy.GetStringCertificatePropertyOrDefault("UPN") ?? string.Empty; DistinguishedName = serverPolicy.GetStringRequestPropertyOrDefault("Request.DistinguishedName") ?? @@ -32,10 +48,10 @@ public CertificateDatabaseRow(CCertServerPolicy serverPolicy) SubjectRelativeDistinguishedNames = serverPolicy.GetSubjectRelativeDistinguishedNames(); SubjectAlternativeNameExtension = GetSubjectAlternativeNameExtension(); } - + // To inject unit tests public CertificateDatabaseRow(string request, int requestType, - Dictionary requestAttributes = null) + Dictionary requestAttributes = null, int requestID = 0) { NotBefore = DateTimeOffset.Now; NotAfter = DateTimeOffset.Now.AddYears(1); @@ -52,6 +68,7 @@ public CertificateDatabaseRow(string request, int requestType, ? new List>() : GetDnComponents(DistinguishedName); SubjectAlternativeNameExtension = GetSubjectAlternativeNameExtension(); + PublicKey = Convert.FromBase64String(certificateRequestPkcs10.PublicKey.EncodedKey); RawRequest = Convert.FromBase64String(certificateRequestPkcs10.get_RawData()); RequestType = CertCli.CR_IN_PKCS10; } @@ -69,6 +86,7 @@ public CertificateDatabaseRow(string request, int requestType, RequestAttributes.Add(keyValuePair.Key, keyValuePair.Value); } } + RequestID = requestID; } public DateTimeOffset NotBefore { get; } @@ -113,6 +131,11 @@ public CertificateDatabaseRow(string request, int requestType, /// public byte[] RawRequest { get; } + /// + /// The internal RequestID. + /// + public int RequestID { get; } + /// /// The request type as defined in certcli.h (PKCS#10, PKCS#7 or CMS). /// @@ -133,6 +156,13 @@ public CertificateDatabaseRow(string request, int requestType, /// templates are identified by their OID. /// public string CertificateTemplate { get; } + + /// + /// Inline request attributes (like process name). These are read on-demand from the inline certificate request. There + /// are rare cases in which it is not possible to parse the inline request. The property returns an empty collection in + /// this case. + /// + public byte[] PublicKey { get; } /// /// Inline request attributes (like process name). These are read on-demand from the inline certificate request. There diff --git a/TameMyCerts/Models/CertificateRequestPolicy.cs b/TameMyCerts/Models/CertificateRequestPolicy.cs index 475e764..d0974c1 100644 --- a/TameMyCerts/Models/CertificateRequestPolicy.cs +++ b/TameMyCerts/Models/CertificateRequestPolicy.cs @@ -1,4 +1,4 @@ -// Copyright 2021-2023 Uwe Gradenegger +// Copyright 2021-2024 Uwe Gradenegger // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -83,6 +84,9 @@ public class CertificateRequestPolicy [XmlElement(ElementName = "DirectoryServicesMapping")] public DirectoryServicesMapping DirectoryServicesMapping { get; set; } + [XmlElement(ElementName = "YubikeyPolicy")] + public List YubikeyPolicy { get; set; } + [XmlElement(ElementName = "SupplementDnsNames")] public bool SupplementDnsNames { get; set; } @@ -135,10 +139,38 @@ public void SaveToFile(string path) public static CertificateRequestPolicy LoadFromFile(string path) { var xmlSerializer = new XmlSerializer(typeof(CertificateRequestPolicy)); + xmlSerializer.UnknownElement += new XmlElementEventHandler(UnknownElementHandler); + xmlSerializer.UnknownAttribute += new XmlAttributeEventHandler(UnknownAttributeHandler); using (var reader = new StreamReader(path)) { return (CertificateRequestPolicy)xmlSerializer.Deserialize(reader.BaseStream); } } + public string SaveToString() + { + var xmlSerializer = new XmlSerializer(typeof(CertificateRequestPolicy)); + + using (var stringWriter = new StringWriter()) + { + using (var xmlWriter = XmlWriter.Create(stringWriter)) + { + xmlSerializer.Serialize(xmlWriter, this); + var xmlData = stringWriter.ToString(); + + return ConvertToHumanReadableXml(xmlData); + } + } + } + private static void UnknownElementHandler(object sender, XmlElementEventArgs e) + { + EWTLogger.Log.TMC_92_Policy_Unknown_XML_Element(e.Element.Name, e.LineNumber, e.LinePosition); + } + + // Event handler for unknown attributes + private static void UnknownAttributeHandler(object sender, XmlAttributeEventArgs e) + { + EWTLogger.Log.TMC_93_Policy_Unknown_XML_Attribute(e.Attr.Name, e.Attr.Value, e.LineNumber, e.LinePosition); + } + } \ No newline at end of file diff --git a/TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs b/TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs index 568e201..d7d3976 100644 --- a/TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs +++ b/TameMyCerts/Models/CertificateRequestPolicyCacheEntry.cs @@ -23,6 +23,7 @@ public CertificateRequestPolicyCacheEntry(string fileName) try { CertificateRequestPolicy = CertificateRequestPolicy.LoadFromFile(fileName); + EWTLogger.Log.TMC_91_Policy_Read(fileName, CertificateRequestPolicy.SaveToString()); } catch (Exception ex) { diff --git a/TameMyCerts/Models/YubikeyObject.cs b/TameMyCerts/Models/YubikeyObject.cs new file mode 100644 index 0000000..c3801d9 --- /dev/null +++ b/TameMyCerts/Models/YubikeyObject.cs @@ -0,0 +1,170 @@ +// Copyright 2021-2024 Uwe Gradenegger +// Copyright 2024 Oscar Virot + +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.DirectoryServices; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Security.Principal; +using System.Text; +using TameMyCerts.Enums; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +namespace TameMyCerts.Models +{ + internal class YubikeyObject + { + private const StringComparison COMPARISON = StringComparison.InvariantCultureIgnoreCase; + + private static X509Certificate2 YubikeyValidationCA = new X509Certificate2(new byte[] { 0x30, 0x82, 0x3, 0x17, 0x30, 0x82, 0x1, 0xFF, 0xA0, 0x3, 0x2, 0x1, 0x2, 0x2, 0x3, 0x4, 0x6, 0x47, 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0xB, 0x5, 0x0, 0x30, 0x2B, 0x31, 0x29, 0x30, 0x27, 0x6, 0x3, 0x55, 0x4, 0x3, 0xC, 0x20, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x50, 0x49, 0x56, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x32, 0x36, 0x33, 0x37, 0x35, 0x31, 0x30, 0x20, 0x17, 0xD, 0x31, 0x36, 0x30, 0x33, 0x31, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x18, 0xF, 0x32, 0x30, 0x35, 0x32, 0x30, 0x34, 0x31, 0x37, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5A, 0x30, 0x2B, 0x31, 0x29, 0x30, 0x27, 0x6, 0x3, 0x55, 0x4, 0x3, 0xC, 0x20, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6F, 0x20, 0x50, 0x49, 0x56, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6C, 0x20, 0x32, 0x36, 0x33, 0x37, 0x35, 0x31, 0x30, 0x82, 0x1, 0x22, 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, 0x82, 0x1, 0xF, 0x0, 0x30, 0x82, 0x1, 0xA, 0x2, 0x82, 0x1, 0x1, 0x0, 0xC3, 0x76, 0x70, 0xC4, 0xCD, 0x47, 0xA6, 0x2, 0x75, 0xC4, 0xC5, 0x47, 0x1B, 0x8F, 0xCB, 0x7D, 0x4F, 0x69, 0xB4, 0x67, 0xE6, 0x6E, 0xA9, 0x27, 0xE9, 0xD2, 0x13, 0x41, 0xD1, 0x5A, 0x9A, 0x1A, 0x33, 0xC7, 0xDC, 0xF3, 0x1, 0xC2, 0xF9, 0x39, 0x9B, 0xF7, 0xC8, 0xE6, 0x36, 0xF8, 0x56, 0x34, 0x4D, 0x84, 0x8A, 0x55, 0x3C, 0xE6, 0xE6, 0xA, 0x7C, 0x41, 0x4F, 0xF5, 0xDE, 0x90, 0xD8, 0x69, 0xB2, 0xB6, 0xA0, 0x67, 0xC5, 0x9B, 0x0, 0x6B, 0x72, 0xAA, 0x66, 0x20, 0x82, 0xC7, 0x62, 0xF0, 0x43, 0x88, 0x98, 0x10, 0xE6, 0xF5, 0x96, 0x58, 0x28, 0xB5, 0x5A, 0xFF, 0xC2, 0x11, 0x29, 0x75, 0x53, 0xAA, 0x8E, 0x85, 0x34, 0x3F, 0x97, 0xB5, 0x8F, 0x5C, 0xBB, 0x39, 0xFC, 0xE, 0xBE, 0x4C, 0xBF, 0xF8, 0x5, 0xC8, 0x37, 0xFF, 0x57, 0xA7, 0x45, 0x45, 0x95, 0x84, 0x64, 0xDA, 0xD4, 0x3D, 0x19, 0xC7, 0x58, 0x28, 0x39, 0xAA, 0x53, 0xE7, 0x5B, 0xF6, 0x22, 0xB0, 0xA4, 0xC, 0xE2, 0x77, 0x8A, 0x7, 0x5, 0x52, 0xC8, 0x86, 0x60, 0xF7, 0xA6, 0xF9, 0x16, 0x69, 0x10, 0x36, 0x1F, 0x70, 0xC0, 0xF6, 0xDE, 0xC7, 0xFC, 0x73, 0x6A, 0xE6, 0xFD, 0xCE, 0x88, 0xED, 0x63, 0xC8, 0xB6, 0x5E, 0x2A, 0xA6, 0x68, 0x31, 0xB3, 0xCE, 0x6E, 0xBC, 0x6A, 0xE, 0xF, 0xBD, 0x7C, 0xE7, 0x52, 0x87, 0x38, 0x1F, 0xC0, 0x2A, 0xA0, 0x4F, 0x75, 0xD5, 0x99, 0x37, 0xA2, 0xC2, 0xF0, 0x52, 0x4D, 0xCB, 0x72, 0x8B, 0xD9, 0x87, 0x41, 0xF6, 0x1D, 0xD8, 0x3C, 0x24, 0x6A, 0xAC, 0x51, 0x9C, 0xB6, 0xCD, 0x57, 0x22, 0xBD, 0xCE, 0x5F, 0x83, 0xCE, 0x34, 0x86, 0xA7, 0xD2, 0x21, 0x54, 0xF8, 0x95, 0xB4, 0x67, 0xAD, 0x5F, 0x4D, 0x9D, 0xC6, 0x14, 0x27, 0x19, 0x2E, 0xCA, 0xE8, 0x13, 0xB4, 0x41, 0xEF, 0x2, 0x3, 0x1, 0x0, 0x1, 0xA3, 0x42, 0x30, 0x40, 0x30, 0x1D, 0x6, 0x3, 0x55, 0x1D, 0xE, 0x4, 0x16, 0x4, 0x14, 0xCA, 0x5F, 0xCA, 0xF2, 0xC4, 0xA2, 0x31, 0x9C, 0xE9, 0x22, 0x5F, 0xF1, 0xEC, 0xF4, 0xD5, 0xDF, 0x2, 0xBF, 0x83, 0xBF, 0x30, 0xF, 0x6, 0x3, 0x55, 0x1D, 0x13, 0x4, 0x8, 0x30, 0x6, 0x1, 0x1, 0xFF, 0x2, 0x1, 0x1, 0x30, 0xE, 0x6, 0x3, 0x55, 0x1D, 0xF, 0x1, 0x1, 0xFF, 0x4, 0x4, 0x3, 0x2, 0x1, 0x6, 0x30, 0xD, 0x6, 0x9, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0xD, 0x1, 0x1, 0xB, 0x5, 0x0, 0x3, 0x82, 0x1, 0x1, 0x0, 0x5C, 0xEC, 0x88, 0x7C, 0x5, 0xCD, 0x5F, 0x90, 0x2F, 0x85, 0xC8, 0xDD, 0x5F, 0x86, 0x35, 0xA2, 0xA0, 0x10, 0x8C, 0xAF, 0x7B, 0xE3, 0x9D, 0xE8, 0x7B, 0x30, 0xB6, 0xC0, 0xEA, 0x44, 0xA8, 0xC9, 0x61, 0x7B, 0xD0, 0xDD, 0xEC, 0x5E, 0x16, 0xD7, 0xBD, 0x3E, 0x1E, 0x46, 0x1D, 0x21, 0xBF, 0x1A, 0xAF, 0x31, 0x93, 0x63, 0x3D, 0x4F, 0xD5, 0x95, 0x19, 0xFA, 0x80, 0xB5, 0x6D, 0xA0, 0x48, 0xA4, 0xC, 0xBA, 0xD8, 0x15, 0x73, 0x7A, 0x1E, 0x1E, 0x96, 0x9B, 0x2C, 0xB5, 0x19, 0x39, 0xEC, 0xA6, 0x73, 0xAF, 0x32, 0xFC, 0xF6, 0x94, 0xB2, 0xAE, 0xCA, 0x6F, 0x4A, 0x61, 0xD6, 0xB, 0xE, 0x9, 0xE3, 0xDC, 0x17, 0x80, 0xBF, 0x32, 0x21, 0x57, 0x3C, 0xD8, 0x49, 0xE5, 0x3B, 0xEF, 0xF0, 0xAE, 0xA6, 0x87, 0xE3, 0xD3, 0xDD, 0xCE, 0xB8, 0xB, 0x30, 0x5B, 0x48, 0xD8, 0xBD, 0x7B, 0x6, 0x4F, 0x28, 0xB1, 0xE8, 0x1D, 0xDD, 0x6D, 0x6E, 0x72, 0x5A, 0xFC, 0x92, 0xF7, 0x33, 0x57, 0x6A, 0xA1, 0x9A, 0x52, 0x63, 0xF7, 0x53, 0xDF, 0xDB, 0xE8, 0x39, 0x47, 0x74, 0x3A, 0x20, 0x30, 0xBB, 0xB7, 0x54, 0xBA, 0x41, 0x7, 0xD6, 0xE6, 0xE5, 0xB8, 0xDA, 0x29, 0x65, 0x89, 0x62, 0x5, 0xA5, 0xB4, 0x25, 0x60, 0x51, 0xB1, 0x6A, 0x16, 0xAC, 0xA2, 0xE3, 0xE2, 0x44, 0xD3, 0x5E, 0x1C, 0x4A, 0x4, 0x79, 0xEC, 0x97, 0x2E, 0xDD, 0xD6, 0x62, 0x7A, 0x10, 0x7A, 0x52, 0xD0, 0xF, 0x81, 0xA7, 0x7D, 0x2F, 0x97, 0xD, 0xBE, 0xE6, 0xBF, 0x21, 0x64, 0x66, 0x9B, 0xE0, 0xD, 0xCB, 0x73, 0xB6, 0x2C, 0x7F, 0xBE, 0x3F, 0x29, 0x7C, 0x49, 0x11, 0x33, 0x53, 0xCA, 0x27, 0x6C, 0x1B, 0x23, 0x32, 0xF, 0x50, 0xE, 0x24, 0x9F, 0xE6, 0x82, 0x4B, 0x2A, 0xF7, 0x7F, 0x45, 0xE9, 0xFE, 0xCC, 0x66, 0x3B }); + + public Dictionary Attributes { get; } = + new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + public YubikeyObject() + { + } + public YubikeyObject(byte[] publicKey, X509Certificate2 AttestationCertificate, X509Certificate2 IntermediateCertificate, KeyAlgorithmFamily keyAlgorithm, int keyLength, int requestID) + { + + + // This is simple in newer .NET versions, where we can load a custom CA Root. .NET Framework does not support this. + // This is cluncky. + + X509Chain chain = new X509Chain(); + + // Set the chain policy 1 = CustomRootTrust, it was unavailable, but was there, something to troubleshoot + chain.ChainPolicy.TrustMode = (X509ChainTrustMode)1; + chain.ChainPolicy.CustomTrustStore.Add(YubikeyValidationCA); + chain.ChainPolicy.ExtraStore.Add(IntermediateCertificate); + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + + if (! (chain.Build(AttestationCertificate))) + { + EWTLogger.Log.YKVal_4208_Yubikey_Attestion_Failed_to_build(requestID); + throw new Exception("Failed to build certificate path"); + } + /* + * Not needed when switched to .NET 8 + if (chain.ChainElements.Count != 3) + { + throw new Exception("Failed to build certificate path"); + } + if (Convert.ToBase64String(chain.ChainElements[2].Certificate.PublicKey.EncodedKeyValue.RawData) != Convert.ToBase64String(YubikeyValidationCA.PublicKey.EncodedKeyValue.RawData)) + { + throw new Exception("Certificate path does not end up at Yubikey CA"); + } + */ + if (! (publicKey.SequenceEqual(chain.ChainElements[0].Certificate.PublicKey.EncodedKeyValue.RawData))) + { + EWTLogger.Log.YKVal_4207_Yubikey_Attestion_Missmatch_with_CSR(requestID); + throw new Exception("Certificate CSR does not match attestion certificate"); + } + + this.Validated = true; + + #region Slot + // the Slot number is located in the Subject for the Attestion certificate. + + Regex slotRegex = new Regex(attestionSlotPattern); + Match slotMatch = slotRegex.Match(AttestationCertificate.Subject); + if (slotMatch.Success) + { + this.Slot = slotMatch.Groups["slot"].Value; + } + #endregion + #region PIN / touch policy + byte[] PinTouchPolicy = AttestationCertificate.Extensions.Cast().FirstOrDefault(x => x.Oid.Value == "1.3.6.1.4.1.41482.3.8")?.RawData; + if ( PinTouchPolicy.Length == 2) + { + // Staring with the PIN Policy + this.PinPolicy = (YubikeyPinPolicy)PinTouchPolicy[0]; + // Update the TouchPolicy + this.TouchPolicy = (YubikeyTouchPolicy)PinTouchPolicy[1]; + } + #endregion + #region FormFactor + byte FormFactor = AttestationCertificate.Extensions.Cast().FirstOrDefault(x => x.Oid.Value == YubikeyX509Extensions.FORMFACTOR)?.RawData[0] ?? 0; + this.FormFactor = (YubikeyFormFactor)FormFactor; + #endregion + #region Firmware Version + // Update the Firmware Version + byte[] FirmwareVersion = AttestationCertificate.Extensions.Cast().FirstOrDefault(x => x.Oid.Value == YubikeyX509Extensions.FIRMWARE)?.RawData; + if (FirmwareVersion.Length == 3) + { + this.FirmwareVersion = new Version(FirmwareVersion[0], FirmwareVersion[1], FirmwareVersion[2]); + } + #endregion + #region Serial Number + // Update the Serial Number + byte[] SerialNumber = AttestationCertificate.Extensions.Cast().FirstOrDefault(x => x.Oid.Value == YubikeyX509Extensions.SERIALNUMBER)?.RawData; + if (! (SerialNumber is null)) + { + if (BitConverter.IsLittleEndian) + { + Array.Reverse(SerialNumber); + } + this.SerialNumber = BitConverter.ToUInt32(SerialNumber, 0).ToString(); + } + #endregion + + #region + // Check for the FIPS extension + if (IntermediateCertificate.Extensions.Cast().Where(x => x.Oid.Value == YubikeyX509Extensions.FIPS_CERTIFIED).Any()) + { + this.Edition = YubikeyEdition.FIPS; + } + else if (IntermediateCertificate.Extensions.Cast().Where(x => x.Oid.Value == YubikeyX509Extensions.CPSN_CERTIFIED).Any()) + { + this.Edition = YubikeyEdition.CSPN; + } + + #endregion + + #region Key Algorithm and length for Policy use + this.keyAlgorithm = keyAlgorithm; + this.KeyLength = keyLength; + #endregion + + // Add to the attributes to allow for replacement + Attributes.Add("FormFactor", this.FormFactor.ToString()); + Attributes.Add("FirmwareVersion", this.FirmwareVersion.ToString()); + Attributes.Add("PinPolicy", this.PinPolicy.ToString()); + Attributes.Add("TouchPolicy", this.TouchPolicy.ToString()); + Attributes.Add("Slot", this.Slot); + Attributes.Add("SerialNumber", this.SerialNumber); + } + + + public YubikeyTouchPolicy TouchPolicy { get; } + public YubikeyPinPolicy PinPolicy { get; } + public YubikeyFormFactor FormFactor { get; } + public string Slot { get; } = ""; + public string SerialNumber { get; } = ""; + public Version FirmwareVersion { get; } = new Version(0, 0, 0); + public KeyAlgorithmFamily keyAlgorithm { get; } + public int KeyLength { get; } + public YubikeyEdition Edition { get; } = YubikeyEdition.Normal; + + public bool? Validated { get; } = false; + private static string attestionSlotPattern = @"CN=YubiKey PIV Attestation (?[0-9A-Fa-f]{2})"; + + } +} \ No newline at end of file diff --git a/TameMyCerts/Models/YubikeyPolicy.cs b/TameMyCerts/Models/YubikeyPolicy.cs new file mode 100644 index 0000000..58933c6 --- /dev/null +++ b/TameMyCerts/Models/YubikeyPolicy.cs @@ -0,0 +1,108 @@ +// Copyright 2021-2024 Uwe Gradenegger +// Copyright 2024 Oscar Virot + +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Collections.Generic; +using System.Xml.Serialization; +using System; +using TameMyCerts.Enums; +using System.IO; +using System.Text; +using System.Xml.Linq; +using System.Xml; +using System.Runtime.CompilerServices; + +namespace TameMyCerts.Models +{ + // Must be public due to XML serialization, otherwise 0x80131509 / System.InvalidOperationException + [XmlRoot(ElementName = "YubikeyPolicy")] + public class YubikeyPolicy + { + + [XmlElement(ElementName = "Action")] + public YubikeyPolicyAction Action { get; set; } = YubikeyPolicyAction.Allow; + + [XmlArray(ElementName = "PinPolicy")] + [XmlArrayItem(ElementName = "string")] + public List PinPolicies { get; set; } = new List(); + [XmlArray(ElementName = "TouchPolicy")] + [XmlArrayItem(ElementName = "string")] + public List TouchPolicies { get; set; } = new List(); + [XmlArray(ElementName = "FormFactor")] + [XmlArrayItem(ElementName = "string")] + public List Formfactor { get; set; } = new List(); + [XmlElement(ElementName = "MaximumFirmwareVersion")] + public String MaximumFirmwareString { get; set; } + [XmlElement(ElementName = "MinimumFirmwareVersion")] + public String MinimumFirmwareString { get; set; } + [XmlArray(ElementName = "Edition")] + [XmlArrayItem(ElementName = "string")] + public List Edition { get; set; } = new List(); + [XmlArray(ElementName = "KeyAlgorithm")] + [XmlArrayItem(ElementName = "string")] + public List KeyAlgorithmFamilies { get; set; } = new List(); + + private static string ConvertToHumanReadableXml(string inputString) + { + var xmlWriterSettings = new XmlWriterSettings + { + OmitXmlDeclaration = true, + Indent = true, + NewLineOnAttributes = true + }; + + var stringBuilder = new StringBuilder(); + + var xElement = XElement.Parse(inputString); + + using (var xmlWriter = XmlWriter.Create(stringBuilder, xmlWriterSettings)) + { + xElement.Save(xmlWriter); + } + + return stringBuilder.ToString(); + } + + public void SaveToFile(string path) + { + var xmlSerializer = new XmlSerializer(typeof(YubikeyPolicy)); + + using (var stringWriter = new StringWriter()) + { + using (var xmlWriter = XmlWriter.Create(stringWriter)) + { + xmlSerializer.Serialize(xmlWriter, this); + var xmlData = stringWriter.ToString(); + + File.WriteAllText(path, ConvertToHumanReadableXml(xmlData)); + } + } + } + public string SaveToString() + { + var xmlSerializer = new XmlSerializer(typeof(YubikeyPolicy)); + + using (var stringWriter = new StringWriter()) + { + using (var xmlWriter = XmlWriter.Create(stringWriter)) + { + xmlSerializer.Serialize(xmlWriter, this); + var xmlData = stringWriter.ToString(); + + return ConvertToHumanReadableXml(xmlData); + } + } + } + } +} \ No newline at end of file diff --git a/TameMyCerts/Policy.cs b/TameMyCerts/Policy.cs index 65902ea..f56e52d 100644 --- a/TameMyCerts/Policy.cs +++ b/TameMyCerts/Policy.cs @@ -38,6 +38,7 @@ public class Policy : ICertPolicy2 private readonly DirectoryServiceValidator _dsValidator = new(); private readonly FinalResultValidator _frValidator = new(); private readonly RequestAttributeValidator _raValidator = new(); + private readonly YubikeyValidator _ykValidator = new YubikeyValidator(); private readonly CertificateTemplateCache _templateCache = new(); private CertificateAuthorityConfiguration _caConfig; private Logger _logger; @@ -168,9 +169,11 @@ public int VerifyRequest(string strConfig, int context, int isNewRequest, int fl result = _crValidator.VerifyRequest(result, policy, dbRow, template); result = _dsValidator.GetMappedActiveDirectoryObject(result, policy, dbRow, template, out var dsObject); + result = _ykValidator.ExtractAttestion(result, policy, dbRow, out var ykObject); result = _dsValidator.VerifyRequest(result, policy, dsObject); - result = _ccValidator.VerifyRequest(result, policy, dbRow, dsObject, _caConfig); + result = _ykValidator.VerifyRequest(result, policy, ykObject, dbRow.RequestID); + result = _ccValidator.VerifyRequest(result, policy, dbRow, dsObject, _caConfig, ykObject); result = _frValidator.VerifyRequest(result, policy, dbRow); #endregion @@ -181,6 +184,8 @@ public int VerifyRequest(string strConfig, int context, int isNewRequest, int fl { if (result.DeniedForIssuance) { + EWTLogger.Log.TMC_5_Analytical_Audit_only_Deny(requestId, template.Name, + string.Join("\n", result.Description)); _logger.Log(Events.REQUEST_DENIED_AUDIT, requestId, template.Name, string.Join("\n", result.Description)); } @@ -229,9 +234,11 @@ public int VerifyRequest(string strConfig, int context, int isNewRequest, int fl switch (disposition) { case CertSrv.VR_PENDING: + EWTLogger.Log.TMC_13_Success_Pending(requestId, template.Name); _logger.Log(Events.SUCCESS_PENDING, requestId, template.Name); break; case CertSrv.VR_INSTANT_OK: + EWTLogger.Log.TMC_12_Success_Issued(requestId, template.Name); _logger.Log(Events.SUCCESS_ISSUED, requestId, template.Name); break; } @@ -243,6 +250,8 @@ public int VerifyRequest(string strConfig, int context, int isNewRequest, int fl #region Deny request in any other case + EWTLogger.Log.TMC_6_Deny_Issuing_Request(requestId, template.Name, + string.Join("\n", result.Description.Distinct().ToList())); _logger.Log(Events.REQUEST_DENIED, requestId, template.Name, string.Join("\n", result.Description.Distinct().ToList())); @@ -266,6 +275,7 @@ public void ShutDown() } catch (Exception ex) { + EWTLogger.Log.TMC_4_PolicyModule_Default_Shutdown_Failed(ex.ToString()); _logger.Log(Events.PDEF_FAIL_SHUTDOWN, ex); throw; } @@ -300,10 +310,11 @@ private void InitializeWindowsDefaultPolicyModule(string strConfig) _windowsDefaultPolicyModule.Initialize(strConfig); - _logger.Log(Events.PDEF_SUCCESS_INIT, _appName, _appVersion); + EWTLogger.Log.TMC_1_PolicyModule_Success_Initiated(_appName, _appVersion); } catch (Exception ex) { + EWTLogger.Log.TMC_2_PolicyModule_Failed_Initiated(ex.ToString()); _logger.Log(Events.PDEF_FAIL_INIT, ex); Marshal.ReleaseComObject(_windowsDefaultPolicyModule); throw; diff --git a/TameMyCerts/TameMyCerts.csproj b/TameMyCerts/TameMyCerts.csproj index de8efc7..0e0fad1 100644 --- a/TameMyCerts/TameMyCerts.csproj +++ b/TameMyCerts/TameMyCerts.csproj @@ -75,4 +75,9 @@ CERTPOLICYLIB.dll + + + PreserveNewest + + \ No newline at end of file diff --git a/TameMyCerts/Validators/CertificateContentValidator.cs b/TameMyCerts/Validators/CertificateContentValidator.cs index 6f6236f..f5bb102 100644 --- a/TameMyCerts/Validators/CertificateContentValidator.cs +++ b/TameMyCerts/Validators/CertificateContentValidator.cs @@ -1,4 +1,5 @@ -// Copyright 2021-2023 Uwe Gradenegger +// Copyright 2021-2024 Uwe Gradenegger +// Copyright 2024 Oscar Virot // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -40,7 +41,7 @@ private static string ReplaceTokenValues(string input, string identifier, if (!list.Any(x => x.Key.Equals(token, StringComparison.InvariantCultureIgnoreCase))) { - throw new Exception(string.Format(LocalizedStrings.Token_invalid, token)); + throw new Exception(string.Format(LocalizedStrings.Token_invalid, identifier, token)); } } @@ -53,6 +54,14 @@ private static string ReplaceTokenValues(string input, string identifier, public CertificateRequestValidationResult VerifyRequest(CertificateRequestValidationResult result, CertificateRequestPolicy policy, CertificateDatabaseRow dbRow, ActiveDirectoryObject dsObject, CertificateAuthorityConfiguration caConfig) + { + YubikeyObject YubikeyObject = new YubikeyObject(); + return VerifyRequest(result, policy, dbRow, dsObject, caConfig, YubikeyObject); + } + + public CertificateRequestValidationResult VerifyRequest(CertificateRequestValidationResult result, + CertificateRequestPolicy policy, CertificateDatabaseRow dbRow, ActiveDirectoryObject dsObject, + CertificateAuthorityConfiguration caConfig, YubikeyObject yubikeyObject) { if (result.DeniedForIssuance) { @@ -123,6 +132,8 @@ public CertificateRequestValidationResult VerifyRequest(CertificateRequestValida value = ReplaceTokenValues(value, "ad", null != dsObject ? dsObject.Attributes.ToList() : new List>()); + value = ReplaceTokenValues(value, "yk", + null != yubikeyObject ? yubikeyObject.Attributes.ToList() : new List>()); value = ReplaceTokenValues(value, "sdn", policy.ReadSubjectFromRequest ? dbRow.InlineSubjectRelativeDistinguishedNames diff --git a/TameMyCerts/Validators/YubikeyValidator.cs b/TameMyCerts/Validators/YubikeyValidator.cs new file mode 100644 index 0000000..1206843 --- /dev/null +++ b/TameMyCerts/Validators/YubikeyValidator.cs @@ -0,0 +1,172 @@ +// Copyright 2021-2024 Uwe Gradenegger +// Copyright 2024 Oscar Virot + +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using TameMyCerts.ClassExtensions; +using TameMyCerts.Enums; +using TameMyCerts.Models; +using TameMyCerts.X509; + +namespace TameMyCerts.Validators +{ + /// + /// This validator will check that the CSR is issued by a real Yubikey + /// + internal class YubikeyValidator + { + private const StringComparison Comparison = StringComparison.InvariantCultureIgnoreCase; + + public CertificateRequestValidationResult VerifyRequest(CertificateRequestValidationResult result, + CertificateRequestPolicy policy, YubikeyObject yubikey, int requestID) + { + // If we are already denied for issuance or the policy does not contain any YubikeyPolicy, just continue + if (result.DeniedForIssuance || !policy.YubikeyPolicy.Any()) + { + return result; + } + // If the Yubikey is not validated, we will not allow it + if (yubikey.Validated == false) + { + EWTLogger.Log.YKVal_4202_Denied_by_Policy(requestID); + result.SetFailureStatus(WinError.CERTSRV_E_TEMPLATE_DENIED, string.Format( + LocalizedStrings.YKVal_Invalid_Attestion_with_YubikeyPolicy)); + return result; + + } + + bool foundMatch = false; + + foreach (YubikeyPolicy ykP in policy.YubikeyPolicy) + { + //Console.WriteLine(ykP.SaveToString()); + if (this.ObjectMatchesPolicy(ykP, yubikey)) + { + if (ykP.Action == YubikeyPolicyAction.Deny) + { + EWTLogger.Log.YKVal_4201_Denied_by_Policy(ykP.SaveToString(), requestID); + result.SetFailureStatus(WinError.CERTSRV_E_TEMPLATE_DENIED, string.Format( + LocalizedStrings.YKVal_Policy_Matches_with_Reject, ykP.SaveToString())); + return result ; + } + EWTLogger.Log.YKVal_4204_Matching_policy(ykP.SaveToString(), requestID); + foundMatch = true; + break; + } + else + { + EWTLogger.Log.YKVal_4206_Debug_failed_to_match_policy(requestID, ykP.SaveToString()); + } + } + + // if non of the pin policies match, we will deny the request, not matching allowed = deny + if (foundMatch == false) + { + // If all policies are deny policies, then if none match, we will allow the request + if (policy.YubikeyPolicy.Any(p => p.Action == YubikeyPolicyAction.Allow)) + { + EWTLogger.Log.YKVal_4203_Denied_due_to_no_matching_policy_default_deny(requestID); + result.SetFailureStatus(WinError.CERTSRV_E_TEMPLATE_DENIED, string.Format( + LocalizedStrings.YKVal_No_Matching_Policy_Found)); + } + } + return result; + } + public CertificateRequestValidationResult ExtractAttestion(CertificateRequestValidationResult result, + CertificateRequestPolicy policy, CertificateDatabaseRow dbRow, out YubikeyObject yubikey) + { + if (result.DeniedForIssuance) + { + yubikey = new YubikeyObject(); + return result; + } + + // Yubikey Attestation is stored in these two extensions in the CSR. If present , extract them, otherwise buuild an empty YubikeyObject. + if (dbRow.CertificateExtensions.ContainsKey(YubikeyX509Extensions.ATTESTION_DEVICE) && dbRow.CertificateExtensions.ContainsKey(YubikeyX509Extensions.ATTESTION_INTERMEDIATE)) + { + try + { + dbRow.CertificateExtensions.TryGetValue(YubikeyX509Extensions.ATTESTION_DEVICE, out var AttestionCertificateByte); + dbRow.CertificateExtensions.TryGetValue(YubikeyX509Extensions.ATTESTION_INTERMEDIATE, out var IntermediateCertificateByte); + X509Certificate2 AttestationCertificate = new X509Certificate2(AttestionCertificateByte); + X509Certificate2 IntermediateCertificate = new X509Certificate2(IntermediateCertificateByte); + yubikey = new YubikeyObject(dbRow.PublicKey, AttestationCertificate, IntermediateCertificate, dbRow.KeyAlgorithm, dbRow.KeyLength, dbRow.RequestID); + } + catch (Exception ex) + { + yubikey = new YubikeyObject(); + EWTLogger.Log.YKVal_4205_Failed_to_extract_Yubikey_Attestion(dbRow.RequestID); + result.SetFailureStatus(WinError.CERTSRV_E_TEMPLATE_DENIED, string.Format(LocalizedStrings.YKVal_Unable_to_read_embedded_certificates, ex.Message)); + } + } + else + { + yubikey = new YubikeyObject(); + } + return result; + } + private bool ObjectMatchesPolicy(YubikeyPolicy policy, YubikeyObject yubikey) + { + #region Firmware Version + if (!(policy.MinimumFirmwareString is null) && ! (new Version(policy.MinimumFirmwareString) <= yubikey.FirmwareVersion)) + { + return false; + } + if (!(policy.MaximumFirmwareString is null) && ! (new Version(policy.MaximumFirmwareString) >= yubikey.FirmwareVersion)) + { + return false; + } + #endregion + + #region PIN Policy + if (policy.PinPolicies.Any() && ! policy.PinPolicies.Contains(yubikey.PinPolicy)) + { + return false; + } + #endregion + + #region Touch Policy + if (policy.TouchPolicies.Any() && !policy.TouchPolicies.Contains(yubikey.TouchPolicy)) + { + return false; + } + #endregion + + #region Form Factor + if (policy.Formfactor.Any() && !policy.Formfactor.Contains(yubikey.FormFactor)) + { + return false; + } + #endregion + + if (policy.KeyAlgorithmFamilies.Any() && ! policy.KeyAlgorithmFamilies.Contains(yubikey.keyAlgorithm)) + { + return false; + } + + if (policy.Edition.Any() && ! policy.Edition.Contains(yubikey.Edition)) + { + return false; + } + + return true; + } + } +} \ No newline at end of file diff --git a/TameMyCerts/install.ps1 b/TameMyCerts/install.ps1 index cff36a3..5853d84 100644 --- a/TameMyCerts/install.ps1 +++ b/TameMyCerts/install.ps1 @@ -171,6 +171,16 @@ process { Write-Verbose -Message "Deleting Windows event source ""$PolicyModuleName""" [System.Diagnostics.EventLog]::DeleteEventSource($PolicyModuleName) } + + # If EWT Logging manifest exist unregister that one. + If (((Get-WinEvent -ListProvider $PolicyModuleName -ErrorAction SilentlyContinue) -ne $Null) -and (Test-Path -Path "$BaseDirectory\$($PolicyModuleName).events.man")) { + Write-Verbose "Found the required files for EWT logging, unregistering with wevtutil" + Start-Process ` + -FilePath "$($env:SystemRoot)\System32\wevtutil.exe" ` + -ArgumentList "um", """$BaseDirectory\$($PolicyModuleName).events.man""" ` + -Wait ` + -WindowStyle Hidden + } } # (Re)Install @@ -203,6 +213,17 @@ process { Write-Verbose -Message "Registering Windows event source ""$PolicyModuleName""" [System.Diagnostics.EventLog]::CreateEventSource($PolicyModuleName, "Application") } + + # If EWT Logging manifest exist register that one. + If ((Test-Path -Path "$BaseDirectory\$($PolicyModuleName).events.dll") -and (Test-Path -Path "$BaseDirectory\$($PolicyModuleName).events.man")) { + Write-Verbose "Found the required files for EWT logging, registering with wevtutil" + Start-Process ` + -FilePath "$($env:SystemRoot)\System32\wevtutil.exe" ` + -ArgumentList "im", """$BaseDirectory\$($PolicyModuleName).events.man""","/resourceFilePath:""$BaseDirectory\$($PolicyModuleName).events.dll""", "/messageFilePath:""$BaseDirectory\$($PolicyModuleName).events.dll""" ` + -Wait ` + -WindowStyle Hidden + } + } Write-Verbose -Message "Starting certification authority service"