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 # NzI1MTUwNzU3WjAjBgkqhkiG9w0BCQQxFgQURyVZlON0xI7zy2qpsp7gcCWCnLcw # DQYJKoZIhvcNAQEBBQAEggEAPuNe/N3aam2vW/mBp0453m9Us+HXh9paFEGAxZ4n # NG8WMugX9Px7kGkCMkSxwT3hqa2XOLOAz+qwYmgv0BA8iTn2BmtbBR+LNMX+MgS1 # oLl9V3TAw1bLeHqf63M51trvw4g1VHBgQANRo/8refgEMi97rnSojFBEEZoR96cr # bhCcg4Drx+aeSigoxShMaQZG1vk6iThKhhvLvD+4X5SwpvRJCzgnOn9cRO00ti+v # O8DnoK04uFNWDO9uBB++hA7rEGhxKOELFK8rM7wWTMFDFhEgxkIqZY24EgUpY7eK # BmnfFnB1FyJg0vYyCpxFhO6H/jRvLi44xnTi449pPHuHnQ== # SIG # End signature block |