Microsoft.AzureStack.ReadinessChecker.Utilities.psm1

function ConvertTo-DeploymentData {
    <#
    .SYNOPSIS
        Validate JSON file provided
    .DESCRIPTION
        Validate JSON file can parsed.
    .EXAMPLE
        Test-ValidationJSON -path .\some.json
    .INPUTS
        path to json file
    .OUTPUTS
        None - logging only
    #>

    [CmdletBinding()]
    param ($path)
    $thisFunction = $MyInvocation.MyCommand.Name
    Try {
        $deploymentData = Get-Content $path -ErrorAction Stop | ConvertFrom-Json
        Write-AzsReadinessLog -Message ('Validated JSON: {0}' -f $path) -Type Info -Function $thisFunction
    }
    Catch {
        if ($_.exception -like '*Invalid JSON primitive*') {
            Write-AzsReadinessLog -Message ('Invalid JSON file provided: {0}' -f $path) -Type Error -Function $thisFunction
            throw ('Invalid JSON file provided: {0}' -f $path)
        }
        else {
            Write-AzsReadinessLog -Message ('Reading JSON file {0} failed with error: {1}' -f $_.exception.message) -Type Error -Function $thisFunction
            throw ('Reading JSON file {0} failed with error: {1}' -f $_.exception)
        }
    }
    $deploymentData
}

function Test-CertificateReuse {
    <#
    .SYNOPSIS
        Checks if certificate validation output contains certificates that are reused.
    .DESCRIPTION
        During validation certificate are given a unique id, that unique id is compared against the certificate thumbprints to detect reuse.
    .EXAMPLE
        PS C:\> Test-CertificateReuse -validationResult $paasCertificateValidationResult
        Checks if certificate validation output contains certificates that are reused.
    #>

    param ($validationResult)
    $thisFunction = $MyInvocation.MyCommand.Name
    Write-AzsReadinessLog -Message 'Certificate Reuse Detection started' -Type Info -Function $thisFunction
    # Write new property to result with ReuseCount
    $thumbprintHash = @{}
    $group = $validationResult |
        Group-Object Thumbprint, CertificateId |
        Select-Object Name |
        ForEach-Object {$_.name.split(',')[0]} |
        Group-Object |
        Select-Object Name, Count
    $group | ForEach-Object { $thumbprintHash[$_.Name] = $_.count}
    foreach ($key in $thumbprintHash.keys) {
        $validationResult | Where-Object thumbprint -eq $key | Add-Member -NotePropertyName ReuseCount -NotePropertyValue $thumbprintHash[$key]
    }
    if ($thumbprintHash.Values -gt 1) {
        $duplicateErrorMsg = 'Duplicate Certificate Detected. We recommend using seperate certificates for each endpoint.'
        Write-AzsReadinessLog -Message "`nWARNING: $duplicateErrorMsg `n" -Type Warning -Function $thisFunction -toScreen
        foreach ($key in $thumbprintHash.keys) {
            if ($thumbprintHash[$key] -gt 1) {
                Write-AzsReadinessLog ("`t Thumbprint {0} : Count {1}" -f $key, $thumbprintHash[$key]) -Type Warning -Function $thisFunction -toScreen
                #inject warning result and failuredetail on ParsePFX test for certificate, for reporting purposes.
                $validationResult | Where-Object {$_.Thumbprint -eq $key -and $_.Test -eq 'Parse PFX'} |
                    ForEach-Object {$_.result = 'Warning'}
                $validationResult | Where-Object {$_.Thumbprint -eq $key -and $_.Test -eq 'Parse PFX'} |
                    ForEach-Object {$_.FailureDetail += $duplicateErrorMsg}
            }
        }
    }
    Write-AzsReadinessLog -Message 'Certificate Reuse Detection Completed' -Type Info -Function $thisFunction
    $validationResult
}

function Test-PasswordLength {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [int] $MinimumCharactersInPassword,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [SecureString]
        $Password,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $CredentialDescription
    )

    if ($Password.Length -lt $MinimumCharactersInPassword) {
        throw ("Password length cannot be fewer than '{0}' characters, for '{1}'" -f $MinimumCharactersInPassword, $CredentialDescription)
    }
    return $true
}

