Get-ADWHfBKeyFromValue.ps1

#
# Get_ADWhfbKeyFromValue.ps1
#
# Copyright 2019 Microsoft. All rights reserved.
#

function Get-ADWHfBKeyFromValue
{
    <#.SYNOPSIS
        Parses a WHfB key from a msdsKeyCredentialLink dn-binary value.
 
    .DESCRIPTION
        Parses a WHfB key from a msdsKeyCredentialLink dn-binary value.
 
    .PARAMETER RawValue
        The raw value of the msdsKeyCredentialLink dn-binary attribute as
        queried via LDAP.
 
    .PARAMETER Logging
        This switch will enable additional diagnostic logging to a file.
 
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory=$true
            )]
            [string]$RawValue,
        [Parameter(
            Mandatory=$false
            )]
            [bool]$Logging
    )

    Process
    {
        $memStream = $null;
        $binReader = $null;

        try
        {
            $parsedLink = $RawValue.Split(':');

            if($parsedLink.Length -ne 4)
            {
                if ($Logging)
                {
                    $output = "Key has unexpected number of elements: $parsedLink.Length"
                    DiagLog $output -logOnly
                }
                Write-Output $null;
                return;
            }

            $valueCount = [Convert]::ToInt32($parsedLink[1]);

            if ($parsedLink[2].Length -ne $valueCount)
            {
                if ($Logging)
                {
                    $output = "Key has unexpected valueCount: $parsedLink[2].Length $valueCount"
                    DiagLog $output -logOnly
                }
                Write-Output $null;
                return;
            }

            $keyBytes = Get-ByteArrayFromHexString -HexString $parsedLink[2];

            $memStream = New-Object System.IO.MemoryStream (,[byte[]]$keyBytes)
            $binReader = New-Object System.IO.BinaryReader $memStream;

            $key = Get-ADWHfBKeyFromRawValueBinary -Reader $binReader

            if($null -eq $key)
            {
                Write-Output $null;
                return;
            }

            $key.KeyLinkTargetDN = $parsedLink[3];
            $key.KeyRawLDAPValue = $RawValue;

            Write-Output $key;
        }
        catch
        {
            $output = "Get-ADWHfBKeyFromValue caught exception: " + $_.Exception.ToString()
            DiagLog $output -logOnly
            Write-Output $null
        }
        finally
        {
            if($null -ne $binReader)
            {
                $binReader.Close();
                $binReader.Dispose();
            }

            if($null -ne $memStream)
            {
                $memStream.Close();
                $memStream.Dispose();
            }
        }
    }
}

<#
    Get-ADWHfBKeyFromRawValueBinary
#>

