diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs
index 25ed55453d..3436ddb606 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 @@ namespace Microsoft.PowerShell.Commands
}
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 @@ namespace Microsoft.PowerShell.Commands
/// Switch that controls whether we should delete private key
/// when remove a certificate.
///
- [Parameter()]
+ [Parameter]
public SwitchParameter DeleteKey
{
get
@@ -1185,15 +1247,11 @@ namespace Microsoft.PowerShell.Commands
}
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);
@@ -2212,7 +2270,7 @@ namespace Microsoft.PowerShell.Commands
///
protected override object GetItemDynamicParameters(string path)
{
- return new CertificateProviderCodeSigningDynamicParameters();
+ return new CertificateProviderDynamicParameters();
}
///
@@ -2234,7 +2292,7 @@ namespace Microsoft.PowerShell.Commands
///
protected override object GetChildItemsDynamicParameters(string path, bool recurse)
{
- return new CertificateProviderCodeSigningDynamicParameters();
+ return new CertificateProviderDynamicParameters();
}
#endregion DriveCmdletProvider overrides
@@ -2607,8 +2665,8 @@ namespace Microsoft.PowerShell.Commands
if (DynamicParameters != null)
{
- CertificateProviderCodeSigningDynamicParameters dp =
- DynamicParameters as CertificateProviderCodeSigningDynamicParameters;
+ CertificateProviderDynamicParameters dp =
+ DynamicParameters as CertificateProviderDynamicParameters;
if (dp != null)
{
if (dp.CodeSigningCert)
@@ -2616,6 +2674,40 @@ namespace Microsoft.PowerShell.Commands
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;
+ }
}
}
@@ -2634,39 +2726,45 @@ namespace Microsoft.PowerShell.Commands
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;
- }
-
- break;
+ return SecuritySupport.CertIsGoodForSigning(cert);
case CertificatePurpose.DocumentEncryption:
- if (SecuritySupport.CertIsGoodForEncryption(cert))
- {
- return true;
- }
+ return SecuritySupport.CertIsGoodForEncryption(cert);
- break;
+ case CertificatePurpose.NotSpecified:
+ case CertificatePurpose.All:
+ return true;
default:
break;
@@ -2675,6 +2773,89 @@ namespace Microsoft.PowerShell.Commands
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)
+ {
+ if (usage.Value.Equals(CertificateFilterInfo.OID_PKIX_KP_SERVER_AUTH, StringComparison.Ordinal))
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// 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;
+ }
+
private static object GetCachedItem(string path)
{
object item = null;
@@ -3195,7 +3376,7 @@ namespace Microsoft.PowerShell.Commands
}
///
- /// 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 64ed73a99d..90d4a71d73 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 @@ namespace System.Management.Automation.Internal
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 @@ namespace System.Management.Automation.Internal
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 @@ namespace System.Management.Automation.Internal
}
///
- /// 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; }
+ get;
- set { _sslServerAuthentication = value; }
+ set;
}
///
- /// DNS name of a certificate.
+ /// Gets or sets DNS name of a certificate.
///
- internal string DnsName
+ internal WildcardPattern DnsName
{
- set { _dnsName = value; }
+ get;
+ set;
}
///
- /// EKU OID list of a certificate.
+ /// Gets or sets EKU OID list of a certificate.
///
- internal string[] Eku
+ internal List Eku
{
- set { _eku = value; }
+ get;
+ set;
}
///
- /// Remaining validity period in days for a certificate.
+ /// Gets or sets validity time for a certificate.
///
- internal int ExpiringInDays
+ internal DateTime Expiring
{
- set { _expiringInDays = value; }
- }
-
- ///
- /// Combine properties into a filter string.
- ///
- internal string FilterString
- {
- 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 9ac4ceb17a..88bccb20a3 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
+ $cert = (Get-item -LiteralPath $leafPath)
+ $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 DNSNameList of certifate by path: " -TestCases $currentUserMyLocations {
+ 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 = (Get-item -LiteralPath $leafPath)
+ $cert | Should -Not -Be $null
$cert.EnhancedKeyUsageList | Should -Not -Be null
- $cert.EnhancedKeyUsageList.Count | Should -Be 1
+ $cert.EnhancedKeyUsageList.Count | Should -Be 3
$cert.EnhancedKeyUsageList[0].ObjectId.Length | Should -Not -Be 0
$cert.EnhancedKeyUsageList[0].ObjectId | Should -Be $expectedOid
}
@@ -158,13 +162,69 @@ Describe "Certificate Provider tests" -Tags "Feature" {
}
}
Context "Get-ChildItem tests"{
- It "Should filter to codesign certificates" {
- $allCerts = Get-ChildItem cert:\CurrentUser\My
- $codeSignCerts = Get-ChildItem cert:\CurrentUser\My -CodeSigningCert
+ BeforeAll {
+ $cert = Get-GoodServerCertificateObject
+ }
+ it "Should filter to codesign certificates" {
+ $allCerts = get-ChildItem cert:\CurrentUser\My
+ $codeSignCerts = get-ChildItem cert:\CurrentUser\My -CodeSigningCert
$codeSignCerts | Should -Not -Be null
$allCerts | Should -Not -Be null
$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 91c87df36d..42e990b714 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
+ $certPaths.Count | Should -Be 3 | 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