ProtectedData.Tests.ps1

Import-Module Pester -ErrorAction Stop

$scriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent

$stringToEncrypt = 'This is my string.'
$secureStringToEncrypt = $stringToEncrypt | ConvertTo-SecureString -AsPlainText -Force

$userName = 'UserName'
$credentialToEncrypt = New-Object System.Management.Automation.PSCredential($userName, $secureStringToEncrypt)

$byteArrayToEncrypt = [byte[]](1..10)

$passwordForEncryption = 'p@ssw0rd' | ConvertTo-SecureString -AsPlainText -Force
$wrongPassword = 'wr0ngp@ssw0rd' | ConvertTo-SecureString -AsPlainText -Force

$testCertificateSubject = 'CN=ProtectedData Test Certificate, OU=Unit Tests, O=ProtectedData, L=Somewhere, S=Ontario, C=CA'

function Get-PlainTextFromSecureString
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.Security.SecureString]
        $SecureString
    )

    process
    {
        $ptr = $null

        try
        {
            $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($SecureString)
            [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)
        }
        finally
        {
            if ($null -ne $ptr) { [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) }
        }
    }
}

function New-TestCertificate
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]
        $Subject,

        [Nullable[DateTime]]
        $NotBefore,

        [Nullable[DateTime]]
        $NotAfter
    )

    if ($null -eq $NotBefore)
    {
        $NotBefore = (Get-Date).AddDays(-7)
    }

    if ($null -eq $NotAfter)
    {
        $NotAfter = (Get-Date).AddDays(7)
    }

    if ($NotBefore -ge $NotAfter)
    {
        throw 'NotAfter date/time must take place after NotBefore'
    }

    $notBeforeString = $NotBefore.ToString('G')
    $notAfterString = $NotAfter.ToString('G')

    $requestfile = [System.IO.Path]::GetTempFileName()
    $certFile = [System.IO.Path]::GetTempFileName()

    Set-Content -Path $requestfile -Encoding Ascii -Value @"
[Version]
 
Signature="`$Windows NT`$"
 
[NewRequest]
 
Subject = "$Subject"
KeyLength = 2048
; Can be 2048, 4096, 8192, or 16384.
; Larger key sizes are more secure, but have
; a greater impact on performance.
Exportable = TRUE
FriendlyName = "ProtectedData"
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = Cert
Silent = True
SuppressDefaults = True
KeySpec = AT_KEYEXCHANGE
KeyUsage = CERT_KEY_ENCIPHERMENT_KEY_USAGE
NotBefore = "$notBeforeString"
NotAfter = "$notAfterString"
 
"@

    
    try
    {
        $oldCerts = @(
            Get-ChildItem Cert:\CurrentUser\My |
            Where-Object { $_.Subject -eq $Subject } |
            Select-Object -ExpandProperty Thumbprint
        )

        $null = certreq -new -f -q $requestfile $certFile 2>&1

        $newCert = Get-ChildItem Cert:\CurrentUser\My -Exclude $oldCerts |
                   Where-Object { $_.Subject -eq $Subject } |
                   Select-Object -ExpandProperty Thumbprint

        return $newCert
    }
    finally
    {
        Remove-Item -Path $requestfile -Force -ErrorAction SilentlyContinue
        Remove-Item -Path $certFile -Force -ErrorAction SilentlyContinue
    }
}

Describe 'Module Load' {
    It 'Loads the module without errors' {
        $moduleManifest = Join-Path -Path $scriptRoot -ChildPath ProtectedData.psd1
        { Import-Module $moduleManifest -Force -ErrorAction Stop } | Should Not Throw
    }
}