function Get-ADWHfBKeyFromRawValueBinary
{
    <#.SYNOPSIS
        Parses a WHfB key from a msdsKeyCredentialLink dn-binary binary value.
 
    .DESCRIPTION
        Parses a WHfB key from a msdsKeyCredentialLink dn-binary binary value.
 
    .PARAMETER Reader
        The binary value loaded into a binary reader object.
 
    .PARAMETER Logging
        This switch will enable additional diagnostic logging to a file.
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory=$true)]
            [System.IO.BinaryReader]$Reader,
        [Parameter(
            Mandatory=$false
            )]
            [bool]$Logging
    )

    Process
    {
        # Create empty key object with all fields
        $Key = [PSCustomObject]@{
            UserDomain = ""
            UserSamAccountName = ""
            UserDistinguishedName = ""
            KeyVersion = -1
            KeyId = ""
            KeyMaterial = $null
            KeySource = ""
            KeyUsage = ""
            KeyDeviceId = ""
            KeyApproximateLastLogonTimestamp = ""
            KeyCreationTime = ""
            CustomKeyInformation = $null
            KeyLinkTargetDN = ""
            ROCAVulnerable = "Unknown"
            OrphanedKey = "Unknown"
            KeyRawLDAPValue = ""
        }

        $keySourceString = [String]::Empty;
        $keySource = @(1);
        $lastReadKeyId = [KEY_OBJECT_ATTR_TYPE]::KeyObjectValueIdMsDsKeyVersion;

        # First four bytes is the key version
        $KeyVersionBytes = 0;
        $KeyVersionBytes = $Reader.ReadUInt32();
        switch($KeyVersionBytes)
        {
            0
            {
                if ($Logging)
                {
                    $output = "Key has zero (0) version : $parsedLink[2].Length $valueCount"
                    DiagLog $output -logOnly
                }
                return $null
            };
            0x100
            {
                $Key.KeyVersion = 1;
                break;
            };
            0x200
            {
                $Key.KeyVersion = 2;
                break;
            }
            default
            {
                if ($Logging)
                {
                    $output = "Key has unrecognized version: " + $KeyVersionBytes.ToString()
                    DiagLog $output -logOnly
                }
                return $null;
            }
        }

        # Each set in this stream is in the form of:
        # { keyValueCount (2bytes), keyId (1byte), keyValue (keyValueCount bytes) }
        do
        {
            # Read the keyValueCount
            $keyValueCount = $Reader.ReadUInt16();

            # Read the keyId
            $keyId = $Reader.ReadByte();

            if ($keyId -ge 10 -or
                $keyId -lt 0)
            {
                if ($Logging)
                {
                    $output = "Key value has unrecognized id: $keyId"
                    DiagLog $output -logOnly
                }
                return $null
            }

            $readKeyId = [KEY_OBJECT_ATTR_TYPE]$keyId;

            if ($lastReadKeyId -ge $readKeyId)
            {
                if ($Logging)
                {
                    $output = "Key has duplicated key id value"
                    DiagLog $output -logOnly
                }
                return $null
            }

            # Read the actual keyValue.
            $keyValue = $Reader.ReadBytes($keyValueCount);

            switch($readKeyId)
            {
                "KeyObjectValueIdMsDsKeyUsage"
                {
                    if ($keyValueCount -eq 1)
                    {
                        switch ($keyValue[0])
                        {
                        0
                        {
                           $Key.KeyUsage = "AdminKey"
                        }
                        1
                        {
                           $Key.KeyUsage = "NGC"
                        }
                        2
                        {
                           $Key.KeyUsage = "STK"
                        }
                        3
                        {
                           $Key.KeyUsage = "BitlockerRecovery"
                        }
                        default
                        {
                           $Key.KeyUsage = "Unknown"
                        }
                        }
                    }
                    else
                    {
                        $Key.KeyUsage = [System.Text.Encoding.UTF8]::GetString($keyValue);
                    }
                    break;
                }

                "KeyObjectValueIdMsDsKeyId"
                {
                    $keyIdBytes = @();
                    $keyIdBytes = $keyValue;

                    if($Key.KeyVersion -eq 1)
                    {
                        # Version 1 keys had a guid in this field
                        $Key.KeyId = [System.BitConverter]::ToString($keyIdBytes).Replace("-", "");
                    }
                    else
                    {
                        # Version 2 keys have a SHA256 hash of the key material here
                        $Key.KeyId = [System.Convert]::ToBase64String($keyIdBytes);
                    }
                    break;
                }

                "KeyObjectValueIdMsDsKeyHash"
                {
                    # Do nothing.
                    break;
                }

                "KeyObjectValueIdMsDsKeyMaterial"
                {
                    $Key.KeyMaterial = [System.Convert]::ToBase64String($keyValue);
                    break;
                }

                "KeyObjectValueIdMsDsKeySource"
                {
                    $keySource = $keyValue;

                    if($Key.KeyVersion -le 1)
                    {
                        $Key.KeySource = "NA";
                    }
                    elseif($keySource[0]-eq 0)
                    {
                        $Key.KeySource = "AD";
                    }
                    elseif($keySource[0]-eq 1)
                    {
                        $Key.KeySource = "AzureAD";
                    }
                    else
                    {
                        $Key.KeySource = "Unknown";
                    }
                    break;
                }

                "KeyObjectValueIdMsDsDeviceId"
                {
                    $deviceId = New-Object System.Guid (,$keyValue)
                    $Key.KeyDeviceId = $deviceId.ToString()
                    break;
                }

                "KeyObjectValueIdMsDsCustomKeyInformation"
                {
                    $Key.CustomKeyInformation = [System.Convert]::ToBase64String($keyValue);
                    break;
                }

                "KeyObjectValueIdMsDsKeyApproximateLastLogonTimeStamp"
                {
                    $Key.KeyApproximateLastLogonTimestamp = Get-KeyTimeFromBytes `
                        -TimeData $keyValue `
                        -KeySource $keySource[0]`
                        -KeyVersion $Key.KeyVersion;
                    break;
                }

                "KeyObjectValueIdMsDsKeyCreationTime"
                {
                    $Key.KeyCreationTime = Get-KeyTimeFromBytes `
                        -TimeData $keyValue `
                        -KeySource $keySource[0]`
                        -KeyVersion $Key.KeyVersion;
                    break;
                }

                default
                {
                    $output = "Ignoring unrecognized key entry id: $readKeyId"
                    DiagLog $output -logOnly
                }
            }
        }
        while($Reader.BaseStream.Position -lt $Reader.BaseStream.Length);

        return $Key
    }
}

