Validation/PesterHelper.psm1
# Change Security Protocol to TLS1.2 in each function that requires a secured connection # Each function will return to the original state so the tests run from the system default. Import-Module ..\Microsoft.AzureStack.ReadinessChecker.psd1 -Force $GLOBAL:restoreSecProtocol = [Net.ServicePointManager]::SecurityProtocol $GLOBAL:targetSecurityProtocol = [Net.SecurityProtocolType]::Tls12 function Connect-Azure { <# .SYNOPSIS Connect to Azure as Service Principal .DESCRIPTION Connect to Azure as Service Principal neccessary for download or upload. This will scan the localmachine store for the certificate specific to the service principal and authenicate with that. If the certificate does not exist, the user will be prompted for AAD creds and the certificate will be retrieved from the devloop keyvault store. .EXAMPLE PS C:\> Connect-Azure Connects to Azure with service principal with download rights .EXAMPLE PS C:\> Connect-Azure [[-retryCount] <int>] [[-intervalSeconds] <int>] [-upload] Connects to Azure with service principal with upload rights and optionally set retry and intervals. .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES General notes #> param ([int]$retryCount = 3, [int]$intervalSeconds = 5, [switch]$upload) Set-SecurityProtocol -securityProtocol $targetSecurityProtocol $testData = Import-LocalizedData -BaseDirectory $here -FileName PesterHelperData.psd1 if ($upload) { $certPath = $testData.pesterdata.spuCertPath $certName = $testData.pesterdata.spuCertname $applicationId = $testData.pesterdata.spuAppId } else { $certPath = $testData.pesterdata.spCertPath $certName = $testData.pesterdata.spCertname $ApplicationId = $testData.pesterdata.spAppId } $tenantId = $testData.pesterdata.spTenantId $ptkSubscriptionId = $testData.pesterdata.ptksubscriptionId $kvCertName = $testData.pesterdata.spkvCertName $kvVaultName = $testData.pesterdata.spkvname $spSubscriptionId = $testData.pesterData.spSubscriptionId #Get Cert do { $cert = Get-ChildItem -Path $certPath | Where-Object subject -eq "CN=$certName" if ($null -eq $cert) { Write-Verbose "Cert was not found in $certPath, user will be prompted for user credentials and attempt to download the certificate from keyvault." Login-AzureRmAccount -Subscription $spSubscriptionId | Out-Null Write-Verbose "Logged in successfully." Write-Verbose "Retrieving Authentication Certificate for Service Principal." $outputPath = Join-Path $ENV:TEMP "$kvCertName.pfx" $pwd = New-RandomPassword $pfx = Save-PFXData -VaultName $kvVaultName -CertName $kvCertName -outputPath $outputPath -pwd $pwd Write-Verbose "Importing Authentication Certificate for Service Principal to local store." Import-PfxCertificate -Exportable -Password $pfx.pfxpassword -CertStoreLocation $certPath -FilePath $pfx.pfxpath | Out-Null if (Test-Path $pfx.pfxpath) { Remove-Item $pfx.pfxpath -force } $retry++ start-Sleep -Seconds $intervalSeconds } } while (-not $cert -AND $retry -le $retryCount) # Login into Azure using cert try { Login-AzureRmAccount -ServicePrincipal -CertificateThumbprint $cert.Thumbprint -ApplicationId $ApplicationId -TenantId $TenantId -Subscription $ptkSubscriptionId | Out-Null Write-Verbose ("Successfully logged in as service principal {0}." -f $ApplicationId) } catch { Write-Error ("Login into Azure Account failed using cert thumbprint {0}" -f $cert.thumbprint) } #Get Context to return to caller $AzureContext = Get-AzureRmContext Set-SecurityProtocol -securityProtocol $restoreSecProtocol return $AzureContext } function New-RandomPassword { <# .SYNOPSIS Creates a new random password. .DESCRIPTION Creates a new random password. .EXAMPLE PS C:\> New-RandomPassword Creates a new random password. #> Add-Type -AssemblyName System.Web $password = [System.Web.Security.Membership]::GeneratePassword(20, 2) return $password } function Save-PFXData { <# .SYNOPSIS Get PFX secrets from Keyvault and save them to disk .DESCRIPTION Get PFX secrets from Keyvault and save them to disk with a random password .EXAMPLE PS C:\> Save-PFXData [[-VaultName] <String>] [[-CertName] <String>] [[-outputPath] <String>] #> param ([string]$VaultName, [string]$CertName, [string]$outputPath,[string]$pwd) Write-Verbose -Message ("Downloading {0} to {1}..." -f $CertName, $outputPath) try { $pfxPassword = ConvertTo-SecureString -String $pwd -AsPlainText -Force $kvSecret = Get-AzureKeyVaultSecret -VaultName $VaultName -Name $CertName if (-not $kvSecret) { throw ("Unable to retrieve $certname from $vaultname") } $kvSecretBytes = [System.Convert]::FromBase64String($kvSecret.SecretValueText) $certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $certCollection.Import($kvSecretBytes, $null, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) $protectedCertificateBytes = $certCollection.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $pwd) [System.IO.File]::WriteAllBytes($outputPath, $protectedCertificateBytes) start-sleep -sec 1 if (Test-Path -Path $outputPath) { Write-Verbose -Message ("Downloading {0} Complete." -f $CertName) } else { throw ("Unable to resovle path $outputPath") } } catch { throw $_.exception } finally { Set-SecurityProtocol -securityProtocol $restoreSecProtocol } return @{'pfxpath' = $outputPath; 'pfxpassword' = $pfxPassword} } function Get-x509 { <# .SYNOPSIS Creates x509 object of a given certificate .DESCRIPTION Creates x509 object of a given certificate .EXAMPLE PS C:\> Get-x509 [[-certKey] <object>] Explanation of what the example does .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES Expects $certInfo = @{'pfxPath' = <string>;'pfxPassword' = <securestring>}} #> param($certInfo) try { Write-Verbose ("Attempting to read certificate {0}" -f $certKey) [byte[]]$CertificateBinary = Get-Content $certInfo.pfxPath -Encoding Byte $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $cert.Import($CertificateBinary, $certInfo.pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet) Write-Verbose -message ("Successfully read certificate {0}, thumbprint {1}" -f $certInfo.pfxPath, $cert.Thumbprint) } catch { throw $_.exception } return $cert } function Get-PFXHash { <# .SYNOPSIS Calls ParsePFX to retrieve a custom PFX hash object of a given certificate .DESCRIPTION Calls ParsePFX to retrieve a custom PFX hash object of a given certificate .EXAMPLE PS C:\> Get-PFXHash [[-certinfo] <object>] Explanation of what the example does .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES Expects $certInfo = @{'pfxPath' = <string>;'pfxPassword' = <securestring>}} #> param($certInfo) try { Write-Verbose ("Attempting to read PFX {0}" -f $certKey) [byte[]]$CertificateBinary = Get-Content $certInfo.pfxPath -Encoding Byte $pfxParseResult = Test-Pfx -certificateBinary $CertificateBinary -certificatePassword $certInfo.pfxPassword $pfxData = $pfxParseResult.outputObject Write-Verbose -message ("Successfully read PFX {0}" -f $certInfo.pfxPath) } catch { throw $_.exception } return $pfxData } function Save-File { <# .SYNOPSIS Retrieves deployment data from keyvault and saves to JSON. .DESCRIPTION Retrieves deployment data from keyvault and saves to JSON. .EXAMPLE PS C:\> Save-File [[-secretName] <String>] [[-VaultName] <String>] [[-outputPath] <String>] .NOTES General notes #> param ([string]$secretName, [string]$VaultName, [string]$outputPath) try { Set-SecurityProtocol -securityProtocol $targetSecurityProtocol Write-Verbose ("Attempting to retrieve file {0} and save to {1}" -f $secretName, $outputPath) $kvSecret = Get-AzureKeyVaultSecret -VaultName $VaultName -Name $secretName $kvSecret.SecretValueText | Out-File $outputPath start-sleep -sec 1 if (Test-Path -Path $outputPath) { Write-Verbose -Message ("Downloading file {0} to {1} Complete." -f $secretName, $outputPath) } else { throw ("Unable to resovle path $outputPath") } } catch { throw $_.exception } finally { Set-SecurityProtocol -securityProtocol $restoreSecProtocol } return $outputPath } function Get-CredentialSecret { <# .SYNOPSIS Retrieves Credential Secrets from Keyvault .DESCRIPTION Retrieves Credential Secrets from Keyvault .EXAMPLE PS C:\> Get-CredentialSecret [[-username] <string>] [[-password] <string>] [[-VaultName] <String>] Retrieves Credential Secrets from Keyvault .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES General notes #> param ([string]$username, [string]$password, [string]$VaultName) try { Set-SecurityProtocol -securityProtocol $targetSecurityProtocol $passwordValue = Get-AzureKeyVaultSecret -VaultName $VaultName -Name $password | Select-Object -ExpandProperty SecretValue $username = Get-PlainTextSecret -VaultName $VaultName -secretName $username $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username, $passwordValue } catch { throw $_.exception } finally { Set-SecurityProtocol -securityProtocol $restoreSecProtocol } return $credential } function Get-PlainTextSecret { <# .SYNOPSIS Retreives a plain text secret .DESCRIPTION Retreives a plain text secret .EXAMPLE PS C:\> Get-PlainTextSecret [[-secretName] <string>] [[-VaultName] <String>] Explanation of what the example does .INPUTS Inputs (if any) .OUTPUTS Output (if any) .NOTES General notes #> param ([string]$secretName, [string]$VaultName) try { Set-SecurityProtocol -securityProtocol $targetSecurityProtocol $secretValue = Get-AzureKeyVaultSecret -VaultName $VaultName -Name $secretName | Select-Object -ExpandProperty SecretValue $fakeuser = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList dummy, $secretValue $plainValue = $fakeuser.GetNetworkCredential().Password } catch { throw $_.exception } finally { Set-SecurityProtocol -securityProtocol $restoreSecProtocol } return $plainValue } function Remove-TestFiles { <# .SYNOPSIS Removes files .DESCRIPTION Checks files exist and then removes files .EXAMPLE PS C:\> Remove-TestFiles [[-files] <Object>] Removes files in array .NOTES General notes #> [CmdletBinding()] param ([psobject]$in) if (-not $keepLogs) { Write-Verbose ("Attempting removal of test assets: {0}" -f ($in -join ',')) foreach ($item in $in) { try { Get-Item -Path $item.pspath -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -Verbose } catch { Get-Item -Path $item -ErrorAction SilentlyContinue | Remove-Item -Recurse -Force -Verbose } } } else { Write-Verbose ("Skipping deletion of {0}" -f $in -join ',') -Verbose } } function Publish-Certificate { <# .SYNOPSIS Upload Certificate to Keyvault .DESCRIPTION Upload Certificate to Keyvault .SYNTAX .EXAMPLE PS C:\> Publish-Certificate [[-InputObject] <Hashtable>] [[-vaultName] <String>] [<CommonParameters>] Uploads certificates from hashtable in to a named vaultname. .INPUTS Hashtable the format of @{'CertFriendlyName' = @ {'pfxPath' = <string>;'pfxPassword' = <securestring>}} .OUTPUTS Output (if any) .NOTES Requires an Azure Context with the appropriate permissions to upload to keyvault. #> [CmdletBinding()] # Parameter help description param ( [Parameter(ValueFromPipeline = $True)] [hashtable] $InputObject, [string] $vaultName ) Set-SecurityProtocol -securityProtocol $targetSecurityProtocol $AzureContext = Get-AzureRmContext $data = Import-LocalizedData -BaseDirectory $PSScriptRoot -FileName PesterHelperData.psd1 if ($AzureContext.Account.Id -ne $data.pesterdata.spuAppId) { Connect-Azure -upload } $i = 1 foreach ($key in $InputObject.Keys) { try { [int]$percentageComplete = $i / $InputObject.Keys.count * 100 Write-Progress -Activity "Uploading Certificates" -Status "$percentageComplete% Complete:" -PercentComplete $percentageComplete -CurrentOperation $key #to make sure the keys are exportable we must import leaf seperately with its storage flag set and add to collection. $leaf = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 Write-Verbose -Verbose ("Getting Content for {0}" -f $InputObject[$key].pfxpath) [byte[]]$CertificateBinary = Get-Content -Path $InputObject[$key].pfxpath -Encoding Byte $CertificatePassword = $InputObject[$key].pfxpassword $flag = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable $leaf.Import($CertificateBinary, $CertificatePassword, $flag) #Add the other certificates as $pfxData = Get-PFXData -Password $InputObject[$key].pfxpassword -FilePath $InputObject[$key].pfxpath $collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection $pfxData.OtherCertificates | ForEach-Object {$collection.add($_) | Out-Null} $collection.Add($leaf) $pkcs12ContentType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12 $clearBytes = $collection.Export($pkcs12ContentType) $fileContentEncoded = [System.Convert]::ToBase64String($clearBytes) $secret = ConvertTo-SecureString -String $fileContentEncoded -AsPlainText -Force $secretContentType = 'application/x-pkcs12' Set-AzureKeyVaultSecret -VaultName $vaultName -Name $key -SecretValue $Secret -ContentType $secretContentType $i++ } catch { $_ } } Set-SecurityProtocol -securityProtocol $restoreSecProtocol } function Set-SecurityProtocol { param ([Net.SecurityProtocolType]$securityProtocol) $thisFunction = $MyInvocation.MyCommand.Name if ([Net.ServicePointManager]::SecurityProtocol -notmatch $securityProtocol) { Write-Verbose -Message ("{0} not found in current Service Point Manager. Current protocol(s): {1}. Attempting to add for session." -f $securityProtocol,[Net.ServicePointManager]::SecurityProtocol) try { [Net.ServicePointManager]::SecurityProtocol = $securityProtocol Write-Verbose -Message ("Successfully added {0} to Service Point Manager." -f $securityProtocol) } catch { Write-Error -Message ("Setting {0} failed with {1}. Script will continue with existing Security Protocol: {2}" -f $securityProtocol,$_.exception,[Net.ServicePointManager]::SecurityProtocol) } } else { Write-Verbose -Message ("{0} found in current Service Point Manager. No action required." -f $securityProtocol) } } # SIG # Begin signature block # MIIjigYJKoZIhvcNAQcCoIIjezCCI3cCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBLx6fxWLxiLGgf # YgtHo27w/taFfRN5BZaSUJpyv6XGRKCCDYUwggYDMIID66ADAgECAhMzAAABUptA # n1BWmXWIAAAAAAFSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCxp4nT9qfu9O10iJyewYXHlN+WEh79Noor9nhM6enUNbCbhX9vS+8c/3eIVazS # YnVBTqLzW7xWN1bCcItDbsEzKEE2BswSun7J9xCaLwcGHKFr+qWUlz7hh9RcmjYS # kOGNybOfrgj3sm0DStoK8ljwEyUVeRfMHx9E/7Ca/OEq2cXBT3L0fVnlEkfal310 # EFCLDo2BrE35NGRjG+/nnZiqKqEh5lWNk33JV8/I0fIcUKrLEmUGrv0CgC7w2cjm # bBhBIJ+0KzSnSWingXol/3iUdBBy4QQNH767kYGunJeY08RjHMIgjJCdAoEM+2mX # v1phaV7j+M3dNzZ/cdsz3oDfAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU3f8Aw1sW72WcJ2bo/QSYGzVrRYcw # VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh # dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ1NDEzNjAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # AJTwROaHvogXgixWjyjvLfiRgqI2QK8GoG23eqAgNjX7V/WdUWBbs0aIC3k49cd0 # zdq+JJImixcX6UOTpz2LZPFSh23l0/Mo35wG7JXUxgO0U+5drbQht5xoMl1n7/TQ # 4iKcmAYSAPxTq5lFnoV2+fAeljVA7O43szjs7LR09D0wFHwzZco/iE8Hlakl23ZT # 7FnB5AfU2hwfv87y3q3a5qFiugSykILpK0/vqnlEVB0KAdQVzYULQ/U4eFEjnis3 # Js9UrAvtIhIs26445Rj3UP6U4GgOjgQonlRA+mDlsh78wFSGbASIvK+fkONUhvj8 # B8ZHNn4TFfnct+a0ZueY4f6aRPxr8beNSUKn7QW/FQmn422bE7KfnqWncsH7vbNh # G929prVHPsaa7J22i9wyHj7m0oATXJ+YjfyoEAtd5/NyIYaE4Uu0j1EhuYUo5VaJ # JnMaTER0qX8+/YZRWrFN/heps41XNVjiAawpbAa0fUa3R9RNBjPiBnM0gvNPorM4 # dsV2VJ8GluIQOrJlOvuCrOYDGirGnadOmQ21wPBoGFCWpK56PxzliKsy5NNmAXcE # x7Qb9vUjY1WlYtrdwOXTpxN4slzIht69BaZlLIjLVWwqIfuNrhHKNDM9K+v7vgrI # bf7l5/665g0gjQCDCN6Q5sxuttTAEKtJeS/pkpI+DbZ/MIIHejCCBWKgAwIBAgIK # YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV # BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv # c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm # aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw # OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD # VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG # 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la # UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc # 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D # dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ # lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk # kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 # A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd # X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL # 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd # sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 # T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS # 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI # bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL # BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD # uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv # c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf # MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF # BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h # cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA # YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn # 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 # v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b # pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ # KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy # CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp # mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi # hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb # BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS # oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL # gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX # cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCFVswghVXAgEBMIGVMH4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p # Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAFSm0CfUFaZdYgAAAAA # AVIwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEID0i # 73sOOfgn5RMks20mXKN45PKo6HKP8rQPgrSEGaT0MEIGCisGAQQBgjcCAQwxNDAy # oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20wDQYJKoZIhvcNAQEBBQAEggEACe/uTDLX+zIq0zeEyfElPzucMNpY2lOqmWDg # /JK28h+rY94ylCpnrS0Pob+04+2x4/wp/GkRwBzuL1B5gdY2JIgFddo5idVu1alt # 3i8apYq2LF4Mk7YniHp4qziKpZo2Iw/TiV94AW2LMr8oL2Ly09ip+tK0xldLrlQN # B3A+C5+jfcquk5J9LsJtQQdw6DQ2kMfLsoYpIeJHp1G780Je7xkVcc8sYXrdO6aT # FVfeUCtrezOsfRCG+g1StJxQodO1zEqP/0SmKuKCRyVzt7/diIbHr+RrqR3Ehl11 # uHpfawVt5/ZmK2H08EyraCENaTDG+GhSMuh7AeXGQLfJ/80U86GCEuUwghLhBgor # BgEEAYI3AwMBMYIS0TCCEs0GCSqGSIb3DQEHAqCCEr4wghK6AgEDMQ8wDQYJYIZI # AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE # WQoDATAxMA0GCWCGSAFlAwQCAQUABCBhXHvPW0PV8HDhfCnnGyeywEgE3vpW8f16 # 92d4C1g/lQIGXi8g9ZSUGBMyMDIwMDIxMjIyMTAxMS45MDdaMASAAgH0oIHQpIHN # MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL # ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjozRTdBLUUzNTktQTI1RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDjwwggTxMIID2aADAgECAhMzAAABIBo529lrn63yAAAA # AAEgMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTE5MTExMzIxNDA0MloXDTIxMDIxMTIxNDA0MlowgcoxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy # aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjNFN0EtRTM1 # OS1BMjVEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIB # IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtQTrg5qYEuZiyQz1EGhHFch+ # WxwS3OC76uYBhxeSdcj4YW4PDUhK1dHz/jeQkMHuCky1PwUTjqR5TvwVdVBjrPgW # rxDxkYlWHYQKips1lPk1mwDlcZK56WvxSegOUOXLyDUojmm8jnx3tnTBQkD5YIAc # 6gt8fZZ5yZFMUqXrX03I+hVQsMWFT5Oec8+DuFKZnohw4lhAaQva+uFR2WTks/PW # ezC9ZaXpzNq/Plp2m2Sz6FNmO6fUKz80B+4L0Irv84HGGDKplSJXxUz0tYjr2WwK # pNVOgTUYJzioqw/tPvMrozOAmP9A5WQw5S31O1YPKI7BoO6XlYfjJ2A8JcihsQID # AQABo4IBGzCCARcwHQYDVR0OBBYEFJnZNgSw30fFH5zd6SxlAnGnIlXPMB8GA1Ud # IwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0 # dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKG # Pmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENB # XzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUH # AwgwDQYJKoZIhvcNAQELBQADggEBAGLpUHZxSxORQizBFD6QTUsvjgicJmO4oQBe # CcntEkPHwydAmIbGFP7h1JfKE07l+SUDjV+kJyklP/v9F7ns0hbbmLDfrla7xiHI # hlwij7j7YBYxSQUlucuMcbq38wLjztdsBiXVQ+CftIoReVq/kTVG1/TD4lPtKrCC # gz7NqQNyjXVwj/ZuFi4v/hW9CjBsKF+l7QwrwR1cNql26nCLZYeCXzPby34woMEu # s2yLe4oASji10VmjqqXiH6rAR1YrTzeUbJ3nlz6h6QTk/sArJu5ZKgwDkvfEQ1si # XvIWQIHnlyxxe4C/5QYEG80nFmMdLYP+PoZT4o8sAAHtEgTA5JgwggZxMIIEWaAD # AgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBD # ZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0yNTA3 # MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjANBgkq # hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RUENWl # CgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBED/Fg # iIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50YWeR # X4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd/Xcf # PfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaRtogI # Neh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQABo4IB # 5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8RhvF # M2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAP # BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjE # MFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kv # Y3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEF # BQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w # a2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSABAf8E # gZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3dy5t # aWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEFBQcC # AjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBtAGUA # bgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Ehb7Pr # psz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7uVOM # zPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqRUgCv # OA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9Va8v # /rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8+n99 # lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+Y1kl # D3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh2rBQ # Hm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRyzR30 # uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoouLGp # 25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx16HS # xVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341Hgi6 # 2jbb01+P3nSISRKhggLOMIICNwIBATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMx # EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT # FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJp # Y2EgT3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046M0U3QS1FMzU5 # LUEyNUQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoB # ATAHBgUrDgMCGgMVAL9a/LhcpMcNjYNHvPBmpZcTZ15MoIGDMIGApH4wfDELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z # b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDh7rczMCIY # DzIwMjAwMjEzMDE0MTA3WhgPMjAyMDAyMTQwMTQxMDdaMHcwPQYKKwYBBAGEWQoE # ATEvMC0wCgIFAOHutzMCAQAwCgIBAAICILUCAf8wBwIBAAICEbEwCgIFAOHwCLMC # AQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEK # MAgCAQACAwGGoDANBgkqhkiG9w0BAQUFAAOBgQCw7iMfyZs7qRQ/y29AZ5e1w0sM # yRotgCiJgEnsTrIadvN9/h5wOzq6hrdpluYkqHN1ne1fcoGGHPhvnM3W6nHsek+S # vJVQCT21iDPlW0hvRMO/yNmsiP6PUQn5SDtubnlQ6Sk278uPvzFb3QJw0tx/iROD # pOdtw2LzYMHas+nvjzGCAw0wggMJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w # IFBDQSAyMDEwAhMzAAABIBo529lrn63yAAAAAAEgMA0GCWCGSAFlAwQCAQUAoIIB # SjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIBz2 # vqwiJvWrqxFXOa2mXbimDlCRWDCqOGdALua9DSw+MIH6BgsqhkiG9w0BCRACLzGB # 6jCB5zCB5DCBvQQgHAxQaxScdtf1UeE7BZLSQUmEIBz+xspjrfdonmfAGlUwgZgw # gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD # VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAASAaOdvZa5+t # 8gAAAAABIDAiBCBFWyvwTtUCgdmaN0QA0rJf7kzCKXZT/D0ig2g4VCcfcDANBgkq # hkiG9w0BAQsFAASCAQBj6/lE/sRwohHi1GDgkZx4ffpoucJd/QpzZkaNUJQ2SrDz # uHjenPYmKeRs12fwwc5g1D+et5YQ1X1HcTDEUOluAc0BRATdZl0C4Ek/zXB8KI+s # h6H70R+Myg/o62FAjwuUdB9jYos7nx6ap+1KzJyB7+LwCS3SnZGqq5XHkzQiKXZz # LIYuJvnf46IexexsPOQCI+VkMMY0hQ8wClgB6t9Bgodc945w2pWJreE5uQrzox1g # o6BfA/JgJMGve0zFPp6DBOOITPpCSB5d6R/1CsnDT5saiEOX2oVbKQApQINnkLQW # twhRx9dXWS62d98cW2yRhvQk2MVBDacg8r0pnMco # SIG # End signature block |