Skip to content

Instantly share code, notes, and snippets.

@gpduck
Last active August 29, 2015 14:21
Show Gist options
  • Save gpduck/1ab2a5effb886a3bab56 to your computer and use it in GitHub Desktop.
Save gpduck/1ab2a5effb886a3bab56 to your computer and use it in GitHub Desktop.
Adds CmsMessage commands that should be mostly compatible with the ones provided in v5 to down-level PowerShell.
<#
Copyright 2015 Chris Duck
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
#>
if(!(Get-Command -Name Protect-CmsMessage -Module Microsoft.PowerShell.Security -ErrorAction SilentlyContinue)) {
Add-Type -AssemblyName System.Security
<#
.PARAMETER Content
The message encrypted with Protect-CmsMessage that should be decrypted.
.PARAMETER LiteralPath
A literal path to a file containing a message encrypted with Protect-CmsMessage.
.PARAMETER Path
A path to a file containing a message encrypted with Protect-CmsMessage.
.PARAMETER To
A list of recipient certificates to use to encrypt the message. This can be in the format of a certificate thumbprint or a wildcard match of the subject name.
The following values for this parameter are allowed in v5, but not supported by this implementation:
* base64 encoded certificate
* path to certificate file
* path to certificate folder
.EXAMPLE
c:\> Protect-CmsMessage -Content "Secret Message" -To "*MyCertificate" -OutFile "c:\cmsmessage.txt"
c:\> Unprotect-CmsMessage -Path "c:\cmsmessage.txt"
Secret Message
#>
function Unprotect-CmsMessage {
param(
[Parameter(Mandatory=$true,ParameterSetName="ByContent",Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[String]$Content,
[Parameter(Mandatory=$true,ParameterSetName="ByLiteralPath",Position=0)]
$LiteralPath,
[Parameter(Mandatory=$true,ParameterSetName="ByPath",Position=0)]
$Path,
[Parameter(ParameterSetName="ByContent",Position=1)]
[Parameter(ParameterSetName="ByLiteralPath",Position=1)]
[Parameter(ParameterSetName="ByPath",Position=1)]
[String[]]$To
)
switch($PSCmdlet.ParameterSetName) {
"ByLiteralPath" {
$Content = [System.IO.File]::ReadAllText( (Resolve-Path -LiteralPath $LiteralPath) )
}
"ByPath" {
$Content = [System.IO.File]::ReadAllText( (Resolve-Path -Path $Path) )
}
}
if($Content -match '(?sm)-----BEGIN CMS-----\r\n(.*)\r\n-----END CMS-----') {
$EncodedContent = $matches[1]
$ContentBytes = [Convert]::FromBase64String($EncodedContent)
$EnvelopedCms = New-Object System.Security.Cryptography.Pkcs.EnvelopedCms
$EnvelopedCms.Decode($ContentBytes)
if($To) {
$To | %{
$Cert = Find-RecipientCert -To $_
$Recipient = $EnvelopedCms.RecipientInfos | ?{$_.RecipientIdentifier.Value.SerialNumber -eq $Cert.SerialNumber}
$EnvelopedCms.Decrypt($Recipient)
}
} else {
$EnvelopedCms.Decrypt()
}
[Text.Encoding]::ASCII.GetString($EnvelopedCms.Encode())
} else {
Write-Error "The message does not appear to be a CMS message"
return
}
}
Export-ModuleMember -Function Unprotect-CmsMessage
<#
.PARAMETER Content
The message encrypted with Protect-CmsMessage that should be decrypted.
.PARAMETER LiteralPath
A literal path to a file containing a message encrypted with Protect-CmsMessage.
.PARAMETER Path
A path to a file containing a message encrypted with Protect-CmsMessage.
.PARAMETER To
A list of recipient certificates to use to encrypt the message. This can be in the format of a certificate thumbprint or a wildcard match of the subject name.
The following values for this parameter are allowed in v5, but not supported by this implementation:
* base64 encoded certificate
* path to certificate file
* path to certificate folder
.PARAMETER OutFile
The path of the file the protected message should be written to.
.EXAMPLE
c:\> Protect-CmsMessage -Content "Secret Message" -To "*MyCertificate" -OutFile "c:\cmsmessage.txt"
c:\> Unprotect-CmsMessage -Path "c:\cmsmessage.txt"
Secret Message
#>
function Protect-CmsMessage {
param(
[Parameter(Mandatory=$true,ParameterSetName="ByContent",Position=0)]
[Parameter(Mandatory=$true,ParameterSetName="ByLiteralPath",Position=0)]
[Parameter(Mandatory=$true,ParameterSetName="ByPath",Position=0)]
[String[]]$To,
[Parameter(Mandatory=$true,ParameterSetName="ByContent",Position=1,ValueFromPipeline=$true)]
$Content,
[Parameter(Mandatory=$true,ParameterSetName="ByLiteralPath",Position=1)]
$LiteralPath,
[Parameter(Mandatory=$true,ParameterSetName="ByPath",Position=1)]
$Path,
[Parameter(ParameterSetName="ByContent",Position=2)]
[Parameter(ParameterSetName="ByLiteralPath",Position=2)]
[Parameter(ParameterSetName="ByPath",Position=2)]
$OutFile
)
$Recipients = @()
$To | %{
$Recipient = $_
$Cert = Find-RecipientCert -To $Recipient
if($Cert) {
$Recipients += New-Object System.Security.Cryptography.Pkcs.CmsRecipient("IssuerAndSerialNumber", $Cert)
} else {
throw "Unable to locate a certificate for $Recipient"
}
}
switch($PSCmdlet.ParameterSetName) {
"ByLiteralPath" {
$Content = [System.IO.File]::ReadAllText( (Resolve-Path -LiteralPath $LiteralPath) )
}
"ByPath" {
$Content = [System.IO.File]::ReadAllText( (Resolve-Path -Path $Path) )
}
}
$ContentInfo = New-Object System.Security.Cryptography.Pkcs.ContentInfo(,[Text.Encoding]::ASCII.GetBytes($Content))
$EnvelopedCms = New-Object System.Security.Cryptography.Pkcs.EnvelopedCms($ContentInfo)
$Recipients | %{
$EnvelopedCms.Encrypt($_)
}
$MessageBase64 = [Convert]::ToBase64String($EnvelopedCms.Encode())
$CmsMessageSB = New-Object System.Text.StringBuilder
$CmsMessageSB.AppendLine("-----BEGIN CMS-----") > $null
$LineLength = 76
$StringPos = 0
while($StringPos -lt $MessageBase64.Length) {
if($StringPos + $LineLength -gt $MessageBase64.Length) {
$SubstringLength = $MessageBase64.Length - $StringPos
} else {
$SubstringLength = $LineLength
}
$CmsMessageSB.AppendLine($MessageBase64.Substring($StringPos, $SubstringLength)) > $null
$StringPos += $SubstringLength
}
$CmsMessageSB.AppendLine("-----END CMS-----") > $null
if($OutFile) {
Set-Content -Path $OutFile -Value $CmsMessageSB.ToString()
} else {
$CmsMessageSB.ToString()
}
}
Export-ModuleMember -Function Protect-CmsMessage
<#
#>
function Get-CmsMessage {
param(
[Parameter(Mandatory=$true,ParameterSetName="ByContent",ValueFromPipeline=$true)]
$Content,
[Parameter(Mandatory=$true,ParameterSetName="ByLiteralPath")]
$LiteralPath,
[Parameter(Mandatory=$true,ParameterSetName="ByPath")]
$Path
)
switch($PSCmdlet.ParameterSetName) {
"ByLiteralPath" {
$Content = [System.IO.File]::ReadAllText( (Resolve-Path -LiteralPath $LiteralPath) )
}
"ByPath" {
$Content = [System.IO.File]::ReadAllText( (Resolve-Path -Path $Path) )
}
}
if($Content -match '(?sm)-----BEGIN CMS-----\r\n(.*)\r\n-----END CMS-----') {
$EncodedContent = $matches[1]
$ContentBytes = [Convert]::FromBase64String($EncodedContent)
$EnvelopedCms = New-Object System.Security.Cryptography.Pkcs.EnvelopedCms
$EnvelopedCms.Decode($ContentBytes)
$EnvelopedCms | Add-Member -MemberType NoteProperty -Name Recipients -Value (@($EnvelopedCms.RecipientInfos.RecipientIdentifier.Value.IssuerName))
$EnvelopedCms | Add-Member -MemberType NoteProperty -Name Content -Value $Content
$EnvelopedCms
} else {
Write-Error "The message does not appear to be a CMS message"
return
}
}
Export-ModuleMember -Function Get-CmsMessage
<#
Internal function to locate certificates in the current user's store.
#>
function Find-RecipientCert {
param(
$To
)
dir cert:\CurrentUser\My | ?{
$EnhancedKeyUsages = $_.Extensions | ?{$_.Oid.Value -eq "2.5.29.37"}
($EnhancedKeyUsages.EnhancedKeyUsages | %{$_.Value}) -contains "1.3.6.1.4.1.311.80.1" -and
($_.Thumbprint -eq $To -or $_.Subject -like $To)
} | select -First 1
}
} else {
Import-Module -Name Microsoft.PowerShell.Security
Export-ModuleMember -Cmdlet Get-CmsMessage,Protect-CmsMessage,Unprotect-CmsMessage
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment