diff --git a/src/Microsoft.PowerShell.Security/project.json b/src/Microsoft.PowerShell.Security/project.json index 0f4a3ef2c86..d0de3cb8ed8 100644 --- a/src/Microsoft.PowerShell.Security/project.json +++ b/src/Microsoft.PowerShell.Security/project.json @@ -47,19 +47,13 @@ "define": [ "CORECLR" ], "compile": { "exclude": [ - "security/CertificateCommands.cs", - "security/CmsCommands.cs", "singleshell/installer/MshSecurityMshSnapin.cs", - "gen/CertificateCommands.cs", - "gen/CmsCommands.cs", "gen/SecurityMshSnapinResources.cs" ] }, "embed": { "exclude": [ - "resources/CertificateCommands.resx", - "resources/CmsCommands.resx", "resources/SecurityMshSnapinResources.resx" ] } diff --git a/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs b/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs index 345c38f272d..11c5c0ee88a 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs @@ -212,26 +212,20 @@ protected override void ProcessRecord() private static X509Certificate2 GetCertFromPfxFile(string path) { - X509Certificate2 cert = new X509Certificate2(); - - cert.Import(path); - + X509Certificate2 cert = new X509Certificate2(path); return cert; } private static X509Certificate2 GetCertFromPfxFile(string path, SecureString password) { - X509Certificate2 cert = new X509Certificate2(); - // // NTRAID#DevDiv Bugs-33007-2004/7/08-kumarp // the following will not be required once X509Certificate2.Import() // accepts a SecureString // - string clearTextPassword = SecurityUtils.GetStringFromSecureString(password); - - cert.Import(path, clearTextPassword, X509KeyStorageFlags.DefaultKeySet); - + string clearTextPassword = Utils.GetStringFromSecureString(password); + + var cert = new X509Certificate2(path, clearTextPassword, X509KeyStorageFlags.DefaultKeySet); return cert; } } diff --git a/src/Microsoft.PowerShell.Security/security/CmsCommands.cs b/src/Microsoft.PowerShell.Security/security/CmsCommands.cs index a4bf918ad5b..40ca423458e 100644 --- a/src/Microsoft.PowerShell.Security/security/CmsCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CmsCommands.cs @@ -3,19 +3,14 @@ --********************************************************************/ using System; -using System.IO; using System.Management.Automation; using System.Text; -using System.Security; -using System.Security.Cryptography; using System.Globalization; using System.Security.Cryptography.Pkcs; using System.Security.Cryptography.X509Certificates; using System.Diagnostics.CodeAnalysis; using System.Collections.ObjectModel; using System.Collections.Generic; -using System.Management.Automation.Security; -using System.Diagnostics.Eventing.Reader; namespace Microsoft.PowerShell.Commands { @@ -279,7 +274,7 @@ protected override void ProcessRecord() { if (_contentBuffer.Length > 0) { - _contentBuffer.Append(Environment.NewLine); + _contentBuffer.Append(System.Environment.NewLine); } _contentBuffer.Append(Content); @@ -456,7 +451,7 @@ protected override void ProcessRecord() { if (_contentBuffer.Length > 0) { - _contentBuffer.Append(Environment.NewLine); + _contentBuffer.Append(System.Environment.NewLine); } _contentBuffer.Append(Content); diff --git a/src/Microsoft.PowerShell.Security/security/Utils.cs b/src/Microsoft.PowerShell.Security/security/Utils.cs index 1bc0d25d5b3..f9e9c1842ff 100644 --- a/src/Microsoft.PowerShell.Security/security/Utils.cs +++ b/src/Microsoft.PowerShell.Security/security/Utils.cs @@ -5,21 +5,9 @@ using System; using System.IO; using System.Management.Automation; -using Microsoft.PowerShell.Commands; using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Security; -using System.Security.Principal; -using System.Security.AccessControl; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; - -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Runtime.InteropServices; -using DWORD = System.UInt32; -using BOOL = System.UInt32; -using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; namespace Microsoft.PowerShell { @@ -47,44 +35,6 @@ internal static long GetFileSize(string filePath) return size; } -#if false - /// - /// throw if file is smaller than 4 bytes in length - /// - /// - /// path to file - /// - /// Does not return a value - /// - /// - /// - internal static void CheckIfFileSmallerThan4Bytes(string filePath) - { - if (GetFileSize(filePath) < 4) - { - string message = - StringUtil.Format( - UtilsStrings.FileSmallerThan4Bytes, - new object[] { filePath } - ); - - throw PSTraceSource.NewArgumentException(message, "path"); - /* - // 2004/10/22-JonN The above form of the constructor - // no longer exists. This should probably be as below, - // however I have not tested this. This method is not - // used so I have removed it. - throw PSTraceSource.NewArgumentException( - "path", - "Utils", - "FileSmallerThan4Bytes", - filePath - ); - */ - } - } -#endif - /// /// present a prompt for a SecureString data /// @@ -109,74 +59,6 @@ internal static SecureString PromptForSecureString(PSHostUserInterface hostUI, return ss; } -#if !CORECLR - /// - /// get plain text string from a SecureString - /// - /// This function will not be required once all of the methods - /// that we call accept a SecureString. The list below has - /// classes/methods that will be changed to accept a SecureString - /// after Whidbey beta1 - /// - /// -- X509Certificate2.Import (String, String, X509KeyStorageFlags) - /// (DCR #33007 in the DevDiv Schedule db) - /// - /// -- NetworkCredential(string, string); - /// - /// - /// - /// input data - /// - /// a string representing clear-text equivalent of ss - /// - /// - /// - [ArchitectureSensitive] - internal static string GetStringFromSecureString(SecureString ss) - { - IntPtr p = Marshal.SecureStringToGlobalAllocUnicode(ss); - string s = Marshal.PtrToStringUni(p); - - Marshal.ZeroFreeGlobalAllocUnicode(p); - - return s; - } -#endif - - /* - /// - /// display sec-desc of a file - /// - /// - /// file security descriptor - /// - /// Does not return a value - /// - /// - /// - internal static void ShowFileSd(FileSecurity sd) - { - string userName = null; - FileSystemRights rights = 0; - AccessControlType aceType = 0; - - rules = sd.GetAccessRules(true, false, typeof(NTAccount)); - - foreach (FileSystemAccessRule r in rules) - { - userName = r.IdentityReference.ToString(); - aceType = r.AccessControlType; - rights = r.FileSystemRights; - - Console.WriteLine("{0} : {1} : {2}", - userName, - aceType.ToString(), - rights.ToString()); - } - } - } - */ - /// /// /// diff --git a/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 index f86fa695fbf..45859c96afa 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -7,7 +7,7 @@ ModuleVersion="3.0.0.0" PowerShellVersion="3.0" AliasesToExport = @() FunctionsToExport = @() -CmdletsToExport="Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "ConvertFrom-SecureString", "ConvertTo-SecureString" +CmdletsToExport="Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "ConvertFrom-SecureString", "ConvertTo-SecureString", "Get-PfxCertificate" NestedModules="Microsoft.PowerShell.Security.dll" HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=390786' } diff --git a/src/Modules/Windows-Full/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Windows-Core+Full/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 similarity index 100% rename from src/Modules/Windows-Full/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 rename to src/Modules/Windows-Core+Full/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 diff --git a/src/Modules/Windows-Core/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Windows-Core/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 deleted file mode 100644 index f47251e8321..00000000000 --- a/src/Modules/Windows-Core/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ /dev/null @@ -1,13 +0,0 @@ -@{ -GUID="A94C8C7E-9810-47C0-B8AF-65089C13A35A" -Author="Microsoft Corporation" -CompanyName="Microsoft Corporation" -Copyright="© Microsoft Corporation. All rights reserved." -ModuleVersion="3.0.0.0" -PowerShellVersion="3.0" -AliasesToExport = @() -FunctionsToExport = @() -CmdletsToExport="Get-Acl", "Set-Acl", "Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "Get-AuthenticodeSignature", "Set-AuthenticodeSignature", "ConvertFrom-SecureString", "ConvertTo-SecureString", "New-FileCatalog" , "Test-FileCatalog" -NestedModules="Microsoft.PowerShell.Security.dll" -HelpInfoURI = 'https://go.microsoft.com/fwlink/?linkid=390786' -} diff --git a/src/System.Management.Automation/CoreCLR/CorePsStub.cs b/src/System.Management.Automation/CoreCLR/CorePsStub.cs index 662356ccf40..b4bc1c57a23 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsStub.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsStub.cs @@ -793,31 +793,6 @@ public enum RollbackSeverity #endregion PSTransaction - #region CMS - - internal static class CmsUtils - { - internal static string BEGIN_CERTIFICATE_SIGIL = "-----BEGIN CERTIFICATE-----"; - internal static string END_CERTIFICATE_SIGIL = "-----END CERTIFICATE-----"; - - internal static string Encrypt(byte[] contentBytes, CmsMessageRecipient[] recipients, SessionState sessionState, out ErrorRecord error) - { - throw new NotImplementedException("CmsUtils.Encrypt(...) is not implemented in CoreCLR powershell."); - } - - internal static string GetAsciiArmor(byte[] bytes) - { - throw new NotImplementedException("CmsUtils.GetAsciiArmor(...) is not implemented in CoreCLR powershell."); - } - - internal static byte[] RemoveAsciiArmor(string actualContent, string beginMarker, string endMarker, out int startIndex, out int endIndex) - { - throw new NotImplementedException("CmsUtils.RemoveAsciiArmor(...) is not implemented in CoreCLR powershell."); - } - } - - #endregion CMS - #region ApartmentState internal enum ApartmentState diff --git a/src/System.Management.Automation/project.json b/src/System.Management.Automation/project.json index 904f2496f79..9af39fe58d5 100644 --- a/src/System.Management.Automation/project.json +++ b/src/System.Management.Automation/project.json @@ -183,6 +183,7 @@ "System.Reflection.Emit.Lightweight": "4.3.0", "System.Security.AccessControl": "4.3.0", "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Pkcs": "4.3.0", "System.Security.Cryptography.X509Certificates": "4.3.0", "System.Threading.Thread": "4.3.0", "System.Threading.Tasks.Parallel": "4.3.0", diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index b1c72471921..fe6f53daab2 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -1012,17 +1012,11 @@ internal enum CertificatePurpose namespace System.Management.Automation { -#if !CORECLR - using System.Security.Cryptography.Pkcs; /// /// Utility class for CMS (Cryptographic Message Syntax) related operations /// - /// - /// The namespace 'System.Security.Cryptography.Pkcs' is not available in CoreCLR, - /// so the Cryptographic Message Syntax (CMS) will not be supported on OneCore PS. - /// internal static class CmsUtils { internal static string Encrypt(byte[] contentBytes, CmsMessageRecipient[] recipients, SessionState sessionState, out ErrorRecord error) @@ -1086,8 +1080,7 @@ internal static string GetAsciiArmor(byte[] bytes) StringBuilder output = new StringBuilder(); output.AppendLine(BEGIN_CMS_SIGIL); - string encodedString = Convert.ToBase64String( - bytes, Base64FormattingOptions.InsertLineBreaks); + string encodedString = ClrFacade.ToBase64StringWithLineBreaks(bytes); output.AppendLine(encodedString); output.Append(END_CMS_SIGIL); @@ -1131,8 +1124,6 @@ internal static byte[] RemoveAsciiArmor(string actualContent, string beginMarker } } -#endif - /// /// Represents a message recipient for the Cms cmdlets. /// @@ -1271,7 +1262,7 @@ private void ResolveFromBase64Encoding(ResolutionPurpose purpose, out ErrorRecor return; } - List certificatesToProcess = new List(); ; + List certificatesToProcess = new List(); try { X509Certificate2 newCertificate = new X509Certificate2(messageBytes); diff --git a/src/System.Management.Automation/utils/ClrFacade.cs b/src/System.Management.Automation/utils/ClrFacade.cs index 4583f925f14..2c7a8f713aa 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -661,6 +661,36 @@ internal static void SetCurrentThreadUiCulture(CultureInfo uiCultureInfo) #region Misc + /// + /// Facade for Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks) + /// Inserts line breaks after every 76 characters in the string representation. + /// + internal static string ToBase64StringWithLineBreaks(byte[] bytes) + { +#if CORECLR + // Inserts line breaks after every 76 characters in the string representation. + string encodedRawString = Convert.ToBase64String(bytes); + if (encodedRawString.Length <= 76) + return encodedRawString; + + StringBuilder builder = new StringBuilder(encodedRawString.Length); + int index = 0, remainingLen = encodedRawString.Length; + while (remainingLen > 76) + { + builder.Append(encodedRawString, index, 76); + builder.Append(System.Environment.NewLine); + + index += 76; + remainingLen -= 76; + } + + builder.Append(encodedRawString, index, remainingLen); + return builder.ToString(); +#else + return Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks); +#endif + } + /// /// Facade for RemotingServices.IsTransparentProxy(object) /// diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 new file mode 100644 index 00000000000..1f500b2bb30 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 @@ -0,0 +1,421 @@ + +Function Create-TestCertificate +{ + $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 +} + + +Describe "CmsMessage cmdlets and Get-PfxCertificate basic tests" -Tags "CI" { + + BeforeAll { + $certLocation = Create-TestCertificate + $certLocation | Should Not BeNullOrEmpty | Out-Null + } + + It "Verify Get-PfxCertificate -FilePath" { + $cert = Get-PfxCertificate -FilePath $certLocation + $cert.Subject | Should Be "CN=MyDataEnciphermentCert" + } + + It "Verify Get-PfxCertificate -LiteralPath" { + $cert = Get-PfxCertificate -LiteralPath $certLocation + $cert.Subject | Should Be "CN=MyDataEnciphermentCert" + } + + It "Verify Get-PfxCertificate positional argument" { + $cert = Get-PfxCertificate $certLocation + $cert.Subject | Should Be "CN=MyDataEnciphermentCert" + } + + It "Verify CMS message recipient resolution by path" -Skip:(!$IsWindows) { + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] $certLocation + $recipient.Resolve($ExecutionContext.SessionState, "Encryption", [ref] $errors) + + $recipient.Certificates.Count | Should Be 1 + $recipient.Certificates[0].Subject | Should Match 'CN=MyDataEnciphermentCert' + } + + It "Verify CMS message recipient resolution by cert" -Skip:(!$IsWindows) { + $errors = $null + $cert = Get-PfxCertificate $certLocation + $recipient = [System.Management.Automation.CmsMessageRecipient] $cert + $recipient.Resolve($ExecutionContext.SessionState, "Encryption", [ref] $errors) + + $recipient.Certificates.Count | Should Be 1 + $recipient.Certificates[0].Subject | Should Match 'CN=MyDataEnciphermentCert' + } + + It "Verify a CMS message can be protected / unprotected" -Skip:(!$IsWindows) { + $protected = "Hello World","How are you?" | Protect-CmsMessage -To $certLocation + $protected.IndexOf("-----BEGIN CMS-----") | Should Be 0 + + $message = $protected | Get-CmsMessage + $message.Recipients.Count | Should Be 1 + $message.Recipients[0].IssuerName | Should Be "CN=MyDataEnciphermentCert" + + $expected = "Hello World" + [System.Environment]::NewLine + "How are you?" + $decrypted = $message | Unprotect-CmsMessage -To $certLocation + $decrypted | Should Be $expected + + $decrypted = $protected | Unprotect-CmsMessage -To $certLocation + $decrypted | Should Be $expected + } +} + + +Describe "CmsMessage cmdlets thorough tests" -Tags "Feature" { + + BeforeAll { + if ($IsWindows) + { + $certLocation = Create-TestCertificate + $certLocation | 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 + + $importedCertPath = & $fullPowerShell -NoProfile -NonInteractive ` + -Command "Import-PfxCertificate $certLocation -CertStoreLocation cert:\CurrentUser\My | % PSPath" + $importedCert = Get-ChildItem $importedCertPath + } finally { + $env:PSMODULEPATH = $modulePathCopy + } + } + else + { + $importedCert = Import-PfxCertificate $certLocation -CertStoreLocation cert:\CurrentUser\My + } + } + else + { + # Skip for non-Windows platforms + $defaultParamValues = $PSdefaultParameterValues.Clone() + $PSdefaultParameterValues = @{ "it:skip" = $true } + } + } + + AfterAll { + if ($IsWindows) + { + if ($importedCert) + { + Remove-Item (Join-Path Cert:\CurrentUser\My $importedCert.Thumbprint) -Force -ErrorAction SilentlyContinue + } + } + else + { + $PSdefaultParameterValues = $defaultParamValues + } + } + + It "Verify message recipient resolution by Base64Cert" { + $certContent = " + -----BEGIN CERTIFICATE----- + MIIDXTCCAkWgAwIBAgIQRTsRwsx0LZBHrx9z5Dag2zANBgkqhkiG9w0BAQUFADAh + MR8wHQYDVQQDDBZNeURhdGFFbmNpcGhlcm1lbnRDZXJ0MCAXDTE0MDcyNTIyMjkz + OVoYDzMwMTQwNzI1MjIzOTM5WjAhMR8wHQYDVQQDDBZNeURhdGFFbmNpcGhlcm1l + bnRDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3SuShUvnRqn + tYOIouJdP3wPZ5rtDi2KYPurpngGNZjM0EGDTrnhmEAI8DL4Kp6n/zz1mYVoX73+ + 6uCpZX/13VDXg1neebJ261XpBX6FzxtclIQr8ywdUtrEgCnUAhgqgvO1Wwm4ogNR + tWGCGkmlnqyaoV1j/V4KSn4WvKqSUIOZm0umGCTtNAJ6VtdpYO+uxxnRAapPUCY+ + qQ7DFzTUECIo1lMlBcuMiXj6NSFr4/D7ltkZ27jCdsZmzI7ZvRnDlfSYTPQnAO/E + 0uYn9uyKY/xfngWkUX/pe+j+10Lm1ypbASrj2Ezgf0KeZRXBwqKUOLhKheEmBJ18 + rLV27qwHeQIDAQABo4GOMIGLMA4GA1UdDwEB/wQEAwIEMDAUBgNVHSUEDTALBgkr + BgEEAYI3UAEwRAYJKoZIhvcNAQkPBDcwNTAOBggqhkiG9w0DAgICAIAwDgYIKoZI + hvcNAwQCAgCAMAcGBSsOAwIHMAoGCCqGSIb3DQMHMB0GA1UdDgQWBBRIyIzwInLJ + 3B+FajVUFMACf1hrxjANBgkqhkiG9w0BAQUFAAOCAQEAfFt4rmUmWfCbbwi2mCrZ + Osq0lfVNUiZ+iLlEKga4VAI3sJZRtErnVM70eXUt7XpRaOdIfxjuXFpsgc37KyLi + ByCORLuRC0itZVs3aba48opfMDXivxBy0ngqCPPLQsyaN9K7WnpvYV1QxiudYwwU + 8U5rFmzlwNLvc3XiyoGWaVZluk2DIJawQ5QYAU9/NMBBCbPHjTG7k0l4cpcEC+Ex + od3RlO6/MOYuK2WB4VTxKsV80EdA3ljlu7Td8P4movnrbB4rG4wpCpk05eREkg/5 + Y54Ilo9m5OSAWtdx4yfS779eebLgUs3P+dk6EKwovXMokVveZA8cenIp3QkqSpeT + cQ== + -----END CERTIFICATE----- + " + + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] $certContent + $recipient.Resolve($ExecutionContext.SessionState, "Encryption", [ref] $errors) + + $recipient.Certificates.Count | Should Be 1 + $recipient.Certificates[0].Subject | Should Match 'CN=MyDataEnciphermentCert' + } + + It "Verify wildcarded recipient resolution by path [Decryption]" { + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] ($certLocation + "*") + $recipient.Resolve($ExecutionContext.SessionState, "Decryption", [ref] $errors) + + # Should have resolved single cert + $recipient.Certificates.Count | Should Be 1 + } + + It "Verify wildcarded recipient resolution by path [Encryption]" { + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] ($certLocation + "*") + $recipient.Resolve($ExecutionContext.SessionState, "Encryption", [ref] $errors) + + $recipient.Certificates.Count | Should Be 1 + } + + 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") + + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] $protectedEventLoggingCertPath + $recipient.Resolve($executionContext.SessionState, "Decryption", [ref] $errors) + + $recipient.Certificates.Count | Should Be 1 + } + + It "Verify resolution by thumbprint" { + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] $importedCert.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 + } + + It "Verify resolution by subject name" { + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] $importedCert.Subject + $recipient.Resolve($ExecutionContext.SessionState, "Decryption", [ref] $errors) + + $recipient.Certificates.Count | Should Be 1 + $recipient.Certificates[0].Thumbprint | Should Be $importedCert.Thumbprint + } + + It "Verify error when no cert found in encryption for encryption" { + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] "SomeCertificateThatDoesNotExist*" + $recipient.Resolve($ExecutionContext.SessionState, "Encryption", [ref] $errors) + + $errors.Count | Should Be 1 + $errors[0].FullyQualifiedErrorId | Should Be "NoCertificateFound" + } + + It "Verify error when encrypting to non-wildcarded identifier for decryption" { + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] "SomeCertificateThatDoesNotExist" + $recipient.Resolve($ExecutionContext.SessionState, "Decryption", [ref] $errors) + + $errors.Count | Should Be 1 + $errors[0].FullyQualifiedErrorId | Should Be "NoCertificateFound" + } + + It "Verify error when encrypting to wrong cert" { + $errors = $null + $goodCerts = @(Get-ChildItem Cert:\currentuser\My -DocumentEncryptionCert | ForEach-Object Thumbprint) + $badCert = Get-ChildItem Cert:\currentuser\My | ? { $_.Thumbprint -notin $goodCerts } | Select -First 1 + + $recipient = [System.Management.Automation.CmsMessageRecipient] $badCert.Thumbprint + $recipient.Resolve($ExecutionContext.SessionState, "Encryption", [ref] $errors) + + $errors.Count | Should Be 1 + $errors[0].FullyQualifiedErrorId | Should Be "CertificateCannotBeUsedForEncryption" + } + + It "Verify no error when encrypting to wildcarded identifier for decryption" { + $errors = $null + $recipient = [System.Management.Automation.CmsMessageRecipient] "SomeCertificateThatDoesNotExist*" + $recipient.Resolve($ExecutionContext.SessionState, "Decryption", [ref] $errors) + + $errors | Should Be $null + $recipient.Certificates.Count | Should Be 0 + } + + It "Verify Protect-CmsMessage emits recipient errors" { + try { + "Hello World" | Protect-CmsMessage -To "SomeThumbprintThatDoesNotExist" -ErrorAction Stop + throw "No Exception!" + } catch { + $_.FullyQualifiedErrorId | Should Be "NoCertificateFound,Microsoft.PowerShell.Commands.ProtectCmsMessageCommand" + } + } + + It "Verify CmsMessage cmdlets works with paths" { + try { + $randomNum = Get-Random -Minimum 1000 -Maximum 9999 + $tempPath = Join-Path $TestDrive "$randomNum-Path-Test-File" + $encryptedPath = $tempPath + ".encrypted.txt" + "Hello World","How are you?" | Set-Content $tempPath + + Protect-CmsMessage -Path $tempPath -To $certLocation -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 | Should Be $expected + + $decrypted = Unprotect-CmsMessage -Path $encryptedPath -To $certLocation + $decrypted | Should Be $expected + } finally { + Remove-Item $tempPath, $encryptedPath -Force -ErrorAction SilentlyContinue + } + } + + It "Verify Unprotect-CmsMessage works with local store" { + try { + $randomNum = Get-Random -Minimum 1000 -Maximum 9999 + $tempPath = Join-Path $TestDrive "$randomNum-Path-Test-File" + "Hello World" | Protect-CmsMessage -To $certLocation -OutFile $tempPath + + # Decrypt using $importedCert in the Cert store + $decrypted = Unprotect-CmsMessage -Path $tempPath + $decrypted | Should Be "Hello World" + } finally { + Remove-Item $tempPath -Force -ErrorAction SilentlyContinue + } + } + + It "Verify Unprotect-CmsMessage emits recipient errors" { + try { + "" | Unprotect-CmsMessage -To "SomeThumbprintThatDoesNotExist" -IncludeContext -ErrorAction Stop + throw "No Exception!" + } catch { + $_.FullyQualifiedErrorId | Should Be "NoCertificateFound,Microsoft.PowerShell.Commands.UnprotectCmsMessageCommand" + } + } + + It "Verify failure to extract Ascii armor generates an error [Unprotect-CmsMessage]" { + try { + "Hello World" | Unprotect-CmsMessage -ErrorAction Stop + throw "No Exception!" + } catch { + $_.FullyQualifiedErrorId | Should Be "InputContainedNoEncryptedContentIncludeContext,Microsoft.PowerShell.Commands.UnprotectCmsMessageCommand" + } + } + + It "Verify failure to extract Ascii armor generates an error [Get-CmsMessage]" { + try { + "Hello World" | Get-CmsMessage -ErrorAction Stop + throw "No Exception!" + } catch { + $_.FullyQualifiedErrorId | Should Be "InputContainedNoEncryptedContent,Microsoft.PowerShell.Commands.GetCmsMessageCommand" + } + } + + It "Verify 'Unprotect-CmsMessage -IncludeContext' with no encrypted input" { + # Should have round-tripped content + $result = "Hello World" | Unprotect-CmsMessage -IncludeContext + $result | Should Be "Hello World" + } + + It "Verify Unprotect-CmsMessage lets you include context" { + $protected = "Hello World" | Protect-CmsMessage -To $certLocation + $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 | Should Be "Hello World" + + $expected = "Pre content" + [System.Environment]::NewLine + "Hello World" + [System.Environment]::NewLine + "Post content" + $decryptedWithContext | Should Be $expected + } + + It "Verify Unprotect-CmsMessage treats event logs as a first class citizen" { + $protected = "Encrypted Message1","Encrypted Message2" | Protect-CmsMessage -To $certLocation + $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 | Should Be $expected + + $processed = $virtualEventLog | Unprotect-CmsMessage -To $certLocation -IncludeContext + $processed.Id | Should Be $savedId + $processed.Message | Should Be $expected + } + + It "Verify -DocumentEncryptionCert parameter works" { + $foundCerts = Get-ChildItem Cert:\CurrentUser -Recurse -DocumentEncryptionCert + + # Validate they all match the EKU + $correctMatching = $foundCerts | ? { + ($_.EnhancedKeyUsageList.Count -gt 0) -and + ($_.EnhancedKeyUsageList[0].ObjectId -eq '1.3.6.1.4.1.311.80.1') + } + # "All Document Encryption Cert should have had correct EKU" + @($foundCerts).Count | Should Be @($correctMatching).Count + } + + It "Verify protect message using OutString" { + $protected = Get-Process -Id $pid | Protect-CmsMessage -To $certLocation + $decrypted = $protected | Unprotect-CmsMessage -To $certLocation + # Should have had PID in output + $decrypted | Should Match $pid + } +}