Describe 'Password-based encryption and decryption' {
    Context 'General Usage' {
        $blankSecureString = New-Object System.Security.SecureString
        $blankSecureString.MakeReadOnly()

        $secondPassword = 'Some other password' | ConvertTo-SecureString -AsPlainText -Force

        It 'Produces an error if a blank password is used' {
            { $null = Protect-Data -InputObject $stringToEncrypt -Password $blankSecureString -ErrorAction Stop } | Should Throw
        }

        It 'Does not produce an error when a non-blank password is used' {
            { $null = Protect-Data -InputObject $stringToEncrypt -Password $passwordForEncryption -ErrorAction Stop } | Should Not Throw
        }

        $protected = Protect-Data -InputObject $stringToEncrypt -Password $passwordForEncryption, $secondPassword

        It 'Produces an error if a decryption attempt with the wrong password is made.' {
            { $null = Unprotect-Data -InputObject $protected -Password $wrongPassword -ErrorAction Stop } | Should Throw
        }

        It 'Allows any of the passwords to be used when decrypting. (First password test)' {
            { $null = Unprotect-Data -InputObject $protected -Password $passwordForEncryption -ErrorAction Stop } | Should Not Throw
        }

        It 'Allows any of the passwords to be used when decrypting. (Second password test)' {
            { $null = Unprotect-Data -InputObject $protected -Password $secondPassword -ErrorAction Stop } | Should Not Throw
        }

        It 'Adds a new password to an existing object' {
            $scriptBlock = { Add-ProtectedDataCredential -InputObject $protected -Password $passwordForEncryption -NewPassword $wrongPassword }
            $scriptBlock | Should Not Throw
        }

        It 'Allows the object to be decrypted with the new password' {
            { $null = Unprotect-Data -InputObject $protected -Password $wrongPassword } | Should Not Throw
        }

        It 'Removes a password from the object' {
            { $null = Remove-ProtectedDataCredential -InputObject $protected -Password $secondPassword } | Should Not Throw
        }

        It 'No longer allows the data to be decrypted with the removed password' {
            { $null = Unprotect-Data -InputObject $protected -Password $secondPassword -ErrorAction Stop } | Should Throw
        }
    }

    Context 'Protecting strings' {
        $protectedData = $stringToEncrypt | Protect-Data -Password $passwordForEncryption
        $decrypted = $protectedData | Unprotect-Data -Password $passwordForEncryption

        It 'Does not return null' {
            $decrypted | Should Not Be $null
        }

        It 'Returns a String object' {
            $decrypted.GetType().FullName | Should Be System.String
        }

        It 'Decrypts the string properly.' {
            $decrypted | Should Be $stringToEncrypt
        }
    }

    Context 'Protecting SecureStrings' {
        $protectedData = $secureStringToEncrypt | Protect-Data -Password $passwordForEncryption
        $decrypted = $protectedData | Unprotect-Data -Password $passwordForEncryption

        It 'Does not return null' {
            $decrypted | Should Not Be $null
        }

        It 'Returns a SecureString object' {
            $decrypted.GetType().FullName | Should Be System.Security.SecureString
        }

        It 'Decrypts the SecureString properly.' {
            Get-PlainTextFromSecureString -SecureString $decrypted | Should Be $stringToEncrypt
        }
    }

    Context 'Protecting PSCredentials' {
        $protectedData = $credentialToEncrypt | Protect-Data -Password $passwordForEncryption
        $decrypted = $protectedData | Unprotect-Data -Password $passwordForEncryption

        It 'Does not return null' {
            $decrypted | Should Not Be $null
        }

        It 'Returns a PSCredential object' {
            $decrypted.GetType().FullName | Should Be System.Management.Automation.PSCredential
        }

        It 'Decrypts the PSCredential properly (username)' {
            $decrypted.UserName | Should Be $userName
        }

        It 'Decrypts the PSCredential properly (password)' {
            Get-PlainTextFromSecureString -SecureString $decrypted.Password | Should Be $stringToEncrypt
        }
    }

    Context 'Protecting Byte Arrays' {
        $protectedData = Protect-Data -InputObject $byteArrayToEncrypt -Password $passwordForEncryption
        $decrypted = Unprotect-Data -InputObject $protectedData -Password $passwordForEncryption

        It 'Does not return null' {
            ,$decrypted | Should Not Be $null
        }

        It 'Returns a byte array' {
            $decrypted.GetType().FullName | Should Be System.Byte[]
        }

        It 'Decrypts the byte array properly' {
            ($byteArrayToEncrypt.Length -eq $decrypted.Length -and (-join $byteArrayToEncrypt) -eq (-join $decrypted)) | Should Be $True
        }
    }
}

