Framework/Core/SVT/Services/KeyVault.ps1

using namespace Microsoft.Azure.Commands.KeyVault.Models
Set-StrictMode -Version Latest 
class KeyVault: SVTBase
{       
    hidden [PSVault] $ResourceObject;

    KeyVault([string] $subscriptionId, [string] $resourceGroupName, [string] $resourceName): 
        Base($subscriptionId, $resourceGroupName, $resourceName) 
    { 
        $this.GetResourceObject();
    }

    KeyVault([string] $subscriptionId, [SVTResource] $svtResource): 
        Base($subscriptionId, $svtResource) 
    { 
        $this.GetResourceObject();
    }

    hidden [PSVault] GetResourceObject()
    {
        if (-not $this.ResourceObject) 
        {
            $this.ResourceObject = Get-AzureRmKeyVault -VaultName $this.ResourceContext.ResourceName `
                                            -ResourceGroupName $this.ResourceContext.ResourceGroupName
            if(-not $this.ResourceObject)
            {
                throw ("Resource '{0}' not found under Resource Group '{1}'" -f ($this.ResourceContext.ResourceName), ($this.ResourceContext.ResourceGroupName))
            }
        }
        return $this.ResourceObject;
    }

    hidden [ControlResult] CheckAdvancedAccessPolicies([ControlResult] $controlResult)
    {
        $accessPolicies = @{};
        $accessPolicies.Add("Enable access to Azure Virtual Machines for deployment", $this.ResourceObject.EnabledForDeployment);
        $accessPolicies.Add("Enable access to Azure Resource Manager for template deployment", $this.ResourceObject.EnabledForTemplateDeployment);
        $accessPolicies.Add("Enable access to Azure Disk Encryption for volume encryption", $this.ResourceObject.EnabledForDiskEncryption);
        
        $controlResult.SetStateData("Key Vault advanced access policies", $accessPolicies);

        if($this.ResourceObject.EnabledForDeployment -and $this.ResourceObject.EnabledForDiskEncryption -and $this.ResourceObject.EnabledForTemplateDeployment)
        {          
            $controlResult.AddMessage([VerificationResult]::Failed,
                                      [MessageData]::new("All Advanced Access Policies are enabled - ["+ $this.ResourceContext.ResourceName +"]"  , 
                                                         $accessPolicies));
        }
        elseif($this.ResourceObject.EnabledForDeployment -or $this.ResourceObject.EnabledForDiskEncryption -or $this.ResourceObject.EnabledForTemplateDeployment)
        {
            $controlResult.AddMessage([VerificationResult]::Verify,
                                     [MessageData]::new("Validate Advanced Access Policies - ["+ $this.ResourceContext.ResourceName +"]" , 
                                                         $accessPolicies));
        }
        else
        {       
            $controlResult.AddMessage([VerificationResult]::Passed, 
                                      [MessageData]::new("All Advanced Access Policies are disabled - ["+ $this.ResourceContext.ResourceName +"]", 
                                                         $accessPolicies));
        }  
        return $controlResult;
    }

    hidden [ControlResult] CheckAccessPolicies([ControlResult] $controlResult)
    {
        $accessPolicies = $this.ResourceObject.AccessPolicies
        $allPermisionUsers = $accessPolicies | Where-Object { $_.PermissionsToKeys -eq "All" -and $_.PermissionsToSecrets -eq "All" -and $_.PermissionsToCertificates -eq "All" } 
        
        if(($allPermisionUsers | Measure-Object).count -gt 0)
        {
            $controlResult.VerificationResult = [VerificationResult]::Failed; 
            $controlResult.AddMessage([MessageData]::new("Applications/Users having full control to Key/Secret/Certificate - ["+ $this.ResourceContext.ResourceName +"]" , 
                                                         $allPermisionUsers));
        }
        else
        {
            $controlResult.VerificationResult = [VerificationResult]::Verify; 
            $controlResult.SetStateData("Access policies and their assigned permissions to Key/Secret/Certificate", $accessPolicies);

        }

        $controlResult.AddMessage([MessageData]::new("Validate access policies and their assigned permissions to Key/Secret/Certificate - ["+ $this.ResourceContext.ResourceName +"]" , 
                                                         $accessPolicies));
        return $controlResult;
    }

    hidden [ControlResult] CheckKeyHSMProtected([ControlResult] $controlResult)
    {
        try
        {
            $allKeys = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop
            
            if(($allKeys | Measure-Object).Count -gt 0) 
            {
                $nonHsmKeys = @();
                $allKeys | ForEach-Object {
                    Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions |
                    ForEach-Object {
                        $nonHsmKeys += Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version | 
                                            Where-Object { $_.Attributes.KeyType -ne $this.ControlSettings.KeyVault.KeyType };
                    }
                }
                
                if(($nonHsmKeys | Measure-Object).Count -eq 0)
                { 
                    $controlResult.AddMessage( [VerificationResult]::Passed,
                        [MessageData]::new("All Keys, including previous versions, are protected by HSM for Key Vault - ["+ $this.ResourceContext.ResourceName +"]"));   
                }
                else 
                {
                    $controlResult.AddMessage([VerificationResult]::Failed,
                        [MessageData]::new("Following Keys, including previous versions, are not protected by HSM. Delete the key versions in order to comply."  , 
                                ($nonHsmKeys | Select-Object Name, Version -ExpandProperty Attributes )));
                }
            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Passed,
                    [MessageData]::new("No Keys found in - ["+ $this.ResourceContext.ResourceName +"]"));
            }
        }
        catch
        {
            if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
            {
                $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Keys."));
            }
            else
            {
                throw $_
            }
        }

        return $controlResult;
    }
    
    hidden [ControlResult] CheckKeyMinimumOperations([ControlResult] $controlResult)
    {
        try
        {
            $allKeys = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop
            
            if(($allKeys| Measure-Object).Count -gt 0)    
            {
                $keyDetails = @();
                $allKeys | ForEach-Object {
                    Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions |
                    ForEach-Object {
                        $key = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version #| Where-Object {$null -eq $_.Attributes.Expires }
                        $keyDetails += $key #rename variable
                    }
                }

                $keyOperations = $keyDetails | Select-Object Name, Version, @{Label="Key Operations"; Expression={[system.string]::Join(", ",$_.Key.KeyOps)}} 

                $controlResult.SetStateData("Key Vault key operations", $keyOperations);

                $controlResult.AddMessage([VerificationResult]::Verify,
                                            [MessageData]::new("Verify the operations permitted using Key on - ["+ $this.ResourceContext.ResourceName +"]",
                                            ($keyOperations )) );   

            }
            else
            {
                $controlResult.AddMessage([VerificationResult]::Passed,
                                            [MessageData]::new("No Keys found in - ["+ $this.ResourceContext.ResourceName +"]"));   
            }
        }
        catch 
        {
            if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
            {
            $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Keys."));
            }
            else
            {
                throw $_
            }
        }
        return $controlResult;
    }

    hidden [ControlResult] CheckAppAuthenticationCertificate([ControlResult] $controlResult)
    {
        try{
              $outputList = @();
              $appList = $this.GetAzureRmKeyVaultApplications()
              $appList |
                ForEach-Object {
                    $credentials = Get-AzureRmADAppCredential -ApplicationId $_.ApplicationId
                    $compliance =  if (($credentials| Where-Object { $_.Type -eq $this.ControlSettings.KeyVault.ADAppCredentialTypePwd } | Measure-Object).Count -eq 0 ) { "Yes" } else { "No" } ;
                    $output = New-Object System.Object
                    $output | Add-Member -type NoteProperty -name AzureADAppName -Value $_.DisplayName
                    $output | Add-Member -type NoteProperty -name ApplicationId -Value $_.ApplicationId
                    $output | Add-Member -type NoteProperty -name CertificateCredentialCount -Value ($credentials | Where-Object { $_.Type -eq $this.ControlSettings.KeyVault.ADAppCredentialTypeCrt } | Measure-Object ).Count
                    $output | Add-Member -type NoteProperty -name PasswordCredentialCount -Value ($credentials | Where-Object { $_.Type -eq $this.ControlSettings.KeyVault.ADAppCredentialTypePwd } | Measure-Object).Count
                    $output | Add-Member -type NoteProperty -name Compliance -Value $compliance
                    $outputList += $output;
                }
         
                if(($outputList| Measure-Object).Count -gt 0)
                {
                    $controlResult.AddMessage([MessageData]::new("Compliance details of Azure Active Directory applications:",
                                                                  $outputList));
          
                    if (($outputList | Where-Object { ($_.Compliance -eq "No") } | Measure-Object ).Count -gt 0)
                    {
                        $controlResult.AddMessage([VerificationResult]::Failed ,
                                                  [MessageData]::new("Remove the password credentials from Azure AD Applications which are non-compliant.") );
                    }
                    else
                    {
                        $controlResult.VerificationResult = [VerificationResult]::Passed
                    }
                }
                else
                {
                    $controlResult.AddMessage([VerificationResult]::Passed ,
                                                  [MessageData]::new("No Azure AD Applications have access to Key Vault.") );
                }
        }
        catch
        {
             if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
             {
                $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Keys."));
             }
             else
             {
                 throw $_
             }    
        }
        return $controlResult;
    }

   
    hidden [ControlResult] CheckAppsSharingKayVault([ControlResult] $controlResult)
    {
        $appList = $this.GetAzureRmKeyVaultApplications() 
      
        $controlResult.SetStateData("Key Vault sharing app list", $appList);

        if( ($appList | Measure-Object ).Count -gt 1)
        {
            $controlResult.AddMessage([VerificationResult]::Verify,
                                        [MessageData]::new("Validate that Azure AD Applications requires access to Key Vault. Total:" + ($appList | Measure-Object ).Count , 
                                                            $appList)); 
        }
        elseif( ($appList | Measure-Object ).Count -eq 1)
        {
           $controlResult.AddMessage([VerificationResult]::Passed,
                                     "Only 1 Azure AD Application has access to Key Vault.", $appList); 
        } 
        else
        {
           $controlResult.AddMessage([VerificationResult]::Passed,
                                     "No Azure AD Applications have access to Key Vault."); 
        } 
      
        return $controlResult;
    }

    hidden [ControlResult] CheckKeyExpirationDate([ControlResult] $controlResult)
    {
        $IsKeysCompliant = $True
        $IsAccessDenied = $False
        try
        {
            $allKeys = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop
            
            if(($allKeys| Measure-Object).Count -gt 0) 
            {
                $keysWithoutExpiry = @();
                $keysWithExpiry = @();
                $longDurationActiveKeys = @();
                
                $allKeys | 
                ForEach-Object {
                        Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions |
                        ForEach-Object {
                            $key = Get-AzureKeyVaultKey -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version #| Where-Object { $_.Attributes.Expires -eq $null }
                            if($null -eq $key.Attributes.Expires)
                            {
                                $keysWithoutExpiry += $key
                            }
                            else
                            {
                                $keysWithExpiry += $key
                            }
                        }
                    }

                if(($keysWithoutExpiry | Measure-Object ).Count -gt 0)
                {
                    $IsKeysCompliant = $False
                    $controlResult.AddMessage([MessageData]::new("Following Keys, including previous versions, does not have expiry date. Set the expiry date in order to comply.", 
                                                            ($keysWithoutExpiry | Select-Object -Property Name, Version -ExpandProperty Attributes) ));
                }
             
                if(($keysWithExpiry | Measure-Object ).Count -gt 0) 
                { 
                    $controlResult.AddMessage([MessageData]::new("Keys, including previous versions, have expiry date for Key Vault - ["+$this.ResourceContext.ResourceName+"]", 
                                                 ($keysWithExpiry | Select-Object -Property Name, Version -ExpandProperty Attributes ) ));
                    $keysWithExpiry | 
                    ForEach-Object {
                        $startDate = [DateTime] $_.Attributes.Created
                        if($null -ne $_.Attributes.NotBefore)
                        {
                            $startDate = [DateTime] $_.Attributes.NotBefore;
                        }

                        if ((((([DateTime] $_.Attributes.Expires) - $startDate).TotalDays)/($this.ControlSettings.KeyVault.KeyRotationDuration_Days)) -gt 1)
                        {
                            $longDurationActiveKeys += $_ 
                        }               
                    }

                    if (($longDurationActiveKeys| Measure-Object ).Count -gt 0)
                    {
                        $IsKeysCompliant = $False
                        $controlResult.AddMessage([MessageData]::new("Following Keys, including previous versions, are active for more than $($this.ControlSettings.KeyVault.KeyRotationDuration_Days) Days. Please delete/rotate the keys in order to comply.", 
                                                    ($longDurationActiveKeys | Select-Object -Property Name, Version -ExpandProperty Attributes ) ));
                    }
                }                     
            }
            else
            {
                $controlResult.AddMessage( [MessageData]::new("No Keys found in - ["+ $this.ResourceContext.ResourceName +"]")); 
            }
        }
        catch
        {
            $IsKeysCompliant = $False
            $IsAccessDenied = $True
             if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
             {
               $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Keys."));
             }
             else
             {
                 throw $_
             }    
        }
  
        $IsSecretCompliant = $True
        try
        {
            $allSecrets = Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -ErrorAction Stop
            
            if(($allSecrets| Measure-Object).Count -gt 0) 
            {     
                $secretsWithoutExpiry = @();
                $secretsWithExpiry = @();
                $longDurationActiveSecrets = @();
  
                $allSecrets | 
                ForEach-Object {
                    Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -Name $_.Name -IncludeVersions |
                    ForEach-Object {
                        $secret = Get-AzureKeyVaultSecret -VaultName $this.ResourceContext.ResourceName -Name $_.Name -Version $_.Version #| Where-Object { $_.Attributes.Expires -eq $null }
                        if( -not ([Helpers]::CheckMember($secret, "Attributes.Expires")))  
                        {
                            $secretsWithoutExpiry += $secret
                        }
                        else
                        {
                            $secretsWithExpiry += $secret
                        }
                    }
                }

                if(($secretsWithoutExpiry| Measure-Object ).Count -gt 0)
                {
                    $IsSecretCompliant = $False
                    $controlResult.AddMessage([MessageData]::new("Following Secrets, including previous versions, does not have expiry date. Set the expiry date in order to comply.", 
                                                      ($secretsWithoutExpiry | Select-Object Name, Version -ExpandProperty Attributes  ) ));
                }

                if(($secretsWithExpiry| Measure-Object ).Count -gt 0)
                {                       
                    $controlResult.AddMessage([MessageData]::new("Secrets, including previous versions, have expiry date for Key Vault - ["+ $this.ResourceContext.ResourceName +"]", 
                                             ( $secretsWithExpiry | Select-Object Name, Version -ExpandProperty Attributes  ) ));
                    $secretsWithExpiry | 
                    ForEach-Object {
                        $startDate = [DateTime] $_.Attributes.Created
                        if($null -ne $_.Attributes.NotBefore)
                        {
                            $startDate = [DateTime] $_.Attributes.NotBefore;
                        }

                        if ((((([DateTime] $_.Attributes.Expires) - $startDate).TotalDays)/($this.ControlSettings.KeyVault.SecretRotationDuration_Days)) -gt 1)
                        {
                            $longDurationActiveSecrets += $_ 
                        }  
                    }

                    if (($longDurationActiveSecrets| Measure-Object ).Count -gt 0)
                    {
                         $IsSecretCompliant = $False                   
                         $controlResult.AddMessage([MessageData]::new("Following Secrets, including previous versions, are active for more than $($this.ControlSettings.KeyVault.SecretRotationDuration_Days) Days. Please delete/renew the secrets in order to comply.", 
                                                  ( $longDurationActiveSecrets | Select-Object Name, Version -ExpandProperty Attributes   ) ));
                    }
                }
            }
            else
            {
                $controlResult.AddMessage( [MessageData]::new("No Secrets found in - ["+ $this.ResourceContext.ResourceName +"]")); 
            }
    }
    catch
        {
             $IsSecretCompliant = $False
             $IsAccessDenied = $True
             if ($_.Exception.GetType().FullName -eq "Microsoft.Azure.KeyVault.Models.KeyVaultErrorException")
             {
                $controlResult.AddMessage([MessageData]::new("Access denied: Read access is required on Key Vault Secrets."));
             }
             else
             {
                 throw $_
             }    
        }
 
        #Got exception because of access denied, Keeping status Verify.
        if(-not $IsAccessDenied)
        {
            if($IsKeysCompliant -and $IsSecretCompliant)
            {
                $controlResult.VerificationResult = [VerificationResult]::Passed
            }
            else
            {
                $controlResult.VerificationResult = [VerificationResult]::Failed
            }
        }
        return $controlResult;
   }

    hidden [PSObject] GetAzureRmKeyVaultApplications ()  
     {
        $applicationList = @();
        $this.ResourceObject.AccessPolicies  | 
        ForEach-Object { 
            $svcPrincipal= Get-AzureRmADServicePrincipal -ObjectId $_.ObjectId
            if($svcPrincipal){
                $application = Get-AzureRmADApplication -ApplicationId $svcPrincipal.ApplicationId
                if($application){
                    $applicationList += $application
                }
            }
        }
        return $applicationList;
}
}
# SIG # Begin signature block
# MIIkAgYJKoZIhvcNAQcCoIIj8zCCI+8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCnuoLgR+DW6RXJ
# a8A0pcCb9fwKUBLlHjlQGn3189ZJs6CCDZMwggYRMIID+aADAgECAhMzAAAAjoeR
# pFcaX8o+AAAAAACOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTYxMTE3MjIwOTIxWhcNMTgwMjE3MjIwOTIxWjCBgzEL
# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v
# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UECxMETU9Q
# UjEeMBwGA1UEAxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMIIBIjANBgkqhkiG9w0B
# AQEFAAOCAQ8AMIIBCgKCAQEA0IfUQit+ndnGetSiw+MVktJTnZUXyVI2+lS/qxCv
# 6cnnzCZTw8Jzv23WAOUA3OlqZzQw9hYXtAGllXyLuaQs5os7efYjDHmP81LfQAEc
# wsYDnetZz3Pp2HE5m/DOJVkt0slbCu9+1jIOXXQSBOyeBFOmawJn+E1Zi3fgKyHg
# 78CkRRLPA3sDxjnD1CLcVVx3Qv+csuVVZ2i6LXZqf2ZTR9VHCsw43o17lxl9gtAm
# +KWO5aHwXmQQ5PnrJ8by4AjQDfJnwNjyL/uJ2hX5rg8+AJcH0Qs+cNR3q3J4QZgH
# uBfMorFf7L3zUGej15Tw0otVj1OmlZPmsmbPyTdo5GPHzwIDAQABo4IBgDCCAXww
# HwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0OBBYEFKvI1u2y
# FdKqjvHM7Ww490VK0Iq7MFIGA1UdEQRLMEmkRzBFMQ0wCwYDVQQLEwRNT1BSMTQw
# MgYDVQQFEysyMzAwMTIrYjA1MGM2ZTctNzY0MS00NDFmLWJjNGEtNDM0ODFlNDE1
# ZDA4MB8GA1UdIwQYMBaAFEhuZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEsw
# SaBHoEWGQ2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0Nv
# ZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsG
# AQUFBzAChkVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p
# Y0NvZFNpZ1BDQTIwMTFfMjAxMS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkq
# hkiG9w0BAQsFAAOCAgEARIkCrGlT88S2u9SMYFPnymyoSWlmvqWaQZk62J3SVwJR
# avq/m5bbpiZ9CVbo3O0ldXqlR1KoHksWU/PuD5rDBJUpwYKEpFYx/KCKkZW1v1rO
# qQEfZEah5srx13R7v5IIUV58MwJeUTub5dguXwJMCZwaQ9px7eTZ56LadCwXreUM
# tRj1VAnUvhxzzSB7pPrI29jbOq76kMWjvZVlrkYtVylY1pLwbNpj8Y8zon44dl7d
# 8zXtrJo7YoHQThl8SHywC484zC281TllqZXBA+KSybmr0lcKqtxSCy5WJ6PimJdX
# jrypWW4kko6C4glzgtk1g8yff9EEjoi44pqDWLDUmuYx+pRHjn2m4k5589jTajMW
# UHDxQruYCen/zJVVWwi/klKoCMTx6PH/QNf5mjad/bqQhdJVPlCtRh/vJQy4njpI
# BGPveJiiXQMNAtjcIKvmVrXe7xZmw9dVgh5PgnjJnlQaEGC3F6tAE5GusBnBmjOd
# 7jJyzWXMT0aYLQ9RYB58+/7b6Ad5B/ehMzj+CZrbj3u2Or2FhrjMvH0BMLd7Hald
# G73MTRf3bkcz1UDfasouUbi1uc/DBNM75ePpEIzrp7repC4zaikvFErqHsEiODUF
# he/CBAANa8HYlhRIFa9+UrC4YMRStUqCt4UqAEkqJoMnWkHevdVmSbwLnHhwCbww
# ggd6MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv
# ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5
# MDlaFw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIw
# MTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQ
# TTS68rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULT
# iQ15ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYS
# L+erCFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494H
# DdVceaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZ
# PrGMXeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5
# bmR/U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGS
# rhwjp6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADh
# vKwCgl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON
# 7E1JMKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xc
# v3coKPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqw
# iBfenk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMC
# AQAwHQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQM
# HgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1Ud
# IwQYMBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0
# dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0Nl
# ckF1dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUF
# BzAChkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0Nl
# ckF1dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGC
# Ny4DMIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp
# b3BzL2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcA
# YQBsAF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZI
# hvcNAQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4s
# PvjDctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKL
# UtCw/WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7
# pKkFDJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft
# 0N3zDq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4
# MnEnGn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxv
# FX1Fp3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG
# 0QaxdR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf
# 0AApxbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkY
# S//WsyNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrv
# QQqxP/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIV
# xTCCFcECAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAA
# AI6HkaRXGl/KPgAAAAAAjjANBglghkgBZQMEAgEFAKCBsDAZBgkqhkiG9w0BCQMx
# DAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkq
# hkiG9w0BCQQxIgQge9RHvjWx6WBh/RathuAMfeS8Tdm7TAr/uF6K7PXY+UkwRAYK
# KwYBBAGCNwIBDDE2MDSgEoAQAEEAegBTAEQASwAyADUAMqEegBxodHRwczovL2Fr
# YS5tcy9henNka29zc2RvY3MgMA0GCSqGSIb3DQEBAQUABIIBAJG9ePO1MlB9M4F2
# V83NnEmIUXDdNTQf5ETs7/+XYkCsvPiIX9VOEKr9D9YG9fsBbIj+KFFaIw7O+JOM
# NoJ5RBsozubjRfP6prg/NGp90dLqf5kWv4un3FoCPP7hNRYI9Z5XStT4n4W/K476
# ziZblRKaDa86b/ciAoYB6yiD6YI9P8DWDuuy4aWm8fKJhMlxzj4I8Iv0vvn/inYU
# SM5q+DZciRcgIOqqxnHudQM3TRi+fM1iCITA5y1KVHGw3yCnSy4RJ9yuX3rb8NWR
# WD5+Ks+sHSUnAimC/z7O+anYVWuS4HJQwUgxxEpWtiwvxORQq6JzkAMhMu6Dkzdw
# m37o0G6hghNNMIITSQYKKwYBBAGCNwMDATGCEzkwghM1BgkqhkiG9w0BBwKgghMm
# MIITIgIBAzEPMA0GCWCGSAFlAwQCAQUAMIIBPQYLKoZIhvcNAQkQAQSgggEsBIIB
# KDCCASQCAQEGCisGAQQBhFkKAwEwMTANBglghkgBZQMEAgEFAAQg1GzYb5GcjNff
# vyVqNjABdVLxnTotDNzFWVYm47Q1DOgCBlmSMx1TlBgTMjAxNzA5MDUwOTM3MTMu
# OTk2WjAHAgEBgAIB9KCBuaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0Ug
# RVNOOjMxQzUtMzBCQS03QzkxMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIO0DCCBnEwggRZoAMCAQICCmEJgSoAAAAAAAIwDQYJKoZIhvcN
# AQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAw
# BgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEw
# MB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIxNDY1NVowfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCp
# HQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF++18aEssX8XD5WHCdrc+Zitb8BVT
# JwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRDDNdNuDgIs0Ldk6zWczBXJoKjRQ3Q
# 6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSxz5NMksHEpl3RYRNuKMYa+YaAu99h
# /EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1rL2KQk1AUdEPnAY+Z3/1ZsADlkR+
# 79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16HgcsOmZzTznL0S6p/TcZL2kAcEgCZN4
# zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB4jAQBgkrBgEEAYI3FQEEAwIBADAd
# BgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqFbVUwGQYJKwYBBAGCNxQCBAweCgBT
# AHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgw
# FoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov
# L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0
# XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0
# cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAx
# MC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCBkjCBjwYJKwYBBAGCNy4DMIGBMD0G
# CCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vUEtJL2RvY3MvQ1BT
# L2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAFAAbwBs
# AGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4IC
# AQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUxvs8F4qn++ldtGTCzwsVmyWrf9efw
# eL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GASinbMQEBBm9xcF/9c+V4XNZgkVkt0
# 70IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1L3mBZdmptWvkx872ynoAb0swRCQi
# PM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWOM7tiX5rbV0Dp8c6ZZpCM/2pif93F
# SguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4pm3S4Zz5Hfw42JT0xqUKloakvZ4a
# rgRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45V3aicaoGig+JFrphpxHLmtgOR5qA
# xdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x4QDf5zEHpJM692VHeOj4qEir995y
# fmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEegPsbiSpUObJb2sgNVZl6h3M7COaY
# LeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKnQqLJzxlBTeCG+SqaoxFmMNO7dDJL
# 32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp3lfB0d4wwP3M5k37Db9dT+mdHhk4
# L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvTX4/edIhJEjCCBNowggPCoAMCAQIC
# EzMAAACgGph4PmbYqtcAAAAAAKAwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwHhcNMTYwOTA3MTc1NjQ4WhcNMTgwOTA3MTc1NjQ4
# WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsGA1UE
# CxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjMxQzUtMzBCQS03Qzkx
# MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIBIjANBgkq
# hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA52wU4jDRZ9KtBK622yI96CopNe6hQakb
# U8w8PfgtvRENeMUEEQVrWEOU5GMkHWUj6cUz05BrFQ7RcEnqs+QiapTlbWMS5s9o
# 1H1oNneIeM1ZtkbGvMhKOX/0a4QxOnGr9Ajeaxy3JO4fi1+H6HphTDlmr06jco1D
# DXWcuw5AqgsT5SzQdaXICnaH8d0V6T7ovybN4WpSGAKTd/0PEV6J8bYIXqNTJVK+
# lWVDAPS9O6o6tEtsBFJpKi2fzjh/Hjc/OoeQsR5jHlah2GDLv9jRWkR8cIoeU/on
# qawxpmsKnXysMJejJraEpcSl8bx1yMmM8BgRQeXPz9gYP00zBCOcGwIDAQABo4IB
# GzCCARcwHQYDVR0OBBYEFMiwxPehFQIOYhdwOn5SNpFalHYBMB8GA1UdIwQYMBaA
# FNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j
# cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1RpbVN0YVBDQV8y
# MDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3RhUENBXzIwMTAt
# MDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJ
# KoZIhvcNAQELBQADggEBABAJeQeXD5MucL1J1jbwVDnyQPaPP9IJdRzzWudobl06
# 1TgqE2sirzCtFR9B1MxTRbn4v+coz1BfKzXm6h77818Q9ievvSB50lpsvkMITije
# +mTemKXeWyZNw2lJJztKPLCgwEyTfUadJoI8vC6Bfca1gjILJald4Grg2+Lhe6oi
# YssFtwY9bkW+8sGZfrAL0CyuggARt6snq64iDIQJM9B1ATsKsMuqIi/kBE2nrpD1
# ZHT4zFZsYS6+IVefhBdcu3KRrXngpCDrLgH0H9L/KQyb2vgm4striVBVomm15MBY
# aaQoLn0AZfK9ScP6vzPGmUc+G2aJzVZqqzvrZ7V9UlKhggN5MIICYQIBATCB46GB
# uaSBtjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjENMAsG
# A1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNOOjMxQzUtMzBCQS03
# QzkxMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiUKAQEw
# CQYFKw4DAhoFAAMVAIQVUWUii4Xre1VA0VgDHA3Fcm/AoIHCMIG/pIG8MIG5MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMQ0wCwYDVQQLEwRNT1BS
# MScwJQYDVQQLEx5uQ2lwaGVyIE5UUyBFU046NTdGNi1DMUUwLTU1NEMxKzApBgNV
# BAMTIk1pY3Jvc29mdCBUaW1lIFNvdXJjZSBNYXN0ZXIgQ2xvY2swDQYJKoZIhvcN
# AQEFBQACBQDdWK1ZMCIYDzIwMTcwOTA1MDQ1ODAxWhgPMjAxNzA5MDYwNDU4MDFa
# MHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAN1YrVkCAQAwCgIBAAICCvkCAf8wBwIB
# AAICGDswCgIFAN1Z/tkCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoD
# AaAKMAgCAQACAxbjYKEKMAgCAQACAwehIDANBgkqhkiG9w0BAQUFAAOCAQEAMiwG
# xMv2sBvhEofejqAcEtUvQWYNW9NN81UIv4OySJZz08kx50azc6afv9kwoKM+hTeo
# 70gH0Fi+6QFAnhEHgCmBKfaSD7NLpARgOWGdQHXkKoxVuBtBjKJp7jYXgguoYodZ
# KLkePFiEgunXZI+EXsrhdmMkeAOeXZtOR0WSU4DzCRxKoeBtUiLN4ouSaQQ5+TPa
# gbQrT5R5+HlsrZXQUO/Q059SJuz6nBGKY5pmZW8RFiy+lPyiNwgghDR2fOsRDjbO
# 1XChhcDvrPZcECXua4zfS/M/qT48GLPJLgFt+qnj5C7xNiicVn9fMtDk4Gp++lEM
# k5SJTl1ZNSPw1lpJ3TGCAvUwggLxAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwAhMzAAAAoBqYeD5m2KrXAAAAAACgMA0GCWCGSAFlAwQCAQUAoIIB
# MjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIBIe
# ghpIjRCZo0zWQeIRkNyzbgaWDCdxpDXmucT7zvyMMIHiBgsqhkiG9w0BCRACDDGB
# 0jCBzzCBzDCBsQQUhBVRZSKLhet7VUDRWAMcDcVyb8AwgZgwgYCkfjB8MQswCQYD
# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAKAamHg+Ztiq1wAAAAAAoDAWBBTA
# 85YJQWPAB1Gd0BMAT0WYJRkPXTANBgkqhkiG9w0BAQsFAASCAQBsY6//SMKGNoFY
# fdUE7Jd4xdcF5XFmSFjwggxsRZgXxLsCzo8j/VXzLqD/CL4ScH4fUf+LoHnXkZrh
# HCVhxQTgIhKGqdtZy7Xt6wVY0qbsModjycfoycX6tUM9lVf/o57tCVNOipG9AzES
# +NxCGHjSrlNqA6/DLPRgOOuOBbl6GfXh2t//PCZ9WFWcnT6atqQlD2sRJmDgUsH7
# Q+4M+16uuaMr+JBryROLgW0dDupQ1soFNcji55Ifk5r2n0bNyQeMd+QfF3R0fidm
# za/K9GfXYBVKuwLkvc+x4gF5EiW5DKqbwc7xQ2cuVFtFisG/EnrmbykXn80jiTXQ
# z5t1fKeS
# SIG # End signature block