NuGet/NuGetFeedClass.ps1
|
#requires -Version 5.0 [NuGetFeed[]] $NuGetFeedCache = @() # PROOF OF CONCEPT PREVIEW: This class holds the connection to a NuGet feed class NuGetFeed { [string] $url [string] $token [string[]] $patterns [string[]] $fingerprints [string] $searchQueryServiceUrl [string] $packagePublishUrl [string] $packageBaseAddressUrl [hashtable] $orgType = @{} [hashtable] $searchResultsCache = @{} [int] $searchResultsCacheRetentionPeriod [string] $cacheFolder NuGetFeed([string] $nuGetServerUrl, [string] $nuGetToken, [string[]] $patterns, [string[]] $fingerprints, [int] $nuGetSearchResultsCacheRetentionPeriod, [string] $nuGetCacheFolder) { $this.url = $nuGetServerUrl $this.token = $nuGetToken $this.patterns = $patterns $this.fingerprints = $fingerprints $this.searchResultsCacheRetentionPeriod = $nuGetSearchResultsCacheRetentionPeriod $this.cacheFolder = $nugetCacheFolder # When trusting nuget.org, you should only trust packages signed by an author or packages matching a specific pattern (like using a registered prefix or a full name) if ($nuGetServerUrl -like 'https://api.nuget.org/*' -and $patterns.Contains('*') -and (!$fingerprints -or $fingerprints.Contains('*'))) { throw "Trusting all packages on nuget.org is not supported" } try { $prev = $global:ProgressPreference; $global:ProgressPreference = "SilentlyContinue" $capabilities = Invoke-RestMethod -UseBasicParsing -Method GET -Headers ($this.GetHeaders()) -Uri $this.url $global:ProgressPreference = $prev $this.searchQueryServiceUrl = $capabilities.resources | Where-Object { $_.'@type' -eq 'SearchQueryService' } | Select-Object -ExpandProperty '@id' | Select-Object -First 1 if (!$this.searchQueryServiceUrl) { # Azure DevOps doesn't support SearchQueryService, but SearchQueryService/3.0.0-beta $this.searchQueryServiceUrl = $capabilities.resources | Where-Object { $_.'@type' -eq 'SearchQueryService/3.0.0-beta' } | Select-Object -ExpandProperty '@id' | Select-Object -First 1 } $this.packagePublishUrl = $capabilities.resources | Where-Object { $_."@type" -eq 'PackagePublish/2.0.0' } | Select-Object -ExpandProperty '@id' | Select-Object -First 1 $this.packageBaseAddressUrl = $capabilities.resources | Where-Object { $_."@type" -eq 'PackageBaseAddress/3.0.0' } | Select-Object -ExpandProperty '@id' | Select-Object -First 1 if (!$this.searchQueryServiceUrl -or !$this.packagePublishUrl -or !$this.packageBaseAddressUrl) { Write-Host "Capabilities of NuGet server $($this.url) are not supported" $capabilities.resources | ForEach-Object { Write-Host "- $($_.'@type')"; Write-Host "-> $($_.'@id')" } } Write-Verbose "Capabilities of NuGet server $($this.url) are:" Write-Verbose "- SearchQueryService=$($this.searchQueryServiceUrl)" Write-Verbose "- PackagePublish=$($this.packagePublishUrl)" Write-Verbose "- PackageBaseAddress=$($this.packageBaseAddressUrl)" } catch { throw (GetExtendedErrorMessage $_) } } static [NuGetFeed] Create([string] $nuGetServerUrl, [string] $nuGetToken, [string[]] $patterns, [string[]] $fingerprints, [int] $nuGetSearchResultsCacheRetentionPeriod, [string] $nuGetCacheFolder) { $nuGetFeed = $script:NuGetFeedCache | Where-Object { $_.url -eq $nuGetServerUrl -and $_.token -eq $nuGetToken -and (-not (Compare-Object $_.patterns $patterns)) -and (-not (Compare-Object $_.fingerprints $fingerprints)) -and $_.searchResultsCacheRetentionPeriod -eq $nuGetSearchResultsCacheRetentionPeriod } if (!$nuGetFeed) { $nuGetFeed = [NuGetFeed]::new($nuGetServerUrl, $nuGetToken, $patterns, $fingerprints, $nuGetSearchResultsCacheRetentionPeriod, $nugetCacheFolder) $script:NuGetFeedCache += $nuGetFeed } return $nuGetFeed } [void] Dump([string] $message) { Write-Host $message } [hashtable] GetHeaders() { $headers = @{ "Content-Type" = "application/json; charset=utf-8" } # nuget.org only support anonymous access if ($this.token -and $this.url -notlike 'https://api.nuget.org/*') { $headers += @{ "Authorization" = "Basic $([Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("user:$($this.token)")))" } } return $headers } [bool] IsTrusted([string] $packageId) { return ($packageId -and ($this.patterns | Where-Object { $packageId -like $_ })) } [hashtable[]] Search([string] $packageName) { $useCache = $this.searchResultsCacheRetentionPeriod -gt 0 $hasCache = $this.searchResultsCache.ContainsKey($packageName) if ($hasCache) { if ($useCache) { # Clear cache older than the retention period $clearCache = $this.searchResultsCache[$packageName].timestamp.AddSeconds($this.searchResultsCacheRetentionPeriod) -lt (Get-Date) } else { # Clear cache if we are not using it $clearCache = $true } if ($clearCache) { $this.searchResultsCache.Remove($packageName) $hasCache = $false } } if ($useCache -and $hasCache) { Write-Host "Search package using cache" $matching = $this.searchResultsCache[$packageName].matching } elseif ($this.searchQueryServiceUrl -match '^https://nuget.pkg.github.com/(.*)/query$') { # GitHub support for SearchQueryService is unstable and is not usable # use GitHub API instead # GitHub API unfortunately doesn't support filtering, so we need to filter ourselves $organization = $matches[1] $headers = @{ "Accept" = "application/vnd.github+json" "X-GitHub-Api-Version" = "2022-11-28" } if ($this.token) { $headers += @{ "Authorization" = "Bearer $($this.token)" } } if (-not $this.orgType.ContainsKey($organization)) { $orgMetadata = Invoke-RestMethod -Method GET -Headers $headers -Uri "https://api.github.com/users/$organization" if ($orgMetadata.type -eq 'Organization') { $this.orgType[$organization] = 'orgs' } else { $this.orgType[$organization] = 'users' } } $cacheKey = "GitHubPackages:$($this.orgType[$organization])/$organization" $matching = @() if ($this.searchResultsCacheRetentionPeriod -gt 0 -and $this.searchResultsCache.ContainsKey($cacheKey)) { if ($this.searchResultsCache[$cacheKey].timestamp.AddSeconds($this.searchResultsCacheRetentionPeriod) -lt (Get-Date)) { Write-Host "Cache expired, removing cache $cacheKey" $this.searchResultsCache.Remove($cacheKey) } else { Write-Host "Search available packages using cache $cacheKey" $matching = $this.searchResultsCache[$cacheKey].matching Write-Host "$($matching.Count) packages found" } } if (-not $matching) { $per_page = 50 $queryUrl = "https://api.github.com/$($this.orgType[$organization])/$organization/packages?package_type=nuget&per_page=$($per_page)&page=" $page = 1 $matching = @() while ($true) { Write-Host -ForegroundColor Yellow "Search package using $queryUrl$page" $result = Invoke-RestMethod -UseBasicParsing -Method GET -Headers $headers -Uri "$queryUrl$page" Write-Host "$($result.Count) packages found" if ($result.Count -eq 0) { break } $matching += @($result) if ($result.Count -ne $per_page) { break } $page++ } Write-Host "Total of $($matching.Count) packages found" $this.searchResultsCache[$cacheKey] = @{ matching = $matching timestamp = (Get-Date) } } $matching = @($matching | Where-Object { $_.name -like "*$packageName*" -and $this.IsTrusted($_.name) } | Sort-Object { $_.name.replace('.symbols','') } | ForEach-Object { @{ "id" = $_.name; "versions" = @() } } ) } else { $queryUrl = "$($this.searchQueryServiceUrl)?q=$packageName&take=50" try { Write-Host -ForegroundColor Yellow "Search package using $queryUrl" $prev = $global:ProgressPreference; $global:ProgressPreference = "SilentlyContinue" $searchResult = Invoke-RestMethod -UseBasicParsing -Method GET -Headers ($this.GetHeaders()) -Uri $queryUrl $global:ProgressPreference = $prev } catch { throw (GetExtendedErrorMessage $_) } # Check that the found pattern matches the package name and the trusted patterns $matching = @($searchResult.data | Where-Object { $_.id -like "*$($packageName)*" -and $this.IsTrusted($_.id) } | Sort-Object { $_.id.replace('.symbols','') } | ForEach-Object { @{ "id" = $_.id; "versions" = @($_.versions.version) } } ) } $exact = $matching | Where-Object { $_.id -eq $packageName -or $_.id -eq "$packageName.symbols" } if ($exact) { Write-Host "Exact match found for $packageName" $matching = $exact } else { Write-Host "$($matching.count) matching packages found" } if ($useCache -and !$hasCache) { # Cache the search results $this.searchResultsCache[$packageName] = @{ matching = $matching timestamp = (Get-Date) } } return $matching | ForEach-Object { Write-Host "- $($_.id)"; $_ } } [string[]] GetVersions([hashtable] $package, [bool] $descending, [bool] $allowPrerelease) { if (!$this.IsTrusted($package.id)) { throw "Package $($package.id) is not trusted on $($this.url)" } if ($package.versions.count -eq 0) { $queryUrl = "$($this.packageBaseAddressUrl.TrimEnd('/'))/$($package.Id.ToLowerInvariant())/index.json" try { Write-Host -ForegroundColor Yellow "Get versions using $queryUrl" $prev = $global:ProgressPreference; $global:ProgressPreference = "SilentlyContinue" $versions = Invoke-RestMethod -UseBasicParsing -Method GET -Headers ($this.GetHeaders()) -Uri $queryUrl $global:ProgressPreference = $prev } catch { throw (GetExtendedErrorMessage $_) } $package.versions = @($versions.versions) } $versionsArr = $package.versions Write-Host "$($versionsArr.count) versions found" $versionsArr = @($versionsArr | Where-Object { $allowPrerelease -or !$_.Contains('-') } | Sort-Object { ($_ -replace '-.+$') -as [System.Version] }, { "$($_)z" } -Descending:$descending | ForEach-Object { "$_" }) Write-Host "First version is $($versionsArr[0])" Write-Host "Last version is $($versionsArr[$versionsArr.Count-1])" return $versionsArr } # Normalize name or publisher name to be used in nuget id static [string] Normalize([string] $name) { return $name -replace '[^a-zA-Z0-9_\-]','' } static [string] NormalizeVersionStr([string] $versionStr) { $idx = $versionStr.IndexOf('-') $version = [System.version]($versionStr.Split('-')[0]) if ($version.Build -eq -1) { $version = [System.Version]::new($version.Major, $version.Minor, 0, 0) } if ($version.Revision -eq -1) { $version = [System.Version]::new($version.Major, $version.Minor, $version.Build, 0) } if ($idx -gt 0) { return "$version$($versionStr.Substring($idx))" } else { return "$version" } } static [Int32] CompareVersions([string] $version1, [string] $version2) { $version1 = [NuGetFeed]::NormalizeVersionStr($version1) $version2 = [NuGetFeed]::NormalizeVersionStr($version2) $ver1 = $version1 -replace '-.+$' -as [System.Version] $ver2 = $version2 -replace '-.+$' -as [System.Version] if ($ver1 -eq $ver2) { # add a 'z' to the version to make sure that 5.1.0 is greater than 5.1.0-beta # Tags are sorted alphabetically (alpha, beta, rc, etc.), even though this shouldn't matter # New prerelease versions will always have a new version number return [string]::Compare("$($version1)z", "$($version2)z") } elseif ($ver1 -gt $ver2) { return 1 } else { return -1 } } # Test if version is included in NuGet version range # https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#version-ranges static [bool] IsVersionIncludedInRange([string] $versionStr, [string] $nuGetVersionRange) { $versionStr = [NuGetFeed]::NormalizeVersionStr($versionStr) $version = $versionStr -replace '-.+$' -as [System.Version] if ($nuGetVersionRange -match '^\s*([\[(]?)([\d\.]*)(,?)([\d\.]*)([\])]?)\s*$') { $inclFrom = $matches[1] -ne '(' $range = $matches[3] -eq ',' $inclTo = $matches[5] -eq ']' if ($matches[1] -eq '' -and $matches[5] -eq '') { $range = $true } if ($matches[2]) { $fromver = [System.Version]([NuGetFeed]::NormalizeVersionStr($matches[2])) } else { $fromver = [System.Version]::new(0,0,0,0) if ($inclFrom) { Write-Host "Invalid NuGet version range $nuGetVersionRange" return $false } } if ($matches[4]) { $tover = [System.Version]([NuGetFeed]::NormalizeVersionStr($matches[4])) } elseif ($range) { $tover = [System.Version]::new([int32]::MaxValue,[int32]::MaxValue,[int32]::MaxValue,[int32]::MaxValue) if ($inclTo) { Write-Host "Invalid NuGet version range $nuGetVersionRange" return $false } } else { $tover = $fromver } if (!$range -and (!$inclFrom -or !$inclTo)) { Write-Host "Invalid NuGet version range $nuGetVersionRange" return $false } if ($inclFrom) { if ($inclTo) { return $version -ge $fromver -and $version -le $tover } else { return $version -ge $fromver -and $version -lt $tover } } else { if ($inclTo) { return $version -gt $fromver -and $version -le $tover } else { return $version -gt $fromver -and $version -lt $tover } } } return $false } [string] FindPackageVersion([hashtable] $package, [string] $nuGetVersionRange, [string[]] $excludeVersions, [string] $select, [bool] $allowPrerelease) { $versions = $this.GetVersions($package, !($select -eq 'Earliest' -or $select -eq 'AllAscending'), $allowPrerelease) if ($excludeVersions) { Write-Host "Exclude versions: $($excludeVersions -join ', ')" } $versionList = @() foreach($version in $versions ) { if ($excludeVersions -contains $version) { continue } if (($select -eq 'Exact' -and [NuGetFeed]::NormalizeVersionStr($nuGetVersionRange) -eq [NuGetFeed]::NormalizeVersionStr($version)) -or ($select -ne 'Exact' -and [NuGetFeed]::IsVersionIncludedInRange($version, $nuGetVersionRange))) { if ($select -eq 'AllAscending' -or $select -eq 'AllDescending') { Write-Host "Include $version" } elseif ($nuGetVersionRange -eq '0.0.0.0') { Write-Host "$select version is $version" } else { Write-Host "$select version matching '$nuGetVersionRange' is $version" } $versionList += @($version) } } return ($versionList -join ',') } # Download the specs for the package with id = packageId and version = version # The following properties are returned: # - id: the package id # - name: the package name (either title, description or id from the nuspec) # - version: the package version # - authors: the package authors # - dependencies: the package dependencies (id and version range) [PSCustomObject] DownloadPackageSpec([string] $packageId, [string] $version) { $nuSpecName = "$($packageId.ToLowerInvariant())/$($version.ToLowerInvariant()).json" $nuSpecFileName = Join-Path $this.cacheFolder $nuSpecName $nuSpecMutex = New-Object System.Threading.Mutex($false, $nuSpecName.Replace('/','_').Replace('\','_')) try { try { if (!$nuSpecMutex.WaitOne(1000)) { Write-Host "Waiting for other process downloading nuspec '$($nuSpecName)'" $nuSpecMutex.WaitOne() | Out-Null Write-Host "Other process completed download" } } catch [System.Threading.AbandonedMutexException] { Write-Host "Other process terminated abnormally" } if (Test-Path $nuSpecFileName) { Write-Host "Using cached nuspec for $packageId version $version" (Get-Item $nuSpecFileName).LastWriteTime = Get-Date return (Get-Content -Path $nuSpecFileName | ConvertFrom-Json | ConvertTo-HashTable) } if (!$this.IsTrusted($packageId)) { throw "Package $packageId is not trusted on $($this.url)" } if ($this.packageBaseAddressUrl -like 'https://nuget.pkg.github.com/*') { $queryUrl = "$($this.packageBaseAddressUrl.SubString(0,$this.packageBaseAddressUrl.LastIndexOf('/')))/$($packageId.ToLowerInvariant())/$($version.ToLowerInvariant()).json" Write-Host "Download nuspec using $queryUrl" $response = Invoke-WebRequest -UseBasicParsing -Method GET -Headers ($this.GetHeaders()) -Uri $queryUrl $content = $response.Content | ConvertFrom-Json if (!($content.PSObject.Properties.Name -eq 'catalogEntry') -or ($null -eq $content.catalogEntry)) { throw "Package $packageId version $version not found on" } $returnValue = @{ "id" = $content.catalogEntry.id "name" = $content.catalogEntry.description "version" = $content.catalogEntry.version "authors" = $content.catalogEntry.authors "dependencies" = $content.catalogEntry.dependencyGroups | ForEach-Object { $_.dependencies | ForEach-Object { @{"id" = $_.id; "version" = $_.range.replace(' ','') } } } } } else { $queryUrl = "$($this.packageBaseAddressUrl.TrimEnd('/'))/$($packageId.ToLowerInvariant())/$($version.ToLowerInvariant())/$($packageId.ToLowerInvariant()).nuspec" try { Write-Host "Download nuspec using $queryUrl" $prev = $global:ProgressPreference; $global:ProgressPreference = "SilentlyContinue" $tmpFile = Join-Path ([System.IO.Path]::GetTempPath()) "$([GUID]::NewGuid().ToString()).nuspec" Invoke-RestMethod -UseBasicParsing -Method GET -Headers ($this.GetHeaders()) -Uri $queryUrl -OutFile $tmpFile $nuspec = [xml](Get-Content -Path $tmpfile -Encoding UTF8 -Raw) Remove-Item -Path $tmpFile -Force $global:ProgressPreference = $prev } catch { throw (GetExtendedErrorMessage $_) } if ($nuspec.package.metadata.PSObject.Properties.Name -eq 'title') { $appName = $nuspec.package.metadata.title } elseif ($nuspec.package.metadata.PSObject.Properties.Name -eq 'description') { $appName = $nuspec.package.metadata.description } else { $appName = $nuspec.package.metadata.id } if ($nuspec.package.metadata.PSObject.Properties.Name -eq 'Dependencies') { $dependencies = @($nuspec.package.metadata.Dependencies.GetEnumerator() | ForEach-Object { @{"id" = $_.id; "version" = $_.version } }) } else { $dependencies = @() } $returnValue = @{ "id" = $nuspec.package.metadata.id "name" = $appName "version" = $nuspec.package.metadata.version "authors" = $nuspec.package.metadata.authors "dependencies" = $dependencies } } New-Item -Path $nuSpecFileName -ItemType File -Force -Value ($returnValue | ConvertTo-Json -Depth 99) | Out-Null return $returnValue } finally { $nuSpecMutex.ReleaseMutex() } } [string] DownloadPackage([string] $packageId, [string] $version) { $packageSubFolder = "$($packageId.ToLowerInvariant())/$($version.ToLowerInvariant())" $packageMutex = New-Object System.Threading.Mutex($false, $packageSubFolder.Replace('/','_').Replace('\','_')) try { try { if (!$packageMutex.WaitOne(1000)) { Write-Host "Waiting for other process downloading package '$($packageSubFolder)'" $packageMutex.WaitOne() | Out-Null Write-Host "Other process completed download" } } catch [System.Threading.AbandonedMutexException] { Write-Host "Other process terminated abnormally" } if (!$this.IsTrusted($packageId)) { throw "Package $packageId is not trusted on $($this.url)" } $queryUrl = "$($this.packageBaseAddressUrl.TrimEnd('/'))/$($packageId.ToLowerInvariant())/$($version.ToLowerInvariant())/$($packageId.ToLowerInvariant()).$($version.ToLowerInvariant()).nupkg" $packageCacheFolder = Join-Path $this.cacheFolder $packageSubFolder if (test-Path $packageCacheFolder) { Write-Host "Using cached package for $packageId version $version" (Get-Item $packageCacheFolder).LastWriteTime = Get-Date } else { try { Write-Host -ForegroundColor Green "Download package using $queryUrl" $prev = $global:ProgressPreference; $global:ProgressPreference = "SilentlyContinue" $filename = "$($packageCacheFolder).zip" New-Item -Path $packageCacheFolder -ItemType Container | Out-Null Invoke-RestMethod -UseBasicParsing -Method GET -Headers ($this.GetHeaders()) -Uri $queryUrl -OutFile $filename if ($this.fingerprints) { $arguments = @("nuget", "verify", $filename) if ($this.fingerprints.Count -eq 1 -and $this.fingerprints[0] -eq '*') { Write-Host "Verifying package using any certificate" } else { Write-Host "Verifying package using $($this.fingerprints -join ', ')" $arguments += @("--certificate-fingerprint $($this.fingerprints -join ' --certificate-fingerprint ')") } cmddo -command 'dotnet' -arguments $arguments -silent -messageIfCmdNotFound "dotnet not found. Please install it from https://dotnet.microsoft.com/download" } Expand-Archive -Path $filename -DestinationPath $packageCacheFolder -Force $global:ProgressPreference = $prev Write-Host "Package successfully downloaded" } catch { if (Test-Path $packageCacheFolder) { Remove-Item $packageCacheFolder -Recurse -Force } throw (GetExtendedErrorMessage $_) } finally { if (Test-Path $filename) { Remove-Item $filename -Force } } } $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) ([GUID]::NewGuid().ToString()) New-Item -Path $tmpFolder -ItemType Container | Out-Null Copy-Item -Path (Join-Path $packageCacheFolder '*') -Destination $tmpFolder -Recurse -Force return $tmpFolder } finally { $packageMutex.ReleaseMutex() } } [void] PushPackage([string] $package) { if (!($this.token)) { throw "NuGet token is required to push packages" } Write-Host "Preparing NuGet Package for submission" $headers = $this.GetHeaders() $headers += @{ "X-NuGet-ApiKey" = $this.token "X-NuGet-Client-Version" = "6.3.0" } $boundary = [System.Guid]::NewGuid().ToString(); $LF = "`r`n"; $tmpFile = Join-Path ([System.IO.Path]::GetTempPath()) ([GUID]::NewGuid().ToString()) $fs = [System.IO.File]::OpenWrite($tmpFile) $fs | Add-Member -MemberType ScriptMethod -Name WriteBytes -Value { param($bytes) $this.Write($bytes, 0, $bytes.Length) } try { $fs.WriteBytes([System.Text.Encoding]::UTF8.GetBytes("--$boundary$LF")) $fs.WriteBytes([System.Text.Encoding]::UTF8.GetBytes("Content-Type: application/octet-stream$($LF)Content-Disposition: form-data; name=package; filename=""$([System.IO.Path]::GetFileName($package))""$($LF)$($LF)")) $fs.WriteBytes([System.IO.File]::ReadAllBytes($package)) $fs.WriteBytes([System.Text.Encoding]::UTF8.GetBytes("$LF--$boundary--$LF")) } finally { $fs.Close() } Write-Host "Submitting NuGet package" try { Invoke-RestMethod -UseBasicParsing -Uri $this.packagePublishUrl -ContentType "multipart/form-data; boundary=$boundary" -Method Put -Headers $headers -inFile $tmpFile | Out-Host Write-Host -ForegroundColor Green "NuGet package successfully submitted" # Clear matching search results caches @( $this.searchResultsCache.Keys ) | Where-Object { $package -like "*$($_)*" -or $_ -like 'GitHubPackages:*' } | ForEach-Object { $this.searchResultsCache.Remove($_) } } catch [System.Net.WebException] { if ($_.Exception.Status -eq "ProtocolError" -and $_.Exception.Response -is [System.Net.HttpWebResponse]) { $response = [System.Net.HttpWebResponse]($_.Exception.Response) if ($response.StatusCode -eq [System.Net.HttpStatusCode]::Conflict) { Write-Host -ForegroundColor Yellow "NuGet package already exists" } else { throw (GetExtendedErrorMessage $_) } } else { throw (GetExtendedErrorMessage $_) } } catch { throw (GetExtendedErrorMessage $_) } finally { Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue } } } # SIG # Begin signature block # MIIoJQYJKoZIhvcNAQcCoIIoFjCCKBICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBdGF7Irg0X+w+2 # DVWFeXSvLHR00Sy/He2XlSdY1wVu2qCCDXYwggX0MIID3KADAgECAhMzAAAEhV6Z # 7A5ZL83XAAAAAASFMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjUwNjE5MTgyMTM3WhcNMjYwNjE3MTgyMTM3WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDASkh1cpvuUqfbqxele7LCSHEamVNBfFE4uY1FkGsAdUF/vnjpE1dnAD9vMOqy # 5ZO49ILhP4jiP/P2Pn9ao+5TDtKmcQ+pZdzbG7t43yRXJC3nXvTGQroodPi9USQi # 9rI+0gwuXRKBII7L+k3kMkKLmFrsWUjzgXVCLYa6ZH7BCALAcJWZTwWPoiT4HpqQ # hJcYLB7pfetAVCeBEVZD8itKQ6QA5/LQR+9X6dlSj4Vxta4JnpxvgSrkjXCz+tlJ # 67ABZ551lw23RWU1uyfgCfEFhBfiyPR2WSjskPl9ap6qrf8fNQ1sGYun2p4JdXxe # UAKf1hVa/3TQXjvPTiRXCnJPAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUuCZyGiCuLYE0aU7j5TFqY05kko0w # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwNTM1OTAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBACjmqAp2Ci4sTHZci+qk # tEAKsFk5HNVGKyWR2rFGXsd7cggZ04H5U4SV0fAL6fOE9dLvt4I7HBHLhpGdE5Uj # Ly4NxLTG2bDAkeAVmxmd2uKWVGKym1aarDxXfv3GCN4mRX+Pn4c+py3S/6Kkt5eS # DAIIsrzKw3Kh2SW1hCwXX/k1v4b+NH1Fjl+i/xPJspXCFuZB4aC5FLT5fgbRKqns # WeAdn8DsrYQhT3QXLt6Nv3/dMzv7G/Cdpbdcoul8FYl+t3dmXM+SIClC3l2ae0wO # lNrQ42yQEycuPU5OoqLT85jsZ7+4CaScfFINlO7l7Y7r/xauqHbSPQ1r3oIC+e71 # 5s2G3ClZa3y99aYx2lnXYe1srcrIx8NAXTViiypXVn9ZGmEkfNcfDiqGQwkml5z9 # nm3pWiBZ69adaBBbAFEjyJG4y0a76bel/4sDCVvaZzLM3TFbxVO9BQrjZRtbJZbk # C3XArpLqZSfx53SuYdddxPX8pvcqFuEu8wcUeD05t9xNbJ4TtdAECJlEi0vvBxlm # M5tzFXy2qZeqPMXHSQYqPgZ9jvScZ6NwznFD0+33kbzyhOSz/WuGbAu4cHZG8gKn # lQVT4uA2Diex9DMs2WHiokNknYlLoUeWXW1QrJLpqO82TLyKTbBM/oZHAdIc0kzo # STro9b3+vjn2809D0+SOOCVZMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGgUwghoBAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAASFXpnsDlkvzdcAAAAABIUwDQYJYIZIAWUDBAIB # BQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJKoZIhvcNAQkEMSIE # IJUwT2St+I5QZWKlhddP3bTngAGSoRapS5mKK/y05K56MEIGCisGAQQBgjcCAQwx # NDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29m # dC5jb20wDQYJKoZIhvcNAQEBBQAEggEASuOyjXMldU/dA6DxsVLaC21op5WP+yjN # Rll8h6iow2+IXfMn05eYCCDClg5i65jp3GF1miXFDq2l32N/dKYRMOK7BqpZrOqi # 1LG+66t61n5GgDZYGkknXuCpyJ0pi9YvIxSKudckvd6l4vWrW8PDd1jJtCZ1VgNI # ZxTxZbaDSzpwPHl6O5XSv9S3aeocjWDKs1bKQT8AcQJIYm56YfxLAvnzcQjysHyY # 8U7XC/gMefg+nKzbJbE6maXCK0hM6QLpCa+8yLxANn1ynoAvXBLqFogVoUdbxYwf # n+4KLoT0mbu0V6B7+Xwy8BN7IxtamPGo5EsQhNBJw7E2mV+1htAdtKGCF60wghep # BgorBgEEAYI3AwMBMYIXmTCCF5UGCSqGSIb3DQEHAqCCF4YwgheCAgEDMQ8wDQYJ # YIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYB # BAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCBjyqjRaYJA+O/Hl8BGz9/61DA6M4RU # qruhK1L4erzhIwIGaSc4ej93GBMyMDI1MTIwODEyNDYyNy40MzdaMASAAgH0oIHZ # pIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYD # VQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNV # BAsTHm5TaGllbGQgVFNTIEVTTjo0QzFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWlj # cm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEfswggcoMIIFEKADAgECAhMzAAAC # GCXZkgXi5+XkAAEAAAIYMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgyNVoXDTI2MTExMzE4NDgyNVowgdMx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1p # Y3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNo # aWVsZCBUU1MgRVNOOjRDMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQg # VGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC # AgEAsdzo6uuQJqAfxLnvEBfIvj6knK+p6bnMXEFZ/QjPOFywlcjDfzI8Dg1nzDlx # m7/pqbvjWhyvazKmFyO6qbPwClfRnI57h5OCixgpOOCGJJQIZSTiMgui3B8DPiFt # JPcfzRt3FsnxjLXwBIjGgnjGfmQl7zejA1WoYL/qBmQhw/FDFTWebxfo4m0RCCOx # f2qwj31aOjc2aYUePtLMXHsXKPFH0tp5SKIF/9tJxRSg0NYEvQqVilje8aQkPd3q # zAux2Mc5HMSK4NMTtVVCYAWDUZ4p+6iDI9t5BNCBIsf5ooFNUWtxCqnpFYiLYkHf # FfxhVUBZ8LGGxYsA36snD65s2Hf4t86k0e8WelH/usfhYqOM3z2yaI8rg08631Ik # wqUzyQoEPqMsHgBem1xpmOGSIUnVvTsAv+lmECL2RqrcOZlZax8K0aiij8h6UkWB # N2IA/ikackTSGVRBQmWWZuLFWV/T4xuNzscC0X7xo4fetgpsqaEA0jY/QevkTvLv # 4OlNN9eOL8LNh7Vm0R65P7oabOQDqtUFAwCgjgPJ0iV/jQCaMAcO3SYpG5wSAYiJ # kk4XLjNSlNxU2Idjs1sORhl7s7LC6hOb7bVAHVwON74GxfFNiEIA6BfudANjpQJ0 # nUc/ppEXpT4pgDBHsYtV8OyKSjKsIxOdFR7fIJIjDc8DvUkCAwEAAaOCAUkwggFF # MB0GA1UdDgQWBBQkLqHEXDobY7dHuoQCBa4sX7aL0TAfBgNVHSMEGDAWgBSfpxVd # AF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIw # UENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBo # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUy # MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYG # A1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B # AQsFAAOCAgEAnkjRhjwPgdoIpvt4YioT/j0LWuBxF3ARBKXDENggraKvC0oRPwbj # AmsXnPEmtuo5MD8uJ9Xw9eYrxqqkK4DF9snZMrHMfooxCa++1irLz8YoozC4tci+ # a4N37Sbke1pt1xs9qZtvkPgZGWn5BcwVfmAwSZLHi2CuZ06Y0/X+t6fNBnrbMVov # NaDX4WPdyI9GEzxfIggDsck2Ipo4VXL/Arcz7p2F7bEZGRuyxjgMC+woCkDJaH/y # k/wcZpAsixe4POdN0DW6Zb35O3Dg3+a6prANMc3WIdvfKDl75P0aqcQbQAR7b0f4 # gH4NMkUct0Wm4GN5KhsE1YK7V/wAqDKmK4jx3zLz3a8Hsxa9HB3GyitlmC5sDhOl # 4QTGN5kRi6oCoV4hK+kIFgnkWjHhSRNomz36QnbCSG/BHLEm2GRU9u3/I4zUd9E1 # AC97IJEGfwb+0NWb3QEcrkypdGdWwl0LEObhrQR9B1V7+edcyNmsX0p2BX0rFpd1 # PkXJSbxf8IcEiw/bkNgagZE+VlDtxXeruLdo5k3lGOv7rPYuOEaoZYxDvZtpHP9P # 36wmW4INjR6NInn2UM+krP/xeLnRbDBkm9RslnoDhVraliKDH62BxhcgL9tiRgOH # lcI0wqvVWLdv8yW8rxkawOlhCRqT3EKECW8ktUAPwNbBULkT+oWcvBcwggdxMIIF # WaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNy # b3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAx # ODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy # MDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL # 1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5K # Wv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTeg # Cjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv62 # 6GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SH # JMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss25 # 4o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/Nme # Rd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afo # mXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLi # Mxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb # 0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W2 # 9R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQF # AgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1Ud # DgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdM # g30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp # b3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJ # KwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF # MAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8w # TTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVj # dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBK # BggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9N # aWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1V # ffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1 # OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce57 # 32pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihV # J9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZ # UnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW # 9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k # +SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pF # EUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L # +DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1 # ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6 # CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDVjCCAj4CAQEwggEBoYHZ # pIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYD # VQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNV # BAsTHm5TaGllbGQgVFNTIEVTTjo0QzFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWlj # cm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAnWtGrXWi # uNE8QrKfm4CtGr57z+mggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg # MjAxMDANBgkqhkiG9w0BAQsFAAIFAOzg35UwIhgPMjAyNTEyMDgwNTI0MzdaGA8y # MDI1MTIwOTA1MjQzN1owdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA7ODflQIBADAH # AgEAAgIThTAHAgEAAgISwzAKAgUA7OIxFQIBADA2BgorBgEEAYRZCgQCMSgwJjAM # BgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEB # CwUAA4IBAQC80vQoiucJy/c1Mp6nnsNVhMHSrhui6BMNrWsGfMW0RpbIM4F/Dpvu # 13fLM1AB8s5xyRbvOWP42ahy/7/0wsRo2eYNmcjtLoThxG3bV+/wn8MQ/1OWXChL # x6K2P/x3kdqGeJamix+/2P/mL/xBDlR86C+tTz9cQlCOGJoyvVUt+XgCOZ90XSkQ # 92IcNqfmcw8sdU5kuBfg+fTa1lhHVT987gKXDjtv7WJaQ6hEF2cEYkBclP8rFPeO # iHJDa51EB2MxQjhsZTQfuzOKcehkpoATzHAcVPRJh9WeoD+z9bht0ir1M7aW6qX4 # QgZB/ec0plNblO2OFQoyNhmv4swuFQ5KMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UE # BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc # BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0 # IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIYJdmSBeLn5eQAAQAAAhgwDQYJYIZI # AWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG # 9w0BCQQxIgQgUrHKfrH52lCWXfTeywe2DyxtQ15ruBIzr1v6aemGjMcwgfoGCyqG # SIb3DQEJEAIvMYHqMIHnMIHkMIG9BCCZE9yJuOTItIwWaES6lzGKK1XcSoz1ynRz # aOVzx9eFajCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMz # AAACGCXZkgXi5+XkAAEAAAIYMCIEIAj3wYJ636hVtct9nnvch2SwJAasPeAlCiaZ # tU27McgFMA0GCSqGSIb3DQEBCwUABIICAGCazR3IoN8LGs0RBNGhaFz6nb73Bai6 # V81EVtva/tMLBrAywG+CAAYQ0wiIOAPibTYRJBzw+MnelnVTqF2VbvS7Yqx7jxPR # 51e0aLv6bzO0XlnCgrUcVxGxwXMC/PRQKF2CqeXgjZGNoq4tXf6UhQq06Frgn10K # ZeiioE7F9TSNNV2shJ7cQqaRXc/t3IzuoKkneeYPGPWsZY/t5jsM/KdTA6nKd7mI # zvylQM+BJ9s6CVPuonbG+UiiC0J1aAnVGuWk2t2B6+b0UuiG3+CbpBXMv5cb/PNt # JorRog1I6ccKvbGf1ebqVL5do8cb+iPqtltPJSQx+AU8iiz3lMveNC7mDcra6H1w # ocMdP569idNxf7WwCjWsD3AHahcQl0AMLbkDBjufVJ8b/gid9DY1iLk5BtLTP9U2 # MuF/blMLu+FPfHhsLXMIjfyyqnGfPkbJkyXeuI7OcARw3B9dXAOGZxkf6nWPQiuw # rEHL+1ZNYVmc9rXJa9I2HN5SZSwGbyZrTtL3EMp/7rfONedpswKsN+WA3LeV/eam # eUnRGqdkairG5pvdFI8kEJReq0EilncLcxmiVKMwEFnbK+t6klO2hs+3fdYJ2RXb # M2s2mImx5kwZgZ5tmT2jDCxuaLIphMKdTIAPyen3W/oKhBh796297bUuvJYTdVPd # 8FoX/xxs+Gy6 # SIG # End signature block |