<#
    Get-KeyTimeFromBytes
#>

function Get-KeyTimeFromBytes
{
    <#.SYNOPSIS
        Parse time from byte array. The time format is inferred from the
        key source and version.
 
    .DESCRIPTION
        Parse time from byte array. The time format is inferred from the
        key source and version.
 
    .PARAMETER TimeData
        Byte array containing the time information.
 
    .PARAMETER KeySource
        The time source (AD or AAD).
 
    .PARAMETER KeyVersion
        The WHfB key version
 
    #>


    [CmdletBinding()]
    param (
        [Parameter(
            Mandatory=$true,
            Position=0)]
            [byte[]]$TimeData,

        [Parameter(
            Mandatory=$true,
            Position=1)]
            [int]$KeySource,

        [Parameter(
            Mandatory=$true,
            Position=2)]
            [int]$KeyVersion
    )

    PROCESS
    {
        $dateTime64 = [System.BitConverter]::ToInt64($TimeData, 0);

        if ($dateTime64 -eq 0)
        {
            $time = [DateTime]::MinValue
        }
        elseif (($KeyVersion -le 1) -or ($KeySource -eq 1))
        {
            $time = [DateTime]::FromBinary($dateTime64);
        }
        elseif ($KeySource -eq 0)
        {
            $time = [DateTime]::FromFileTime($dateTime64);
        }
        else
        {
            throw New-Object System.Exception -ArgumentList "Unexpected time format.";
        }

        Write-Output $time.ToString("o");
    }
}