Describe 'Certificate-based encryption and decryption' {
    Get-ChildItem Cert:\CurrentUser\My |
    Where-Object { $_.Subject -eq $testCertificateSubject } |
    Remove-Item

    $certThumbprint = New-TestCertificate -Subject $testCertificateSubject
    $secondCertThumbprint = New-TestCertificate -Subject $testCertificateSubject
    $wrongCertThumbprint = New-TestCertificate -Subject $testCertificateSubject

    Context 'Finding suitable certificates for encryption and decryption' {
        $certificates = @(
            Get-KeyEncryptionCertificate -SkipCertificateVerification -RequirePrivateKey |
            Where-Object { ($certThumbprint, $secondCertThumbprint, $wrongCertThumbprint) -contains $_.Thumbprint }
        )

        It 'Find the test certificates' {
            $certificates.Count | Should Be 3
        }
    }

    Context 'General Usage' {
        It 'Produces an error if a self-signed certificate is used, without the -SkipCertificateVerification switch' {
            { $null = Protect-Data -InputObject $stringToEncrypt -CertificateThumbprint $certThumbprint -ErrorAction Stop } | Should Throw
        }

        It 'Does not produce an error when a self-signed certificate is used, if the -SkipCertificateVerification switch is also used.' {
            { $null = Protect-Data -InputObject $stringToEncrypt -CertificateThumbprint $certThumbprint -SkipCertificateVerification -ErrorAction Stop } | Should Not Throw
        }

        $protected = Protect-Data -InputObject $stringToEncrypt -CertificateThumbprint $certThumbprint, $secondCertThumbprint -SkipCertificateVerification

        It 'Produces an error if a decryption attempt with the wrong certificate is made.' {
            { $null = Unprotect-Data -InputObject $protected -CertificateThumbprint $wrongCertThumbprint -SkipCertificateVerification -ErrorAction Stop } | Should Throw
        }

        It 'Allows any of the specified certificates to be used during decryption (First thumbprint test)' {
            { $null = Unprotect-Data -InputObject $protected -CertificateThumbprint $certThumbprint -SkipCertificateVerification -ErrorAction Stop } | Should Not Throw            
        }

        It 'Allows any of the specified certificates to be used during decryption (Second thumbprint test)' {
            { $null = Unprotect-Data -InputObject $protected -CertificateThumbprint $secondCertThumbprint -SkipCertificateVerification -ErrorAction Stop } | Should Not Throw            
        }

        It 'Adds a new certificate to an existing object' {
            $scriptBlock = {
                Add-ProtectedDataCredential -InputObject $protected -CertificateThumbprint $secondCertThumbprint -NewCertificateThumbprint $wrongCertThumbprint -SkipCertificateVerification
            }

            $scriptBlock | Should Not Throw
        }

        It 'Allows the object to be decrypted with the new certificate' {
            { $null = Unprotect-Data -InputObject $protected -CertificateThumbprint $wrongCertThumbprint -SkipCertificateVerification -ErrorAction Stop } | Should Not Throw
        }

        It 'Removes a certificate from the object' {
            { $null = Remove-ProtectedDataCredential -InputObject $protected -CertificateThumbprint $secondCertThumbprint } | Should Not Throw
        }

        It 'No longer allows the data to be decrypted with the removed password' {
            { $null = Unprotect-Data -InputObject $protected -CertificateThumbprint $secondCertThumbprint -SkipCertificateVerification -ErrorAction Stop } | Should Throw
        }
    }

    Context 'Protecting strings' {
        $protectedData = $stringToEncrypt | Protect-Data -CertificateThumbprint $certThumbprint -SkipCertificateVerification
        $decrypted = $protectedData | Unprotect-Data -CertificateThumbprint $certThumbprint -SkipCertificateVerification

        It 'Does not return null' {
            $decrypted | Should Not Be $null
        }

        It 'Returns a String object' {
            $decrypted.GetType().FullName | Should Be System.String
        }

        It 'Decrypts the string properly.' {
            $decrypted | Should Be $stringToEncrypt
        }
    }

    Context 'Protecting SecureStrings' {
        $protectedData = $secureStringToEncrypt | Protect-Data -CertificateThumbprint $certThumbprint -SkipCertificateVerification
        $decrypted = $protectedData | Unprotect-Data -CertificateThumbprint $certThumbprint -SkipCertificateVerification

        It 'Does not return null' {
            $decrypted | Should Not Be $null
        }

        It 'Returns a SecureString object' {
            $decrypted.GetType().FullName | Should Be System.Security.SecureString
        }

        It 'Decrypts the SecureString properly.' {
            Get-PlainTextFromSecureString -SecureString $decrypted | Should Be $stringToEncrypt
        }
    }

    Context 'Protecting PSCredentials' {
        $protectedData = $credentialToEncrypt | Protect-Data -CertificateThumbprint $certThumbprint -SkipCertificateVerification
        $decrypted = $protectedData | Unprotect-Data -CertificateThumbprint $certThumbprint -SkipCertificateVerification

        It 'Does not return null' {
            $decrypted | Should Not Be $null
        }

        It 'Returns a PSCredential object' {
            $decrypted.GetType().FullName | Should Be System.Management.Automation.PSCredential
        }

        It 'Decrypts the PSCredential properly (username)' {
            $decrypted.UserName | Should Be $userName
        }

        It 'Decrypts the PSCredential properly (password)' {
            Get-PlainTextFromSecureString -SecureString $decrypted.Password | Should Be $stringToEncrypt
        }
    }

    Context 'Protecting Byte Arrays' {
        $protectedData = Protect-Data -InputObject $byteArrayToEncrypt -CertificateThumbprint $certThumbprint -SkipCertificateVerification
        $decrypted = Unprotect-Data -InputObject $protectedData -CertificateThumbprint $certThumbprint -SkipCertificateVerification

        It 'Does not return null' {
            ,$decrypted | Should Not Be $null
        }

        It 'Returns a byte array' {
            $decrypted.GetType().FullName | Should Be System.Byte[]
        }

        It 'Decrypts the byte array properly' {
            ($byteArrayToEncrypt.Length -eq $decrypted.Length -and (-join $byteArrayToEncrypt) -eq (-join $decrypted)) | Should Be $True
        }
    }
}
# SIG # Begin signature block
# MIIhfgYJKoZIhvcNAQcCoIIhbzCCIWsCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUO99HTmncQ7M5e0jAyhW9x6+J
# IwCgghywMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B
# AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk
# IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw
# CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
# ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
# Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg
# +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT
# XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5
# a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g
# 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1
# roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
# GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G
# A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL
# gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3
# cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr
# EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+
# fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q
# Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu
# 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw
# 8jCCBQswggPzoAMCAQICEAOiV15N2F/TLPzy+oVrWjMwDQYJKoZIhvcNAQEFBQAw
# bzELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ
# d3d3LmRpZ2ljZXJ0LmNvbTEuMCwGA1UEAxMlRGlnaUNlcnQgQXNzdXJlZCBJRCBD
# b2RlIFNpZ25pbmcgQ0EtMTAeFw0xNDA1MDUwMDAwMDBaFw0xNTA1MTMxMjAwMDBa
# MGExCzAJBgNVBAYTAkNBMQswCQYDVQQIEwJPTjERMA8GA1UEBxMIQnJhbXB0b24x
# GDAWBgNVBAoTD0RhdmlkIExlZSBXeWF0dDEYMBYGA1UEAxMPRGF2aWQgTGVlIFd5
# YXR0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvcX51YAyViQE16mg
# +IVQCQ0O8QC/wXBzTMPirnoGK9TThmxQIYgtcekZ5Xa/dWpW0xKKjaS6dRwYYXET
# pzozoMWZbFDVrgKaqtuZNu9TD6rqK/QKf4iL/eikr0NIUL4CoSEQDeGLXDw7ntzZ
# XKM86RuPw6MlDapfFQQFIMjsT7YaoqQNTOxhbiFoHVHqP7xL3JTS7TApa/RnNYyl
# O7SQ7TSNsekiXGwUNxPqt6UGuOP0nyR+GtNiBcPfeUi+XaqjjBmpqgDbkEIMLDuf
# fDO54VKvDLl8D2TxTFOcKZv61IcToOs+8z1sWTpMWI2MBuLhRR3A6iIhvilTYRBI
# iX5FZQIDAQABo4IBrzCCAaswHwYDVR0jBBgwFoAUe2jOKarAF75JeuHlP9an90WP
# NTIwHQYDVR0OBBYEFDS4+PmyUp+SmK2GR+NCMiLd+DpvMA4GA1UdDwEB/wQEAwIH
# gDATBgNVHSUEDDAKBggrBgEFBQcDAzBtBgNVHR8EZjBkMDCgLqAshipodHRwOi8v
# Y3JsMy5kaWdpY2VydC5jb20vYXNzdXJlZC1jcy1nMS5jcmwwMKAuoCyGKmh0dHA6
# Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9hc3N1cmVkLWNzLWcxLmNybDBCBgNVHSAEOzA5
# MDcGCWCGSAGG/WwDATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2Vy
# dC5jb20vQ1BTMIGCBggrBgEFBQcBAQR2MHQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBMBggrBgEFBQcwAoZAaHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ29kZVNpZ25pbmdDQS0xLmNydDAM
# BgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBBQUAA4IBAQBbzAp8wys0A5LcuENslW0E
# oz7rc0A8h+XgjJWdJOFRohE1mZRFpdkVxM0SRqw7IzlSFtTMCsVVPNwU6O7y9rCY
# x5agx3CJBkJVDR/Y7DcOQTmmHy1zpcrKAgTznZuKUQZLpoYz/bA+Uh+bvXB9woCA
# IRbchos1oxC+7/gjuxBMKh4NM+9NIvWs6qpnH5JeBidQDQXp3flPkla+MKrPTL/T
# /amgna5E+9WHWnXbMFCpZ5n1bI1OvgNVZlYC/JTa4fjPEk8d16jYVP4GlRz/QUYI
# y6IAGc/z6xpkdtpXWVCbW0dCd5ybfUYTaeCJumGpS/HSJ7JcTZj694QDOKNvhfrm
# MIIGajCCBVKgAwIBAgIQBmQBRumA4A5goU2PREpZWDANBgkqhkiG9w0BAQUFADBi
# MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
# d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENB
# LTEwHhcNMTQwNTIwMDAwMDAwWhcNMTUwNjAzMDAwMDAwWjBHMQswCQYDVQQGEwJV
# UzERMA8GA1UEChMIRGlnaUNlcnQxJTAjBgNVBAMTHERpZ2lDZXJ0IFRpbWVzdGFt
# cCBSZXNwb25kZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpiRj2
# PPRxOH/sRrYt+MkDJSUJPTbcGk2M2As/ngcmXBWQ5G8amisbEZ6DdtNUByvkg0Km
# O23s8/OdbI9WmoGp2cCvETiimoDikBT8EZdCplCdLqmz4EhXLwRJGvXXXSOboHcQ
# 7HPFbxrtzdYTFFtV0PBBMEZIwC56AqrgDo4R/eMkyjA7+Zinu+AnqWkTyNrOfjX8
# 4UX3fPJkFEhBmAMfzojKaB4Qj/GUodhsK/C9a5GFldk7hUyWkC/xLedYAyOA1MzR
# 6FqmUhoRrmNHWqqzPyJgUfb+0rmNBC0/tas1depk00z60EB1kgQmpcIvLOHb68Fr
# 75j00CQ1jx7AFBZBAgMBAAGjggM1MIIDMTAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0T
# AQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDCCAb8GA1UdIASCAbYwggGy
# MIIBoQYJYIZIAYb9bAcBMIIBkjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGln
# aWNlcnQuY29tL0NQUzCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMA
# ZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8A
# bgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAA
# dABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAA
# dABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0A
# ZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQA
# eQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgA
# ZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjALBglghkgBhv1s
# AxUwHwYDVR0jBBgwFoAUFQASKxOYspkH7R7for5XDStnAs0wHQYDVR0OBBYEFDT8
# D0Z+q7fZa134U3JF5gSR08L7MH0GA1UdHwR2MHQwOKA2oDSGMmh0dHA6Ly9jcmwz
# LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3JsMDigNqA0hjJo
# dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNy
# bDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj
# ZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcnQwDQYJKoZIhvcNAQEFBQADggEBABBA
# kLNxn/AeOwLcP7xMFecOORLAhkAWGqBlyJNbwwewpIhED5CUR141wwWy/tidHtT0
# t37GByFeZg/lNbKkHwQqQjDmJ08nYjTAZpTCAi9HeSZKnUpcBLUESPMreUkaRxS8
# FuXHuGdQIL2sxLT9qyGALGCmG6t87wc8QO5pGE3WJ+I0WeEpQiOzPUOdbh6XxN2C
# +PKhFPiN/GZ9ZOxANwEE3kxVTj/TIvhGzy5YwMuwpb7g5RuLSFyyEZECzLlc7P0e
# dSX+fiUWuiwShB/b8Q75BFOy+E2cBkYzcXWGhuNUD9frs9VYrytah8SgMA0zxqbx
# ML7V+381vsbij9kZ75QwggajMIIFi6ADAgECAhAPqEkGFdcAoL4hdv3F7G29MA0G
# CSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0
# IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMTAyMTExMjAwMDBaFw0yNjAyMTAxMjAw
# MDBaMG8xCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNV
# BAsTEHd3dy5kaWdpY2VydC5jb20xLjAsBgNVBAMTJURpZ2lDZXJ0IEFzc3VyZWQg
# SUQgQ29kZSBTaWduaW5nIENBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
# AoIBAQCcfPmgjwrKiUtTmjzsGSJ/DMv3SETQPyJumk/6zt/G0ySR/6hSk+dy+PFG
# hpTFqxf0eH/Ler6QJhx8Uy/lg+e7agUozKAXEUsYIPO3vfLcy7iGQEUfT/k5mNM7
# 629ppFwBLrFm6aa43Abero1i/kQngqkDw/7mJguTSXHlOG1O/oBcZ3e11W9mZJRr
# u4hJaNjR9H4hwebFHsnglrgJlflLnq7MMb1qWkKnxAVHfWAr2aFdvftWk+8b/HL5
# 3z4y/d0qLDJG2l5jvNC4y0wQNfxQX6xDRHz+hERQtIwqPXQM9HqLckvgVrUTtmPp
# P05JI+cGFvAlqwH4KEHmx9RkO12rAgMBAAGjggNDMIIDPzAOBgNVHQ8BAf8EBAMC
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwggHDBgNVHSAEggG6MIIBtjCCAbIGCGCG
# SAGG/WwDMIIBpDA6BggrBgEFBQcCARYuaHR0cDovL3d3dy5kaWdpY2VydC5jb20v
# c3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBu
# AHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0
# AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBl
# ACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAg
# AGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBn
# AHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBi
# AGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0
# AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjAS
# BgNVHRMBAf8ECDAGAQH/AgEAMHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MIGB
# BgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNl
# cnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2Vy
# dC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMB0GA1UdDgQWBBR7aM4p
# qsAXvkl64eU/1qf3RY81MjAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823I
# DzANBgkqhkiG9w0BAQUFAAOCAQEAe3IdZP+IyDrBt+nnqcSHu9uUkteQWTP6K4fe
# qFuAJT8Tj5uDG3xDxOaM3zk+wxXssNo7ISV7JMFyXbhHkYETRvqcP2pRON60Jcvw
# q9/FKAFUeRBGJNE4DyahYZBNur0o5j/xxKqb9to1U0/J8j3TbNwj7aqgTWcJ8zqA
# PTz7NkyQ53ak3fI6v1Y1L6JMZejg1NrRx8iRai0jTzc7GZQY1NWcEDzVsRwZ/4/I
# a5ue+K6cmZZ40c2cURVbQiZyWo0KSiOSQOiG3iLCkzrUm2im3yl/Brk8Dr2fxIac
# gkdCcTKGCZlyCXlLnXFp9UH/fzl3ZPGEjb6LHrJ9aKOlkLEM/zCCBs0wggW1oAMC
# AQICEAb9+QOWA63qAArrPye7uhswDQYJKoZIhvcNAQEFBQAwZTELMAkGA1UEBhMC
# VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0
# LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTA2
# MTExMDAwMDAwMFoXDTIxMTExMDAwMDAwMFowYjELMAkGA1UEBhMCVVMxFTATBgNV
# BAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8G
# A1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMIIBIjANBgkqhkiG9w0BAQEF
# AAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKVpYBzQHDSnlZUXKnE0kEGj8kz/E1FkVyB
# n+0snPgWWd+etSQVwpi5tHdJ3InECtqvy15r7a2wcTHrzzpADEZNk+yLejYIA6sM
# NP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/2DDD/6b3+6LNb3Mj/qxWBZDwMiEWicZw
# iPkFl32jx0PdAug7Pe2xQaPtP77blUjE7h6z8rwMK5nQxl0SQoHhg26Ccz8mSxSQ
# rllmCsSNvtLOBq6thG9IhJtPQLnxTPKvmPv2zkBdXPao8S+v7Iki8msYZbHBc63X
# 8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIUYJX9BwSiCQIDAQABo4IDejCCA3YwDgYD
# VR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYB
# BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCDCCAdIGA1UdIASCAckwggHFMIIBtAYK
# YIZIAYb9bAABBDCCAaQwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQu
# Y29tL3NzbC1jcHMtcmVwb3NpdG9yeS5odG0wggFkBggrBgEFBQcCAjCCAVYeggFS
# AEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBj
# AGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBu
# AGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQ
# AFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAg
# AEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABp
# AGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwBy
# AGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBl
# AC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB/wQIMAYBAf8CAQAweQYIKwYBBQUHAQEE
# bTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYB
# BQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0
# dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5j
# cmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e36K+Vw0rZwLNMB8GA1UdIwQYMBaAFEXr
# oq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBBQUAA4IBAQBGUD7Jtygkpzgd
# tlspr1LPUukxR6tWXHvVDQtBs+/sdR90OPKyXGGinJXDUOSCuSPRujqGcq04eKx1
# XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann4+erYs37iy2QwsDStZS9Xk+xBdIOPRqp
# FFumhjFiqKgz5Js5p8T1zh14dpQlc+Qqq8+cdkvtX8JLFuRLcEwAiR78xXm8TBJX
# /l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD7eeSDY2xaYxP+1ngIw/Sqq4AfO6cQg7P
# kdcntxbuD8O9fAqg7iwIVYUiuOsYGk38KiGtSTGDR5V3cdyxG0tLHBCcdxTBnU8v
# WpUIKRAmMYIEODCCBDQCAQEwgYMwbzELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp
# Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEuMCwGA1UEAxMl
# RGlnaUNlcnQgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EtMQIQA6JXXk3YX9Ms
# /PL6hWtaMzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZ
# BgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYB
# BAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUh2SDSX2Fe7qWBwUHo6yzFZOi0ikwDQYJ
# KoZIhvcNAQEBBQAEggEAqoOf71KiRd+cjIM//3/fj94M/9AZAPwIbsheFNymD2P1
# i4pSjBsWtOA38L3jK5M2FoIVmh1LsioSxmVNd+DRKXiyKWmaLz2da22Av/5uA9es
# nmOrw3tq5v+jKZZG62RpagzIBVjnDqou9VE6Lje3LvAvPGu5RklN4k2bmWl4PMq3
# WxR7KRIf5iCInmLjhE7C+zX/yvXIUf86Yq30ODZdQhPnuyimlpAc36U9svSk824Q
# 1nRvbW1KkB6DlRtWbTVvtRuyh/DYLDihzksvcr2PNetumgig1ly5eNCU5zSXLZoe
# yPjr4ITVFYU8A3qMoGjrT95DhScMv8RaijPQ47l5KKGCAg8wggILBgkqhkiG9w0B
# CQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD
# ZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQBmQBRumA4A5goU2PREpZWDAJBgUrDgMCGgUA
# oF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTQw
# NzI1MTQ1NDUzWjAjBgkqhkiG9w0BCQQxFgQURyVZlON0xI7zy2qpsp7gcCWCnLcw
# DQYJKoZIhvcNAQEBBQAEggEAMEr/xCNYmLbG3L+JZ5Uz1tg8SMKhJoCwkillHZS6
# 3KQngiA9pm7WFPXSMXRNNHXHxU7+d4c7nz8gfjVEIBpeqLvh2ZQ04WlHVKryHPyS
# Hjfj/UvpnDX7s0YlNHuDSvQMtb6ktmUiHhF1TMpadnlpn1CoLuBx+AvDtZG75qjm
# 0mV+P1Eh3OizuW7mQSlSAX63IYe4PRZcyILAC+RinBoaDEf2M8JdKGOLHay1+AX3
# D5GdoAXH87clv1/WNiE3a+1kjoaZy6cw+2o6xyWipVZcm1NfdSr0uIq9XFDi5rYp
# sgjyYT6iAtPB0JnAWVRXCok0lvTnosXnEYclKJMNuYwyWg==
# SIG # End signature block