decode.smime.lib.ps1
<#PSScriptInfo
.VERSION 1.0.1 .GUID 7ec44055-f24b-455d-93df-e7b7e1797391 .AUTHOR rob.kalmar .COMPANYNAME .COPYRIGHT Rob Kalmar .TAGS SMIME Email .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .DESCRIPTION Decode encrypted/signed messages If you like this script please consider donating: https://bunq.me/robkalmar/5.00/PowerShellSMIMEToolkit Thanks! #> # PowerShell S/MIME Toolkit v1.0 # # # Copyright 2020 Rob Kalmar # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # Functions to decode signed and encrypted s/mime messages # # Tested platforms: # Windows Server 2008 R2 (Needs this hotfix: http://support.microsoft.com/kb/2480994) # Windows Server 2012 # Windows Server 2012 R2 # Windows Server 2016 # Windows Server 2019 # # Copyright 2016-2020 by Rob Kalmar Function Show-License { [CmdletBinding()] param () Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.Windows.Forms $Form1 = New-Object -TypeName System.Windows.Forms.Form $Form1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (400,450) $Form1.Text = 'PowerShell S/MIME Toolkit v1.0 license' $Form1.StartPosition = 'CenterScreen' $Form1.FormBorderStyle = 'FixedDialog' $Form1.MaximizeBox = $false $Form1.MinimizeBox = $false $License = 'Copyright 2020 Rob Kalmar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.' $Label1 = New-Object -TypeName System.Windows.Forms.Label $Label1.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (10,20) $Label1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (380,280) $Label1.Text = $License $null = $Form1.Controls.Add($Label1) $Label2 = New-Object -TypeName System.Windows.Forms.Label $Label2.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (10,320) $Label2.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (380,20) $Label2.Text = 'If you like these PowerShell scripts please feel free to make a donation...' $null = $Form1.Controls.Add($Label2) $LinkLabel1 = New-Object -TypeName System.Windows.Forms.LinkLabel $LinkLabel1.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (10,340) $LinkLabel1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (150,20) $LinkLabel1.LinkColor = 'BLUE' $LinkLabel1.ActiveLinkColor = 'RED' $LinkLabel1.Text = 'Make donation...' $null = $Form1.Controls.Add($LinkLabel1) $null = $LinkLabel1.Add_Click( { [Diagnostics.Process]::Start('https://bunq.me/robkalmar/5.00/PowerShellSMIMEToolkit') } ) $Label3 = New-Object -TypeName System.Windows.Forms.Label $Label3.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (10,360) $Label3.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (380,20) $Label3.Text = 'Thank you!' $null = $Form1.Controls.Add($Label3) $Button1 = New-Object -TypeName System.Windows.Forms.Button $Button1.Location = New-Object -TypeName System.Drawing.Size -ArgumentList (290,380) $Button1.Size = New-Object -TypeName System.Drawing.Size -ArgumentList (80,20) $Button1.Text = 'Close' $null = $Form1.Controls.Add($Button1) $null = $Button1.Add_Click( { $Form1.Close() } ) $null = $Form1.ShowDialog() Return $null } function Get-AllHeaders { param ( [parameter(Mandatory=$true)][string]$Headers ) [array]$AllHeaders = $Headers -split "`r`n" $Key = $null $Value = $null Foreach ($Line in $AllHeaders) { If ($Line.StartsWith(' ') -or $Line.StartsWith("`t")) { filter script:Headers { If ($_.$Key -ne $Value.Trim()) { $_ } } $AllHeaders = $AllHeaders | Headers $Value = $Value + $Line } Else { $Key,$Value = $Line -split ':', 2 } If ($Value -ne $null) { $AllHeaders += @{$Key = $Value.Trim()} } } Return $AllHeaders } function New-MailMessage { param ( [parameter(Mandatory=$true)][string]$Message ) [array]$AllMessageItems = $null $Headers = $null $Body = $null $Headers,$Body = $Message -split "`r`n`r`n", 2 $AllMessageItems = Get-AllHeaders -Headers $Headers filter script:MessageItems { If (($_.Keys -eq 'To') -or ($_.Keys -eq 'Subject') -or ($_.Keys -eq 'Content-Type') -or ($_.Keys -eq 'Content-Transfer-Encoding') -or ($_.Keys -eq 'Content-Disposition') -or ($_.Keys -eq 'From')) { $_ } } $AllMessageItems = $AllMessageItems | MessageItems $AllMessageItems += @{'Body' = $Body} $Boundary = $null $ContentType = ($AllMessageItems.'Content-Type' -split ';').Trim() $Boundary = $ContentType -match 'boundary=*' If ($Boundary) { $Boundary = (($Boundary -split '=', 2)[1]).Trim('"') $Boundary = '--' + $Boundary } $AllMessageItems += @{'Boundary' = $Boundary} $MessageObject = new-object -TypeName PSObject $MessageObject | Add-Member -Name To -Value $AllMessageItems.'To' -MemberType NoteProperty $MessageObject | Add-Member -Name From -Value $AllMessageItems.'From' -MemberType NoteProperty $MessageObject | Add-Member -Name Subject -Value $AllMessageItems.'Subject' -MemberType NoteProperty $MessageObject | Add-Member -Name ContentType -Value $AllMessageItems.'Content-Type' -MemberType NoteProperty $MessageObject | Add-Member -Name ContentTransferEncoding -Value $AllMessageItems.'Content-Transfer-Encoding' -MemberType NoteProperty $MessageObject | Add-Member -Name ContentDisposition -Value $AllMessageItems.'Content-Disposition' -MemberType NoteProperty $MessageObject | Add-Member -Name Boundary -Value $AllMessageItems.'Boundary' -MemberType NoteProperty $MessageObject | Add-Member -Name Body -Value $AllMessageItems.'Body' -MemberType NoteProperty Return $MessageObject } Function Get-ContentOid { param ( [parameter(Mandatory=$true)][byte[]]$Content ) Add-Type -AssemblyName System.Security Try { $Oid = [Security.Cryptography.Pkcs.ContentInfo]::GetContentType($Content) $Result = $Oid.Value } Catch { $Result = 'Error getting Oid of content.' } Return $Result } function New-DecryptedContent { param ( [parameter(Mandatory=$true)][byte[]]$Content, [Security.Cryptography.X509Certificates.X509Certificate2]$Certificate = $null ) Add-Type -AssemblyName System.Security $DecryptedBytes = $null $ContentInfo = New-Object -TypeName System.Security.Cryptography.Pkcs.ContentInfo -ArgumentList (,$Content) $EnvelopedCMS = New-Object -TypeName System.Security.Cryptography.Pkcs.EnvelopedCms -ArgumentList $ContentInfo Try { $null = $EnvelopedCMS.Decode($Content) } Catch { Write-Verbose -Message 'Could not decode data.' Return $False } If ($Certificate -eq $null) { Try { $null = $EnvelopedCMS.Decrypt() [byte[]]$DecryptedBytes = $EnvelopedCMS.ContentInfo.Content } Catch { Write-Verbose -Message 'Could not decrypt data.' Return $False } } Else { Try { $CertificateCollection = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection -ArgumentList $Certificate $null = $EnvelopedCMS.Decrypt($CertificateCollection) [byte[]]$DecryptedBytes = $EnvelopedCMS.ContentInfo.Content } Catch { Write-Verbose -Message 'Could not decrypt data.' Return $False } } $ContentInfo = $null $EnvelopedCMS = $null Return $DecryptedBytes } function Assert-EnhancedProtection { param ( [parameter(Mandatory=$true)][Security.Cryptography.Pkcs.SignedCms]$SignedCMS, [string]$From = '', [string]$To = '', [string]$Subject = '' ) $SignerInfos = $SignedCMS.SignerInfos $SignedAttributes = $SignerInfos.SignedAttributes Foreach ($SignedAttribute in $SignedAttributes) { $Oid = $SignedAttribute.Oid.Value Switch ($Oid) { '1.3.6.1.4.1.311.88.2.1' #DocumentName { $DocumentName = $SignedAttribute.Values.DocumentName If (-Not ( ($To -eq $DocumentName) -or ($To -eq "<$DocumentName>") ) ) { Write-Verbose -Message "Enhanced protection: Message 'To' field does NOT match signed attribute 'To' Field! [To: $To SignedAttribute: $DocumentName]" Return $False } Else { Write-Verbose -Message "Enhanced protection: Message 'To' field matches signed attribute 'To' Field! [To: $To SignedAttribute: $DocumentName]" } } '1.3.6.1.4.1.311.88.2.2' #DocumentDescription { $DocumentDescription = $SignedAttribute.Values.DocumentDescription If ($Subject -ne $DocumentDescription) { Write-Verbose -Message "Enhanced protection: Message 'Subject' field does NOT match signed attribute 'Subject' Field! [Subject: $Subject SignedAttribute: $DocumentDescription]" Return $False } Else { Write-Verbose -Message "Enhanced protection: Message 'Subject' field matches signed attribute 'Subject' Field! [Subject: $Subject SignedAttribute: $DocumentDescription]" } } } } $SenderCertificate = $SignerInfos.Certificate $SenderCetificateName = $($SenderCertificate.GetNameInfo('EmailName', $False)) If ($SenderCetificateName -ne $From) { Write-Verbose -Message "Enhanced protection: Message 'From' field does NOT match Certificate subject name used to sign this message! [From: $From Certificate subject name: $SenderCetificateName]" Return $False } Else { Write-Verbose -Message "Enhanced protection: Message 'From' field matches Certificate subject name used to sign this message! [From: $From Certificate subject name: $SenderCetificateName]" } Return $True } function New-VerifiedSignatureContent { param ( [parameter(Mandatory=$true)][byte[]]$Content, [byte[]]$Signature = $null, [bool]$Detached = $False, [string]$From = '', [string]$To = '', [string]$Subject = '', [bool]$VerifySignatureOnly = $False, [bool]$EnhancedProtection = $True ) Add-Type -AssemblyName System.Security $UnsignedBytes = $null $ContentInfo = New-Object -TypeName System.Security.Cryptography.Pkcs.ContentInfo -ArgumentList (,$Content) $SignedCMS = New-Object -TypeName System.Security.Cryptography.Pkcs.SignedCms -ArgumentList $ContentInfo, $Detached If ($Signature -eq $null) { $Signature = $Content } Try { $null = $SignedCMS.Decode($Signature) } Catch { Write-Verbose -Message 'Could not decode message.' Return $False } If ($EnhancedProtection) { $EnhancedProtected = Assert-EnhancedProtection -SignedCMS $SignedCMS -From $From -To $To -Subject $Subject If (-Not($EnhancedProtected)) { Write-Verbose -Message 'Failed Enhanced Protection check.' Return $False } } Try { $null = $SignedCMS.CheckSignature($VerifySignatureOnly) [byte[]]$UnsignedBytes = $SignedCMS.ContentInfo.Content } Catch { Write-Verbose -Message "Signature check failed. VerifySignatureOnly: $VerifySignatureOnly" Return $False } $ContentInfo = $null $SignedCMS = $null Return $UnsignedBytes } function New-EscBoundary { param ( [parameter(Mandatory=$true)][string]$Boundary ) [string]$EscBoundary = $null Foreach ($Character in $Boundary.ToCharArray()) { If ($Character -in '(', ')', '+', '.') { $EscBoundary += '\' + $Character } Else { $EscBoundary += $Character } } Return $EscBoundary } function Get-AllMimeParts { param ( [parameter(Mandatory=$true)][string]$Body, [parameter(Mandatory=$true)][string]$Boundary ) $Boundary = New-EscBoundary -Boundary $Boundary $AllMimeParts = $Body -split $Boundary $RelevantMimeParts = New-Object -TypeName System.Collections.ArrayList Foreach ($MimePart in $AllMimeParts) { If ($MimePart -ne '') { $TestMimePart = $null $TestMimePart = New-MailMessage -Message $MimePart If ($TestMimePart.ContentType -ne $null) { #Remove single \r\n from beginning of string and single! \r\n from end of string. $StrippedMimePart = $MimePart.Substring(2,$($MimePart.Length-4)) $null = $RelevantMimeParts.Add($StrippedMimePart) } } } Return $RelevantMimeParts } function Get-MimePartFilename { [CmdletBinding()] param ( [array]$ContentType = $null, [array]$ContentDisposition = $null ) $FileName = $null If ($ContentType -ne $null) { $FileName = ($ContentType[0].ToString()).SubString(5,($ContentType[0].Length-5)).Trim('"') } If (($FileName -eq $null) -and ($ContentDisposition -ne $null)) { $FileName = ($ContentDisposition[0].ToString()).SubString(9,($ContentDisposition[0].Length-9)).Trim('"') } Return $FileName } function Get-MailMessageSubject { [CmdletBinding()] param ( [string]$Subject = $null ) [string]$Output = $null If (($Subject -ne $null) -and ($Subject.StartsWith('=?utf-8?B?'))) { # This should be encoded using UTF-8 $Output = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Subject.SubString(10,$Subject.Length-12))) } Else { $Output = $Subject } Return $Output } function Convert-ToBytes { param ( [parameter(Mandatory=$true)][string]$InputString, [parameter(Mandatory=$true)][string]$Base64 ) [byte[]]$Output = $null If ($Base64 -eq 'base64') { Try { $Output = [Convert]::FromBase64String($InputString) } Catch { Write-Verbose -Message 'Could not convert Base64 string to bytes.' } } Else { $Output = [Text.Encoding]::Default.GetBytes($InputString) } Return $Output } Function Invoke-ProcessMailMessage { param ( [parameter(Mandatory=$true)][PSObject]$MailMessage, [Security.Cryptography.X509Certificates.X509Certificate2]$DecryptionCertificate = $null, [bool]$VerifySignatureOnly = $False, [bool]$EnhancedProtection = $True, [parameter(Mandatory=$true)][string]$OutputLocation, [bool]$SaveOnlyAttachments = $True ) $Output = $null $ContentType = $MailMessage.ContentType Switch -wildcard ($ContentType) { 'application/*pkcs7-mime;*' { $ContentTransferEncoding = $MailMessage.ContentTransferEncoding $Content = Convert-ToBytes -InputString $($MailMessage.Body) -Base64 $ContentTransferEncoding $ContentOid = Get-ContentOid -Content $Content Switch ($ContentOid) { #Signed message '1.2.840.113549.1.7.2' { Write-Verbose -Message 'signed message found' $From = $MailMessage.From $To = $MailMessage.To $Subject = Get-MailMessageSubject -Subject $($MailMessage.Subject) $Result = New-VerifiedSignatureContent -Content $Content -Signature $null -Detached $False -From $From -To $To -Subject $Subject -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection If (-Not($Result)) { Return $False } $MailMessage = New-MailMessage -Message ([Text.Encoding]::Default.GetString($Result)) $MailMessage.From = $From $MailMessage.To = $To $MailMessage.Subject = $Subject $Output = Invoke-ProcessMailMessage -MailMessage $MailMessage -DecryptionCertificate $DecryptionCertificate -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments Return $Output } #Encrypted message '1.2.840.113549.1.7.3' { Write-Verbose -Message 'encrypted message found' $From = $MailMessage.From $To = $MailMessage.To $Subject = Get-MailMessageSubject -Subject $($MailMessage.Subject) $Result = New-DecryptedContent -Content $Content -Certificate $DecryptionCertificate If (-Not($Result)) { Return $False } $MailMessage = New-MailMessage -Message ([Text.Encoding]::Default.GetString($Result)) $MailMessage.From = $From $MailMessage.To = $To $MailMessage.Subject = $Subject $Output = Invoke-ProcessMailMessage -MailMessage $MailMessage -DecryptionCertificate $DecryptionCertificate -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments Return $Output } #Cannot determine if signed and/or encrypted. 'Error getting Oid of content.' { Write-Verbose -Message 'Error getting Oid of content.' Return $False } } } '*multipart/signed*' { Write-Verbose -Message 'multipart signed message found' $AllMimeParts = Get-AllMimeParts -Body $MailMessage.Body -Boundary $MailMessage.Boundary $Content = [Text.Encoding]::Default.GetBytes($AllMimeParts[0]) $SignaturePart = New-MailMessage -Message $AllMimeParts[1] $Signature = Convert-ToBytes -InputString $($SignaturePart.Body) -Base64 $($SignaturePart.ContentTransferEncoding) $From = $MailMessage.From $To = $MailMessage.To $Subject = Get-MailMessageSubject -Subject $($MailMessage.Subject) $Result = New-VerifiedSignatureContent -Content $Content -Signature $Signature -Detached $True -From $From -To $To -Subject $Subject -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection If (-Not($Result)) { Return $False } $MailMessage = New-MailMessage -Message ([Text.Encoding]::Default.GetString($Result)) $MailMessage.From = $From $MailMessage.To = $To $MailMessage.Subject = $Subject $Output = Invoke-ProcessMailMessage -MailMessage $MailMessage -DecryptionCertificate $DecryptionCertificate -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments Return $Output } '*multipart/mixed*' { Write-Verbose -Message 'multipart mixed message found' $Output = $True $AllMimeParts = Get-AllMimeParts -Body $MailMessage.Body -Boundary $MailMessage.Boundary Foreach ($MimePart in $AllMimeParts) { $From = $MailMessage.From $To = $MailMessage.To $Subject = $MailMessage.Subject $MailMessage = New-MailMessage -Message $MimePart $MailMessage.From = $From $MailMessage.To = $To $MailMessage.Subject = $Subject $Result = Invoke-ProcessMailMessage -MailMessage $MailMessage -DecryptionCertificate $DecryptionCertificate -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments If (-Not($Result)) { $Output = $False } } Return $Output } Default { Write-Verbose -Message 'single part found' filter script:ContentType { If ($_ -match 'name') { $_ } } [array]$ContentType = ($MailMessage.ContentType -split ';').Trim() | ContentType filter script:ContentDisposition { If ($_ -match 'filename') { $_ } } [array]$ContentDisposition = ($MailMessage.ContentDisposition -split ';').Trim() | ContentDisposition $ContentTransferEncoding = $MailMessage.ContentTransferEncoding $Content = Convert-ToBytes -InputString $($MailMessage.Body) -Base64 $ContentTransferEncoding $FileName = Get-MimePartFilename -ContentType $ContentType -ContentDisposition $ContentDisposition $Exists = Test-Path -Path $OutputLocation If (-Not($Exists)) { $null = New-Item -Path $OutputLocation -ItemType 'directory' } $DateTime = $([DateTimeOffset]::Now.ToString('o')) -replace '[/:.+]', '-' If ($FileName) { $FileName = $FileName.Trim() If ($FileName.StartsWith('=?UTF-8?B?')) { $Filename = $FileName.replace('=?UTF-8?B?','') $Filename = $FileName.replace('?=','') $Filename = $FileName.replace(' ','') $FileName = [Convert]::FromBase64String($FileName) $FileName = [Text.Encoding]::UTF8.GetString($FileName) } Write-Verbose -Message "Saving file: $OutputLocation\$DateTime-$FileName" Try { $null = [IO.File]::WriteAllBytes("$OutputLocation\$DateTime-$FileName",$Content) $Output = $True } Catch { $Output = $False } Return $Output } Else { If (-Not($SaveOnlyAttachments)) { Write-Verbose -Message "No Filename, saving as $OutputLocation\$DateTime-body.txt" Try { $null = [IO.File]::WriteAllBytes("$OutputLocation\$DateTime-body.txt",$Content) $Output = $True } Catch { $Output = $False } Return $Output } Else { Write-Verbose -Message 'No Filename, skipping...' Return $True } } } } } function Invoke-DecodeMailMessage { param ( [parameter(Mandatory=$true)][byte[]]$Message, [Security.Cryptography.X509Certificates.X509Certificate2]$DecryptionCertificate = $null, [bool]$VerifySignatureOnly = $False, [bool]$EnhancedProtection = $True, [string]$OutputLocation = '', [bool]$SaveOnlyAttachments = $True ) # Store current code page default encoding and switch to code page default 28591 encoding to get full byte/character mapping $OriginalOutputEncoding = $OutputEncoding $OutputEncoding = [Text.Encoding]::GetEncoding(28591) # Default text encoding is now code page 28591 $MessageContent = [Text.Encoding]::Default.GetString($Message) $MailMessage = New-MailMessage -Message $MessageContent #Remove trailing '.<cr><lf>' if present in message. $BodyLength = $MailMessage.Body.Length $TrailingBytes = $MailMessage.Body[$($BodyLength-3)..$BodyLength] If ($([Text.Encoding]::Default.GetString($TrailingBytes)) -eq ".`r`n") { $MailMessage.Body = $MailMessage.Body.Substring(0,$($BodyLength-4)) } # Cleanup From and To field $From = $($MailMessage.From) If (($From -ne $null) -AND ($From.Contains('<'))) { $MailMessage.From = ($(($From -split '<')[1]) -split '>')[0] } $To = $($MailMessage.To) If (($To -ne $null ) -AND ($To.Contains('<'))) { $MailMessage.To = ($(($To -split '<')[1]) -split '>')[0] } # Set save location if none is specified (current execution location plus localpart of From mail address If ($OutputLocation -eq '') { $OutputLocation = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)" } $From = ($($MailMessage.From) -split '\@')[0] $OutputLocation = "$OutputLocation\$From" # Process the MailMessage and start decoding it. $Result = Invoke-ProcessMailMessage -MailMessage $MailMessage -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -DecryptionCertificate $DecryptionCertificate -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments #Restore original code page encoding. $OutputEncoding = $OriginalOutputEncoding Return $Result } # example # show license $null = Show-License # enter the directory which contains the encrypted and/or signed mail files: # set to smtp sent folder in this example $InputLocation = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)\Sent\*" # enter the directory where to store the decoded messages: $OutputLocation = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)\Decoded" # set security settings: # if set to $True only the digital signatures or verified # if set to $False also the signers certificate is validated and the purpose of the certificate $VerifySignatureOnly = $True # this checks signed attributes (if present) to the signed part # it checks the recipient address to Pkcs9DocumentName # and checks the subject to Pkcs9DocumentDescription # also checks if the From: field matches the mail address used in the signing certificate. $EnhancedProtection = $True # set the decyption certificate: # if set to $null the Windows Certificate store will be used # normally you would use: # $CertFile = "$(Split-Path -Path $script:MyInvocation.MyCommand.Path)\certificates\persontwo.pfx" # $CertPassword = 'password' # # for this example we use: $CertBase64 = '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 cHY2eHMubmxfUm9vdC1TZWN1cmUtQ0EoMSkuY3J0MCkGCCsGAQUFBzABhh1odHRwOi8vYmx1ZWJv eC5pcHY2eHMubmwvb2NzcDBFBgNVHREEPjA8oCQGCisGAQQBgjcUAgOgFgwUcGVyc29uLnR3b0Bp cHY2eHMubmyBFHBlcnNvbi50d29AaXB2NnhzLm5sMA0GCSqGSIb3DQEBCwUAA4ICAQA0hbYLbI44 ERZ4x2wY5hVqvgzsZBnXpMZtkL/JQ62JUwxIdQBxIYH+LwTNeWpgDoXxysMzT/Lg2CXus/OsNeHc Z6KddBIIXlfPICkfHTlwTlTYzSC+T5bHFB8+60fXe0QSoIefhIvOBTyEMjE0KH9Ud0UTlmMY6aCG vpuJGKj+ZOPq1GcUCeGuQByBzTV+e6sppyfpxTR+hNSxKPLo5uZ7dzz7iNbNqDuelNjZm2KVmQG7 HVvGw4pTqcXfg6e9pTPW28GCE9yKA8OUqqh6TwHv7RwquvM/DtEuqqT31CfMphUgmV2oT3fjLES9 q2UL9wT+gBrsdWvyk6eqY8/aQOFYkd+m22xJ/9mbDslqilzJTpWMpfLW24scBc2PUHxdugorZKaq g30tMmzLkG1er6zFfZ0hIZf+7VW3FnwNsu5vhTi8PfcbUk/nKGnFeN8zVWo8/4C13EHfecsWTTaS 3hXNd9K3qiwyMSkZ/1z6tntHm4gKP6y0ezc9JvI9viZ7pVnX49dlmMXWMpNkrXizFGS/USJSBt6k N4Lgksm+5zBQB5liTQ09clNQHK1FZABceoFuMvixkHmS+Dd2h0YdY/wkbZQL5gaPjLNeGh0NhSue JYlHtMcVESYehv4od3rg8LbMiI+949W0bN0FFsGDYJsy7vpj8YDpAEXVYvPQCtZs/DGCARYwEwYJ KoZIhvcNAQkVMQYEBAEAAAAwMgYKKwYBBAGCNxEDRzEkBCJvAHYAaABiAG8AeAAuAGkAcAB2ADYA eABzAC4AbgBsAAAAMIHKBgorBgEEAYI3EQNXMYG7BIG4AAAAAAAAAAACAAAAAAAAAAIAAABsAGQA YQBwADoAAAB7AEUANAA5ADcANgA0AEMAMwAtADYAMQA3ADEALQA0ADkAMwA0AC0AQQA5ADEAOAAt ADUANQAzADIAOQBGAEIANwAwADQAOAA5AH0AAABCAEwAVQBFAEIATwBYAC4AaQBwAHYANgB4AHMA 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' $CertBytes = [Convert]::FromBase64String($CertBase64) $CertPassword = 'password' $DecryptionCertificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList ($CertBytes, $CertPassword) # should the bodytext of the mail also be saved: $SaveOnlyAttachments = $False # get all the encrypted/signed files to be processed: $Files = Get-ChildItem -Path $InputLocation # process each file Foreach ($File in $Files) { # read the message file $Message = [IO.File]::ReadAllBytes("$($File.FullName)") # decode the message $Result = Invoke-DecodeMailMessage -Message $Message -VerifySignatureOnly $VerifySignatureOnly -EnhancedProtection $EnhancedProtection -DecryptionCertificate $DecryptionCertificate -OutputLocation $OutputLocation -SaveOnlyAttachments $SaveOnlyAttachments $Result } |