# SIG # Begin signature block
# MIIoCQYJKoZIhvcNAQcCoIIn+jCCJ/YCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD9PSpjJKruAPsO
# y4vS6PE/zIqzm9jn1NHXRYF1CVZ1LaCCEXAwggiCMIIHaqADAgECAhM2AAAA6Gad
# G0hCpUxFAAEAAADoMA0GCSqGSIb3DQEBCwUAMEExEzARBgoJkiaJk/IsZAEZFgNH
# QkwxEzARBgoJkiaJk/IsZAEZFgNBTUUxFTATBgNVBAMTDEFNRSBDUyBDQSAwMTAe
# Fw0xOTAzMjUxMzE3MTBaFw0yMDAzMjQxMzE3MTBaMC8xLTArBgNVBAMTJE1pY3Jv
# c29mdCBBenVyZSBEZXBlbmRlbmN5IENvZGUgU2lnbjCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBANzmuspUXRFHozpraZYcR8lLf8oaCK27bhl7agSI00Vc
# GZy2slaHQ8JZ+Bqiao7e2hpEVxeJ0s9lfIUe30ye3RwDa7oNloC8u8fa/W0QusGD
# T7p4c7wwbRJ5MrXowwB/nSgsOKtoCoJMpk6LDpRhgwhn4va4PjVrajBxrxN1eVIP
# Kw1GPkzh3qkLQ0SIhuiYUlZur4HSG6Mdkl7jYk1ifVtNupf+FO4Nflw+ZRsczZUg
# jo74fVEoBI9qzrZSkCg5wku1qGsTXrd9DtRgOQEEp96NruISfEb22ZZtpMrJg71H
# p/oAEqqlOBN5FIjzWi+mxznE1CNMBNFCbtsZOvHVSJMCAwEAAaOCBYMwggV/MCkG
# CSsGAQQBgjcVCgQcMBowDAYKKwYBBAGCN1sCATAKBggrBgEFBQcDAzA9BgkrBgEE
# AYI3FQcEMDAuBiYrBgEEAYI3FQiGkOMNhNW0eITxiz6Fm90Wzp0SgWCE2K5IhaaW
# JgIBZAIBDTCCAnYGCCsGAQUFBwEBBIICaDCCAmQwYgYIKwYBBQUHMAKGVmh0dHA6
# Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2lpbmZyYS9DZXJ0cy9CWTJQS0lDU0NBMDEu
# QU1FLkdCTF9BTUUlMjBDUyUyMENBJTIwMDEoMSkuY3J0MFIGCCsGAQUFBzAChkZo
# dHRwOi8vY3JsMS5hbWUuZ2JsL2FpYS9CWTJQS0lDU0NBMDEuQU1FLkdCTF9BTUUl
# MjBDUyUyMENBJTIwMDEoMSkuY3J0MFIGCCsGAQUFBzAChkZodHRwOi8vY3JsMi5h
# bWUuZ2JsL2FpYS9CWTJQS0lDU0NBMDEuQU1FLkdCTF9BTUUlMjBDUyUyMENBJTIw
# MDEoMSkuY3J0MFIGCCsGAQUFBzAChkZodHRwOi8vY3JsMy5hbWUuZ2JsL2FpYS9C
# WTJQS0lDU0NBMDEuQU1FLkdCTF9BTUUlMjBDUyUyMENBJTIwMDEoMSkuY3J0MFIG
# CCsGAQUFBzAChkZodHRwOi8vY3JsNC5hbWUuZ2JsL2FpYS9CWTJQS0lDU0NBMDEu
# QU1FLkdCTF9BTUUlMjBDUyUyMENBJTIwMDEoMSkuY3J0MIGtBggrBgEFBQcwAoaB
# oGxkYXA6Ly8vQ049QU1FJTIwQ1MlMjBDQSUyMDAxLENOPUFJQSxDTj1QdWJsaWMl
# MjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERD
# PUFNRSxEQz1HQkw/Y0FDZXJ0aWZpY2F0ZT9iYXNlP29iamVjdENsYXNzPWNlcnRp
# ZmljYXRpb25BdXRob3JpdHkwHQYDVR0OBBYEFMhZoWfS7cerFd3KJYxV4M3+DOEi
# MA4GA1UdDwEB/wQEAwIHgDBQBgNVHREESTBHpEUwQzEpMCcGA1UECxMgTWljcm9z
# b2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xFjAUBgNVBAUTDTIzNjE2OSs0NTMz
# NzQwggHUBgNVHR8EggHLMIIBxzCCAcOgggG/oIIBu4Y8aHR0cDovL2NybC5taWNy
# b3NvZnQuY29tL3BraWluZnJhL0NSTC9BTUUlMjBDUyUyMENBJTIwMDEuY3Jshi5o
# dHRwOi8vY3JsMS5hbWUuZ2JsL2NybC9BTUUlMjBDUyUyMENBJTIwMDEuY3Jshi5o
# dHRwOi8vY3JsMi5hbWUuZ2JsL2NybC9BTUUlMjBDUyUyMENBJTIwMDEuY3Jshi5o
# dHRwOi8vY3JsMy5hbWUuZ2JsL2NybC9BTUUlMjBDUyUyMENBJTIwMDEuY3Jshi5o
# dHRwOi8vY3JsNC5hbWUuZ2JsL2NybC9BTUUlMjBDUyUyMENBJTIwMDEuY3JshoG6
# bGRhcDovLy9DTj1BTUUlMjBDUyUyMENBJTIwMDEsQ049QlkyUEtJQ1NDQTAxLENO
# PUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1D
# b25maWd1cmF0aW9uLERDPUFNRSxEQz1HQkw/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
# TGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1dGlvblBvaW50MB8GA1Ud
# IwQYMBaAFBtmohn8m+ul2oSPGJjpEKTDe5K9MB8GA1UdJQQYMBYGCisGAQQBgjdb
# AgEGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IBAQCVZn2MgvVx6Lix7aYfjSp6
# t9NYHifbtXl3VxQk20PyxzmNbXoXHQMtgWioOWP4SaQ0Jpw23TgpczqPCS/BWy0E
# t2iZ12XatAQRPqrsU4YKcTTExKEPa/gdOz63Okmi+5GQH78d53M5I39DJWzPja7d
# RdZgASuswapduSUZMAFJJOslCFtBYAuzrJDy+UH2k7NZQ4+ExtFuuwZZA16c1IbQ
# O9JwjGdYna9ZlNtWIc2RSDeNjX8St40y7hhxxrszktg13D1oGZX8+oeQBsPyzigY
# U+5JuPoUWGkFBUL8LKR6z3wddTWFZWrW5zfy2aQTxSYOpRYNcUmuAYfLBVhSNI/j
# MIII5jCCBs6gAwIBAgITHwAAABS0xR/G8oC+cQAAAAAAFDANBgkqhkiG9w0BAQsF
# ADA8MRMwEQYKCZImiZPyLGQBGRYDR0JMMRMwEQYKCZImiZPyLGQBGRYDQU1FMRAw
# DgYDVQQDEwdhbWVyb290MB4XDTE2MDkxNTIxMzMwM1oXDTIxMDkxNTIxNDMwM1ow
# QTETMBEGCgmSJomT8ixkARkWA0dCTDETMBEGCgmSJomT8ixkARkWA0FNRTEVMBMG
# A1UEAxMMQU1FIENTIENBIDAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
# AQEA1VeBAtb5+tD3G4C53TfNJNxmYfzhiXKtKQzSGxuav660bTS1VEeDDjSnFhsm
# nlb6GkPCeYmCJwWgZGs+3oWJ8yad3//VoP99bXG8azzTJmT2PFM1yKxUXUJgi7I9
# y3C4ll/ATfBwbGGRXD+2PdkdlVpxKWzeNEPVwbCtxWjUhHr6Ecy9R6O23j+2/RSZ
# SgfzYctDzDWhNf0PvGPflm31PSk4+ozca337/Ozu0+naDKg5i/zFHhfSJZkq5dPP
# G6C8wDrdiwHh6G5IGrMd2QXnmvEfjtpPqE+G8MeWbszaWxlxEjQJQC6PBwn+8Qt4
# Vqlc0am3Z3fBw8kzRunOs8Mn/wIDAQABo4IE2jCCBNYwEAYJKwYBBAGCNxUBBAMC
# AQEwIwYJKwYBBAGCNxUCBBYEFJH8M85CnvaT5uJ9VNcIGLu413FlMB0GA1UdDgQW
# BBQbZqIZ/JvrpdqEjxiY6RCkw3uSvTCCAQQGA1UdJQSB/DCB+QYHKwYBBQIDBQYI
# KwYBBQUHAwEGCCsGAQUFBwMCBgorBgEEAYI3FAIBBgkrBgEEAYI3FQYGCisGAQQB
# gjcKAwwGCSsGAQQBgjcVBgYIKwYBBQUHAwkGCCsGAQUFCAICBgorBgEEAYI3QAEB
# BgsrBgEEAYI3CgMEAQYKKwYBBAGCNwoDBAYJKwYBBAGCNxUFBgorBgEEAYI3FAIC
# BgorBgEEAYI3FAIDBggrBgEFBQcDAwYKKwYBBAGCN1sBAQYKKwYBBAGCN1sCAQYK
# KwYBBAGCN1sDAQYKKwYBBAGCN1sFAQYKKwYBBAGCN1sEAQYKKwYBBAGCN1sEAjAZ
# BgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwEgYDVR0TAQH/
# BAgwBgEB/wIBADAfBgNVHSMEGDAWgBQpXlFeZK40ueusnA2njHUB0QkLKDCCAWgG
# A1UdHwSCAV8wggFbMIIBV6CCAVOgggFPhiNodHRwOi8vY3JsMS5hbWUuZ2JsL2Ny
# bC9hbWVyb290LmNybIYxaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraWluZnJh
# L2NybC9hbWVyb290LmNybIYjaHR0cDovL2NybDIuYW1lLmdibC9jcmwvYW1lcm9v
# dC5jcmyGI2h0dHA6Ly9jcmwzLmFtZS5nYmwvY3JsL2FtZXJvb3QuY3JshoGqbGRh
# cDovLy9DTj1hbWVyb290LENOPUFNRVJPT1QsQ049Q0RQLENOPVB1YmxpYyUyMEtl
# eSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9QU1F
# LERDPUdCTD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xh
# c3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwggGrBggrBgEFBQcBAQSCAZ0wggGZMDcG
# CCsGAQUFBzAChitodHRwOi8vY3JsMS5hbWUuZ2JsL2FpYS9BTUVST09UX2FtZXJv
# b3QuY3J0MEcGCCsGAQUFBzAChjtodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp
# aW5mcmEvY2VydHMvQU1FUk9PVF9hbWVyb290LmNydDA3BggrBgEFBQcwAoYraHR0
# cDovL2NybDIuYW1lLmdibC9haWEvQU1FUk9PVF9hbWVyb290LmNydDA3BggrBgEF
# BQcwAoYraHR0cDovL2NybDMuYW1lLmdibC9haWEvQU1FUk9PVF9hbWVyb290LmNy
# dDCBogYIKwYBBQUHMAKGgZVsZGFwOi8vL0NOPWFtZXJvb3QsQ049QUlBLENOPVB1
# YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRp
# b24sREM9QU1FLERDPUdCTD9jQUNlcnRpZmljYXRlP2Jhc2U/b2JqZWN0Q2xhc3M9
# Y2VydGlmaWNhdGlvbkF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAKLdKhpqP
# H6QBaM3CAOqQi8oA4WQeZLW3QOXNmWm7UA018DQEa1yTqEQbuD5OlR1Wu/F289Dm
# XNTdsZM4GTKEaZehIiVaMoLvEJtu5h6CTyfWqPetNyOJqR1sGqod0Xwn5/G/zcTY
# Sxn5K3N8KdlcDrZAIyfq3yaEJYHGnA9eJ/f1RrfbJgeo/RAhICctOONwfpsBXcgi
# TuTmlD/k0DqogvzJgPq9GOkIyX/dxk7IkPzX/n484s0zHR4IKU58U3G1oPSQmZ5O
# HAvgHaEASkdN5E20HyJv5zN7du+QY08fI+VIci6pagLfXHYaTX3ZJ/MUM9XU+oU5
# y4qMLzTj1JIG0LVfuHK8yoB7h2inyTe7bn6h2G8NxZ02aKZ0xa+n/JnoXKNsaVPG
# 1SoTuItMsXV5pQtIShsBqnXqFjY3bJMlMhIofMcjiuOwRCW+prZ+PoYvE2P+ML7g
# s3L65GZ9BdKF3fSW3TvmpOujPQ23rzSle9WGxFJ02fNbaF9C7bG44uDzMoZU4P+u
# vQaB7KE4OMqAvYYfFy1tv1dpVIN/qhx0H/9oNiOJpuZZ39ZibLt9DXbsq5qwyHmd
# JXaisxwB53wJshUjc1i76xqFPUNGb8EZQ3aFKl2w9B47vfBi+nU3sN0tpnLPtew4
# LHWq4LBD5uiNZVBOYosZ6BKhSlk1+Y/0y1IxghXvMIIV6wIBATBYMEExEzARBgoJ
# kiaJk/IsZAEZFgNHQkwxEzARBgoJkiaJk/IsZAEZFgNBTUUxFTATBgNVBAMTDEFN
# RSBDUyBDQSAwMQITNgAAAOhmnRtIQqVMRQABAAAA6DANBglghkgBZQMEAgEFAKCB
# rjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYK
# KwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgLwGCNYHH04VTe3dz78azxGeGHpi7
# MFlQ9SL0mPG+oiUwQgYKKwYBBAGCNwIBDDE0MDKgFIASAE0AaQBjAHIAbwBzAG8A
# ZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTANBgkqhkiG9w0BAQEFAASC
# AQA/2dm8YsAkdmuN74EDnLTwSSfBZFo6JlAG/Uteat04b0KKk76CQc7KrLn5ROgM
# +U8wcA5SSAtS2wG6qk8JRyYYSXFyMJij6pFTeIJIOmQX/HjlZ1AK6GhbCc4Tn3YO
# xY+OeZLzGNZbSD7T4wSNeQiZOPj/Zfax30YIukUNYqijyqz4a/2YASMtUSBHC++X
# TER3tM5buV6XplsladjS0jPVRTX5Gs4DUhj/dhmxUkn35xKC1hbq/UFtE8tx63YD
# TKpnZLGmXn7Q+mRldNU228HIWqLWNsW9hfEqUmTo3+0BUV3SsOzRf0E+gSC+V8EG
# MK5yk/N3gp5CoEMjths3G4VpoYITtzCCE7MGCisGAQQBgjcDAwExghOjMIITnwYJ
# KoZIhvcNAQcCoIITkDCCE4wCAQMxDzANBglghkgBZQMEAgEFADCCAVgGCyqGSIb3
# DQEJEAEEoIIBRwSCAUMwggE/AgEBBgorBgEEAYRZCgMBMDEwDQYJYIZIAWUDBAIB
# BQAEIEwfwnR0gSXNIt3W0buy24uyd+w36YO/1MawY32gFOJZAgZd5nWE7+YYEzIw
# MTkxMjA0MDgyNDM4LjkwNFowBwIBAYACAfSggdSkgdEwgc4xCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpCMUI3
# LUY2N0YtRkVDMjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZaCCDx8wggT1MIID3aADAgECAhMzAAABA+pOK3i2KieTAAAAAAEDMA0GCSqGSIb3
# DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk
# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTE5MDkwNjIw
# NDExN1oXDTIwMTIwNDIwNDExN1owgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0
# byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpCMUI3LUY2N0YtRkVDMjEl
# MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCASIwDQYJKoZI
# hvcNAQEBBQADggEPADCCAQoCggEBALCRV96uFMhQPoeefQrvxbzOW3Y7LKElUOxY
# oMR9HW2+DOgS3ECplmsQJZbcXZJ1686/7CV7skdSO95rG7k9cnI9tvX4OW0WDRjt
# caFOvmDdENyBUYmIf+kuPHwA8Edbzeg7OPkBCtvM5nUJ5xVnQuiT9R5NF9axjwoY
# kHaIcaj9GWQgW5h+awLofbWyY0rWyXYk8GndPVK3MkT3FnFPWLGY6OB2lRMEgpBL
# w1uKpCUHqp4Fpff5gnvb93EVSliLr11lwfYwEF5RjNDegi9P39MjpnuXGwI2LjGK
# UsF66VGaVw4rdaUKQlPKJ0P/h/b9LCcVZyBVfwAD/XapWoo2JEsCAwEAAaOCARsw
# ggEXMB0GA1UdDgQWBBTiNi2Y/aGPja1lJu2PPODMEW9aCDAfBgNVHSMEGDAWgBTV
# YzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3Js
# Lm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNUaW1TdGFQQ0FfMjAx
# MC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0YVBDQV8yMDEwLTA3
# LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqG
# SIb3DQEBCwUAA4IBAQB8Naig5oqAZfKHIUhs99mL2pqtVa6yca9TwUyeM9yC/Sod
# uk4CXbtGtn+/j7yMpQldXXFefBh9PIg3bdlLchCtL8RXVxdmxDQYcjyxYtEmxCxx
# S+6EOBfl9I1ouXDj8Aq8wGZZ9PbAraJDeqxgVqd9W/YCrC4la3tg4HzF1hot6L0o
# xNlTnu+RyXmZ7f6f/Vq6KZAZPyaKbI5P6qHBG3REPAgr45+GySpbWAQhZjUNZ9pJ
# 3NcePbuaa8N2xiIJsc9t8/FTSCj1xoiK9q2YvXoA64/hkyUIn/fBFY3BD6meMnQC
# G500ruiAm0GujHYOmZxAbosMOxtLPahmQUkcbQCPMIIGcTCCBFmgAwIBAgIKYQmB
# KgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
# dCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNh
# dGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcNMjUwNzAxMjE0NjU1
# WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIwDQYJKoZIhvcNAQEB
# BQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0VBDVpQoAgoX77Xxo
# SyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEwRA/xYIiEVEMM1024
# OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQedGFnkV+BVLHPk0yS
# wcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKxXf13Hz3wV3WsvYpC
# TUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4GkbaICDXoeByw6ZnN
# POcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEAAaOCAeYwggHiMBAG
# CSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7fEYbxTNoWoVtVTAZ
# BgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
# BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8E
# TzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9k
# dWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBM
# MEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRz
# L01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0gAQH/BIGVMIGSMIGP
# BgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93d3cubWljcm9zb2Z0
# LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYBBQUHAgIwNB4yIB0A
# TABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUAbQBlAG4AdAAuIB0w
# DQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOhIW+z66bM9TG+zwXi
# qf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS+7lTjMz0YBKKdsxA
# QEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlKkVIArzgPF/UveYFl
# 2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon/VWvL/625Y4zu2Jf
# mttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOiPPp/fZZqkHimbdLh
# nPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/fmNZJQ96LjlXdqJx
# qgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCIIYdqwUB5vvfHhAN/n
# MQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0cs0d9LiFAR6A+xuJ
# KlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7aKLixqduWsqdCosnP
# GUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQcdeh0sVV42neV8HR
# 3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+NR4Iuto229Nfj950
# iEkSoYIDrTCCApUCAQEwgf6hgdSkgdEwgc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpCMUI3LUY2N0YtRkVD
# MjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIlCgEBMAkG
# BSsOAwIaBQADFQBrXDo2eDr68GRJRlXYVs3aqGzas6CB3jCB26SB2DCB1TELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9z
# b2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJzAlBgNVBAsTHm5DaXBoZXIgTlRT
# IEVTTjo0REU5LTBDNUUtM0UwOTErMCkGA1UEAxMiTWljcm9zb2Z0IFRpbWUgU291
# cmNlIE1hc3RlciBDbG9jazANBgkqhkiG9w0BAQUFAAIFAOGRuvIwIhgPMjAxOTEy
# MDQxMjU2MThaGA8yMDE5MTIwNTEyNTYxOFowdDA6BgorBgEEAYRZCgQBMSwwKjAK
# AgUA4ZG68gIBADAHAgEAAgIjCjAHAgEAAgIXjDAKAgUA4ZMMcgIBADA2BgorBgEE
# AYRZCgQCMSgwJjAMBgorBgEEAYRZCgMBoAowCAIBAAIDFuNgoQowCAIBAAIDB6Eg
# MA0GCSqGSIb3DQEBBQUAA4IBAQBIXllBeEox4PM1V7Usrc++goLL7nvk8cpL5Ff/
# BCylRyT5rwHQEFeS1SAcWuAWTinXtAFVraR6GtW2m+n99qH2Db3/c5fWQzX/WA25
# 6AlJeDias/dgdMTc8aV3kPCcPU919jnzNod6VB0/Yx0FOoM+P91czipGJjvWgMHA
# Jw4gE1FNfApYeh1NOtG23A2O4GBdKfv5vjpPT7esf7XuZ5disGm4QZH6VwtArZBM
# wFkvMUWYsxSudHrLzWgJcGLAYklVln48iSlc/9LscEeJy9vqGzneIvaoqKh3gLLl
# V0fuR2scX5Vvflk9+OEb8zR2eV95FwM7GviYeMK++ZRZ5csCMYIC9TCCAvECAQEw
# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAED6k4reLYqJ5MA
# AAAAAQMwDQYJYIZIAWUDBAIBBQCgggEyMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B
# CRABBDAvBgkqhkiG9w0BCQQxIgQgnjKS7lpEfSI0da2+7IA+SF38WAEjSfLNEvV5
# rw2DGtowgeIGCyqGSIb3DQEJEAIMMYHSMIHPMIHMMIGxBBRrXDo2eDr68GRJRlXY
# Vs3aqGzaszCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMz
# AAABA+pOK3i2KieTAAAAAAEDMBYEFDOmp8mqbV5A967ie0+lA+2SGQ8KMA0GCSqG
# SIb3DQEBCwUABIIBACV2anr8IUztrqtxeUy+nNm4xYwUe6Nfe4YI9f/FdheslP9b
# qqTyZAHclbegHrpe5ImYul8/IZm0lEem1vl1Mld4lvi/d1bR8SyT8S9+m2AHjss0
# 6IfkUldeJuvxIqUxUv+kXO0RqxZUKyGP72PSNCRDUjnE9njgO580n5c6oAj38PGx
# u+UYisHUShc6ivDgju3cjnCvmmhecEmKqQYEvV5MC2NIbtSqgEOOIEaYTbWanxzt
# xfmC1H1YZFLx0f6dx+hroTlM3DtzRaK1XbZvvhTLKuaKHB9+2h7JzRFp+hCE/C6A
# YRTpWYRrbgnv2fCnPkxwfTF2DdPUWsTdxNlIxOY=
# SIG # End signature block