From c4f735ef03b17416968b7cc31c9ae24082c18416 Mon Sep 17 00:00:00 2001 From: Ilya Date: Tue, 24 Sep 2019 15:39:30 +0500 Subject: [PATCH 1/2] Add ExpiringInDays filter in Certificate provider --- .../security/CertificateProvider.cs | 257 +++++++++++++++--- .../security/SecuritySupport.cs | 247 +++++++---------- .../CertificateProvider.Tests.ps1 | 84 +++++- .../certificateCommon.psm1 | 118 +++++++- 4 files changed, 505 insertions(+), 201 deletions(-) diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs index 7bdb8286598..0f4b38b3494 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -31,20 +31,18 @@ namespace Microsoft.PowerShell.Commands { /// /// Defines the Certificate Provider dynamic parameters. - /// /// We only support one dynamic parameter for Win 7 and earlier: /// CodeSigningCert /// If provided, we only return certificates valid for signing code or /// scripts. /// - - internal sealed class CertificateProviderCodeSigningDynamicParameters + internal sealed class CertificateProviderDynamicParameters { /// - /// Switch that controls whether we only return + /// Gets or sets a switch that controls whether we only return /// code signing certs. /// - [Parameter()] + [Parameter] public SwitchParameter CodeSigningCert { get { return _codeSigningCert; } @@ -53,6 +51,70 @@ public SwitchParameter CodeSigningCert } private SwitchParameter _codeSigningCert = new SwitchParameter(); + + /// + /// Gets or sets a filter that controls whether we only return + /// data encipherment certs. + /// + [Parameter] + public SwitchParameter DocumentEncryptionCert + { + get; + set; + } + + /// + /// Gets or sets a filter that controls whether we only return + /// server authentication certs. + /// + [Parameter] + public SwitchParameter SSLServerAuthentication + { + get; + set; + } + + /// + /// Gets or sets a filter by DNSName. + /// Expected content is a single DNS Name that may start and/or end + /// with '*': "contoso.com" or "*toso.c*". + /// All WildcardPattern class features supported. + /// + [Parameter] + public string DnsName + { + get; + set; + } + + /// + /// Gets or sets a filter by EKU. + /// Expected content is one or more OID strings: + /// "1.3.6.1.5.5.7.3.1", "*Server*", etc. + /// For a cert to match, it must be valid for all listed OIDs. + /// All WildcardPattern class features supported. + /// + [Parameter] + public string[] Eku + { + get; + set; + } + + /// + /// Gets or sets a filter by the number of valid days. + /// Expected content is a non-negative integer. + /// "0" matches all certs that have already expired. + /// "1" matches all certs that are currently valid and will expire + /// by next day (local time). + /// + [Parameter] + [ValidateRange(ValidateRangeKind.NonNegative)] + public int ExpiringInDays + { + get; + set; + } = -1; } /// @@ -169,7 +231,7 @@ internal sealed class ProviderRemoveItemDynamicParameters /// Switch that controls whether we should delete private key /// when remove a certificate. /// - [Parameter()] + [Parameter] public SwitchParameter DeleteKey { get @@ -1184,15 +1246,11 @@ protected override void GetItem(string path) } else { - // The filter is non null. If the certificate - // satisfies the filter, output it. Otherwise, don't. - + // The filter is non null. If the certificate + // satisfies the filter, output it. Otherwise, don't. X509Certificate2 cert = item as X509Certificate2; Dbg.Diagnostics.Assert(cert != null, "item should be a certificate"); - // If it's Win8 or above, filter matching for certain properties is done by - // the certificate enumeration filter at the API level. In that case, - // filter.Purpose will be 'None' and MatchesFilter will return 'True'. if (MatchesFilter(cert, filter)) { WriteItemObject(item, path, isContainer); @@ -2211,7 +2269,7 @@ protected override bool IsItemContainer(string path) /// protected override object GetItemDynamicParameters(string path) { - return new CertificateProviderCodeSigningDynamicParameters(); + return new CertificateProviderDynamicParameters(); } /// @@ -2233,7 +2291,7 @@ protected override object GetItemDynamicParameters(string path) /// protected override object GetChildItemsDynamicParameters(string path, bool recurse) { - return new CertificateProviderCodeSigningDynamicParameters(); + return new CertificateProviderDynamicParameters(); } #endregion DriveCmdletProvider overrides @@ -2606,8 +2664,8 @@ private CertificateFilterInfo GetFilter() if (DynamicParameters != null) { - CertificateProviderCodeSigningDynamicParameters dp = - DynamicParameters as CertificateProviderCodeSigningDynamicParameters; + CertificateProviderDynamicParameters dp = + DynamicParameters as CertificateProviderDynamicParameters; if (dp != null) { if (dp.CodeSigningCert) @@ -2615,6 +2673,40 @@ private CertificateFilterInfo GetFilter() filter = new CertificateFilterInfo(); filter.Purpose = CertificatePurpose.CodeSigning; } + + if (dp.DocumentEncryptionCert) + { + filter = filter ?? new CertificateFilterInfo(); + filter.Purpose = CertificatePurpose.DocumentEncryption; + } + + if (dp.DnsName != null) + { + filter = filter ?? new CertificateFilterInfo(); + filter.DnsName = new WildcardPattern(dp.DnsName, WildcardOptions.IgnoreCase); + } + + if (dp.Eku != null) + { + filter = filter ?? new CertificateFilterInfo(); + filter.Eku = new List(); + foreach (var pattern in dp.Eku) + { + filter.Eku.Add(new WildcardPattern(pattern, WildcardOptions.IgnoreCase)); + } + } + + if (dp.ExpiringInDays >= 0) + { + filter = filter ?? new CertificateFilterInfo(); + filter.Expiring = DateTime.Now.AddDays(dp.ExpiringInDays); + } + + if (dp.SSLServerAuthentication) + { + filter = filter ?? new CertificateFilterInfo(); + filter.SSLServerAuthentication = true; + } } } @@ -2633,42 +2725,131 @@ private bool IncludeArchivedCerts() return includeArchivedCerts; } - // If it's Win8 or above, filter matching for certain properties is done by - // the certificate enumeration filter at the API level. In that case, - // filter.Purpose will be 'None' and MatchesFilter will return 'True'. - private static bool MatchesFilter(X509Certificate2 cert, - CertificateFilterInfo filter) + private static bool MatchesFilter(X509Certificate2 cert, CertificateFilterInfo filter) { - // - // no filter means, match everything - // - if ((filter == null) || - (filter.Purpose == CertificatePurpose.NotSpecified) || - (filter.Purpose == CertificatePurpose.All)) + // No filter means, match everything + if (filter == null) { return true; } + if (filter.Expiring > DateTime.MinValue && !SecuritySupport.CertExpiresByTime(cert, filter.Expiring)) + { + return false; + } + + if (filter.DnsName != null && !CertContainsName(cert, filter.DnsName)) + { + return false; + } + + if (filter.Eku != null && !CertContainsEku(cert, filter.Eku)) + { + return false; + } + + if (filter.SSLServerAuthentication && !CertIsSSLServerAuthentication(cert)) + { + return false; + } + switch (filter.Purpose) { case CertificatePurpose.CodeSigning: - if (SecuritySupport.CertIsGoodForSigning(cert)) - { - return true; - } + return SecuritySupport.CertIsGoodForSigning(cert); + + case CertificatePurpose.DocumentEncryption: + return SecuritySupport.CertIsGoodForEncryption(cert); + case CertificatePurpose.NotSpecified: + case CertificatePurpose.All: + return true; + + default: break; + } - case CertificatePurpose.DocumentEncryption: - if (SecuritySupport.CertIsGoodForEncryption(cert)) + return false; + } + + /// + /// Check if the specified certificate has the name in DNS name list. + /// + /// Certificate object. + /// Wildcard pattern for DNS name to search. + /// True on success, false otherwise. + internal static bool CertContainsName(X509Certificate2 cert, WildcardPattern pattern) + { + List list = (new DnsNameProperty(cert)).DnsNameList; + foreach (DnsNameRepresentation dnsName in list) + { + if (pattern.IsMatch(dnsName.Unicode)) + { + return true; + } + } + + return false; + } + + /// + /// Check if the specified certificate is a server authentication certificate. + /// + /// Certificate object. + /// True on success, false otherwise. + internal static bool CertIsSSLServerAuthentication(X509Certificate2 cert) + { + X509ExtensionCollection extentionList = cert.Extensions; + foreach (var extension in extentionList) + { + if (extension is X509EnhancedKeyUsageExtension eku) + { + foreach (Oid usage in eku.EnhancedKeyUsages) { - return true; + if (usage.Value.Equals(CertificateFilterInfo.OID_PKIX_KP_SERVER_AUTH, StringComparison.Ordinal)) + { + return true; + } } + } + } - break; + return false; + } - default: - break; + /// + /// Check if the specified certificate contains EKU matching all of these patterns. + /// + /// Certificate object. + /// EKU patterns. + /// True on success, false otherwise. + internal static bool CertContainsEku(X509Certificate2 cert, List ekuPatterns) + { + X509ExtensionCollection extensionList = cert.Extensions; + foreach (var extension in extensionList) + { + if (extension is X509EnhancedKeyUsageExtension eku) + { + OidCollection enhancedKeyUsages = eku.EnhancedKeyUsages; + foreach (WildcardPattern ekuPattern in ekuPatterns) + { + bool patternPassed = false; + foreach (var usage in enhancedKeyUsages) + { + if (ekuPattern.IsMatch(usage.Value) || ekuPattern.IsMatch(usage.FriendlyName)) + { + return true; + } + } + + if (!patternPassed) + { + return false; + } + } + + return true; + } } return false; @@ -3192,7 +3373,7 @@ public List DnsNameList } /// - /// Constructor for EkuList. + /// Constructor for DnsNameProperty. /// public DnsNameProperty(X509Certificate2 cert) { diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index d9c1f0d3994..eb7ab6e56ea 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -4,22 +4,23 @@ #pragma warning disable 1634, 1691 #pragma warning disable 56523 +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.PowerShell; -using Microsoft.PowerShell.Commands; -using System.Management.Automation.Security; +using System.Globalization; using System.Management.Automation.Configuration; using System.Management.Automation.Internal; +using System.Management.Automation.Security; +using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Globalization; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Runtime.InteropServices; +using Microsoft.PowerShell; +using Microsoft.PowerShell.Commands; + using DWORD = System.UInt32; namespace Microsoft.PowerShell @@ -629,6 +630,17 @@ internal static bool CertIsGoodForEncryption(X509Certificate2 c) CertHasKeyUsage(c, X509KeyUsageFlags.KeyEncipherment))); } + /// + /// Check to see if the specified cert is expiring by the time. + /// + /// Certificate object. + /// Certificate expire time. + /// True on success, false otherwise. + internal static bool CertExpiresByTime(X509Certificate2 c, DateTime expiring) + { + return c.NotAfter < expiring; + } + private static bool CertHasOid(X509Certificate2 c, string oid) { foreach (var extension in c.Extensions) @@ -665,6 +677,64 @@ private static bool CertHasKeyUsage(X509Certificate2 c, X509KeyUsageFlags keyUsa return false; } + /// + /// Get the EKUs of a cert. + /// + /// Certificate object. + /// A collection of cert eku strings. + [ArchitectureSensitive] + internal static Collection GetCertEKU(X509Certificate2 cert) + { + Collection ekus = new Collection(); + IntPtr pCert = cert.Handle; + int structSize = 0; + IntPtr dummy = IntPtr.Zero; + + if (Security.NativeMethods.CertGetEnhancedKeyUsage(pCert, 0, dummy, + out structSize)) + { + if (structSize > 0) + { + IntPtr ekuBuffer = Marshal.AllocHGlobal(structSize); + + try + { + if (Security.NativeMethods.CertGetEnhancedKeyUsage(pCert, 0, + ekuBuffer, + out structSize)) + { + Security.NativeMethods.CERT_ENHKEY_USAGE ekuStruct = + (Security.NativeMethods.CERT_ENHKEY_USAGE) + Marshal.PtrToStructure(ekuBuffer); + IntPtr ep = ekuStruct.rgpszUsageIdentifier; + IntPtr ekuptr; + + for (int i = 0; i < ekuStruct.cUsageIdentifier; i++) + { + ekuptr = Marshal.ReadIntPtr(ep, i * Marshal.SizeOf(ep)); + string eku = Marshal.PtrToStringAnsi(ekuptr); + ekus.Add(eku); + } + } + else + { + throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); + } + } + finally + { + Marshal.FreeHGlobal(ekuBuffer); + } + } + } + else + { + throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); + } + + return ekus; + } + /// /// Convert an int to a DWORD. /// @@ -698,174 +768,53 @@ internal CertificateFilterInfo() } /// - /// Purpose of a certificate. + /// Gets or sets purpose of a certificate. /// internal CertificatePurpose Purpose { - get { return _purpose; } - - set { _purpose = value; } - } + get; + set; + } = CertificatePurpose.NotSpecified; /// - /// SSL Server Authentication. + /// Gets or sets SSL Server Authentication. /// internal bool SSLServerAuthentication { - get { return _sslServerAuthentication; } - - set { _sslServerAuthentication = value; } - } + get; - /// - /// DNS name of a certificate. - /// - internal string DnsName - { - set { _dnsName = value; } + set; } /// - /// EKU OID list of a certificate. + /// Gets or sets DNS name of a certificate. /// - internal string[] Eku + internal WildcardPattern DnsName { - set { _eku = value; } + get; + set; } /// - /// Remaining validity period in days for a certificate. + /// Gets or sets EKU OID list of a certificate. /// - internal int ExpiringInDays + internal List Eku { - set { _expiringInDays = value; } + get; + set; } /// - /// Combine properties into a filter string. + /// Gets or sets validity time for a certificate. /// - internal string FilterString + internal DateTime Expiring { - get - { - string filterString = string.Empty; - - if (_dnsName != null) - { - filterString = AppendFilter(filterString, "dns", _dnsName); - } - - string ekuT = string.Empty; - if (_eku != null) - { - for (int i = 0; i < _eku.Length; i++) - { - if (ekuT.Length != 0) - { - ekuT = ekuT + ","; - } - - ekuT = ekuT + _eku[i]; - } - } - - if (_purpose == CertificatePurpose.CodeSigning) - { - if (ekuT.Length != 0) - { - ekuT = ekuT + ","; - } - - ekuT = ekuT + CodeSigningOid; - } - - if (_purpose == CertificatePurpose.DocumentEncryption) - { - if (ekuT.Length != 0) - { - ekuT = ekuT + ","; - } - - ekuT = ekuT + DocumentEncryptionOid; - } - - if (_sslServerAuthentication) - { - if (ekuT.Length != 0) - { - ekuT = ekuT + ","; - } - - ekuT = ekuT + szOID_PKIX_KP_SERVER_AUTH; - } - - if (ekuT.Length != 0) - { - filterString = AppendFilter(filterString, "eku", ekuT); - if (_purpose == CertificatePurpose.CodeSigning || - _sslServerAuthentication) - { - filterString = AppendFilter(filterString, "key", "*"); - } - } - - if (_expiringInDays >= 0) - { - filterString = AppendFilter( - filterString, - "ExpiringInDays", - _expiringInDays.ToString(System.Globalization.CultureInfo.InvariantCulture)); - } - - if (filterString.Length == 0) - { - filterString = null; - } - - return filterString; - } - } - - private string AppendFilter( - string filterString, - string name, - string value) - { - string newfilter = value; - - // append a "name=value" filter to the existing filter string. - // insert a separating "&" if existing filter string is not empty. - - // if the value is empty, do nothing. - - if (newfilter.Length != 0) - { - // if the value contains an equal sign or an ampersand, throw - // an exception to avoid compromising the native code parser. - - if (newfilter.Contains("=") || newfilter.Contains("&")) - { - Marshal.ThrowExceptionForHR(Security.NativeMethods.E_INVALID_DATA); - } - - newfilter = name + "=" + newfilter; - if (filterString.Length != 0) - { - newfilter = "&" + newfilter; - } - } - - return filterString + newfilter; - } - - private CertificatePurpose _purpose = 0; - private bool _sslServerAuthentication = false; - private string _dnsName = null; - private string[] _eku = null; - private int _expiringInDays = -1; + get; + set; + } = DateTime.MinValue; internal const string CodeSigningOid = "1.3.6.1.5.5.7.3.3"; - internal const string szOID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"; + internal const string OID_PKIX_KP_SERVER_AUTH = "1.3.6.1.5.5.7.3.1"; // The OID arc 1.3.6.1.4.1.311.80 is assigned to PowerShell. If we need // new OIDs, we can assign them under this branch. diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 index 61d30f584a6..46ea72f1c05 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 @@ -117,25 +117,29 @@ Describe "Certificate Provider tests" -Tags "Feature" { it "Should be able to get DnsNameList of certifate by path: " -TestCases $currentUserMyLocations { param([string] $path) $expectedThumbprint = (Get-GoodCertificateObject).Thumbprint - $expectedName = (Get-GoodCertificateObject).DnsNameList[0].Unicode - $expectedEncodedName = (Get-GoodCertificateObject).DnsNameList[0].Punycode + $expectedName = (Get-GoodCertificateObject).DnsNameList + $expectedEncodedName = (Get-GoodCertificateObject).DnsNameList $leafPath = Join-Path -Path $path -ChildPath $expectedThumbprint $cert = (Get-item -LiteralPath $leafPath) - $cert | Should -Not -Be null - $cert.DnsNameList | Should -Not -Be null - $cert.DnsNameList.Count | Should -Be 1 - $cert.DnsNameList[0].Unicode | Should -Be $expectedName - $cert.DnsNameList[0].Punycode | Should -Be $expectedEncodedName - } - it "Should be able to get DNSNameList of certifate by path: " -TestCases $currentUserMyLocations { + $cert | Should -Not -Be $null + $cert.DnsNameList | Should -Not -Be $null + $cert.DnsNameList.Count | Should -Be 3 + $cert.DnsNameList[0].Unicode | Should -Be $expectedName[0].Unicode + $cert.DnsNameList[0].Punycode | Should -Be $expectedEncodedName[0].Punycode + $cert.DnsNameList[1].Unicode | Should -Be $expectedName[1].Unicode + $cert.DnsNameList[1].Punycode | Should -Be $expectedEncodedName[1].Punycode + $cert.DnsNameList[2].Unicode | Should -Be $expectedName[2].Unicode + $cert.DnsNameList[2].Punycode | Should -Be $expectedEncodedName[2].Punycode + } + it "Should be able to get EnhancedKeyUsageList of certifate by path: " -TestCases $currentUserMyLocations { param([string] $path) $expectedThumbprint = (Get-GoodCertificateObject).Thumbprint $expectedOid = (Get-GoodCertificateObject).EnhancedKeyUsageList[0].ObjectId $leafPath = Join-Path -Path $path -ChildPath $expectedThumbprint $cert = (Get-item -LiteralPath $leafPath) - $cert | Should -Not -Be null - $cert.EnhancedKeyUsageList | Should -Not -Be null - $cert.EnhancedKeyUsageList.Count | Should -Be 1 + $cert | Should -Not -Be $null + $cert.EnhancedKeyUsageList | should not be null + $cert.EnhancedKeyUsageList.Count | Should -Be 3 $cert.EnhancedKeyUsageList[0].ObjectId.Length | Should -Not -Be 0 $cert.EnhancedKeyUsageList[0].ObjectId | Should -Be $expectedOid } @@ -158,6 +162,9 @@ Describe "Certificate Provider tests" -Tags "Feature" { } } Context "Get-ChildItem tests"{ + BeforeAll { + $cert = Get-GoodServerCertificateObject + } it "Should filter to codesign certificates" { $allCerts = get-ChildItem cert:\CurrentUser\My $codeSignCerts = get-ChildItem cert:\CurrentUser\My -CodeSigningCert @@ -166,5 +173,58 @@ Describe "Certificate Provider tests" -Tags "Feature" { $nonCodeSignCertCount = $allCerts.Count - $codeSignCerts.Count $nonCodeSignCertCount | Should -Not -Be 0 } + it "Should filter to ExpiringInDays certificates" { + $thumbprint = $cert.Thumbprint + $NotAfter = $cert.NotAfter + $before = ($NotAfter.AddDays(-1) - (Get-Date)).Days + $after = ($NotAfter.AddDays(+1) - (Get-Date)).Days + $beforeCerts = Get-ChildItem cert:\CurrentUser\My\$thumbprint -ExpiringInDays $before + $afterCerts = Get-ChildItem cert:\CurrentUser\My\$thumbprint -ExpiringInDays $after + + $beforeCerts.Count | Should -Be 0 + $afterCerts.Count | Should -Be 1 + $afterCerts.Thumbprint | Should -BeExactly $thumbprint + } + it "Should filter to DocumentEncryptionCert certificates" { + $thumbprint = $cert.Thumbprint + $certs = Get-ChildItem cert:\CurrentUser\My\$thumbprint -DocumentEncryptionCert + + $certs.Count | Should -Be 1 + $certs.Thumbprint | Should -BeExactly $thumbprint + } + it "Should filter to DNSName certificates: " -TestCases @( + @{ Name = "in Subject"; SearchName = '*ncipher*'; Count = 1; Thumbprint = $cert.Thumbprint } + @{ Name = "in Subject Alternative Name"; SearchName = '*conto*'; Count = 1; Thumbprint = $cert.Thumbprint } + @{ Name = "not existing name"; SearchName = '*QWERTY*'; Count = 0; Thumbprint = $null } + ) { + param($name, $searchName, $count, $thumbprint) + + $certs = Get-ChildItem cert:\CurrentUser\My\$thumbprint -DNSName $searchName + + $certs.Count | Should -Be $count + $certs.Thumbprint | Should -BeExactly $thumbprint + + } + it "Should filter to SSLServerAuthentication certificates" { + $thumbprint = $cert.Thumbprint + + $certs = Get-ChildItem cert:\CurrentUser\My\$thumbprint -SSLServerAuthentication + + $certs.Count | Should -Be 1 + $certs.Thumbprint | Should -BeExactly $thumbprint + } + it "Should filter to EKU certificates: " -TestCases @( + @{ Name = "can filter by name"; EKU = '*encryp*'; Count = 1; Thumbprint = $cert.Thumbprint } + @{ Name = "can filter by OID"; EKU = '*1.4.1.311.80.1*'; Count = 1; Thumbprint = $cert.Thumbprint } + @{ Name = "all patterns should be passed - positive test"; EKU = "1.3.6.1.5.5.7.3.2","*1.4.1.311.80.1*"; Count = 1; Thumbprint = $cert.Thumbprint } + @{ Name = "all patterns should be passed - negative test"; EKU = "*QWERTY*","*encryp*"; Count = 0; Thumbprint = $null } + ) { + param($name, $ekuSearch, $count, $thumbprint) + + $certs = Get-ChildItem cert:\CurrentUser\My\$thumbprint -EKU $ekuSearch + + $certs.Count | Should -Be $count + $certs.Thumbprint | Should -BeExactly $thumbprint + } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 index 91c87df36d2..dba23826923 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. + Function New-GoodCertificate { <# @@ -68,6 +69,109 @@ OksttXT1kXf+aez9EzDlsgQU4ck78h0WTy01zHLwSKNWK4wFFQM= return $certLocation } +Function New-GoodServerCertificate +{ + <# + .NOTES + This certificate properties: + Subject: + CN = MyDataEnciphermentCert + Subject Alternative Name: + DNS Name=www.fabrikam.com + DNS Name=www.contoso.com + EnhancedKey Usage: + Client Authentication (1.3.6.1.5.5.7.3.2) + Server Authentication (1.3.6.1.5.5.7.3.1) + Document Encryption (1.3.6.1.4.1.311.80.1) + Key Usage: + Key Encipherment, Data Encipherment (30) + Thumbprint: + b79428ca5aa0f0620e5eba19223fdf7885fcf3c6 + Serial Number: + 40c14ec2f84344be4965954d091c266b + + Howto update: + 1. Import the module and test certificates + Import-Module certificateCommon.psm1 -Force + Install-TestCertificates + 2. Read the certificate + $cert = Get-Item Cert:\CurrentUser\My\ + 3. Clone the certificate with new properties: + $b=New-SelfSignedCertificate -CloneCert $cert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1,1.3.6.1.4.1.311.80.1") -CertStoreLocation "Cert:\CurrentUser\My" -DnsName "www.fabrikam.com", "www.contoso.com" + $b.SerialNumber + 4. Export new certificate to file and encode to Base64 (press Enter key at password prompt): + certutil -exportpfx -user my C:\temp\newcert.pfx + certutil -encode C:\temp\newcert.pfx C:\temp\newcert.txt + 5. Replace $dataEnciphermentCert with new value from C:\temp\newcert.txt + #> + $dataEnciphermentCert = " +MIIKmgIBAzCCClYGCSqGSIb3DQEHAaCCCkcEggpDMIIKPzCCBgAGCSqGSIb3DQEH +AaCCBfEEggXtMIIF6TCCBeUGCyqGSIb3DQEMCgECoIIE/jCCBPowHAYKKoZIhvcN +AQwBAzAOBAi0Gq/DrqxgYgICB9AEggTYy168efMBdfynAWPkh7S6VyhoaMOWR6Nt +l6MnGcWuGGFxLS2TxfZSN+mNYsAGG0yGnTyYZ/2uCW46irfMNPnq1OrYjWJB4Zmb +pRF2WI+ipVkKUSVtuCKR8GQR9Yw7C3PASqgm0JV1YpK9fw0EJ4tzeF4w81xzb5Ix +jm8jOwSBefYXjOPXwIvqVntOQHkMj75EVEjyTi/Sb3SoYk0b7uzqFsqtiQ3S9rtp +FuM3gRNY6zjHYa/iDrwt4aty8IU74yipyLj/9c4+BnaBiMCwX51FrHYGx1OYkcbN +K0iTWzxG3oYFxY9V9P6uzUqDUTsZNawkjne2WIFJrfPq25nU8vMHej0T/YqMczPF +6NtiHv0yMFNqxEh6vZgc00gitEV+Cbr9/lsctJ2YiYFW2Sc5q7wovc231xrLaDXZ +BLdnJZqqalobqdwfu84o7GiXw7y9pdvwNl4vKH4yiDv4kqCcuzqLxJ7QTk63EuGw +uiEhoC1ibvvi7+fCm60IxHxtGlUAZCaEidh70/VffRkea+oor9WLSIZ7q07ycB1+ +2k0ecpeutaOPAckfIPLDb1m9BwlM47dVaSJNN5/5oQsRM7327TDjYphfVoUDX19p +eAiRcO68GbcmXQQuM8CxbCSdPC3F6Jr5YzMBnM6Yqacv8ywdYaZHmKBADF2FtuFb +zyFHFCzTYJyj3YPczK1Xmw7d4btJuIAvl5JOrbDqSEbwRvXZL2f5iIRydekcPnma +nu/mlRqupQWiasv7qoga8Gq5sKMIUAruTwAzhkNA01hj5Iv7NcFY9Ruc+MHmnGmE +BiDSh/wOuyRIGIfveR9e4msg6rP9KO7q6mnghBVy6Is6Xz5Ak/xS2v7RjxzgBxrE +LaPoeHpD2+LKN+w8eN9Pd2ewAwwHzrw65jN7P1avuioMi0KejYZZAt3scmIGTT+n +jzcmxKvAPYllRifZRoDe+hAeYHlGAA8hm/zmw5522L8hOR1j69JcbCbl8MtWww+W +2MiGYbEbbGg/HV+9PnMMU3i+eHtuxTukBYb1ksH5z7742WBYH+gE3CvPX5AZRY/N +rVo8qZPH2ZK1smmc7v2cv0ZhIGWiMe+LakjjB1QcFpCTh/uwhQhNGXOgU1oNKbuE +FE7iH3XHkDVLFxEhdtgvwXJIQp+7ROVry6sZW2cgi2yUOTWNjqDd6LlV8vy+lti6 +/7L+rhfezHUpk89eWu8glEFqB96xmc9nbSF0On8aewdb43gW8h5TbBuSE1JA5SfE +cQNSxs4z+Bbx7cyX+f3CDriJkzXs5PvT861pNcYB1zjxJBStehMeazWf+D2Td10p +43nmEYaXHq/KwY1jn8yzQyj2V+c5Csw75879KHZ+6LwfzeWDCUlma+n1ZDNeDm2+ +s40qBC4s7x9sEdUBQwl/JOtJt4ZlFje4xle5/RlS2il/e/X05n4XUuX7azWZjSoG +fflcIvMcnXydt/Nl05newUGobDLEw9sa9lDy8bG6+IGywg2x1A4hqsA2qUbVLyy/ +VLdhIY38+FmcliL3o3uk9vsSHefODKrG1ZD+qu3+/9s3B7KmlT652cj6+fi54+N/ +jM/VAT/6e8OntolNHVoauudBmgO0WRYbznvZd/C6ehTAZQo9HKiGkADk1i8Gw6NF +dgH4LZdHyWOgrvAL1FqOqzGB0zATBgkqhkiG9w0BCRUxBgQEAQAAADBdBgkqhkiG +9w0BCRQxUB5OAHQAZQAtAGYAZgBiADIAYQBiADMAYgAtAGMAZQA5ADEALQA0ADEA +MABhAC0AOAA1AGMAMQAtADkAYwBmADgAYwA3AGEAMAA5ADkAYwA4MF0GCSsGAQQB +gjcRATFQHk4ATQBpAGMAcgBvAHMAbwBmAHQAIABTAHQAcgBvAG4AZwAgAEMAcgB5 +AHAAdABvAGcAcgBhAHAAaABpAGMAIABQAHIAbwB2AGkAZABlAHIwggQ3BgkqhkiG +9w0BBwagggQoMIIEJAIBADCCBB0GCSqGSIb3DQEHATAcBgoqhkiG9w0BDAEDMA4E +CNnzLxDoo0cMAgIH0ICCA/ChXpj5kGwmqH++L8JmdidMyhQAk//fnIxsE695lW4B +yUQ0wM7k2eWSdebuCMSvD1bL2A8B6qM/sfkuoAUHrSGZS56Qeh4C3j5FqLyMOg1H +7w4hkHDYTQp4s9fxk5NedqsctmmKnZmrET65g8KRMSiolFYqd69D1SWGnftUVMvU +MdrFQRP0GJPSvDzx17NJWRRiUXzYxakfpGW8QfV0I9/ZlP8uUEZlVqc1v7ikQf1e +A3i6+njdj9lkpa2CEdtAdpwVTpQ49UwGq4tD5aMlzjWNuzQpP0mEh9XzWr6J3aFz +poEpCuC7gT4tIZcC3BKS9Uvv53AShpxJCiWwG7k+CYzzKabq3guJh1uPKMqpHJGG +T/5a4lQJOQr2IyfoDfsNw2JtAVjM62haUap4ZZbuoJq9B6gECknUAbSsk1XdEktq +OhMP5HtlCEAdLeEo0ae3YGRHsKJgFSx/8R+MMdjMWMYT8+6mVSVC63KQMiBmVEBs +mDcOWY45eCHo4aXuEKcHZSxRtWvviACdEd/CSDGwFGuA8f+D0f6iLabQCLLeVRVN +iUiKER3Adw+dg6pegifM4r3trqR2/H1Z33TrarGMKGNJSy6ur8bW1L6aYjxx1hj+ +dW9Tcj1QTLC1PJFaLdOD1ELho/EXdoa9VPuC2MWS9I6TqpY4Rkrcbr4KMqDLUTeN +SzgSZARVPv7VL8EDqeME24aQammby6nr25tA+Q45KBH8nc/hURVcoayl/cmH9Up3 +D87oCdVuCvmiJnFhZnd7q6S3ChfIQhyGNlZQjRjDve9MhR0TwaDEm0nsBpSwSVwP +PyJ00Md/cq4nImXvMYXf1fZ3Tlp90kZ2ffQ9+EMzFXNBNLof+CjVR8EZI1tOjUQg +0GYJvDFCt+hz7AAQU+2Ggrp1L7FaSJmgEJIVYwQP4bM25ee4BiQt3hHJL8Nfr97W +NEg8W4AbpYHJkcXa6jrxZLvsKxZNnLF0eczpG6X2jq7JdbumlZ1viom2j1aPB5GF +owQOcBfI3nhA5nludg2CUT2cqtO4mKJyPXwGA0OcFZ4aVJeO7wnS/21g7AB/Opmt +BCxZBwysPns0JJbH39p2PNRw/q2Kp+pxm0MEyJJe27T/f4cEUaezaAu+7qU7diSb +bUKQZzH+zfvmGr+UyQf6t3UHMyOBMfURmYEVesY47Vu2swdN4QW+tnJ2JIdIjEIb +W+OiON35K4wJDY893wZRI9uPPOQ/6yCAuI0uEu7WV2xqmpvMOj0E4yqkX0IChFxj +Mb1wqu/wHSwktbpqZLykVfJYq5ol/gexrY04AJJSZEDXGvHLR47Qfzx2M0XcjZpi +vsa+CHXqcU5T1e+1tPp8TdcwOzAfMAcGBSsOAwIaBBQiJeRFYB+55Wp+h8bnYt0D +YSQdMwQUYoaBDgXqLdO2G97FQNL1XPhVEEkCAgfQ +" + + $dataEnciphermentCert = $dataEnciphermentCert -replace '\s','' + $certBytes = [Convert]::FromBase64String($dataEnciphermentCert) + $certLocation = Join-Path $TestDrive "ProtectedEventLogging.pfx" + [IO.File]::WriteAllBytes($certLocation, $certBytes) + + return $certLocation +} + Function New-CertificatePassword { $script:protectedCertPassword = ConvertTo-SecureString -Force -AsPlainText (New-RandomHexString) @@ -138,6 +242,9 @@ function Install-TestCertificates $script:certLocation = New-GoodCertificate $script:certLocation | Should -Not -BeNullOrEmpty | Out-Null + $script:certServerLocation = New-GoodServerCertificate + $script:certServerLocation | Should -Not -BeNullOrEmpty | Out-Null + $script:badCertLocation = New-BadCertificate $script:badCertLocation | Should -Not -BeNullOrEmpty | Out-Null @@ -148,15 +255,17 @@ function Install-TestCertificates $command = @" Import-PfxCertificate $script:certLocation -CertStoreLocation cert:\CurrentUser\My | ForEach-Object PSPath +Import-PfxCertificate $script:certServerLocation -CertStoreLocation cert:\CurrentUser\My | ForEach-Object PSPath Import-Certificate $script:badCertLocation -CertStoreLocation Cert:\CurrentUser\My | ForEach-Object PSPath "@ $certPaths = & $fullPowerShell -NoProfile -NonInteractive -Command $command $certPaths.Count | Should -Be 2 | Out-Null $script:importedCert = Get-ChildItem $certPaths[0] - $script:testBadCert = Get-ChildItem $certPaths[1] + $script:importedServerCert = Get-ChildItem $certPaths[1] + $script:testBadCert = Get-ChildItem $certPaths[2] } - elseif($IsWindows) + elseif ($IsWindows) { $script:importedCert = Import-PfxCertificate $script:certLocation -CertStoreLocation cert:\CurrentUser\My $script:testBadCert = Import-Certificate $script:badCertLocation -CertStoreLocation Cert:\CurrentUser\My @@ -176,6 +285,11 @@ function Get-GoodCertificateObject return $script:importedCert } +function Get-GoodServerCertificateObject +{ + return $script:importedServerCert +} + function Get-BadCertificateObject { return $script:testBadCert From ee6f752fa152eb188df0cfd53d1beeca5fa77d34 Mon Sep 17 00:00:00 2001 From: Ilya Date: Mon, 30 Mar 2020 00:13:46 +0500 Subject: [PATCH 2/2] Fix test --- .../Microsoft.PowerShell.Security/certificateCommon.psm1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 index dba23826923..42e990b714e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 @@ -259,7 +259,7 @@ Import-PfxCertificate $script:certServerLocation -CertStoreLocation cert:\Curren Import-Certificate $script:badCertLocation -CertStoreLocation Cert:\CurrentUser\My | ForEach-Object PSPath "@ $certPaths = & $fullPowerShell -NoProfile -NonInteractive -Command $command - $certPaths.Count | Should -Be 2 | Out-Null + $certPaths.Count | Should -Be 3 | Out-Null $script:importedCert = Get-ChildItem $certPaths[0] $script:importedServerCert = Get-ChildItem $certPaths[1]