Monday, August 26, 2013

Sending Encrypted S/MIME Messages with PowerShell

First you need the public key of the recipient.  In this example we will retrieve the public key from AD.  Public keys are stored in userCertificate which is a multivalued attribute, so we must pick a valid certificate from all the certificates that may be in the attribute.  We’re looking for a certificate which is usable for Secure Email and is currently valid for use (within a 5 minute skew).  We will pick the first certificate that meets those criteria.
$RecipientCN='someusername'
$RootDSE = [ADSI]("LDAP://RootDSE")
$SearchForestForPerson = New-Object DirectoryServices.DirectorySearcher
$SearchForestForPerson.SearchRoot = "GC://" + $RootDSE.rootDomainNamingContext
$SearchForestForPerson.SearchScope = "subtree"
$SearchForestForPerson.PropertiesToLoad.Add("distinguishedname") | Out-Null
$SearchForestForPerson.PropertiesToLoad.Add("mail") | Out-Null
$SearchForestForPerson.PropertiesToLoad.Add("usercertificate") | Out-Null
$SearchForestForPerson.Filter = ("(&(objectClass=person)(CN=$RecipientCN))")
$Recipient = $SearchForestForPerson.FindOne()
$ChosenCertificate = $null
$Now = Get-Date
If ($Recipient.Properties.usercertificate -ne $null) {
    ForEach ($UserCertificate in $Recipient.Properties.usercertificate) {
        $ValidForSecureEmail = $false
        $Certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]$UserCertificate
        $Extensions = $Certificate.Extensions
        ForEach ($Extension in $Extensions) {
            If ($Extension.EnhancedKeyUsages -ne $null) {
                ForEach ($EnhancedKeyUsage in $Extension.EnhancedKeyUsages) {
                    If ($EnhancedKeyUsage.FriendlyName -eq "Secure Email") {
                        $ValidForSecureEmail = $true
                        break
                    }
                }
                If ($ValidForSecureEmail) {
                    break
                }
            }
        }
        If ($ValidForSecureEmail) {
            If ($Now -gt $Certificate.NotBefore.AddMinutes(-5) -and $Now -lt $Certificate.NotAfter.AddMinutes(5)) {
                $ChosenCertificate = $Certificate
            }
        }
        If ($null -ne $ChosenCertificate) {
            break
        }
    }
}
Now that we have a valid certificate, compose and send the email.  The forum message at http://social.msdn.microsoft.com/Forums/en-US/74e4711e-1f66-43a7-9e3b-bc9cfbcd1b73/sending-smime-encrypted-email-using-c provided most of the basis for getting the encryption correct.
Add-Type -assemblyName "System.Security"
$MailClient = New-Object System.Net.Mail.SmtpClient "relay.widgets.com"
$Message = New-Object System.Net.Mail.MailMessage
$Message.To.Add($Recipient.properties.mail.item(0))
$Message.From = "noreply@widgets.com"
$Message.Subject = "Unencrypted subject of the message"
$Body = "Encrypted body of the message"
$MIMEMessage = New-Object system.Text.StringBuilder
$MIMEMessage.AppendLine('Content-Type: text/plain; charset="iso-8859-1"') | Out-Null
$MIMEMessage.AppendLine('Content-Transfer-Encoding: 7bit') | Out-Null
$MIMEMessage.AppendLine() | Out-Null
$MIMEMessage.AppendLine($Body) | Out-Null
[Byte[]] $BodyBytes = [System.Text.Encoding]::ASCII.GetBytes($MIMEMessage.ToString())
$ContentInfo = New-Object System.Security.Cryptography.Pkcs.ContentInfo (,$BodyBytes)
$CMSRecipient = New-Object System.Security.Cryptography.Pkcs.CmsRecipient $ChosenCertificate
$EnvelopedCMS = New-Object System.Security.Cryptography.Pkcs.EnvelopedCms $ContentInfo
$EnvelopedCMS.Encrypt($CMSRecipient)
[Byte[]] $EncryptedBytes = $EnvelopedCMS.Encode()
$MemoryStream = New-Object System.IO.MemoryStream @(,$EncryptedBytes)
$AlternateView = New-Object System.Net.Mail.AlternateView($MemoryStream, "application/pkcs7-mime; smime-type=enveloped-data;name=smime.p7m")
$Message.AlternateViews.Add($AlternateView)
$MailClient.Send($Message)