-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Port CmsMessage cmdlets and Get-PfxCertificate to powershell core #3224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
/// Facade for Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks) | ||
/// Inserts line breaks after every 76 characters in the string representation. | ||
/// </summary> | ||
internal static string ToBase64StringWithLineBreaks(byte[] bytes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I add workarround later. Cannot we use it too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't [convert]::T
8000
oBase64String($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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe better put Windows only tests in separate Describe? (Get-PfxCertificate tests in separate Describe)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Closed.
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove unneeded empty line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe safer check isWindows
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
||
if ($importedCert) | ||
{ | ||
Remove-Item (Join-Path Cert:\CurrentUser\My $importedCert.Thumbprint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add -Force -ErrorAction SilentlyContinue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you use new pattern from #3161 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I put the same comment in that PR. I will postpone changing to this pattern for now.
There was a problem hiding this c 628C omment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well, I don't want to lose the flexibility to just run a single test case using Invoke-Pester
😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I commented in that PR.
|
||
$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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without Catch
can we silently skip the test if there is a throw?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same about silently skip.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closed.
} | ||
|
||
It "Verify CMS message recipient resolution by cert" -Skip:(!$IsWindows) { | ||
$ers = $null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confuse $ers <-> $cert :-) Below too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Will change it to $errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$errors is already busy by PowerShell :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$Error
is used by powershell, $errors
is good :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am going to sleep :-)
|
||
string clearTextPassword = Utils.GetStringFromSecureString(password); | ||
|
||
var cert = new X509Certificate2(path, clearTextPassword, X509KeyStorageFlags.DefaultKeySet); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closed.
|
||
BeforeAll { | ||
$certLocation = Create-TestCertificate | ||
$certLocation | Should Not BeNullOrEmpty | Out-Null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we use Out-Null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-informative exception may be here. Can we use $message.Recipients.IssuerName
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
||
try { | ||
$modulePathCopy = $env:PSMODULEPATH | ||
$env:PSMODULEPATH = $null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this block import PKI module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a hack to import a certificate u 1CF5 sing 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear.
Closed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add the comment to the code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we reuse $dataEnciphermentCert
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clear. Ok.
Closed.
|
||
$decryptedWithContext | Should Match "Pre content" | ||
$decryptedWithContext | Should Match "World" | ||
$decryptedWithContext | Should Match "Post content" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we combine three Should Match
in one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use platform independent NewLine for future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Will do.
$virtualEventLog.Message = $protected | ||
|
||
$decrypted = $virtualEventLog | Unprotect-CmsMessage -To $certLocation | ||
$decrypted | Should Be "Encrypted Message1`r`nEncrypted Message2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use platform independent NewLine for future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
|
||
$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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use platform independent NewLine for future?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
} | ||
|
||
It "Verify protect message using OutString" { | ||
$protected = Get-Process -Id $pid | Protect-CmsMessage -To $certLocation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe replace Get-Process
on something more fast?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's OK. This test is with feature
tag, and only run in nightly build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closed.
{ | ||
if ($importedCert) | ||
{ | ||
Remove-Item (Join-Path Cert:\CurrentUser\My $importedCert.Thumbprint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't understand why not add -Force -ErrorAction SilentlyContinue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want the test maybe make explicit test? Why use cleanup code for test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it should be a separate It. The errors are similar, but the tests are different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
StringBuilder builder = new StringBuilder(encodedRawString.Length); | ||
int index = 0, remainingLen = encodedRawString.Length; | ||
while (remainingLen > 76) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
F438This 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened #3235 to track this category of issues.
@mirichmo I have addressed your comments, can you please take another look? |
Fix #2452
CmsMessage depends on Cert store, so they are not enabled in non-windows. Only Get-PfxCertificate is enabled on non-windows.