CertificateValidation/Microsoft.AzureStack.CertificateValidation.psm1
#Requires -RunAsAdministrator #Requires -Version 5.0 <############################################################# # # # Copyright (C) Microsoft Corporation. All rights reserved. # # # #############################################################> <# .SYNOPSIS This module is intended to be given to the customer along with their deploymentdata.json in order to validate the certificates are suitable before Azure Stack deployment. .DESCRIPTION The validation checks the following: Read PFX. Checks for valid PFX file, correct password and warns if public information is not protected by the password. Signature Algorithm. Checks the Signature Algorithm is not SHA1 Private Key. Checks the private key is present and is exported with the Local Machine attribute. Cert Chain. Checks certificate chain is in tact including for self-signed certificates. DNS Names. Checks the SAN contains relevant DNS names for each endpoint or if a supporting wildcard is present. Key Usage. Checks Key Usage contains Digital Signature and Key Encipherment and Enhanced Key Usage contains Server Authentication and Client Authentication. Key Size. Checks Key Size is 2048 or larger Chain Order. Checks the order of the other certificates making the chain is correct. Other Certificates. Ensure no other certificates have been packaged in PFX other than the relevant leaf certificate and its chain. No Profile. Checks a new user can load the PFX data without a user profile loaded, mimicking the behavior of gMSA accounts during certificate servicing. .EXAMPLE To Check certificates are ready for install run the following: Import-Module .\Microsoft.AzureStack.CertificateValidation.psm1 $password = Read-Host -Prompt "Enter PFX Password" -AsSecureString Start-CertChecker -CertificatePath .\Certificates\ -pfxPassword $password -deploymentDataJSONPath .\DeploymentData.json To import/export certificates as indicated by the output of the above command Import-Module .\Microsoft.AzureStack.CertificateValidation.psm1 $password = Read-Host -Prompt "Enter PFX Password" -AsSecureString Start-CertChecker -pfxPassword $password -pfxPath \certificates\acs\old_ssl.pfx -ExportToPath \certificates\acs\new_ssl.pfx [<CommonParameters>] .NOTES The script expects the certificates in the same directory structure as the install: i.e. \Certificates\ \ACS \ssl.pfx \ADFS \ssl.pfx The script requires the deploymentdata.json that will be used for deployment. .LINK #> function Test-AzsCertificates{ [CmdletBinding()] Param( [Parameter(Mandatory=$false,HelpMessage="Enter Path to Certificates Directory")] [string] $CertificatePath = "$PSScriptRoot\Certificates", [Parameter(Mandatory=$true,HelpMessage="Enter Password for PFX Certificates as a secure string")] [securestring] $pfxPassword, [Parameter(Mandatory=$false,ParameterSetName="ValidateJSON")] [string] $deploymentDataJSONPath = "$PSScriptRoot\DeploymentData.json", [Parameter(Mandatory=$true,ParameterSetName="Validate")] [string] $RegionName, [Parameter(Mandatory=$true,ParameterSetName="Validate")] [string] $externalFQDN, [Parameter(Mandatory=$true,ParameterSetName="Validate")] [switch] $UseADFS ) $thisFunction = $MyInvocation.MyCommand.Name if ($PSCmdlet.ParameterSetName -contains 'ValidateJSON') { Try { $deploymentDataJSON = Get-Content $deploymentDataJSONPath | ConvertFrom-Json } Catch { if ($_.exception -like '*Invalid JSON primitive*') { Write-Log -Message "Invalid JSON file provided $deploymentDataJSONPath" -type Error -function $thisFunction } break } # detect deployment type and get region name and fqdn. if ($deploymentDataJSON.DeploymentData.UseAdfs) { $UseADFS = $true } elseif ($deploymentDataJSON.DeploymentData.InfraAzureEnvironment) { $UseADFS = $false } else { Write-Error -message "Failed to parse identity store in DeploymentJSON" } $RegionName = $deploymentDataJSON.DeploymentData.RegionName $externalFQDN = $deploymentDataJSON.DeploymentData.ExternalDomainFQDN } Test-AzsCertificateFolderStructure -UseADFS:$UseADFS -certificatePath $CertificatePath Try { # Tests external certs #ADFS or not if ($UseADFS) { $dirName = 'ADFS' } else { $dirname = 'AAD' } #make the path and copy the certs into a temp cache per the identity system as in deployment $testPath = new-item $ENV:TEMP\Certchecker\certificates\$dirname -ItemType Directory -Force Get-ChildItem $CertificatePath -Recurse -Directory | Copy-Item -Destination $testPath -Recurse $allCertResults = Test-AzureStackCerts -ExpectedDomainFQDN "$RegionName.$externalFQDN" ` -CertificatePassword $pfxPassword ` -UseADFS $UseADFS ` -PfxFilesPath $testPath.Parent.FullName } Catch { $certResult = $_ } Finally { if ($certResult) { Write-Host "Failed" -ForegroundColor Red Write-Host "Detail: $certResult" Write-Host "Please ensure the certificates meet all the requirements as per the companion guide." } #Write Summary to log Write-Log -Message "Validation Summary Results:" -Type Info -Function $thisfunction $allCertResults | Out-File $PSScriptRoot\CertChecker.log -Append -Force #Clean up temp cache Remove-Item $ENV:TEMP\Certchecker -Force -Recurse $allCertResults | Select-Object Result, FailureDetail, Test, Path } } function Test-AzsCertificateFolderStructure { param ([switch]$UseADFS,$certificatePath) # set expected folder names if ($UseADFS) { $validContainers = 'ADFS','Graph','ACSBlob','ACSQueue','ACSTable','Admin Portal','ARM Admin','ARM Public','KeyVault','KeyVaultInternal','Public Portal' } else { $validContainers = 'ACSBlob','ACSQueue','ACSTable','Admin Portal','ARM Admin','ARM Public','KeyVault','KeyVaultInternal','Public Portal' } # Check directory structure is valid $actualContainers = (Get-ChildItem -Path $certificatePath -Directory).Name $compareContainers = Compare-Object -ReferenceObject $validContainers -DifferenceObject $actualContainers if ($compareContainers) { $invalidContainers = $compareContainers | Where-Object SideIndicator -eq '=>' | Select-Object -ExpandProperty InputObject $missingContainers = $compareContainers | Where-Object SideIndicator -eq '<=' | Select-Object -ExpandProperty InputObject if (-not $invalidContainers){$invalidContainers = '[none]'} if (-not $missingContainers){$missingContainers = '[none]'} Write-Error ("Invalid folder structure found in certificate folder {0}, ensure only required folders are present. `nRequired folders: {1} `nInvalid folders: {2} `nMissing folders: {3}" -f ` $CertificatePath, ` ($validContainers -join ', '), ` ($invalidContainers -join ', '), ` ($missingContainers -join ', ') ) } # Check there is only a single cert in each folder foreach ($folder in (Get-ChildItem -Path $certificatePath -Directory)) { $pfxfiles = Get-ChildItem $folder.FullName -Recurse -Filter *.pfx -ErrorAction SilentlyContinue if ($pfxfiles.count -ne 1) { Write-Error ("The certificate Path '{0}' should only contain 1 certificate. `nEnsure all certificate folders only contain a single certificate." -f $folder.FullName) } } } <# .SYNOPSIS Invoke-Microsoft.AzureStack.CertificateValidation is a standalone wrapper for "certchecker" .DESCRIPTION Either invokes certificate validation for Azure Stack public certificates or import/export routine to correct Azure Stack public certificates .PARAMETER CertificatePath Path to directory structure for Azure Stack Public Certificates. The path given for this parameter is expected to contain one of the following sets of sub-directories, each with a single pfx certificate: Identity System AAD: ACSBlob, ACSQueue, ACSTable, Admin Portal, ARM Admin, ARM Public, KeyVault, KeyVaultInternal, Public Portal Identity System ADFS: ADFS, Graph, ACSBlob, ACSQueue, ACSTable, Admin Portal, ARM Admin, ARM Public, KeyVault, KeyVaultInternal, Public Portal .PARAMETER pfxPassword A securestring containing a single password that is common across all certificates .PARAMETER deploymentDataJSONPath Path to deploymentdatajsonPath relevant for the Azure Stack Deployment. .PARAMETER pfxPath Path to pfx file that needs to be imported and exported for remediation purposes. .PARAMETER ExportToPath Path to place exported pfx file after import/export routine. .EXAMPLE To Check certificates are ready for install run the following: Import-Module .\Microsoft.AzureStack.CertificateValidation.psm1 $password = Read-Host -Prompt "Enter PFX Password" -AsSecureString .\Start-CertChecker -CertificatePath .\Certificates\ -pfxPassword $password -deploymentDataJSONPath .\DeploymentData.json To import/export certificates as indicated by the output of the above command Import-Module .\Microsoft.AzureStack.CertificateValidation.psm1 $password = Read-Host -Prompt "Enter PFX Password" -AsSecureString .\Start-CertChecker -pfxPassword $password -pfxPath \certificates\acs\old_ssl.pfx -ExportToPath \certificates\acs\new_ssl.pfx [<CommonParameters>] .NOTES #> function Invoke-AzureStackCertificateValidation { [CmdletBinding()] [Alias("Start-CertChecker","Start-AzsCertChecker","Start-AzureStackCertChecker","CertChecker")] Param( [Parameter(HelpMessage="Enter Path to Certificates Directory")] [Parameter(Mandatory=$false,HelpMessage="Enter Path to Certificates Directory",ParameterSetName="ValidateJSON")] [Parameter(Mandatory=$false,HelpMessage="Enter Path to Certificates Directory",ParameterSetName="Validate")] [string] $CertificatePath = "$PSScriptRoot\Certificates", [Parameter(Mandatory=$false,HelpMessage="Enter Password for PFX Certificates as a secure string")] [securestring] $pfxPassword, [Parameter(Mandatory=$true,HelpMessage="Enter Path to DeploymentData.json",ParameterSetName="ValidateJSON")] [Parameter(Mandatory=$false,ParameterSetName="ValidatePaaS")] [ValidateScript({Test-Path $_ -Include *.json})] [string] $deploymentDataJSONPath, [Parameter(Mandatory=$true,ParameterSetName="Validate")] [Parameter(Mandatory=$false,ParameterSetName="ValidatePaaS")] [string] $RegionName, [Parameter(Mandatory=$true,ParameterSetName="Validate")] [Parameter(Mandatory=$false,ParameterSetName="ValidatePaaS")] [string] $externalFQDN, [Parameter(Mandatory=$true,ParameterSetName="Validate")] [switch] $UseADFS, [Parameter(Mandatory=$true,HelpMessage="Path to PFX on disk",ParameterSetName="ImportExport")] [ValidateScript({Test-Path $_ -Include *.pfx -PathType Leaf})] [string] $pfxPath, [Parameter(Mandatory=$true,HelpMessage="Destination Path for PFX export",ParameterSetName="ImportExport")] [ValidateScript({Test-Path -Path (split-path -Path $_ -Parent)})] [string] $ExportPFXPath, [Parameter(Mandatory=$true,ParameterSetName="ValidatePaaS")] [hashtable] $PaaSCertificates ) $ErrorActionPreference = 'Stop' #setting global variable to true $GLOBAL:standalone = $true $thisFunction = $MyInvocation.MyCommand.Name $mod = Import-Module $PSScriptRoot\PublicCertHelper.psd1 -Force -PassThru if ($PSCmdlet.ParameterSetName -eq 'ValidateJSON') { Write-Host ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version) Write-Log -Message ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version) -Type Info -Function $thisfunction $TestAzsCertificatesResults = Test-AzsCertificates -pfxPassword $pfxPassword -CertificatePath $CertificatePath -deploymentDataJSONPath $deploymentDataJSONPath Write-Log -Message "Ending Certificate Validation" -Type Info -Function $thisfunction $TestAzsCertificatesResults } if ($PSCmdlet.ParameterSetName -eq 'Validate') { Write-Host ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version) Write-Log -Message ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version) -Type Info -Function $thisfunction $TestAzsCertificatesResults = Test-AzsCertificates -pfxPassword $pfxPassword -CertificatePath $CertificatePath -RegionName $RegionName -externalFQDN $externalFQDN -UseADFS:$UseADFS Write-Log -Message "Ending Certificate Validation" -Type Info -Function $thisfunction $TestAzsCertificatesResults } if ($PSCmdlet.ParameterSetName -eq 'ValidatePaaS') { Write-Host ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version) Write-Log -Message ("Starting Azure Stack PaaS Certificate Validation {0} " -f $mod.Version) -Type Info -Function $thisfunction if ($deploymentDataJSONPath) { $deploymentDataJSON = Get-Content $deploymentDataJSONPath | ConvertFrom-Json $RegionName = $deploymentDataJSON.DeploymentData.RegionName $externalFQDN = $deploymentDataJSON.DeploymentData.ExternalDomainFQDN } $TestAzsPaaSCertificatesResults = foreach ($key in $PaaSCertificates.keys) { $PaaSCertificate = $PaaSCertificates[$key] Switch ($key) { 'PaaSDBCert' { $ExpectedPrefix = '*.dbadapter' } 'PaaSDefaultCert' { $ExpectedPrefix = '*.appservice','*.scm.appservice','*.sso.appservice' } 'PaaSAPICert' { $ExpectedPrefix = 'api.appservice' } 'PaaSFTPCert' { $ExpectedPrefix = 'ftp.appservice' } 'PaaSSSOCert' { $ExpectedPrefix = 'sso.appservice' } } Test-AzSCertificate -PfxFile $PaaSCertificate.pfxPath -CertificatePassword $PaaSCertificate.pfxPassword -expectedPrefix $ExpectedPrefix -ExpectedDomainFQDN "$RegionName.$externalFQDN" } Write-Log -Message "Ending PaaS Certificate Validation" -Type Info -Function $thisfunction $TestAzsPaaSCertificatesResults } if ($PSCmdlet.ParameterSetName -eq 'ImportExport') { Write-Host ("Starting Azure Stack Certificate Import/Export {0} " -f $mod.Version) Write-Host "Importing PFX $pfxPath into Local Machine Store" $certificate = Import-AzsCertificate -pfxPath $pfxPath -pfxPassword $pfxPassword Write-Host "Exporting $($certificate.pspath) to $ExportPFXPath" Export-AzsCertificate -filePath $ExportPFXPath -certPath $certificate -pfxPassword $pfxPassword Write-Host "Export complete: $ExportPFXPath" } } # SIG # Begin signature block # MIIdqAYJKoZIhvcNAQcCoIIdmTCCHZUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU34JttV0r+q3qDBRWHAlxcb8a # jp2gghhUMIIEwjCCA6qgAwIBAgITMwAAAL+RbPt8GiTgIgAAAAAAvzANBgkqhkiG # 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw # HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ5 # WhcNMTgwOTA3MTc1ODQ5WjCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046 # NTdDOC0yRDE1LTFDOEIxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl # cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt7X+GwPaidVcV # TRT2yohV/L1dpTMCvf4DHlCY0GUmhEzD4Yn22q/qnqZTHDd8IlI/OHvKhWC9ksKE # F+BgBHtUQPSg7s6+ZXy69qX64r6m7X/NYizeK31DsScLsDHnqsbnwJaNZ2C2u5hh # cKsHvc8BaSsv/nKlr6+eg2iX2y9ai1uB1ySNeunEtdfchAr1U6Qb7AJHrXMTdKl8 # ptLov67aFU0rRRMwQJOWHR+o/gQa9v4z/f43RY2PnMRoF7Dztn6ditoQ9CgTiMdS # MtsqFWMAQNMt5bZ8oY1hmgkSDN6FwTjVyUEE6t3KJtgX2hMHjOVqtHXQlud0GR3Z # LtAOMbS7AgMBAAGjggEJMIIBBTAdBgNVHQ4EFgQU5GwaORrHk1i0RjZlB8QAt3kX # nBEwHwYDVR0jBBgwFoAUIzT42VJGcArtQPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJ # oEegRYZDaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljcm9zb2Z0VGltZVN0YW1wUENBLmNybDBYBggrBgEFBQcBAQRMMEowSAYIKwYB # BQUHMAKGPGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z # b2Z0VGltZVN0YW1wUENBLmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG # 9w0BAQUFAAOCAQEAjt62jcZ+2YBqm7RKit827DRU9OKioi6HEERT0X0bL+JjUTu3 # 7k4piPcK3J/0cfktWuPjrYSuySa/NbkmlvAhQV4VpoWxipx3cZplF9HK9IH4t8AD # YDxUI5u1xb2r24aExGIzWY+1uH92bzTKbAjuwNzTMQ1z10Kca4XXPI4HFZalXxgL # fbjCkV3IKNspU1TILV0Dzk0tdKAwx/MoeZN1HFcB9WjzbpFnCVH+Oy/NyeJOyiNE # 4uT/6iyHz1+XCqf2nIrV/DXXsJYKwifVlOvSJ4ZrV40MYucq3lWQuKERfXivLFXl # dKyXQrS4eeToRPSevRisc0GBYuZczpkdeN5faDCCBgEwggPpoAMCAQICEzMAAADE # 6Yn4eoFQ6f8AAAAAAMQwDQYJKoZIhvcNAQELBQAwfjELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2ln # bmluZyBQQ0EgMjAxMTAeFw0xNzA4MTEyMDIwMjRaFw0xODA4MTEyMDIwMjRaMHQx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHjAcBgNVBAMTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC # ggEBAIiKuCTDB4+agHkV/CZg/HKILPr0o5eIlka3o8tfiS86My4ekXj6fKkfggG1 # essavAPKRuvFmff7BB3yhQr/Im6h8mc9xScY5Sgf9QSUQWPs47oVjO0TmjXeOHBU # bzvsrUUJMEnBvo8wmQzLdsn3c5UWd9GLu5THCIUg7R6oNfFxwuB0AEuK0tyR69Z4 # /o36rWCIPb25H65il7/FhLGQrtavK9NU+zXazXGS5h7/7HFry38IdnTgEFFI1PEA # yEhMowc15VkN/XycyOZa44X11poPH46m5IQXwdbKnx0Bx/1IpxOSM5chSDL4wiSi # ALK+U8qDbilbge84boDzu+wTC+sCAwEAAaOCAYAwggF8MB8GA1UdJQQYMBYGCisG # AQQBgjdMCAEGCCsGAQUFBwMDMB0GA1UdDgQWBBTL1mKEz2A56v9nwlzSyLurt8MT # mDBSBgNVHREESzBJpEcwRTENMAsGA1UECxMETU9QUjE0MDIGA1UEBRMrMjMwMDEy # K2M4MDRiNWVhLTQ5YjQtNDIzOC04MzYyLWQ4NTFmYTIyNTRmYzAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # AAYWH9tXwlDII0+iUXjX7fj9zb3VwPH5G1btU8hpRwXVxMvs4vyZW5VfETgowAVF # E+CaeYi8Zqvbu+sCVSO3PSN4QW2u+PEAWpSZihzMCZXQmhxEMKmlFse6R1v1KzSL # n49YN8NOHK8iyhDN2IIQqTXwriLIjySmgYvfJxzkZh2JPi7/VwNNwW6DoDLrtLMv # UFZdBrEVjMgdY7dzDOPWeiYPKpZFpzKDPpY+V0l3I4n+sRDHiuUIFVHFK1oxWzlq # lqikiGuWKG/xxK7qvUUXzGJOgbVUGkeOmKVtwG4nxvgnH8jtIKkLsfHOC5qU4mqd # aYOhNtdtIP6F1f/DuJc2Cf49FMGYFKnAhszvgsGrVSRDGLVIhXiG0PnSnT8Z2RSJ # 542faCSIaDupx4BOJucIIUxj/ZyTFU0ztVZgT9dKuTiO/y7dsV+kQ2vJeM+xu2uP # g2yHcqrqpfuf3RrWOfxkyW0+COV8g7GtvKO6e8+WVqR6WMsSR2LSIe/8PMQxC/cv # PmSlN29gUD+3RJBPoAuLvn5Y9sdnh2HbnpjEyIzLb0fhwC6U7bH2sDBt7GpJqOmW # dsi9CMT+O/WuczcGslbPGdS79ZTKhxzygGoBT7YbgXOz01siPzpYGN+I7mfESacv # 3CWLPV7Q7DREkR28kQx2gj7vxNgtoQQCjkj5790CzwOiMIIGBzCCA++gAwIBAgIK # YRZoNAAAAAAAHDANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZImiZPyLGQBGRYDY29t # MRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNyb3NvZnQg # Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcwNDAzMTI1MzA5WhcNMjEw # NDAzMTMwMzA5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSEwHwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwggEiMA0GCSqGSIb3 # DQEBAQUAA4IBDwAwggEKAoIBAQCfoWyx39tIkip8ay4Z4b3i48WZUSNQrc7dGE4k # D+7Rp9FMrXQwIBHrB9VUlRVJlBtCkq6YXDAm2gBr6Hu97IkHD/cOBJjwicwfyzMk # h53y9GccLPx754gd6udOo6HBI1PKjfpFzwnQXq/QsEIEovmmbJNn1yjcRlOwhtDl # KEYuJ6yGT1VSDOQDLPtqkJAwbofzWTCd+n7Wl7PoIZd++NIT8wi3U21StEWQn0gA # SkdmEScpZqiX5NMGgUqi+YSnEUcUCYKfhO1VeP4Bmh1QCIUAEDBG7bfeI0a7xC1U # n68eeEExd8yb3zuDk6FhArUdDbH895uyAc4iS1T/+QXDwiALAgMBAAGjggGrMIIB # pzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQjNPjZUkZwCu1A+3b7syuwwzWz # DzALBgNVHQ8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwgZgGA1UdIwSBkDCBjYAU # DqyCYEBWJ5flJRP8KuEKU5VZ5KShY6RhMF8xEzARBgoJkiaJk/IsZAEZFgNjb20x # GTAXBgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eYIQea0WoUqgpa1Mc1j0BxMuZTBQBgNV # HR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9w # cm9kdWN0cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEESDBGMEQG # CCsGAQUFBzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p # Y3Jvc29mdFJvb3RDZXJ0LmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG # 9w0BAQUFAAOCAgEAEJeKw1wDRDbd6bStd9vOeVFNAbEudHFbbQwTq86+e4+4LtQS # ooxtYrhXAstOIBNQmd16QOJXu69YmhzhHQGGrLt48ovQ7DsB7uK+jwoFyI1I4vBT # Fd1Pq5Lk541q1YDB5pTyBi+FA+mRKiQicPv2/OR4mS4N9wficLwYTp2Oawpylbih # OZxnLcVRDupiXD8WmIsgP+IHGjL5zDFKdjE9K3ILyOpwPf+FChPfwgphjvDXuBfr # Tot/xTUrXqO/67x9C0J71FNyIe4wyrt4ZVxbARcKFA7S2hSY9Ty5ZlizLS/n+YWG # zFFW6J1wlGysOUzU9nm/qhh6YinvopspNAZ3GmLJPR5tH4LwC8csu89Ds+X57H21 # 46SodDW4TsVxIxImdgs8UoxxWkZDFLyzs7BNZ8ifQv+AeSGAnhUwZuhCEl4ayJ4i # IdBD6Svpu/RIzCzU2DKATCYqSCRfWupW76bemZ3KOm+9gSd0BhHudiG/m4LBJ1S2 # sWo9iaF2YbRuoROmv6pH8BJv/YoybLL+31HIjCPJZr2dHYcSZAI9La9Zj7jkIeW1 # sMpjtHhUBdRBLlCslLCleKuzoJZ1GtmShxN1Ii8yqAhuoFuMJb+g74TKIdbrHk/J # mu5J4PcBZW+JC33Iacjmbuqnl84xKf8OxVtc2E0bodj6L54/LlUWa8kTo/0wggd6 # MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQg # Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDla # Fw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS6 # 8rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15 # ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+er # CFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVc # eaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGM # XeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/ # U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwj # p6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwC # gl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1J # MKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3co # KPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfe # nk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAw # HQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoA # UwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQY # MBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6 # Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1 # dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAC # hkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1 # dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4D # MIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz # L2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBs # AF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcN # AQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjD # ctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw # /WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkF # DJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3z # Dq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEn # Gn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1F # p3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0Qax # dR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AAp # xbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//W # syNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqx # P/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIEvjCC # BLoCAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO # BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAMTp # ifh6gVDp/wAAAAAAxDAJBgUrDgMCGgUAoIHSMBkGCSqGSIb3DQEJAzEMBgorBgEE # AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJ # BDEWBBRqJOO+VJU5jZF38XcQwfXkjRzxTzByBgorBgEEAYI3AgEMMWQwYqBIgEYA # TQBpAGMAcgBvAHMAbwBmAHQAIABBAHoAdQByAGUAUwB0AGEAYwBrACAAUABhAHIA # dABuAGUAcgBUAG8AbwBsAGsAaQB0oRaAFGh0dHA6Ly9Db2RlU2lnbkluZm8gMA0G # CSqGSIb3DQEBAQUABIIBAHyyFh5iK+q5dd/jnWUwT2//RAHPksiZ8xIMaUeO1Vht # sxWvfe3PHQjK39asWUMMfXJ+QgF/awqJiivoUzdCVUpOvg7gjUwS/PkVhlmiezhP # DLcS/LvIs3Gkh0J4RT7kvIvAPH2hiDgL4WlHUhiKWlRuH3kPjvZn1SmmZnbx8lzd # ldPBEZCLOz/Np2N2xfWOqZzpaz3TwYWweduz+9Dlu5kLVrpcO57jmlLnB8tfsmZM # 6MIytZaxJZJmJoWOIbxO9Rh/m7swhzTtFzV+Yn5VrxU3BjDJPz7KZnhfnTmUBLGw # OC1zf3shvpyw0POZe99/cyWd4dVU4L+jNmkQyMi7t0ChggIoMIICJAYJKoZIhvcN # AQkGMYICFTCCAhECAQEwgY4wdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEhMB8GA1UEAxMYTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBAhMzAAAA # v5Fs+3waJOAiAAAAAAC/MAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZI # hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODA0MjcwOTI5MzVaMCMGCSqGSIb3DQEJ # BDEWBBT7sVk30aDKAcMO1A9Si5d5c7lu1DANBgkqhkiG9w0BAQUFAASCAQBIXZ7/ # urFfuIwabblAajIy/GMwOBHkum0p3ndSaME3e1erinudiQvjlu8RdApVXRpwI1u8 # D94Jtf6W4oGODcjjoMJTG3ar8NhlPPvlP97bfhKLCInkClCHQEl92tQXs1hOdXfJ # HaSphQXJ29iopTQ1+xXvLeh+ZbL4Bh0QTDvFK/3yRJbI1l97cmL7WZ2OabpZbpvB # KtDXtaAZcO8JgfFHuteZa0awc9GQHL6xXTgBeycy3xMT1D1SQ7hXmf65si1XZgkp # 7PzuaPi4QGRt06C3X2uNkAvA9kFrhnETuG+RlzvPv07b611Kk3DuIy5AGn1yCez7 # LCvUj7f1/2BLBTEX # SIG # End signature block |