Port CmsMessage cmdlets and Get-PfxCertificate to powershell core#3224
Port CmsMessage cmdlets and Get-PfxCertificate to powershell core#3224daxian-dbw merged 9 commits intoPowerShell:masterfrom
Conversation
There was a problem hiding this comment.
Haven't we still Base64FormattingOptions in CoreCLR? 😕
Workarround:
PS C:\Program Files\PowerShell\6.0.0.16> $test="0123456789" *10
PS C:\Program Files\PowerShell\6.0.0.16> $test
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
PS C:\Program Files\PowerShell\6.0.0.16> [convert]::ToBase64String($test.ToCharArray(), 0, 100,1)
MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2
Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OQ==
PS C:\Program Files\PowerShell\6.0.0.16> [convert]::ToBase64String($test.ToCharArray(), 0, 100,0)
MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OQ==There was a problem hiding this comment.
So they expose the 'Base64FormattingOptions' overload in runtime assembly but not in contract assembly, interesting. Since this overload is not in contract, we cannot use it in C# code.
There was a problem hiding this comment.
Sorry, I add workarround later. Cannot we use it too?
There was a problem hiding this comment.
Isn't [convert]::ToBase64String($test.ToCharArray(), 0, 100,1) your workaround in powershell? You can do this in powershell because the API ToBase64String(byte[] inArray, int offset, int length, System.Base64FormattingOptions) is exposed in runtime assembly and thus powershell is able to call it using reflection. However, the contract assembly doesn't contain this API. C# code are compiled against the contract assembly, and thus this won't work in C#.
There was a problem hiding this comment.
Thanks for clarify!
Closed.
| $cert.Subject | Should Be "CN=MyDataEnciphermentCert" | ||
| } | ||
|
|
||
| It "Verify CMS message recipient resolution by path" -Skip:(!$IsWindows) { |
There was a problem hiding this comment.
Maybe better put Windows only tests in separate Describe? (Get-PfxCertificate tests in separate Describe)
There was a problem hiding this comment.
I did that initially, but then I had to setup the testing certificate file twice in both Describe. It feels a waste of time given that there are merely 3 Get-PfxCertificate tests and 3 CI level CMS message tests.
| } | ||
| } | ||
|
|
||
|
|
There was a problem hiding this comment.
Please remove unneeded empty line.
There was a problem hiding this comment.
Oh, I intentionally put 2 empty lines between Describe blocks, and 1 empty line between It blocks.
| $PSdefaultParameterValues = $defaultParamValues | ||
| } | ||
|
|
||
| if ($importedCert) |
There was a problem hiding this comment.
Maybe safer check isWindows?
|
|
||
| if ($importedCert) | ||
| { | ||
| Remove-Item (Join-Path Cert:\CurrentUser\My $importedCert.Thumbprint) |
There was a problem hiding this comment.
Add -Force -ErrorAction SilentlyContinue
There was a problem hiding this comment.
I want to make sure the certificate is removed after the test run. The test should try to not change the system state as much as possible.
| "Hello World" | Protect-CmsMessage -To "SomeThumbprintThatDoesNotExist" -ErrorAction Stop | ||
| throw "No Exception!" | ||
| } catch { | ||
| $_.FullyQualifiedErrorId | Should Be "NoCertificateFound,Microsoft.PowerShell.Commands.ProtectCmsMessageCommand" |
There was a problem hiding this comment.
Just learned that ShouldBeErrorId is not a Pester function. I'm a little worried that when using this function, I won't be able to just run Invoke-Pester .\CmsMessage.Tests.ps1 anymore, unless all test files that use this function explicitly import the helper module.
There was a problem hiding this comment.
I put the same comment in that PR. I will postpone changing to this pattern for now.
There was a problem hiding this comment.
Preferred way to run tests is to use Start-PSPester. In that PR I already changed Start-PSPester to import the helper module.
There was a problem hiding this comment.
well, I don't want to lose the flexibility to just run a single test case using Invoke-Pester 😄
|
|
||
| $decrypted = Unprotect-CmsMessage -Path $encryptedPath -To $certLocation | ||
| $decrypted | Should Be "Hello World`r`nHow are you?`r`n" | ||
| } finally { |
There was a problem hiding this comment.
Without Catch can we silently skip the test if there is a throw?
There was a problem hiding this comment.
If there is an exception thrown in the try block, then the test will fail:
PS:20> describe "abc" {
>> it "def" {
>> try {
>> $null.Get() | Should Be "yeah"
>> } finally {
>> Write-Host 'Blah'
>> }
>> }
>> }
Describing abc
Blah
[-] def 34ms
You cannot call a method on a null-valued expression.
at line: 4 in
There was a problem hiding this comment.
Clear. (It is in It block!)
Closed.
| # Decrypt using $importedCert in the Cert store | ||
| $decrypted = Unprotect-CmsMessage -Path $tempPath | ||
| $decrypted | Should Be "Hello World" | ||
| } finally { |
There was a problem hiding this comment.
The same about silently skip.
| } | ||
|
|
||
| It "Verify CMS message recipient resolution by cert" -Skip:(!$IsWindows) { | ||
| $ers = $null |
There was a problem hiding this comment.
Confuse $ers <-> $cert :-) Below too.
There was a problem hiding this comment.
Good point. Will change it to $errors.
There was a problem hiding this comment.
$errors is already busy by PowerShell :-)
There was a problem hiding this comment.
$Error is used by powershell, $errors is good :)
|
|
||
| string clearTextPassword = Utils.GetStringFromSecureString(password); | ||
|
|
||
| var cert = new X509Certificate2(path, clearTextPassword, X509KeyStorageFlags.DefaultKeySet); |
There was a problem hiding this comment.
I see SecureString in corefx but not in alfa.16 😕
PS C:\Program Files\PowerShell\6.0.0.16> [X509Certificate2]::New
OverloadDefinitions
-------------------
System.Security.Cryptography.X509Certificates.X509Certificate2 new()
System.Security.Cryptography.X509Certificates.X509Certificate2 new(byte[] rawData)
System.Security.Cryptography.X509Certificates.X509Certificate2 new(byte[] rawData, string password)
System.Security.Cryptography.X509Certificates.X509Certificate2 new(byte[] rawData, string password,
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags)
System.Security.Cryptography.X509Certificates.X509Certificate2 new(System.IntPtr handle)
System.Security.Cryptography.X509Certificates.X509Certificate2 new(string fileName)
System.Security.Cryptography.X509Certificates.X509Certificate2 new(string fileName, string password)
System.Security.Cryptography.X509Certificates.X509Certificate2 new(string fileName, string password,
System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags)There was a problem hiding this comment.
Thanks for noticing this. We need to update our code to use the new APIs once we move to a newer version of .NET Core packages. I opened this issue #3228 to track this effort as well as the list of similar code instances. Please feel free to update the list when you spot one.
|
|
||
| BeforeAll { | ||
| $certLocation = Create-TestCertificate | ||
| $certLocation | Should Not BeNullOrEmpty | Out-Null |
There was a problem hiding this comment.
It will output True to the console without Out-Null.
PS:18> describe "abc" { BeforeAll { "abc" | Should Not BeNullOrEmpty } }
Describing abc
True
There was a problem hiding this comment.
Clear (no "It" block).
Closed.
| $protected.IndexOf("-----BEGIN CMS-----") | Should Be 0 | ||
|
|
||
| $message = $protected | Get-CmsMessage | ||
| $message.Recipients[0].IssuerName | Should Be "CN=MyDataEnciphermentCert" |
There was a problem hiding this comment.
Non-informative exception may be here. Can we use $message.Recipients.IssuerName ?
|
|
||
| try { | ||
| $modulePathCopy = $env:PSMODULEPATH | ||
| $env:PSMODULEPATH = $null |
There was a problem hiding this comment.
Isn't this block import PKI module?
There was a problem hiding this comment.
It's a hack to import a certificate using PKI module from powershell core. PKI module is not carried by powershell core, but we only run those tests on windows platform, which makes this hack possible.
There was a problem hiding this comment.
Maybe add the comment to the code?
There was a problem hiding this comment.
There is a comment there:
PKI module is not available for PowerShell Core, so we need to use Windows PowerShell to import the cert
| } | ||
|
|
||
| It "Verify message recipient resolution by Base64Cert" { | ||
| $certContent = " |
There was a problem hiding this comment.
Can we reuse $dataEnciphermentCert?
There was a problem hiding this comment.
I would like to keep this Base64 encoded cert for
- to use a Base64 encoded cert directly for
CmsMessageRecipient, ascii armor"-----BEGIN CERTIFICATE-----"and"-----END CERTIFICATE-----"need to be present to surround the cert text. - this test is to verify if the passed-in text can be properly decoded (remove armor and then decode Base64), and thus it chooses the
"Encryption"purpose because that doesn't require the private key in the cert. That's why the cert text here is smaller in size.
|
|
||
| $decryptedWithContext | Should Match "Pre content" | ||
| $decryptedWithContext | Should Match "World" | ||
| $decryptedWithContext | Should Match "Post content" |
There was a problem hiding this comment.
Can we combine three Should Match in one?
There was a problem hiding this comment.
Fixed. Use Should Be instead.
|
|
||
| It "Verify Unprotect-CmsMessage lets you include context" { | ||
| $protected = "Hello World" | Protect-CmsMessage -To $certLocation | ||
| $adjustedProtected = "Pre content`r`n" + $protected + "`r`nPost content" |
There was a problem hiding this comment.
Maybe use platform independent NewLine for future?
| $virtualEventLog.Message = $protected | ||
|
|
||
| $decrypted = $virtualEventLog | Unprotect-CmsMessage -To $certLocation | ||
| $decrypted | Should Be "Encrypted Message1`r`nEncrypted Message2" |
There was a problem hiding this comment.
Maybe use platform independent NewLine for future?
|
|
||
| $processed = $virtualEventLog | Unprotect-CmsMessage -To $certLocation -IncludeContext | ||
| $processed.Id | Should Be $savedId | ||
| $processed.Message | Should Be "Encrypted Message1`r`nEncrypted Message2" |
There was a problem hiding this comment.
Maybe use platform independent NewLine for future?
| } | ||
|
|
||
| It "Verify protect message using OutString" { | ||
| $protected = Get-Process -Id $pid | Protect-CmsMessage -To $certLocation |
There was a problem hiding this comment.
Maybe replace Get-Process on something more fast?
There was a problem hiding this comment.
I think it's OK. This test is with feature tag, and only run in nightly build.
| { | ||
| if ($importedCert) | ||
| { | ||
| Remove-Item (Join-Path Cert:\CurrentUser\My $importedCert.Thumbprint) |
There was a problem hiding this comment.
Sorry, I don't understand why not add -Force -ErrorAction SilentlyContinue
There was a problem hiding this comment.
Ah, -Force is desired, but not -EA SilentlyConteinue. I will update the test. If it fails to remove, then I want the test to fail.
There was a problem hiding this comment.
If we want the test maybe make explicit test? Why use cleanup code for test?
There was a problem hiding this comment.
Good point. Maybe we should have some Cert:\ provider tests to cover this. I just did a quick search and we have zero Cert provider test 😦
There was a problem hiding this comment.
-ErrorAction SilentlyContinue added.
|
Tests LGTM. |
|
+@mirichmo to Reviewers. |
| } | ||
|
|
||
| # Should have round-tripped content | ||
| $result = "Hello World" | Unprotect-CmsMessage -IncludeContext |
There was a problem hiding this comment.
This looks like it should be a separate It. The errors are similar, but the tests are different.
| return encodedRawString; | ||
|
|
||
| StringBuilder builder = new StringBuilder(encodedRawString.Length); | ||
| int index = 0, remainingLen = encodedRawString.Length; |
There was a problem hiding this comment.
Was this copied from somewhere in CoreCLR or is it new implementation? I'm curious if it needs additional testing or if it is sufficiently exercised by your tests.
There was a problem hiding this comment.
This is new implementation based on the base64LineBreakPosition from the source code of [System.Convert]::ToBase64String(byte[] inArray, Base64FormattingOptions options).
This method is used in Protect-CmsMessage through CmsUtils.Encrypt. I believe it's well exercised by the tests.
There was a problem hiding this comment.
@daxian-dbw Perhaps it makes sense to open new Issue to migrate to .Net Core method when it become available?
There was a problem hiding this comment.
Opened #3235 to track this category of issues.

Fix #2452
CmsMessage depends on Cert store, so they are not enabled in non-windows. Only Get-PfxCertificate is enabled on non-windows.