From bbda8f30233df7be14f4f776a37d9917126ed1d9 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Thu, 18 May 2017 18:45:35 -0700 Subject: [PATCH 01/10] Remove undocumented certificate APIs --- .../security/CertificateProvider.cs | 308 ++---------------- .../security/nativeMethods.cs | 53 --- 2 files changed, 31 insertions(+), 330 deletions(-) diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs index 1449afbedcb..0ff16d0f94c 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -15,6 +15,7 @@ using System.Collections; using System.Runtime.InteropServices; using System.Management.Automation.Provider; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; using System.Globalization; @@ -351,42 +352,6 @@ public IntPtr Handle } } - /// - /// Defines the safe handle class for native cert store handles, - /// HCERTSTORE. - /// - internal sealed class CertificateFilterHandle : SafeHandle - { - public CertificateFilterHandle() : base(IntPtr.Zero, true) - { - return; - } - - public override bool IsInvalid - { - get { return handle == IntPtr.Zero; } - } - - protected override bool ReleaseHandle() - { - bool fResult = false; - - if (IntPtr.Zero != handle) - { - Security.NativeMethods.CCFindCertificateFreeFilter(handle); - handle = IntPtr.Zero; - fResult = true; - } - return fResult; - } - - public IntPtr Handle - { - get { return handle; } - set { handle = value; } - } - } - /// /// Defines the Certificate Provider store handle class /// @@ -480,22 +445,6 @@ public void Open(bool includeArchivedCerts) public IntPtr GetFirstCert( CertificateFilterInfo filter) { - _filterHandle = null; - if (DownLevelHelper.NativeFilteringSupported() && filter != null) - { - IntPtr hFilter = IntPtr.Zero; - - _filterHandle = new CertificateFilterHandle(); - int hr = Security.NativeMethods.CCFindCertificateBuildFilter( - filter.FilterString, - ref hFilter); - if (hr != Security.NativeConstants.S_OK) - { - _filterHandle = null; - throw new System.ComponentModel.Win32Exception(hr); - } - _filterHandle.Handle = hFilter; - } return GetNextCert(IntPtr.Zero); } @@ -508,19 +457,9 @@ public IntPtr GetNextCert(IntPtr certContext) } if (Valid) { - if (_filterHandle != null) - { - certContext = Security.NativeMethods.CCFindCertificateFromFilter( - _storeHandle.Handle, - _filterHandle.Handle, - certContext); - } - else - { - certContext = Security.NativeMethods.CertEnumCertificatesInStore( - _storeHandle.Handle, - certContext); - } + certContext = Security.NativeMethods.CertEnumCertificatesInStore( + _storeHandle.Handle, + certContext); } else { @@ -635,7 +574,6 @@ public bool Valid private X509StoreLocation _storeLocation = null; private string _storeName = null; private CertificateStoreHandle _storeHandle = null; - private CertificateFilterHandle _filterHandle = null; private bool _valid = false; private bool _open = false; } @@ -2092,10 +2030,7 @@ private void DoRemove(X509Certificate2 cert, bool fDeleteKey, bool fMachine, str CommitUserDS(context.hCertStore); } - if (DownLevelHelper.LogCertChangeSupported()) - { - Security.NativeMethods.LogCertDelete(fMachine, cert.Handle); - } + //TODO: Log Cert Delete //delete private key if (fDeleteKey && fHasPrivateKey) @@ -2173,18 +2108,7 @@ private void DoMove(string destination, X509Certificate2 cert, X509NativeStore s throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } - if (DownLevelHelper.LogCertChangeSupported()) - { - bool fMachine = store.Location.Location == StoreLocation.LocalMachine; - - if (cert.HasPrivateKey && - String.Equals(store.StoreName, "MY", StringComparison.OrdinalIgnoreCase)) - { - Security.NativeMethods.LogCertCopy(fMachine, cert.Handle); - } - - Security.NativeMethods.LogCertDelete(fMachine, cert.Handle); - } + //TODO: log cert move } //commit the change to physical store @@ -2499,14 +2423,7 @@ protected override bool IsItemContainer(string path) /// protected override object GetItemDynamicParameters(string path) { - if (DownLevelHelper.NativeFilteringSupported()) - { - return new CertificateProviderDynamicParameters(); - } - else - { - return new CertificateProviderCodeSigningDynamicParameters(); - } + return new CertificateProviderCodeSigningDynamicParameters(); } /// @@ -2531,14 +2448,7 @@ protected override object GetItemDynamicParameters(string path) /// protected override object GetChildItemsDynamicParameters(string path, bool recurse) { - if (DownLevelHelper.NativeFilteringSupported()) - { - return new CertificateProviderDynamicParameters(); - } - else - { - return new CertificateProviderCodeSigningDynamicParameters(); - } + return new CertificateProviderCodeSigningDynamicParameters(); } #endregion DriveCmdletProvider overrides @@ -2952,62 +2862,14 @@ private CertificateFilterInfo GetFilter() if (DynamicParameters != null) { - if (DownLevelHelper.NativeFilteringSupported()) + CertificateProviderCodeSigningDynamicParameters dp = + DynamicParameters as CertificateProviderCodeSigningDynamicParameters; + if (dp != null) { - CertificateProviderDynamicParameters dp = - DynamicParameters as CertificateProviderDynamicParameters; - if (dp != null) + if (dp.CodeSigningCert) { - bool filterSpecified = false; - filter = new CertificateFilterInfo(); - if (dp.CodeSigningCert) - { - filter.Purpose = CertificatePurpose.CodeSigning; - filterSpecified = true; - } - if (dp.DocumentEncryptionCert) - { - filter.Purpose = CertificatePurpose.DocumentEncryption; - filterSpecified = true; - } - if (dp.SSLServerAuthentication) - { - filter.SSLServerAuthentication = true; - filterSpecified = true; - } - if (dp.DnsName.Punycode != null) - { - filter.DnsName = dp.DnsName.Punycode; - filterSpecified = true; - } - if (dp.Eku != null) - { - filter.Eku = dp.Eku; - filterSpecified = true; - } - if (dp.ExpiringInDays >= 0) - { - filter.ExpiringInDays = dp.ExpiringInDays; - filterSpecified = true; - } - if (!filterSpecified) - { - filter = null; - } - } - } - else - { - CertificateProviderCodeSigningDynamicParameters dp = - DynamicParameters as CertificateProviderCodeSigningDynamicParameters; - if (dp != null) - { - if (dp.CodeSigningCert) - { - filter = new CertificateFilterInfo(); - filter.Purpose = CertificatePurpose.CodeSigning; - } + filter.Purpose = CertificatePurpose.CodeSigning; } } } @@ -3541,32 +3403,15 @@ public List EnhancedKeyUsageList /// public EnhancedKeyUsageProperty(X509Certificate2 cert) { - if (DownLevelHelper.NativeFilteringSupported()) + foreach (X509Extension extension in cert.Extensions) { - Collection ekuCollection = System.Management.Automation.Internal.SecuritySupport.GetCertEKU(cert); - - foreach (string oidString in ekuCollection) + if (extension.Oid.FriendlyName == "Enhanced Key Usage") { - if (!String.IsNullOrEmpty(oidString)) + X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)extension; + OidCollection oids = ext.EnhancedKeyUsages; + foreach (Oid oid in oids) { - IntPtr stringAnsi = (IntPtr)Marshal.StringToHGlobalAnsi(oidString); - - EnhancedKeyUsageRepresentation ekuString; - IntPtr oidPtr = Security.NativeMethods.CryptFindOIDInfo( - Security.NativeConstants.CRYPT_OID_INFO_OID_KEY, - stringAnsi, - 0); - if (oidPtr != IntPtr.Zero) - { - Security.NativeMethods.CRYPT_OID_INFO oidInfo = - ClrFacade.PtrToStructure(oidPtr); - ekuString = new EnhancedKeyUsageRepresentation(oidInfo.pwszName, oidString); - } - else //if oidInfo is not available - { - ekuString = new EnhancedKeyUsageRepresentation(null, oidString); - } - + EnhancedKeyUsageRepresentation ekuString = new EnhancedKeyUsageRepresentation(oid.FriendlyName, oid.Value); _ekuList.Add(ekuString); } } @@ -3598,81 +3443,25 @@ public List DnsNameList /// public DnsNameProperty(X509Certificate2 cert) { - if (DownLevelHelper.NativeFilteringSupported()) - { - if (cert != null) - { - //need to get subject alternative name from the certificate context - _dnsList = GetCertNames( - cert.Handle, - Security.NativeMethods.AltNameType.CERT_ALT_NAME_DNS_NAME); - } - } - } - - // Wrapper function for CCGetCertNameList and CCFreeStringArray - private List GetCertNames(IntPtr certHandle, Security.NativeMethods.AltNameType nameType) - { - DWORD cPunycodeName; - IntPtr papwszPunycodeNames = IntPtr.Zero; - IntPtr papwszUnicodeNames = IntPtr.Zero; - List names = new List(); - int hr; - - if (certHandle != IntPtr.Zero) + _dnsList = new List(); + foreach (X509Extension extension in cert.Extensions) { - hr = Security.NativeMethods.CCGetCertNameList( - certHandle, - nameType, - 0, // no conversion to Unicode - out cPunycodeName, - out papwszPunycodeNames); - if (hr != Security.NativeConstants.S_OK) - { - if (hr != Security.NativeMethods.CRYPT_E_NOT_FOUND) - { - throw Marshal.GetExceptionForHR(hr); - } - cPunycodeName = 0; - } - - try + if (extension.Oid.FriendlyName == "Subject Alternative Name") { - if (0 < cPunycodeName) + string[] names = extension.Format(true).Split(Environment.NewLine); + foreach(string nameLine in names) { - DWORD cUnicodeName; - - hr = Security.NativeMethods.CCGetCertNameList( - certHandle, - nameType, - Security.NativeMethods.CryptDecodeFlags.CRYPT_DECODE_ENABLE_IA5CONVERSION_FLAG, - out cUnicodeName, - out papwszUnicodeNames); - if (hr != Security.NativeConstants.S_OK) + // Get the part after 'DNS Name=' + if(nameLine.Length > 9) { - throw Marshal.GetExceptionForHR(hr); - } - if (cPunycodeName != cUnicodeName) - { - throw Marshal.GetExceptionForHR( - Security.NativeMethods.E_INVALID_DATA); - } - for (int i = 0; i < cPunycodeName; i++) - { - names.Add(new DnsNameRepresentation( - Marshal.PtrToStringUni(Marshal.ReadIntPtr(papwszPunycodeNames, i * Marshal.SizeOf(papwszPunycodeNames))), - Marshal.PtrToStringUni(Marshal.ReadIntPtr(papwszUnicodeNames, i * Marshal.SizeOf(papwszUnicodeNames))))); + //TODO: detect and handle punyCode + string name = nameLine.Substring(9); + DnsNameRepresentation dnsName = new DnsNameRepresentation(name); + _dnsList.Add(dnsName); } } } - finally - { - Security.NativeMethods.CCFreeStringArray(papwszPunycodeNames); - Security.NativeMethods.CCFreeStringArray(papwszUnicodeNames); - } } - - return names; } } @@ -3689,9 +3478,6 @@ internal static bool IsWin8AndAbove() { if (!s_isWin8Set) { -#if CORECLR - s_isWin8 = true; -#else System.OperatingSystem osInfo = System.Environment.OSVersion; PlatformID platform = osInfo.Platform; Version version = osInfo.Version; @@ -3702,44 +3488,12 @@ internal static bool IsWin8AndAbove() { s_isWin8 = true; } -#endif + s_isWin8Set = true; } return s_isWin8; } - private static bool s_nativeFilteringSet = false; - private static bool s_nativeFiltering = false; - - internal static bool NativeFilteringSupported() - { - if (!s_nativeFilteringSet) - { - if (IsWin8AndAbove() && Security.NativeMethods.IsSystem32DllPresent("certca.dll")) - { - s_nativeFiltering = true; - } - s_nativeFilteringSet = true; - } - return s_nativeFiltering; - } - - private static bool s_logChangesSet = false; - private static bool s_logChanges = false; - - internal static bool LogCertChangeSupported() - { - if (!s_logChangesSet) - { - if (IsWin8AndAbove() && Security.NativeMethods.IsSystem32DllPresent("certenroll.dll")) - { - s_logChanges = true; - } - s_logChangesSet = true; - } - return s_logChanges; - } - internal static bool TrustedIssuerSupported() { return IsWin8AndAbove(); diff --git a/src/System.Management.Automation/security/nativeMethods.cs b/src/System.Management.Automation/security/nativeMethods.cs index 0c26ee17145..6bcd9ee48b1 100644 --- a/src/System.Management.Automation/security/nativeMethods.cs +++ b/src/System.Management.Automation/security/nativeMethods.cs @@ -1453,59 +1453,6 @@ internal enum CryptDecodeFlags : uint CRYPT_DECODE_ENABLE_UTF8PERCENT_FLAG = 0x04000000, CRYPT_DECODE_ENABLE_IA5CONVERSION_FLAG = (CRYPT_DECODE_ENABLE_PUNYCODE_FLAG | CRYPT_DECODE_ENABLE_UTF8PERCENT_FLAG), } - - // ------------------------------------------------------------------- - // certca.dll stuff - // - - [DllImport("certca.dll")] - internal static extern - int CCFindCertificateBuildFilter( - [MarshalAsAttribute(UnmanagedType.LPWStr)] - string filter, - ref IntPtr certFilter); - - [DllImport("certca.dll")] - internal static extern - void CCFindCertificateFreeFilter( - IntPtr certFilter); - - - [DllImport("certca.dll")] - internal static extern - IntPtr CCFindCertificateFromFilter( - IntPtr storeHandle, - IntPtr certFilter, - IntPtr prevCertContext); - - [DllImport("certca.dll")] - internal static extern - int // HRESULT - CCGetCertNameList( - IntPtr certContext, - AltNameType dwAltNameChoice, - CryptDecodeFlags dwFlags, - out DWORD cName, - out IntPtr papwszName); - - [DllImport("certca.dll")] - internal static extern - void CCFreeStringArray(IntPtr papwsz); - } - - internal static partial class NativeMethods - { - // HRESULT LogCertDelete(") - // __in bool fMachine,") - // __in PCCERT_CONTEXT pCertContext) - [DllImport("certenroll.dll")] - internal static extern int LogCertDelete(bool fMachine, IntPtr pCertContext); - - // HRESULT LogCertCopy(") - // __in bool fMachine,") - // __in PCCERT_CONTEXT pCertContext) - [DllImport("certenroll.dll")] - internal static extern int LogCertCopy(bool fMachine, IntPtr pCertContext); } #region Check_UI_Allowed From 0348ee2b8654acdfc8e15486af01de1ba6540d48 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 19 May 2017 12:34:25 -0700 Subject: [PATCH 02/10] Make the cert provider properly support punycode --- .../security/CertificateProvider.cs | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs index 0ff16d0f94c..b06c0349fbb 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -3426,6 +3426,9 @@ public EnhancedKeyUsageProperty(X509Certificate2 cert) public sealed class DnsNameProperty { private List _dnsList = new List(); + private System.Globalization.IdnMapping idnMapping = new System.Globalization.IdnMapping(); + private static string dnsNamePrefix = "DNS Name="; + private static string distinguishedNamePrefix = "CN="; /// /// get property of DnsNameList @@ -3443,7 +3446,33 @@ public List DnsNameList /// public DnsNameProperty(X509Certificate2 cert) { + string name; + string unicodeName; + DnsNameRepresentation dnsName; _dnsList = new List(); + + // extract DNS name from subject distinguish name + // if it exists and does not contain a comma + // a comma, indicates it is not a DNS name + if(cert.Subject.Length > distinguishedNamePrefix.Length && + cert.Subject.StartsWith(distinguishedNamePrefix, System.StringComparison.InvariantCulture) && + cert.Subject.IndexOf(",",System.StringComparison.InvariantCulture)==-1) + { + name = cert.Subject.Substring(distinguishedNamePrefix.Length); + try + { + unicodeName = idnMapping.GetUnicode(name); + } + catch(System.ArgumentException) + { + // The name is not valid punyCode, assume it's valid ascii. + unicodeName = name; + } + + dnsName = new DnsNameRepresentation(name,unicodeName); + _dnsList.Add(dnsName); + } + foreach (X509Extension extension in cert.Extensions) { if (extension.Oid.FriendlyName == "Subject Alternative Name") @@ -3452,12 +3481,26 @@ public DnsNameProperty(X509Certificate2 cert) foreach(string nameLine in names) { // Get the part after 'DNS Name=' - if(nameLine.Length > 9) + if(nameLine.Length > dnsNamePrefix.Length && nameLine.StartsWith(dnsNamePrefix, System.StringComparison.InvariantCulture)) { - //TODO: detect and handle punyCode - string name = nameLine.Substring(9); - DnsNameRepresentation dnsName = new DnsNameRepresentation(name); - _dnsList.Add(dnsName); + name = nameLine.Substring(dnsNamePrefix.Length); + try + { + unicodeName = idnMapping.GetUnicode(name); + } + catch(System.ArgumentException) + { + // The name is not valid punyCode, assume it's valid ascii. + unicodeName = name; + } + + dnsName = new DnsNameRepresentation(name,unicodeName); + + // Only add the name if it is not the same as an existing name. + if(!_dnsList.Contains(dnsName)) + { + _dnsList.Add(dnsName); + } } } } From f3f7995daf47648bff3ce369d082eb4bc73d4bfb Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 19 May 2017 14:38:43 -0700 Subject: [PATCH 03/10] refactor certificate code into common file --- .../CmsMessage.Tests.ps1 | 186 +++--------------- .../certificateCommon.psm1 | 150 ++++++++++++++ 2 files changed, 180 insertions(+), 156 deletions(-) create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 index 2e021e250c8..edf97107ee7 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 @@ -1,91 +1,4 @@ - -Function Create-GoodCertificate -{ - $dataEnciphermentCert = " -MIIKYAIBAzCCCiAGCSqGSIb3DQEHAaCCChEEggoNMIIKCTCCBgoGCSqGSIb3DQEHAaCCBfsEggX3 -MIIF8zCCBe8GCyqGSIb3DQEMCgECoIIE/jCCBPowHAYKKoZIhvcNAQwBAzAOBAgPOFDMBkCffQIC -B9AEggTYjY55RrmAhdj1grENxXjiPrVNdS++pb5UOn3M7O78BR0U1i2h5zvjkPjOwdLoOCbq5pgg -F0PKaMjHVu8EoZxSqsib17ptR5Rx5N23hseuJUzS8fTAHiBet9payNOJlPfkpuqMfQEmCAo9gAPz -w4RiyZNOA3NhxkfGl9yU4O9GSEr2koWKCUoCNelkIXVbkV728L7zSiWRSqRb7V4QJAtwtgPLTbl/ -zo2SFhdNAGPeXbcOsKCv9trhuxPZ0FH4fukbXkHs0I3b5mYgMUI5Mds7UwT3wCtz+Ev9pLbmYN8X -NfH0tAK8ZGnQS1GcI4xCMEM8T9Tx3uwWY4arvRM3GTLwyt8JZEVZNTuYL9A9+RyeiO5d5xEKG8H4 -snOCoTriT45tdl8hzMBCdc3jWxWiydmNRw47irifv5BX7BK/6FLAxkMRwACAxNO63ezG/OxuDDEz -ml+KzeZNvr4u4mTBcgZ49vMSyfRt/my+5+iLnSMGp4Rt0uix8489wctkxGlgyXGk23pA4Cj4hq/6 -txopcl2gHn+DAFrHIgLg4JR8lcuOzBw8nFOrhK7iR9aMK21apxwImIaDCKJ1grOfbuElq4pkMpov -SltJe00WB94o69LibOg5LqpTrHW2/DY951sIgElF83FdUBhoZHasfCme/RxgliQf3QHedmENXSjr -8R8PAWX4wC0ZZVC0qcq5XP0PkwtuDKmfqq69R32nmBzpRrfypm9S+PfsYZeCeuROh6YKQ0ZBMnLb -8Y9povpXh0lYwVLuPanvAFiCT4vI7oRd1Mg1Zr9ZooMFAVomall1UnQQ29fvsoADjEDcPymVT80o -kTkw3NXnTX6fGZ94Eh0KZcuMgjTqIO3OKpH0lcaSBxlES4V0sO/mwP4ULy8l3dcnn440Nei33VDo -B7n2jhjJl/HvtltfEEEw1DW9AWDvkJDp878sD6VoyQZehvvxBNT0FwMr20TbVKeAGxf9n+xJ9Alo -VUS3qE7XnpxAAAR5L2OG+tMd4dyDSge1qrkVNZ3/uUzKKCZei2P7ICR9cX1FRsmcINdNfydIA2cA -Pmeq+UkRdqsJxt1NSK5bLvMM4EHRQZMMbXVKxxJ+kQDrzfQtERFPyd3Hdm2F4T/JXUQ+PrTQnRqY -LruTAiZfxygZuFrxJnNTRydRdbEaTAtMjFCMRFZ2wctJsgCb3yN9tt0JDYxIvm0MSehXiF+sCrl4 -yZvvUzJqrgppHRTBR4Sao+MZ/rJ30vVU19Q3oBi9ikTqDY+4SrHsp5Y4llnsbrz0Web+h2jLvyJz -LgKuqs87qHhToVMLuULy/HLqY3m661EMwNqh5D76gSFI+TP2/rzT5mVOGglahFoc848o4cshtPWE -9MjAkDfsMbIfeKH3uh3D+eBIxYmZ5Cq2aHzqdQ0pU/nDNX7BDjC3E80VcQnXx4U6tRsQHsGtbcld -MFTp0yHJ2KLkz+inH3WPy/lYuVZ0QJe+LqvGt+bt1DgQmLBMD9WLFML3d0TtkuY3RhD5Y0wr2zt9 -tT6WVTn8Hob1cJns4N7tDEr8Q3TdIar0I5Bzj3qoesJt+4lxwnVdUA1bNJ2zxXIkDfX/MTB464FI -2g9uhUs3lIOEiCjeJCwBebgZa1HlfhyCRu0E7fnNnKLaGWRs8LVy7MZIfe1kJoDVgTGB3TATBgkq -hkiG9w0BCRUxBgQEAQAAADBdBgkrBgEEAYI3EQExUB5OAE0AaQBjAHIAbwBzAG8AZgB0ACAAUwB0 -AHIAbwBuAGcAIABDAHIAeQBwAHQAbwBnAHIAYQBwAGgAaQBjACAAUAByAG8AdgBpAGQAZQByMGcG -CSqGSIb3DQEJFDFaHlgAQwBlAHIAdABSAGUAcQAtAGEAYgA5AGUAZgA3AGYAOAAtAGUAYwA2AGEA -LQA0ADUAMwA4AC0AOQA4AGQAOAAtADAAYQA4AGUAYwA4ADUAOQBkADkAMgAxMIID9wYJKoZIhvcN -AQcGoIID6DCCA+QCAQAwggPdBgkqhkiG9w0BBwEwHAYKKoZIhvcNAQwBBjAOBAiYL6rZmAGN9QIC -B9CAggOw8eaNgIqx26SlOBKKQZ5O7NDZQHbytHTWn4ifNhVFUkbuaj22/VnYOFB9//8BLY6t0Dvw -X6wqXSMnbr1jOuUYaFdJzOBZBsYQfFTfoJ4iOb2jwwIpZSgTHeqgXbvnI7MIxauu6F4UseWVxt3u -ZhHjEZQjKWeC5mNCb6wX0IOQk96n1RJnJ3v1D4Q5YrVekOVq70VhRNtLOZMkrJV7vDMNlUYXD69D -PbcyPajVvETq0W98YNKB89oNwFWuKoMLNPWmSwIfn10oSEtybNEEr2IVgCBt2w2eb+nIDuA3c/Rc -qKPXwVGMzoUyAiGwTcueCdMRmiuQAuKCUyi9P/JqeIbgHtg15nAoGtw+l4MsFXfdMJjTCDd0WYff -l9ipaNnw8erCPquD0+wXeMnNivXaFzu2+CGwCSwbDl2M49HAQdtpKhNj5jKJBEP1GRQIk173gbEZ -n69IXUCsf0GDZiZVNbAQHBOuRoEHpBhendFgTJFAU2LDHlmV6OA0LYHaSn7CP/vOXOhWXJ1yGL2p -SeUepciwQV7sOHqDExWY82fd1kHSHcgCAkWLSSdIPlWhyeqjC1agSP6b74VK8uLRPkin5F9wGIPi -ewe/LsW3PTtDkDnj3DiSioKlQRUUxVxzi5qPBs+7vJEhbuO7UhtsMCWeUygDbw6n8BKan4w9iLhx -7/z6zVvQmLnK4HZChTPFuThRy1NctupoX7nE+CDgyhcmryaTDXohkviMWl4Od+8uGh8Quv2bHk6Y -UnFqNB/hSqYMkTuMLH4F9sVzoQEsYu96CDwbQaggbLMnPtmHKsPtzdnWQnys+oGT4uD8vl9xFdEW -AZdestrxbDK0La0AgGszUE+6B/GtOs2pv0fMXXYV2h+dAlwfz7oLxzm9E+SFgzviL+6PuI9fDHNd -pWeq/Rr5OpFb3rSotGTl84aIjk3hPd3uHujPQh8GO2EQ5k6p6ukk+a7gOUB+pH8fHihFl5L7pI0z -yRp0FvbZo//hmACYMvINoy2EQxjYLh7QLeE4qEr8bkzJVgEURUvcUpyHFJT6PGzUMqGx/Wjh2jJc -HfEDPMUDoTE/QRzLW7XrmQgJIRuHgPI/cqmOyvpEvuwdRhYyHEKktRO3tGjeflohDCyDW9bxOaJV -ZP64KBordM28ZHCQbnSdU0I5us6qiFX2PiLlBzRMH2ftUNMYReioqZyR+Xv5wjaoydV3//BDMH8M -1lh9GazUO8+OtzQEH0jiBi6ctlzFT8nNI2C+cOB9S3yMAjCEQa8wNzAfMAcGBSsOAwIaBBR96vF2 -OksttXT1kXf+aez9EzDlsgQU4ck78h0WTy01zHLwSKNWK4wFFQM= -" - - $dataEnciphermentCert = $dataEnciphermentCert -replace '\s','' - $certBytes = [Convert]::FromBase64String($dataEnciphermentCert) - $certLocation = Join-Path $TestDrive "ProtectedEventLogging.pfx" - [IO.File]::WriteAllBytes($certLocation, $certBytes) - - return $certLocation -} - -Function Create-BadCertificate -{ - $codeSigningCert = " -MIIDAjCCAeqgAwIBAgIQW/oHcNaftoFGOYb4w5A0JTANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQD -DA5DTVNUZXN0QmFkQ2VydDAeFw0xNzAzMDcwNjEyMDNaFw0xODAzMDcwNjMyMDNaMBkxFzAVBgNV -BAMMDkNNU1Rlc3RCYWRDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAogOnCBPp -xCQXvCJOe4KUXrTL5hwzKSV/mA4pF/kWCVLseWkeTzll+02S0SzMR1oqyxGZOU+KiakmH8sIxDpS -pBpYi3R+JtaUYeK7IwvM7yMgxzYbVUFupNXDIdFcgd7FwX4R9wJGwd/hEw5fe+ua96G6bBlfQC8j -I8iHfqHZ2GmssIuSt72WhT6tKZhPJIMjwmKaB8j/gm6EC7eH83wNmVW/ss2AsG5cMT0Zmk2vkXPd -7rVYbAh9WcvxYzTYZLsPkXx/s6uanLo7pBPMqQ8fgImSXiD5EBO9d6SzqoagoAkH/l3oKCUztsqU -PAfTu1aTAYRW5O26AcICTbIYOMkDMQIDAQABo0YwRDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAww -CgYIKwYBBQUHAwMwHQYDVR0OBBYEFLSLHDqLWoDBj0j/UavIf0hAHZ2YMA0GCSqGSIb3DQEBCwUA -A4IBAQB7GJ3ykI07k2D1mfiQ10+Xse4b6KXylbzYfJ1k3K0NEBwT7H/lhMu4yz95A7pXU41yKKVE -OzmpX8QGyczRM269+WpUvVOXwudQL7s/JFeZyEhxPYRP0JC8U9rur1iJeULvsPZJU2kGeLceTl7k -psuZeHouYeNuFeeKR66GcHKzqm+5odAJBxjQ/iGP+CVfNVX56Abhu8mXX6sFiorrBSV/NzPThqja -mtsMC3Fq53xANMjFT4kUqMtK+oehPf0+0jHHra4hpCVZ2KoPLLPxpJPko8hUO5LxATLU+UII7w3c -nMbw+XY4C8xdDnHfS6mF+Hol98dURB/MC/x3sZ3gSjKo -" - - $codeSigningCert = $codeSigningCert -replace '\s','' - $certBytes = [Convert]::FromBase64String($codeSigningCert) - $certLocation = Join-Path $TestDrive "CMSTestBadCert" - [IO.File]::WriteAllBytes($certLocation, $certBytes) - - return $certLocation -} - +Import-Module (Join-Path -Path $PSScriptRoot 'certificateCommon.psm1') -Force Describe "CmsMessage cmdlets and Get-PfxCertificate basic tests" -Tags "CI" { @@ -148,62 +61,23 @@ Describe "CmsMessage cmdlets and Get-PfxCertificate basic tests" -Tags "CI" { Describe "CmsMessage cmdlets thorough tests" -Tags "Feature" { - BeforeAll { - if ($IsWindows) + BeforeAll{ + if($IsWindows) { - $certLocation = Create-GoodCertificate - $certLocation | Should Not BeNullOrEmpty | Out-Null - - $badCertLocation = Create-BadCertificate - $badCertLocation | Should Not BeNullOrEmpty | Out-Null - - if ($IsCoreCLR) - { - # PKI module is not available for PowerShell Core, so we need to use Windows PowerShell to import the cert - $fullPowerShell = Join-Path "$env:SystemRoot" "System32\WindowsPowerShell\v1.0\powershell.exe" - - try { - $modulePathCopy = $env:PSModulePath - $env:PSModulePath = $null - - $command = @" -Import-PfxCertificate $certLocation -CertStoreLocation cert:\CurrentUser\My | % PSPath -Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % PSPath -"@ - $certPaths = & $fullPowerShell -NoProfile -NonInteractive -Command $command - $certPaths.Count | Should Be 2 | Out-Null - - $importedCert = Get-ChildItem $certPaths[0] - $testBadCert = Get-ChildItem $certPaths[1] - } finally { - $env:PSModulePath = $modulePathCopy - } - } - else - { - $importedCert = Import-PfxCertificate $certLocation -CertStoreLocation cert:\CurrentUser\My - $testBadCert = Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My - } + Install-TestCertificates } else { # Skip for non-Windows platforms $defaultParamValues = $PSdefaultParameterValues.Clone() $PSdefaultParameterValues = @{ "it:skip" = $true } - } + } } - + AfterAll { - if ($IsWindows) + if($IsWindows) { - if ($importedCert) - { - Remove-Item (Join-Path Cert:\CurrentUser\My $importedCert.Thumbprint) -Force -ErrorAction SilentlyContinue - } - if ($testBadCert) - { - Remove-Item (Join-Path Cert:\CurrentUser\My $testBadCert.Thumbprint) -Force -ErrorAction SilentlyContinue - } + Remove-TestCertificates } else { @@ -246,7 +120,7 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % It "Verify wildcarded recipient resolution by path [Decryption]" { $errors = $null - $recipient = [System.Management.Automation.CmsMessageRecipient] ($certLocation + "*") + $recipient = [System.Management.Automation.CmsMessageRecipient] ((Get-GoodCertificateLocation) + "*") $recipient.Resolve($ExecutionContext.SessionState, "Decryption", [ref] $errors) # Should have resolved single cert @@ -255,7 +129,7 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % It "Verify wildcarded recipient resolution by path [Encryption]" { $errors = $null - $recipient = [System.Management.Automation.CmsMessageRecipient] ($certLocation + "*") + $recipient = [System.Management.Automation.CmsMessageRecipient] ((Get-GoodCertificateLocation) + "*") $recipient.Resolve($ExecutionContext.SessionState, "Encryption", [ref] $errors) $recipient.Certificates.Count | Should Be 1 @@ -264,9 +138,9 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % It "Verify resolution by directory" { $protectedEventLoggingCertPath = Join-Path $TestDrive ProtectedEventLoggingDir $null = New-Item -ItemType Directory $protectedEventLoggingCertPath -Force - Copy-Item $certLocation $protectedEventLoggingCertPath - Copy-Item $certLocation (Join-Path $protectedEventLoggingCertPath "SecondCert.pfx") - Copy-Item $certLocation (Join-Path $protectedEventLoggingCertPath "ThirdCert.pfx") + Copy-Item (Get-GoodCertificateLocation) $protectedEventLoggingCertPath + Copy-Item (Get-GoodCertificateLocation) (Join-Path $protectedEventLoggingCertPath "SecondCert.pfx") + Copy-Item (Get-GoodCertificateLocation) (Join-Path $protectedEventLoggingCertPath "ThirdCert.pfx") $errors = $null $recipient = [System.Management.Automation.CmsMessageRecipient] $protectedEventLoggingCertPath @@ -277,21 +151,21 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % It "Verify resolution by thumbprint" { $errors = $null - $recipient = [System.Management.Automation.CmsMessageRecipient] $importedCert.Thumbprint + $recipient = [System.Management.Automation.CmsMessageRecipient] (Get-GoodCertificateObject).Thumbprint $recipient.Resolve($ExecutionContext.SessionState, "Decryption", [ref] $errors) # "Should have certs from thumbprint in 'My' store" $recipient.Certificates.Count | Should Be 1 - $recipient.Certificates[0].Thumbprint | Should Be $importedCert.Thumbprint + $recipient.Certificates[0].Thumbprint | Should Be (Get-GoodCertificateObject).Thumbprint } It "Verify resolution by subject name" { $errors = $null - $recipient = [System.Management.Automation.CmsMessageRecipient] $importedCert.Subject + $recipient = [System.Management.Automation.CmsMessageRecipient] (Get-GoodCertificateObject).Subject $recipient.Resolve($ExecutionContext.SessionState, "Decryption", [ref] $errors) $recipient.Certificates.Count | Should Be 1 - $recipient.Certificates[0].Thumbprint | Should Be $importedCert.Thumbprint + $recipient.Certificates[0].Thumbprint | Should Be (Get-GoodCertificateObject).Thumbprint } It "Verify error when no cert found in encryption for encryption" { @@ -314,7 +188,7 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % It "Verify error when encrypting to wrong cert" { $errors = $null - $recipient = [System.Management.Automation.CmsMessageRecipient] $testBadCert.Thumbprint + $recipient = [System.Management.Automation.CmsMessageRecipient] (Get-BadCertificateObject).Thumbprint $recipient.Resolve($ExecutionContext.SessionState, "Encryption", [ref] $errors) $errors.Count | Should Be 1 @@ -346,17 +220,17 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % $encryptedPath = $tempPath + ".encrypted.txt" "Hello World","How are you?" | Set-Content $tempPath - Protect-CmsMessage -Path $tempPath -To $certLocation -OutFile $encryptedPath + Protect-CmsMessage -Path $tempPath -To (Get-GoodCertificateLocation) -OutFile $encryptedPath $message = Get-CmsMessage -LiteralPath $encryptedPath $message.Recipients.Count | Should Be 1 $message.Recipients[0].IssuerName | Should Be "CN=MyDataEnciphermentCert" $expected = "Hello World" + [System.Environment]::NewLine + "How are you?" + [System.Environment]::NewLine - $decrypted = $message | Unprotect-CmsMessage -To $certLocation + $decrypted = $message | Unprotect-CmsMessage -To (Get-GoodCertificateLocation) $decrypted | Should Be $expected - $decrypted = Unprotect-CmsMessage -Path $encryptedPath -To $certLocation + $decrypted = Unprotect-CmsMessage -Path $encryptedPath -To (Get-GoodCertificateLocation) $decrypted | Should Be $expected } finally { Remove-Item $tempPath, $encryptedPath -Force -ErrorAction SilentlyContinue @@ -367,7 +241,7 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % try { $randomNum = Get-Random -Minimum 1000 -Maximum 9999 $tempPath = Join-Path $TestDrive "$randomNum-Path-Test-File" - "Hello World" | Protect-CmsMessage -To $certLocation -OutFile $tempPath + "Hello World" | Protect-CmsMessage -To (Get-GoodCertificateLocation) -OutFile $tempPath # Decrypt using $importedCert in the Cert store $decrypted = Unprotect-CmsMessage -Path $tempPath @@ -411,11 +285,11 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % } It "Verify Unprotect-CmsMessage lets you include context" { - $protected = "Hello World" | Protect-CmsMessage -To $certLocation + $protected = "Hello World" | Protect-CmsMessage -To (Get-GoodCertificateLocation) $adjustedProtected = "Pre content" + [System.Environment]::NewLine + $protected + [System.Environment]::NewLine + "Post content" - $decryptedNoContext = $adjustedProtected | Unprotect-CmsMessage -To $certLocation - $decryptedWithContext = $adjustedProtected | Unprotect-CmsMessage -To $certLocation -IncludeContext + $decryptedNoContext = $adjustedProtected | Unprotect-CmsMessage -To (Get-GoodCertificateLocation) + $decryptedWithContext = $adjustedProtected | Unprotect-CmsMessage -To (Get-GoodCertificateLocation) -IncludeContext $decryptedNoContext | Should Be "Hello World" @@ -424,16 +298,16 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % } It "Verify Unprotect-CmsMessage treats event logs as a first class citizen" { - $protected = "Encrypted Message1","Encrypted Message2" | Protect-CmsMessage -To $certLocation + $protected = "Encrypted Message1","Encrypted Message2" | Protect-CmsMessage -To (Get-GoodCertificateLocation) $virtualEventLog = Get-WinEvent Microsoft-Windows-PowerShell/Operational -MaxEvents 1 $savedId = $virtualEventLog.Id $virtualEventLog.Message = $protected $expected = "Encrypted Message1" + [System.Environment]::NewLine + "Encrypted Message2" - $decrypted = $virtualEventLog | Unprotect-CmsMessage -To $certLocation + $decrypted = $virtualEventLog | Unprotect-CmsMessage -To (Get-GoodCertificateLocation) $decrypted | Should Be $expected - $processed = $virtualEventLog | Unprotect-CmsMessage -To $certLocation -IncludeContext + $processed = $virtualEventLog | Unprotect-CmsMessage -To (Get-GoodCertificateLocation) -IncludeContext $processed.Id | Should Be $savedId $processed.Message | Should Be $expected } @@ -451,8 +325,8 @@ Import-Certificate $badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % } It "Verify protect message using OutString" { - $protected = Get-Process -Id $pid | Protect-CmsMessage -To $certLocation - $decrypted = $protected | Unprotect-CmsMessage -To $certLocation + $protected = Get-Process -Id $pid | Protect-CmsMessage -To (Get-GoodCertificateLocation) + $decrypted = $protected | Unprotect-CmsMessage -To (Get-GoodCertificateLocation) # Should have had PID in output $decrypted | Should Match $pid } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 new file mode 100644 index 00000000000..4ffff7cb8e6 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 @@ -0,0 +1,150 @@ +Function Create-GoodCertificate +{ + $dataEnciphermentCert = " +MIIKYAIBAzCCCiAGCSqGSIb3DQEHAaCCChEEggoNMIIKCTCCBgoGCSqGSIb3DQEHAaCCBfsEggX3 +MIIF8zCCBe8GCyqGSIb3DQEMCgECoIIE/jCCBPowHAYKKoZIhvcNAQwBAzAOBAgPOFDMBkCffQIC +B9AEggTYjY55RrmAhdj1grENxXjiPrVNdS++pb5UOn3M7O78BR0U1i2h5zvjkPjOwdLoOCbq5pgg +F0PKaMjHVu8EoZxSqsib17ptR5Rx5N23hseuJUzS8fTAHiBet9payNOJlPfkpuqMfQEmCAo9gAPz +w4RiyZNOA3NhxkfGl9yU4O9GSEr2koWKCUoCNelkIXVbkV728L7zSiWRSqRb7V4QJAtwtgPLTbl/ +zo2SFhdNAGPeXbcOsKCv9trhuxPZ0FH4fukbXkHs0I3b5mYgMUI5Mds7UwT3wCtz+Ev9pLbmYN8X +NfH0tAK8ZGnQS1GcI4xCMEM8T9Tx3uwWY4arvRM3GTLwyt8JZEVZNTuYL9A9+RyeiO5d5xEKG8H4 +snOCoTriT45tdl8hzMBCdc3jWxWiydmNRw47irifv5BX7BK/6FLAxkMRwACAxNO63ezG/OxuDDEz +ml+KzeZNvr4u4mTBcgZ49vMSyfRt/my+5+iLnSMGp4Rt0uix8489wctkxGlgyXGk23pA4Cj4hq/6 +txopcl2gHn+DAFrHIgLg4JR8lcuOzBw8nFOrhK7iR9aMK21apxwImIaDCKJ1grOfbuElq4pkMpov +SltJe00WB94o69LibOg5LqpTrHW2/DY951sIgElF83FdUBhoZHasfCme/RxgliQf3QHedmENXSjr +8R8PAWX4wC0ZZVC0qcq5XP0PkwtuDKmfqq69R32nmBzpRrfypm9S+PfsYZeCeuROh6YKQ0ZBMnLb +8Y9povpXh0lYwVLuPanvAFiCT4vI7oRd1Mg1Zr9ZooMFAVomall1UnQQ29fvsoADjEDcPymVT80o +kTkw3NXnTX6fGZ94Eh0KZcuMgjTqIO3OKpH0lcaSBxlES4V0sO/mwP4ULy8l3dcnn440Nei33VDo +B7n2jhjJl/HvtltfEEEw1DW9AWDvkJDp878sD6VoyQZehvvxBNT0FwMr20TbVKeAGxf9n+xJ9Alo +VUS3qE7XnpxAAAR5L2OG+tMd4dyDSge1qrkVNZ3/uUzKKCZei2P7ICR9cX1FRsmcINdNfydIA2cA +Pmeq+UkRdqsJxt1NSK5bLvMM4EHRQZMMbXVKxxJ+kQDrzfQtERFPyd3Hdm2F4T/JXUQ+PrTQnRqY +LruTAiZfxygZuFrxJnNTRydRdbEaTAtMjFCMRFZ2wctJsgCb3yN9tt0JDYxIvm0MSehXiF+sCrl4 +yZvvUzJqrgppHRTBR4Sao+MZ/rJ30vVU19Q3oBi9ikTqDY+4SrHsp5Y4llnsbrz0Web+h2jLvyJz +LgKuqs87qHhToVMLuULy/HLqY3m661EMwNqh5D76gSFI+TP2/rzT5mVOGglahFoc848o4cshtPWE +9MjAkDfsMbIfeKH3uh3D+eBIxYmZ5Cq2aHzqdQ0pU/nDNX7BDjC3E80VcQnXx4U6tRsQHsGtbcld +MFTp0yHJ2KLkz+inH3WPy/lYuVZ0QJe+LqvGt+bt1DgQmLBMD9WLFML3d0TtkuY3RhD5Y0wr2zt9 +tT6WVTn8Hob1cJns4N7tDEr8Q3TdIar0I5Bzj3qoesJt+4lxwnVdUA1bNJ2zxXIkDfX/MTB464FI +2g9uhUs3lIOEiCjeJCwBebgZa1HlfhyCRu0E7fnNnKLaGWRs8LVy7MZIfe1kJoDVgTGB3TATBgkq +hkiG9w0BCRUxBgQEAQAAADBdBgkrBgEEAYI3EQExUB5OAE0AaQBjAHIAbwBzAG8AZgB0ACAAUwB0 +AHIAbwBuAGcAIABDAHIAeQBwAHQAbwBnAHIAYQBwAGgAaQBjACAAUAByAG8AdgBpAGQAZQByMGcG +CSqGSIb3DQEJFDFaHlgAQwBlAHIAdABSAGUAcQAtAGEAYgA5AGUAZgA3AGYAOAAtAGUAYwA2AGEA +LQA0ADUAMwA4AC0AOQA4AGQAOAAtADAAYQA4AGUAYwA4ADUAOQBkADkAMgAxMIID9wYJKoZIhvcN +AQcGoIID6DCCA+QCAQAwggPdBgkqhkiG9w0BBwEwHAYKKoZIhvcNAQwBBjAOBAiYL6rZmAGN9QIC +B9CAggOw8eaNgIqx26SlOBKKQZ5O7NDZQHbytHTWn4ifNhVFUkbuaj22/VnYOFB9//8BLY6t0Dvw +X6wqXSMnbr1jOuUYaFdJzOBZBsYQfFTfoJ4iOb2jwwIpZSgTHeqgXbvnI7MIxauu6F4UseWVxt3u +ZhHjEZQjKWeC5mNCb6wX0IOQk96n1RJnJ3v1D4Q5YrVekOVq70VhRNtLOZMkrJV7vDMNlUYXD69D +PbcyPajVvETq0W98YNKB89oNwFWuKoMLNPWmSwIfn10oSEtybNEEr2IVgCBt2w2eb+nIDuA3c/Rc +qKPXwVGMzoUyAiGwTcueCdMRmiuQAuKCUyi9P/JqeIbgHtg15nAoGtw+l4MsFXfdMJjTCDd0WYff +l9ipaNnw8erCPquD0+wXeMnNivXaFzu2+CGwCSwbDl2M49HAQdtpKhNj5jKJBEP1GRQIk173gbEZ +n69IXUCsf0GDZiZVNbAQHBOuRoEHpBhendFgTJFAU2LDHlmV6OA0LYHaSn7CP/vOXOhWXJ1yGL2p +SeUepciwQV7sOHqDExWY82fd1kHSHcgCAkWLSSdIPlWhyeqjC1agSP6b74VK8uLRPkin5F9wGIPi +ewe/LsW3PTtDkDnj3DiSioKlQRUUxVxzi5qPBs+7vJEhbuO7UhtsMCWeUygDbw6n8BKan4w9iLhx +7/z6zVvQmLnK4HZChTPFuThRy1NctupoX7nE+CDgyhcmryaTDXohkviMWl4Od+8uGh8Quv2bHk6Y +UnFqNB/hSqYMkTuMLH4F9sVzoQEsYu96CDwbQaggbLMnPtmHKsPtzdnWQnys+oGT4uD8vl9xFdEW +AZdestrxbDK0La0AgGszUE+6B/GtOs2pv0fMXXYV2h+dAlwfz7oLxzm9E+SFgzviL+6PuI9fDHNd +pWeq/Rr5OpFb3rSotGTl84aIjk3hPd3uHujPQh8GO2EQ5k6p6ukk+a7gOUB+pH8fHihFl5L7pI0z +yRp0FvbZo//hmACYMvINoy2EQxjYLh7QLeE4qEr8bkzJVgEURUvcUpyHFJT6PGzUMqGx/Wjh2jJc +HfEDPMUDoTE/QRzLW7XrmQgJIRuHgPI/cqmOyvpEvuwdRhYyHEKktRO3tGjeflohDCyDW9bxOaJV +ZP64KBordM28ZHCQbnSdU0I5us6qiFX2PiLlBzRMH2ftUNMYReioqZyR+Xv5wjaoydV3//BDMH8M +1lh9GazUO8+OtzQEH0jiBi6ctlzFT8nNI2C+cOB9S3yMAjCEQa8wNzAfMAcGBSsOAwIaBBR96vF2 +OksttXT1kXf+aez9EzDlsgQU4ck78h0WTy01zHLwSKNWK4wFFQM= +" + + $dataEnciphermentCert = $dataEnciphermentCert -replace '\s','' + $certBytes = [Convert]::FromBase64String($dataEnciphermentCert) + $certLocation = Join-Path $TestDrive "ProtectedEventLogging.pfx" + [IO.File]::WriteAllBytes($certLocation, $certBytes) + + return $certLocation +} + +Function Create-BadCertificate +{ + $codeSigningCert = " +MIIDAjCCAeqgAwIBAgIQW/oHcNaftoFGOYb4w5A0JTANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQD +DA5DTVNUZXN0QmFkQ2VydDAeFw0xNzAzMDcwNjEyMDNaFw0xODAzMDcwNjMyMDNaMBkxFzAVBgNV +BAMMDkNNU1Rlc3RCYWRDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAogOnCBPp +xCQXvCJOe4KUXrTL5hwzKSV/mA4pF/kWCVLseWkeTzll+02S0SzMR1oqyxGZOU+KiakmH8sIxDpS +pBpYi3R+JtaUYeK7IwvM7yMgxzYbVUFupNXDIdFcgd7FwX4R9wJGwd/hEw5fe+ua96G6bBlfQC8j +I8iHfqHZ2GmssIuSt72WhT6tKZhPJIMjwmKaB8j/gm6EC7eH83wNmVW/ss2AsG5cMT0Zmk2vkXPd +7rVYbAh9WcvxYzTYZLsPkXx/s6uanLo7pBPMqQ8fgImSXiD5EBO9d6SzqoagoAkH/l3oKCUztsqU +PAfTu1aTAYRW5O26AcICTbIYOMkDMQIDAQABo0YwRDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAww +CgYIKwYBBQUHAwMwHQYDVR0OBBYEFLSLHDqLWoDBj0j/UavIf0hAHZ2YMA0GCSqGSIb3DQEBCwUA +A4IBAQB7GJ3ykI07k2D1mfiQ10+Xse4b6KXylbzYfJ1k3K0NEBwT7H/lhMu4yz95A7pXU41yKKVE +OzmpX8QGyczRM269+WpUvVOXwudQL7s/JFeZyEhxPYRP0JC8U9rur1iJeULvsPZJU2kGeLceTl7k +psuZeHouYeNuFeeKR66GcHKzqm+5odAJBxjQ/iGP+CVfNVX56Abhu8mXX6sFiorrBSV/NzPThqja +mtsMC3Fq53xANMjFT4kUqMtK+oehPf0+0jHHra4hpCVZ2KoPLLPxpJPko8hUO5LxATLU+UII7w3c +nMbw+XY4C8xdDnHfS6mF+Hol98dURB/MC/x3sZ3gSjKo +" + + $codeSigningCert = $codeSigningCert -replace '\s','' + $certBytes = [Convert]::FromBase64String($codeSigningCert) + $certLocation = Join-Path $TestDrive "CMSTestBadCert" + [IO.File]::WriteAllBytes($certLocation, $certBytes) + + return $certLocation +} + +function Install-TestCertificates +{ + $script:certLocation = Create-GoodCertificate + $script:certLocation | Should Not BeNullOrEmpty | Out-Null + + $script:badCertLocation = Create-BadCertificate + $script:badCertLocation | Should Not BeNullOrEmpty | Out-Null + + if ($IsCoreCLR) + { + # PKI module is not available for PowerShell Core, so we need to use Windows PowerShell to import the cert + $fullPowerShell = Join-Path "$env:SystemRoot" "System32\WindowsPowerShell\v1.0\powershell.exe" + + try { + $modulePathCopy = $env:PSModulePath + $env:PSModulePath = $null + + $command = @" +Import-PfxCertificate $script:certLocation -CertStoreLocation cert:\CurrentUser\My | % PSPath +Import-Certificate $script:badCertLocation -CertStoreLocation Cert:\CurrentUser\My | % 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] + } finally { + $env:PSModulePath = $modulePathCopy + } + } + else + { + $script:importedCert = Import-PfxCertificate $script:certLocation -CertStoreLocation cert:\CurrentUser\My + $script:testBadCert = Import-Certificate $script:badCertLocation -CertStoreLocation Cert:\CurrentUser\My + } +} + +function Get-GoodCertificateLocation +{ + return $script:certLocation +} + +function Get-GoodCertificateObject +{ + return $script:importedCert +} + +function Get-BadCertificateObject +{ + return $script:testBadCert +} + +function Remove-TestCertificates +{ + if ($script:importedCert) + { + Remove-Item (Join-Path Cert:\CurrentUser\My $script:importedCert.Thumbprint) -Force -ErrorAction SilentlyContinue + } + if ($script:testBadCert) + { + Remove-Item (Join-Path Cert:\CurrentUser\My $script:testBadCert.Thumbprint) -Force -ErrorAction SilentlyContinue + } +} \ No newline at end of file From 9d78f523f1a1f55e574c6ec89ec39c51e3155f62 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 19 May 2017 14:38:59 -0700 Subject: [PATCH 04/10] Add tests for certificate provider --- .../CertificateProvider.Tests.ps1 | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 new file mode 100644 index 00000000000..a5bb84983ed --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 @@ -0,0 +1,139 @@ +Import-Module (Join-Path -Path $PSScriptRoot 'certificateCommon.psm1') -Force +Import-Module $PSScriptRoot\..\..\Common\Test.Helpers.psm1 -Force + +$currentUserMyLocations = @( + @{path = 'Cert:\CurrentUser\my'} + @{path = 'cert:\currentuser\my'} + @{path = 'Microsoft.PowerShell.Security\Certificate::CurrentUser\My'} + @{path = 'Microsoft.PowerShell.Security\certificate::currentuser\my'} +) + +$testLocations = @( + @{path = 'cert:\'} + @{path = 'CERT:\'} + @{path = 'Microsoft.PowerShell.Security\Certificate::'} +) + +# Add CurrentUserMyLocations to TestLocations +foreach($location in $currentUserMyLocations) +{ + $testLocations += $location +} + +Describe "Certificate Provider tests" -Tags "CI" { + BeforeAll{ + if(!$IsWindows) + { + # Skip for non-Windows platforms + $defaultParamValues = $PSdefaultParameterValues.Clone() + $PSdefaultParameterValues = @{ "it:skip" = $true } + } + } + + AfterAll { + if(!$IsWindows) + { + $PSdefaultParameterValues = $defaultParamValues + } + } + + Context "Get-Item tests" { + it "Should be able to get a certificate store, path: " -TestCases $testLocations { + param([string] $path) + $expectedResolvedPath = Resolve-Path -LiteralPath $path + $result = Get-Item -LiteralPath $path + $result | should not be null + $result | ForEach-Object { + $resolvedPath = Resolve-Path $_.PSPath + $resolvedPath.Provider | should be $expectedResolvedPath.Provider + $resolvedPath.ProviderPath.TrimStart('\') | should be $expectedResolvedPath.ProviderPath.TrimStart('\') + } + } + it "Should return two items at the root of the provider" { + (Get-Item -Path cert:\*).Count | should be 2 + } + it "Should be able to get multiple items explictly" { + (get-item cert:\LocalMachine , cert:\CurrentUser).Count | should be 2 + } + it "Should return PathNotFound when getting a non-existant certificate store" { + {Get-Item cert:\IDONTEXIST -ErrorAction Stop} | ShouldBeErrorId "PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand" + } + it "Should return PathNotFound when getting a non-existant certificate" { + {Get-Item cert:\currentuser\my\IDONTEXIST -ErrorAction Stop} | ShouldBeErrorId "PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand" + } + } + Context "Get-ChildItem tests"{ + it "should be able to get a container using a wildcard" { + (Get-ChildItem Cert:\CurrentUser\M?).PSPath | should be 'Microsoft.PowerShell.Security\Certificate::CurrentUser\My' + } + it "Should return two items at the root of the provider" { + (Get-ChildItem -Path cert:\).Count | should be 2 + } + } +} + +Describe "Certificate Provider tests" -Tags "Feature" { + BeforeAll{ + if($IsWindows) + { + Install-TestCertificates + Push-Location Cert:\ + } + else + { + # Skip for non-Windows platforms + $defaultParamValues = $PSdefaultParameterValues.Clone() + $PSdefaultParameterValues = @{ "it:skip" = $true } + } + } + + AfterAll { + if($IsWindows) + { + Remove-TestCertificates + Pop-Location + } + else + { + $PSdefaultParameterValues = $defaultParamValues + } + } + + Context "Get-Item tests" { + it "Should be able to get certifate by path: " -TestCases $currentUserMyLocations { + param([string] $path) + $expectedThumbprint = (Get-GoodCertificateObject).Thumbprint + $leafPath = Join-Path -Path $path -ChildPath $expectedThumbprint + $cert = (Get-item -LiteralPath $leafPath) + $cert | should not be null + $cert.Thumbprint | should be $expectedThumbprint + } + it "Should filter to codesign certificates" { + $allCerts = get-item cert:\CurrentUser\My\* + $codeSignCerts = get-item 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 be able to exclude by thumbprint" { + $allCerts = get-item cert:\CurrentUser\My\* + $testThumbprint = (Get-GoodCertificateObject).Thumbprint + $allCertsExceptOne = (Get-Item "cert:\currentuser\my\*" -Exclude $testThumbprint) + $allCerts | should not be null + $allCertsExceptOne | should not be null + $countDifference = $allCerts.Count - $allCertsExceptOne.Count + $countDifference | should be 1 + } + } + Context "Get-ChildItem tests"{ + 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 + } + } +} \ No newline at end of file From 813b21020b51a5a0df300860f130f598fe34bee0 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 19 May 2017 14:56:51 -0700 Subject: [PATCH 05/10] Rename test helper functions to user approved verbs --- .../Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 | 2 +- .../Microsoft.PowerShell.Security/certificateCommon.psm1 | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 index edf97107ee7..e2fb9ddb803 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 @@ -3,7 +3,7 @@ Import-Module (Join-Path -Path $PSScriptRoot 'certificateCommon.psm1') -Force Describe "CmsMessage cmdlets and Get-PfxCertificate basic tests" -Tags "CI" { BeforeAll { - $certLocation = Create-GoodCertificate + $certLocation = New-GoodCertificate $certLocation | Should Not BeNullOrEmpty | Out-Null } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 index 4ffff7cb8e6..391d9144ec8 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 @@ -1,4 +1,4 @@ -Function Create-GoodCertificate +Function New-GoodCertificate { $dataEnciphermentCert = " MIIKYAIBAzCCCiAGCSqGSIb3DQEHAaCCChEEggoNMIIKCTCCBgoGCSqGSIb3DQEHAaCCBfsEggX3 @@ -58,7 +58,7 @@ OksttXT1kXf+aez9EzDlsgQU4ck78h0WTy01zHLwSKNWK4wFFQM= return $certLocation } -Function Create-BadCertificate +Function New-BadCertificate { $codeSigningCert = " MIIDAjCCAeqgAwIBAgIQW/oHcNaftoFGOYb4w5A0JTANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQD @@ -87,10 +87,10 @@ nMbw+XY4C8xdDnHfS6mF+Hol98dURB/MC/x3sZ3gSjKo function Install-TestCertificates { - $script:certLocation = Create-GoodCertificate + $script:certLocation = New-GoodCertificate $script:certLocation | Should Not BeNullOrEmpty | Out-Null - $script:badCertLocation = Create-BadCertificate + $script:badCertLocation = New-BadCertificate $script:badCertLocation | Should Not BeNullOrEmpty | Out-Null if ($IsCoreCLR) From 3b06616df0de03a6100928c1c5de3d2474ef71aa Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Fri, 19 May 2017 15:27:06 -0700 Subject: [PATCH 06/10] Removed import of helper module since it is auto-loaded. --- .../Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 index a5bb84983ed..8ff73dd3c28 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 @@ -1,5 +1,4 @@ Import-Module (Join-Path -Path $PSScriptRoot 'certificateCommon.psm1') -Force -Import-Module $PSScriptRoot\..\..\Common\Test.Helpers.psm1 -Force $currentUserMyLocations = @( @{path = 'Cert:\CurrentUser\my'} From fd691dd0c9f87f240ec6436741d284cd2c545803 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 22 May 2017 12:40:19 -0700 Subject: [PATCH 07/10] Remove dead code Compare OIDs by value instead of name Remove unneeded length comparison when comparing string prefixes --- .../security/CertificateProvider.cs | 165 +++--------------- 1 file changed, 20 insertions(+), 145 deletions(-) diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs index b06c0349fbb..b8bcee8bd4b 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -155,134 +155,6 @@ public override string ToString() } } - /// - /// 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. - /// - /// For Win 8 and later, we also support: - /// SSLServerAuthentication - /// If provided, only return certificates valid for server authentication. - /// - /// DnsName - /// If provided, only return certificates matching the supplied DNS Name. - /// - /// Eku - /// If provided, only return certificates containing all of the OIDs - /// supplied. - /// - /// ExpiringInDays - /// If provided, only return certificates expiring within the specified - /// number of days. - /// - /// - - internal sealed class CertificateProviderDynamicParameters - { - /// - /// switch that controls whether we only return - /// code signing certs. - /// - [Parameter()] - public SwitchParameter CodeSigningCert - { - get { return _codeSigningCert; } - - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method is used by command line processing")] - set - { _codeSigningCert = value; } - } - private SwitchParameter _codeSigningCert = new SwitchParameter(); - - /// - /// switch that controls whether we only return - /// data encipherment certs. - /// - [Parameter()] - public SwitchParameter DocumentEncryptionCert - { - get { return _documentEncryptionCert; } - - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method is used by command line processing")] - set - { _documentEncryptionCert = value; } - } - private SwitchParameter _documentEncryptionCert = new SwitchParameter(); - - /// - /// switch that controls whether we only return - /// code signing certs. - /// - [Parameter()] - public SwitchParameter SSLServerAuthentication - { - get { return _sslServerAuthentication; } - - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method is used by command line processing")] - set - { _sslServerAuthentication = value; } - } - - private SwitchParameter _sslServerAuthentication = new SwitchParameter(); - - /// - /// string to filter certs by DNSName - /// Expected content is a single DNS Name that may start and/or end - /// with '*': "contoso.com" or "*toso.c*" - /// - [Parameter()] - public DnsNameRepresentation DnsName - { - get { return _dnsName; } - - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method is used by command line processing")] - set - { _dnsName = value; } - } - - private DnsNameRepresentation _dnsName; - - /// - /// string to filter certs by EKU - /// Expected content is one or more OID strings: - /// "1.3.6.1.5.5.7.3.1", etc. - /// For a cert to match, it must be valid for all listed OIDs. - /// - [Parameter()] - public string[] Eku - { - get { return _eku; } - - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method is used by command line processing")] - set - { _eku = value; } - } - - private string[] _eku = null; - - /// - /// string to filter certs 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 midnight tonight (local time). - /// - [Parameter()] - public int ExpiringInDays - { - get { return _expiringInDays; } - - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method is used by command line processing")] - set - { _expiringInDays = value; } - } - - private int _expiringInDays = -1; - } - /// /// Defines the Certificate Provider remove-item dynamic parameters. /// @@ -442,8 +314,7 @@ public void Open(bool includeArchivedCerts) } } - public IntPtr GetFirstCert( - CertificateFilterInfo filter) + public IntPtr GetFirstCert() { return GetNextCert(IntPtr.Zero); } @@ -1171,7 +1042,7 @@ protected override bool HasChildItems(string path) if (store != null) { store.Open(IncludeArchivedCerts()); - IntPtr certContext = store.GetFirstCert(null); + IntPtr certContext = store.GetFirstCert(); if (IntPtr.Zero != certContext) { store.FreeCert(certContext); @@ -1882,7 +1753,7 @@ private void RemoveCertStore(string storeName, bool fDeleteKey, string sourcePat // // enumerate over each cert and remove it // - IntPtr certContext = store.GetFirstCert(null); + IntPtr certContext = store.GetFirstCert(); while (IntPtr.Zero != certContext) { X509Certificate2 cert = new X509Certificate2(certContext); @@ -2628,7 +2499,7 @@ private void GetCertificatesOrNames(string path, // // enumerate over each cert and return it (or its name) // - IntPtr certContext = store.GetFirstCert(filter); + IntPtr certContext = store.GetFirstCert(); while (IntPtr.Zero != certContext) { @@ -3405,14 +3276,18 @@ public EnhancedKeyUsageProperty(X509Certificate2 cert) { foreach (X509Extension extension in cert.Extensions) { - if (extension.Oid.FriendlyName == "Enhanced Key Usage") + // Filter to the OID for EKU + if (extension.Oid.Value == "2.5.29.37") { - X509EnhancedKeyUsageExtension ext = (X509EnhancedKeyUsageExtension)extension; - OidCollection oids = ext.EnhancedKeyUsages; - foreach (Oid oid in oids) + X509EnhancedKeyUsageExtension ext = extension as X509EnhancedKeyUsageExtension; + if(ext != null) { - EnhancedKeyUsageRepresentation ekuString = new EnhancedKeyUsageRepresentation(oid.FriendlyName, oid.Value); - _ekuList.Add(ekuString); + OidCollection oids = ext.EnhancedKeyUsages; + foreach (Oid oid in oids) + { + EnhancedKeyUsageRepresentation ekuString = new EnhancedKeyUsageRepresentation(oid.FriendlyName, oid.Value); + _ekuList.Add(ekuString); + } } } } @@ -3427,8 +3302,8 @@ public sealed class DnsNameProperty { private List _dnsList = new List(); private System.Globalization.IdnMapping idnMapping = new System.Globalization.IdnMapping(); - private static string dnsNamePrefix = "DNS Name="; - private static string distinguishedNamePrefix = "CN="; + private const string dnsNamePrefix = "DNS Name="; + private const string distinguishedNamePrefix = "CN="; /// /// get property of DnsNameList @@ -3454,8 +3329,7 @@ public DnsNameProperty(X509Certificate2 cert) // extract DNS name from subject distinguish name // if it exists and does not contain a comma // a comma, indicates it is not a DNS name - if(cert.Subject.Length > distinguishedNamePrefix.Length && - cert.Subject.StartsWith(distinguishedNamePrefix, System.StringComparison.InvariantCulture) && + if(cert.Subject.StartsWith(distinguishedNamePrefix, System.StringComparison.InvariantCultureIgnoreCase) && cert.Subject.IndexOf(",",System.StringComparison.InvariantCulture)==-1) { name = cert.Subject.Substring(distinguishedNamePrefix.Length); @@ -3475,13 +3349,14 @@ public DnsNameProperty(X509Certificate2 cert) foreach (X509Extension extension in cert.Extensions) { - if (extension.Oid.FriendlyName == "Subject Alternative Name") + // Filter to the OID for Subject Alternative Name + if (extension.Oid.Value == "2.5.29.17") { string[] names = extension.Format(true).Split(Environment.NewLine); foreach(string nameLine in names) { // Get the part after 'DNS Name=' - if(nameLine.Length > dnsNamePrefix.Length && nameLine.StartsWith(dnsNamePrefix, System.StringComparison.InvariantCulture)) + if(nameLine.StartsWith(dnsNamePrefix, System.StringComparison.InvariantCultureIgnoreCase)) { name = nameLine.Substring(dnsNamePrefix.Length); try From 907169a88b5ee6e6913eb1c0e3c35adaf3228a8d Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 22 May 2017 12:40:42 -0700 Subject: [PATCH 08/10] Add basic tests for DNSNameList and EKUList --- .../CertificateProvider.Tests.ps1 | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 index 8ff73dd3c28..00d38aaf16d 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 @@ -107,6 +107,31 @@ Describe "Certificate Provider tests" -Tags "Feature" { $cert | should not be null $cert.Thumbprint | should be $expectedThumbprint } + 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 + $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 { + 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.EnhancedKeyUsageList[0].ObjectId.Length | should not be 0 + $cert.EnhancedKeyUsageList[0].ObjectId | should be $expectedOid + } it "Should filter to codesign certificates" { $allCerts = get-item cert:\CurrentUser\My\* $codeSignCerts = get-item cert:\CurrentUser\My\* -CodeSigningCert From 3c7fdef6df93e5501fd36b08fbb59815c496aab2 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 22 May 2017 12:41:53 -0700 Subject: [PATCH 09/10] Update test common code to throw if attempted to run an non-windows platforms --- .../certificateCommon.psm1 | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 index 391d9144ec8..53dbb5d8ec0 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 @@ -93,7 +93,7 @@ function Install-TestCertificates $script:badCertLocation = New-BadCertificate $script:badCertLocation | Should Not BeNullOrEmpty | Out-Null - if ($IsCoreCLR) + if ($IsCoreCLR -and $IsWindows) { # PKI module is not available for PowerShell Core, so we need to use Windows PowerShell to import the cert $fullPowerShell = Join-Path "$env:SystemRoot" "System32\WindowsPowerShell\v1.0\powershell.exe" @@ -115,11 +115,14 @@ Import-Certificate $script:badCertLocation -CertStoreLocation Cert:\CurrentUser\ $env:PSModulePath = $modulePathCopy } } - else + elseif($IsWindows) { $script:importedCert = Import-PfxCertificate $script:certLocation -CertStoreLocation cert:\CurrentUser\My $script:testBadCert = Import-Certificate $script:badCertLocation -CertStoreLocation Cert:\CurrentUser\My } + else { + throw 'Not supported on non-windows platforms' + } } function Get-GoodCertificateLocation @@ -139,12 +142,18 @@ function Get-BadCertificateObject function Remove-TestCertificates { - if ($script:importedCert) + if($IsWindows) { - Remove-Item (Join-Path Cert:\CurrentUser\My $script:importedCert.Thumbprint) -Force -ErrorAction SilentlyContinue + if ($script:importedCert) + { + Remove-Item (Join-Path Cert:\CurrentUser\My $script:importedCert.Thumbprint) -Force -ErrorAction SilentlyContinue + } + if ($script:testBadCert) + { + Remove-Item (Join-Path Cert:\CurrentUser\My $script:testBadCert.Thumbprint) -Force -ErrorAction SilentlyContinue + } } - if ($script:testBadCert) - { - Remove-Item (Join-Path Cert:\CurrentUser\My $script:testBadCert.Thumbprint) -Force -ErrorAction SilentlyContinue + else { + throw 'Not supported on non-windows platforms' } } \ No newline at end of file From 20d85bb93499211b6d9148cdee09b12146ac2b8a Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Mon, 22 May 2017 13:30:35 -0700 Subject: [PATCH 10/10] Only run common code import and test case table setup on windows --- .../CertificateProvider.Tests.ps1 | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 index 00d38aaf16d..c9e84f5c99a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 @@ -1,17 +1,21 @@ -Import-Module (Join-Path -Path $PSScriptRoot 'certificateCommon.psm1') -Force +# The import and table creation work on non-windows, but are currently not needed +if($IsWindows) +{ + Import-Module (Join-Path -Path $PSScriptRoot 'certificateCommon.psm1') -Force -$currentUserMyLocations = @( - @{path = 'Cert:\CurrentUser\my'} - @{path = 'cert:\currentuser\my'} - @{path = 'Microsoft.PowerShell.Security\Certificate::CurrentUser\My'} - @{path = 'Microsoft.PowerShell.Security\certificate::currentuser\my'} -) + $currentUserMyLocations = @( + @{path = 'Cert:\CurrentUser\my'} + @{path = 'cert:\currentuser\my'} + @{path = 'Microsoft.PowerShell.Security\Certificate::CurrentUser\My'} + @{path = 'Microsoft.PowerShell.Security\certificate::currentuser\my'} + ) -$testLocations = @( - @{path = 'cert:\'} - @{path = 'CERT:\'} - @{path = 'Microsoft.PowerShell.Security\Certificate::'} -) + $testLocations = @( + @{path = 'cert:\'} + @{path = 'CERT:\'} + @{path = 'Microsoft.PowerShell.Security\Certificate::'} + ) +} # Add CurrentUserMyLocations to TestLocations foreach($location in $currentUserMyLocations)