# Test that the Password has only valid characters, does not contain the username, and satisfies the complexity requirements
function Test-PasswordComplexity {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Username,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [SecureString]
        $Password,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $CredentialDescription
    )

    $unmanagedString = [System.IntPtr]::Zero;
    try {
        $unmanagedString = [Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($Password)
        $plainPassword = [Runtime.InteropServices.Marshal]::PtrToStringUni($unmanagedString)
    }
    finally {
        [Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($unmanagedString)
    }

    # Letter, Mark, Symbol, Number, Punctuation allowed
    if ($plainPassword -cnotmatch "^[\p{L}\p{M}\p{S}\p{N}\p{P}]+$") {
        throw ("Password contains bad characters. Only Letters, Marks, Symbols, Numbers and Punctuations are allowed. For '{0}'" -f $CredentialDescription)
    }

    # Password should not contain the entire username or part of the username
    if ($Username) {
        # Passwords may not contain the user's samAccountName (Account Name) value or entire displayName (Full Name value). Both checks are not case sensitive.
        # The samAccountName is checked in its entirety only to determine whether it is part of the password.
        # If the samAccountName is less than three characters long, this check is skipped.
        if ($Username.Length -ge 3 -and $plainPassword.ToLower().Contains($Username.ToLower())) {
            throw ("Password should not contain username or part of username. For '{0}'" -f $CredentialDescription)
        }

        # The displayName is parsed for delimiters: commas, periods, dashes or hyphens, underscores, spaces, pound signs, and tabs.
        # If any of these delimiters are found, the displayName is split and all parsed sections (tokens) are confirmed to not be included in the password.
        $usernameTokens = $Username.Split(  [char]0x2010, # Hyphen
            [char]0x0009, # Tab
            [char]0x002C, # Comma
            [char]0x002E, # Period
            [char]0x2012, # Figure Dash
            [char]0x2013, # EN Dash
            [char]0x2014, # EM Dash
            [char]0x2015, # Horizontal bar
            [char]0x2053, # Swung dash
            [char]0x002D, # Hyphen-Minus
            [char]0x005F, # Low line
            [char]0x0020, # Space
            [char]0x00A3) # Pound Sign

        foreach ($usernameToken in $usernameTokens) {
            # Tokens that are less than three characters are ignored, and substrings of the tokens are not checked.
            if ($usernameToken.Length -ge 3 -and $plainPassword.ToLower().Contains($usernameToken.ToLower())) {
                throw ("Password should not contain username or part of username. For '{0}'" -f $CredentialDescription)
            }
        }
    }

    # Validate that password satisifies at least 3 of 5 categories to meet complexity requirements
    $category_count = 0, 0, 0, 0, 0
    for ($i = 0; $i -lt $plainPassword.length; $i++) {
        # Uppercase letters of European languages (A through Z, with diacritic marks, Greek and Cyrillic characters)
        if ($plainPassword[$i] -cmatch "^[\p{Lu}]+$") {
            $category_count[0]++
        }

        # Lowercase letters of European languages (a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters)
        if ($plainPassword[$i] -cmatch "^[\p{Ll}]+$") {
            $category_count[1]++
        }

        # Base 10 digits (0 through 9)
        if ($plainPassword[$i] -cmatch "^[0-9]+$") {
            $category_count[2]++
        }

        # Non-alphanumeric characters (special characters) (for example, !, $, #, %)
        if ($plainPassword[$i] -cmatch "^[\p{P}]+$" -or $plainPassword[$i] -cmatch "^[\p{S}]+$") {
            $category_count[3]++
        }

        # Any Unicode character that is categorized as an alphabetic character but is not uppercase or lowercase. This includes Unicode characters from Asian languages.
        if ($plainPassword[$i] -cmatch "^[\p{L}]+$" -and $plainPassword[$i] -cnotmatch "^[\p{Lu}]+$" -and $plainPassword[$i] -cnotmatch "^[\p{Ll}]+$") {
            $category_count[4]++
        }
    }
    $plainPassword = "" # reset the value, in case it persists

    $total_category_count = 0
    foreach ($count in $category_count) {
        if ($count -gt 0) {
            $total_category_count++
        }
    }

    if ($total_category_count -lt 3) {
        throw ("Password does not meet complexity requirements. It should contain at least 3 of the following: Uppercase letter, lowercase letter, numbers from 0-9, special characters, alphabetical character that is neither uppercase nor lowercase. For '{0}'" -f $CredentialDescription)
    }
    return $true
}

function Set-SecurityProtocol {
    param ([Net.SecurityProtocolType]$securityProtocol)
    $thisFunction = $MyInvocation.MyCommand.Name

    if ([Net.ServicePointManager]::SecurityProtocol -notmatch $securityProtocol) {
        Write-AzsReadinessLog -Message ("{0} not found in current Service Point Manager. Current protocol(s): {1}. Attempting to add for session." -f $securityProtocol, [Net.ServicePointManager]::SecurityProtocol) -Type Info -Function $thisFunction
        try {
            [Net.ServicePointManager]::SecurityProtocol = $securityProtocol
            Write-AzsReadinessLog -Message ("Successfully added {0} to Service Point Manager." -f $securityProtocol) -Type Info -Function $thisFunction
        }
        catch {
            Write-AzsReadinessLog -Message ("Setting {0} failed with {1}. Script will continue with existing Security Protocol: {2}" -f $securityProtocol, $_.exception, [Net.ServicePointManager]::SecurityProtocol) -Type Warning -Function $thisFunction
        }
    }
    else {
        Write-AzsReadinessLog -Message ("{0} found in current Service Point Manager. No action required." -f $securityProtocol) -Type Info -Function $thisFunction
    }
}

function Get-SslCertificateChain {
    <#
    .SYNOPSIS
        Retrieve remote ssl certificate & chain from https endpoint for Desktop and Core
    .NOTES
        Credit: https://github.com/markekraus
    #>

    param (
        [system.uri]
        $url
    )
    try {

        $cs = @'
    using System;
    using System.Collections.Generic;
    using System.Net.Http;
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
 
    namespace CertificateCapture
    {
        public class Utility
        {
             public static Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,Boolean> ValidationCallback =
                (message, cert, chain, errors) => {
                    CapturedCertificates.Clear();
                    var newCert = new X509Certificate2(cert);
                    var newChain = new X509Chain();
                    newChain.Build(newCert);
                    CapturedCertificates.Add(new CapturedCertificate(){
                        Certificate = newCert,
                        CertificateChain = newChain,
                        PolicyErrors = errors,
                        URI = message.RequestUri
                    });
                    return true;
                };
            public static List<CapturedCertificate> CapturedCertificates = new List<CapturedCertificate>();
        }
 
        public class CapturedCertificate
        {
            public X509Certificate2 Certificate { get; set; }
            public X509Chain CertificateChain { get; set; }
            public SslPolicyErrors PolicyErrors { get; set; }
            public Uri URI { get; set; }
        }
    }
'@


        if ($PSEdition -ne 'Core') {
            Add-Type -AssemblyName System.Net.Http
            Add-Type $cs -ReferencedAssemblies System.Net.Http
        }
        else {
            Add-Type $cs
        }

        Write-AzsReadinessLog -Message ("Reading remote SSL certificates for {0}" -f $url.AbsoluteUri) -Type Info

        $Certs = [CertificateCapture.Utility]::CapturedCertificates
        $Handler = [System.Net.Http.HttpClientHandler]::new()
        $Handler.ServerCertificateCustomValidationCallback = [CertificateCapture.Utility]::ValidationCallback
        $Client = [System.Net.Http.HttpClient]::new($Handler)
        $null = $Client.GetAsync($url).Result
        return $Certs.Certificate
    }
    catch {
        Write-AzsReadinessLog -Message ("Reading remote SSL certificate failed with {0}" -f $_.exception) -Type Error -toScreen
    }
}

function Test-Elevation
{
    ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator')
}
# SIG # Begin signature block
# MIInogYJKoZIhvcNAQcCoIInkzCCJ48CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA2p95N83O2YHqQ
# ul2pwIJUXfgUaI8NYmyEIjDqcXvyE6CCDXYwggX0MIID3KADAgECAhMzAAACy7d1
# OfsCcUI2AAAAAALLMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NTU5WhcNMjMwNTExMjA0NTU5WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC3sN0WcdGpGXPZIb5iNfFB0xZ8rnJvYnxD6Uf2BHXglpbTEfoe+mO//oLWkRxA
# wppditsSVOD0oglKbtnh9Wp2DARLcxbGaW4YanOWSB1LyLRpHnnQ5POlh2U5trg4
# 3gQjvlNZlQB3lL+zrPtbNvMA7E0Wkmo+Z6YFnsf7aek+KGzaGboAeFO4uKZjQXY5
# RmMzE70Bwaz7hvA05jDURdRKH0i/1yK96TDuP7JyRFLOvA3UXNWz00R9w7ppMDcN
# lXtrmbPigv3xE9FfpfmJRtiOZQKd73K72Wujmj6/Su3+DBTpOq7NgdntW2lJfX3X
# a6oe4F9Pk9xRhkwHsk7Ju9E/AgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUrg/nt/gj+BBLd1jZWYhok7v5/w4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzQ3MDUyODAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAJL5t6pVjIRlQ8j4dAFJ
# ZnMke3rRHeQDOPFxswM47HRvgQa2E1jea2aYiMk1WmdqWnYw1bal4IzRlSVf4czf
# zx2vjOIOiaGllW2ByHkfKApngOzJmAQ8F15xSHPRvNMmvpC3PFLvKMf3y5SyPJxh
# 922TTq0q5epJv1SgZDWlUlHL/Ex1nX8kzBRhHvc6D6F5la+oAO4A3o/ZC05OOgm4
# EJxZP9MqUi5iid2dw4Jg/HvtDpCcLj1GLIhCDaebKegajCJlMhhxnDXrGFLJfX8j
# 7k7LUvrZDsQniJZ3D66K+3SZTLhvwK7dMGVFuUUJUfDifrlCTjKG9mxsPDllfyck
# 4zGnRZv8Jw9RgE1zAghnU14L0vVUNOzi/4bE7wIsiRyIcCcVoXRneBA3n/frLXvd
# jDsbb2lpGu78+s1zbO5N0bhHWq4j5WMutrspBxEhqG2PSBjC5Ypi+jhtfu3+x76N
# mBvsyKuxx9+Hm/ALnlzKxr4KyMR3/z4IRMzA1QyppNk65Ui+jB14g+w4vole33M1
# pVqVckrmSebUkmjnCshCiH12IFgHZF7gRwE4YZrJ7QjxZeoZqHaKsQLRMp653beB
# fHfeva9zJPhBSdVcCW7x9q0c2HVPLJHX9YCUU714I+qtLpDGrdbZxD9mikPqL/To
# /1lDZ0ch8FtePhME7houuoPcMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# 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
# /Xmfwb1tbWrJUnMTDXpQzTGCGYIwghl+AgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAALLt3U5+wJxQjYAAAAAAsswDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMNnVJsyD1vKRMPc1Eky3cVD
# 3ljlB1cHVoNqZVxs9r89MEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAOdjkXIeJipHi6SkbuMcwirD8c7PxKhKdupwogZmnMf/bK8qFzx4u8pbm
# EMy/UW0Sd6gl13PyfuFYbvqhG0o4v215+zS/a1A2HZR+fLpRxNlU5zwbNFgjrIfS
# 6ImBH8rLOj+sN/IsWfiYGPO5QA24lWAXWtTnUB+g1b7hk5PSppL5I/kq9Sqckyu8
# wg6dkwLvYniw0e2keNuEmr/BSom2XjEbXfXuPCdCTapvQdVlu0Rd+Gew9+i8xz1E
# v8vBUDR2sjTvP6hUeFs0TvGsihv0oXBQtq9tvNq5d0NTnldhJRTNdkyCKeLuu4kX
# pCkO7ow+gWh622Nu9OtcobXfl+vpl6GCFwwwghcIBgorBgEEAYI3AwMBMYIW+DCC
# FvQGCSqGSIb3DQEHAqCCFuUwghbhAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFVBgsq
# hkiG9w0BCRABBKCCAUQEggFAMIIBPAIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCB5YOR+3NuD5pwS1eZdmk1GE554FJBRJptVD1CeEuj3UQIGY8aOb7GO
# GBMyMDIzMDIwODE4MzAwNS40MTJaMASAAgH0oIHUpIHRMIHOMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3Bl
# cmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046NEQy
# Ri1FM0RELUJFRUYxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZp
# Y2WgghFfMIIHEDCCBPigAwIBAgITMwAAAbCh44My6I07wAABAAABsDANBgkqhkiG
# 9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw
# JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMjAzMDIx
# ODUxNDJaFw0yMzA1MTExODUxNDJaMIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVy
# dG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046NEQyRi1FM0RELUJFRUYx
# JTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqG
# SIb3DQEBAQUAA4ICDwAwggIKAoICAQCcxm07DNfSgp0HOUQu1aIJcklzCi7rf8ll
# j0Fg+lQJSYAXsVSsdp9c4F96P8QNmYGfzRRnIDQ0Qie5iYjnlu8Xh56DVz5YOxI2
# FrpX5N6DgI+muzteRr3JKWLLy3MfqPEnvAq3yG+NBCfFtEMeEyF39Mg8ACeP6jve
# HSf4Rmm3iWIOBqdBtLkJocBaLwFkx5Q9XIvrKd+gMU/cCIR6sP+9LczL65wxe45k
# I2lVD54zoDzshVmYla+3uq5EpeGp09bS79t0loV6jLNeMKJb+GXkHFj/OK1dha69
# Sm8JCGtL5R45b+MRvWup5U0X6NAmFEA362TjFwiOSnADdgWen1W9ParQnbFnTTcQ
# dMuJcDI57jZsfORTX8z3DGY5sABfWkVFDCx7+tuiOu7dfnWaFT6Sqn0jZhrVbfQx
# E1pJg4qZxoOPgXU6Zb4BlavRdymTwxR2m8Wy6Uln11vdDGVzrhR/MgjMwyTVM3sg
# KsrRRci2Yq94+E9Rse5UXgjlD8Nablc21irKVezKHWY7TfyFFnVSHZNxz6eEDdcM
# HVb3VzrGHYRvJIIxsgGSA+aK+wv++YcikG+RdGfhHtOLmPSvrA2d5d8/E0GVgH2L
# q22QjFlp5iVbLuVeD0eTzvlOg+7QLTLzFCzWIm0/frMVWSv1kHq9iSfat2e5YxbO
# JYKZn3OgFQIDAQABo4IBNjCCATIwHQYDVR0OBBYEFDrfASQ3ASZuHcugEmR61yBH
# 1jY/MB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYw
# VKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jv
# c29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcB
# AQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lv
# cHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSku
# Y3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZIhvcN
# AQELBQADggIBAN1z4oebDbVHwMi55V6ujGUqQodExfrhvp4SCeOP/3DHEBhFYmdj
# dutzcL60IwhTp4v/qMX++o3JlIXCli15PYYXe73xQYWWc3BeWjbNO1JYoLNuKb3m
# rBboZieMvNjmJtRtTkWLBZ3WXbxf/za2BsWl6lDZUR0JbJFf6ZnHKjtzousCx3Dw
# df1kUyybWGyIosBP7kxRBRC+OcFg/9ZkwjxJBV94ZYlxMqcV83WdZOl6hk8rBgLS
# 11AeyAugh9umMoCkLlxvEI3CQQFBv/Rd8jWTnWxb5+xYp2cjXCFS8ZXe4dGxC30M
# 4SI3pY/ubASoS3GhVNL2425n9FhDYBZp8iTYjKy+/9hWDi7IIkA2yceg6ctRH77k
# RrHS+X/o1VXbOaDGiq4cYFe6BKG6wOmeep51mDeO7MMKLrnB39MptQ0Fh8tgxzhU
# UTe8r/vs3rNBkgjo0UWDyu669UHPjt57HetODoJuZ0fUKoTjnNjkE677UoFwUrbu
# bxelvAz3LJ7Od3EOIHXEdWPTYOSGBMMQmc82LKvaGpcZR/mR/wOie2THkjSjZK1z
# 8eqaRV1MR7gt5OJs1cmTRlj/2YHFDotqldN5uiJsrb4tZHxnumHQod9jzoFnjR/Z
# XyrfndTPquCISS5l9BNmWSAmBG/UNK6JnjF/BmfnG4bjbBYpiYGv3447MIIHcTCC
# BVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWlj
# cm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMw
# MTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg
# MjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3mi
# y9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+
# Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3
# oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+
# tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0
# hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLN
# ueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZ
# nkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n
# 6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC
# 4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vc
# G9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtF
# tvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEE
# BQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNV
# HQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3
# TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br
# aW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkG
# CSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8E
# BTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRP
# ME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1
# Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEww
# SgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv
# TWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCd
# VX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQ
# dTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnu
# e99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYo
# VSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlC
# GVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZ
# lvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/
# ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtq
# RRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+
# y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgk
# NWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqK
# Oghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtIwggI7AgEBMIH8oYHU
# pIHRMIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYD
# VQQLEyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMd
# VGhhbGVzIFRTUyBFU046NEQyRi1FM0RELUJFRUYxJTAjBgNVBAMTHE1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAAKeL5Dd3w+RTQVW
# GZJWXkvyRTwYoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw
# DQYJKoZIhvcNAQEFBQACBQDnjg2SMCIYDzIwMjMwMjA4MTYwMTIyWhgPMjAyMzAy
# MDkxNjAxMjJaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOeODZICAQAwCgIBAAIC
# BkUCAf8wBwIBAAICEUIwCgIFAOePXxICAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYK
# KwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUF
# AAOBgQAyu2oqFRd+eX6/KV/df6QQl+h6OXyEnx3z6sEd2j++BVUPnEWEebCQhIri
# 2kuS1FoPtWEnuj7cw+SJ5PbSQbQmKIPzsZc4lIlLrP+YaZfqqED/Q4HUlA1QZ3ub
# pMk6Ns8w1EhpB8OejcnvRfSMTYgCzSIKn9fUCD41e91atfc4vzGCBA0wggQJAgEB
# MIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABsKHjgzLojTvA
# AAEAAAGwMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcN
# AQkQAQQwLwYJKoZIhvcNAQkEMSIEIGs6GZGaqLiKfjc7ZTTPXPRsD6gdlNh5Lsyu
# pa0RGIODMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgzQYLQ3fLn/Sk4xn9
# RuuyHypnDRSZnlk3eopQMucVhKAwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEG
# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj
# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt
# cCBQQ0EgMjAxMAITMwAAAbCh44My6I07wAABAAABsDAiBCA04z6q9Pk4Dkp5f4Wj
# PqsOjlP9gZ3UFZjAytNKhj0Y/zANBgkqhkiG9w0BAQsFAASCAgBsekfZPJTtQxJS
# gZtls7Ra7NL/yxt17MU4Zw0D4kVdJy+oMDYnfQc3CQQ5hkA1z2TQi52Md4ok6qiK
# 7KOJHpBKtZ4FgkgIB96/l+UlHXHWSjaAoz2etXCMSTC4PSWcH2qQY2oTNWBBaU9/
# 6mDLAzJTbDM8wF2LA2lpEfXWi6jEWYokZak/ifwQjPTJrOtekWFw7wq7l1LLl2iT
# MXhK0J0GWBumch+YLHJcJejj31h2tB7SGos/1gr9BYLVtFIfS/7BvrcKGVRtV+Bw
# oo8YFAMCzuQrGUQWTTHmPoct0e2t4f5HUc9SzZcNztL0JPGq8ghMOpVh1zZ3KVj6
# cmrt1DOXln/EUPOAUR6FCze6pguGopAVXLKi3spCr6YJPgXofoji/IDBecp+NW24
# G2m9K/cGUYBq7q7pPmcYbanCQf7sKPI3oTZeF1kK+TaD/+cBu4CTgK6ihy+UZPEe
# LtBocwPNaVt0JL4TDIJZVnOK5legQNNmmZnTB70AV3flock9r/ZKDd3eKB86ZSLM
# nSoKzU0KazA1Gyz45STjS/JYgoW+0VMrxxvuaKZd72jS9GxhUzKyzF5IxKYERPiO
# A3amPC+1U634vrcw7PFS+ErQPMkjqZP9l47Iy6q+h2eRrDB2w7gcqYJ3uk9ES7dz
# yRE58THgXNSVFdsm/SwXQ8PDxP9Mmw==
# SIG # End signature block