SignPath.psm1

#Requires -Version 3.0
# Also requires .NET Version 4.7.2 or above

$ServiceUnavailableRetryTimeoutInSeconds = 30
$WaitForCompletionRetryTimeoutInSeconds = 5
$DefaultHttpClientTimeoutInSeconds = 100

function Submit-SigningRequest(
  # The URL to the API, e.g. 'https://app.signpath.io/api/'.
  [Parameter()]
  [ValidateNotNullOrEmpty()]
  [string] $ApiUrl = "https://app.signpath.io/api/",

  # The API token you retrieve when adding a new CI user.
  [Parameter(Mandatory)]
  [ValidateNotNullOrEmpty()]
  [string] $CIUserToken,
  
  # The ID of the organization containing the signing policy.
  # Go to "Project > Signing policy" in the web client and copy the first ID from the url to retrieve this value (e.g. https://app.signpath.io/<OrganizationId>/SigningPolicies/<SigningPolicyId>).
  [Parameter(Mandatory)]
  [ValidateNotNullOrEmpty()]
  [string] $OrganizationId,

  # If you use IDs to reference the artifact configuration and signing policy: this is the ID of the artifact configuration you want to use for
  # signing. If not given, the default artifact configuration will be used instead.
  # Go to "Project -> Artifact configuration" in the web client and copy the last ID from the url to retrieve this value (e.g. https://app.signpath.io/<OrganizationId>/ArtifactConfigurations/<ArtifactConfigurationId>).
  [Parameter()]
  [string] $ArtifactConfigurationId,

  # If you use IDs to reference the artifact configuration and signing policy: this is the ID of the signing policy you want to use for signing.
  # Go to "Project > Signing policy" in the web client and copy the last ID from the url to retrieve this value (e.g. https://app.signpath.io/<OrganizationId>/SigningPolicies/<SigningPolicyId>).
  [Parameter()]
  [string] $SigningPolicyId,

  # If you use names to reference the artifact configuration and signing policy: this is the project in which the artifact configuration and signing
  # policy reside in.
  [Parameter()]
  [string] $ProjectName,

  # If you use names to reference the artifact configuration and signing policy: this is the name of the artifact configuration you want to use for
  # signing. If not given, the default artifact configuration will be used instead.
  [Parameter()]
  [string] $ArtifactConfigurationName,

  # If you use names to reference the artifact configuration and signing policy: this is the name of the signing policy you want to use for signing.
  [Parameter()]
  [string] $SigningPolicyName,

  # Specifies the path of the artifact that you want to be signed.
  [Parameter(Mandatory)]
  [ValidateNotNullOrEmpty()]
  [string] $InputArtifactPath,
  
  # An optional description of the uploaded artifact that could be helpful to the approver.
  [Parameter()]
  [string] $Description,

  # The total time in seconds that the cmdlet will wait for a single service call to succeed (across several retries). Defaults to 600 seconds.
  [Parameter()]
  [int] $ServiceUnavailableTimeoutInSeconds = 600,

  # The HTTP timeout used for upload and download HTTP requests. Defaults to 300 seconds.
  [Parameter()]
  [int] $UploadAndDownloadRequestTimeoutInSeconds = 300,

  [Parameter(ParameterSetName="WaitForCompletion")]
  [switch] $WaitForCompletion,

  # Specifies the path of the downloaded signed artifact (result file). If this is not given, the InputArtifactPath with an added ".signed" extension is used (e.g. "Input.dll" => "Input.signed.dll").
  [Parameter(ParameterSetName="WaitForCompletion")]
  [ValidateNotNullOrEmpty()]
  [string] $OutputArtifactPath,

  # The maximum time in seconds that the cmdlet will wait for the signing request to complete (upload and download have no specific timeouts). Defaults to 600 seconds.
  [Parameter(ParameterSetName="WaitForCompletion")]
  [int] $WaitForCompletionTimeoutInSeconds = 600,

  # Allows the cmdlet to overwrite the file at OutputArtifactPath.
  [Parameter(ParameterSetName="WaitForCompletion")]
  [switch] $Force) {

  <#
  .SYNOPSIS
    Submits a signing request via the SignPath REST API.
  .DESCRIPTION
    The Submit-SigningRequest cmdlet submits a signing request with the specified InputArtifactPath via the SignPath REST API.
    When passing the -WaitForCompletion switch the command also waits for the signing request to complete, downloads the signed file and stores it in the path specified by the OutputArtifactPath argument.
    Otherwise the result of the submitted signing request can be downloaded with the Get-SignedArtifact command.
   
    To tweak http related timing issues use the parameters ServiceUnavailableNumberOfRetries and ServiceUnavailableRetryTimeoutInSeconds
  .EXAMPLE
    Submit-SigningRequest
      -InputArtifactPath Program.exe
      -CIUserToken /Joe3s2m7hkhVyoba4H4weqj9UxIk6nKRXGhGbH7nv4=
      -OrganizationId 1c0ab26c-12f3-4c6e-a043-2568e133d2de
      -SigningPolicyId 711960ed-bdb8-41cd-a6bf-a10d0ae3cfcd
  .OUTPUTS
    When using the -WaitForCompletion switch the command returns nothing, but downloads the signed artifact and stores it in the given OutputArtifactPath.
    Otherwise returns the SigningRequestId which can be used with Get-SignedArtifact
  .NOTES
    Author: SignPath GmbH
  #>


  Set-StrictMode -Version 2.0
  
  $ApiUrl = GetVersionedApiUrl($ApiUrl)
  
  $InputArtifactPath = PrepareInputArtifactPath $InputArtifactPath
    
  CreateAndUseAuthorizedHttpClient $CIUserToken -Timeout $DefaultHttpClientTimeoutInSeconds {
    Param ([System.Net.Http.HttpClient] $defaultHttpClient)

    CreateAndUseAuthorizedHttpClient $CIUserToken -Timeout $UploadAndDownloadRequestTimeoutInSeconds {
      Param ([System.Net.Http.HttpClient] $uploadAndDownloadHttpClient)

      if(-not $OutputArtifactPath) {
        $extension = [System.IO.Path]::GetExtension($InputArtifactPath)
        $OutputArtifactPath = [System.IO.Path]::ChangeExtension($InputArtifactPath, "signed$extension")
      }

      $OutputArtifactPath = PrepareOutputArtifactPath $Force $OutputArtifactPath

      $submitUrl = [string]::Join("/", @($ApiUrl.Trim("/"), $OrganizationId, "SigningRequests"))
      $getUrl = SubmitVia `
        -HttpClient $uploadAndDownloadHttpClient `
        -Url $submitUrl `
        -ArtifactConfigurationId $ArtifactConfigurationId `
        -SigningPolicyId $SigningPolicyId `
        -ProjectName $ProjectName `
        -ArtifactConfigurationName $ArtifactConfigurationName `
        -SigningPolicyName $SigningPolicyName `
        -InputArtifactPath $InputArtifactPath `
        -Description $Description

      if($WaitForCompletion.IsPresent) {

        $downloadUrl = WaitForCompletion `
          -HttpClient $defaultHttpClient `
          -Url $getUrl `
          -WaitForCompletionTimeoutInSeconds $WaitForCompletionTimeoutInSeconds `
          -ServiceUnavailableRetryTimeoutInSeconds $ServiceUnavailableRetryTimeoutInSeconds

        DownloadArtifact `
          -HttpClient $uploadAndDownloadHttpClient `
          -Url $downloadUrl `
          -Path $OutputArtifactPath `
          -ServiceUnavailableRetryTimeoutInSeconds $ServiceUnavailableRetryTimeoutInSeconds `
          -UploadAndDownloadRequestTimeoutInSeconds $UploadAndDownloadRequestTimeoutInSeconds

      } else {
        $guidRegex = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
        $pattern = [regex]"SigningRequests/($guidRegex)"
        $succeeded = $getUrl -match $pattern
        $wholeMatch = $matches[0]
        $signingRequestId = $matches[1]
        return $signingRequestId
      }
    }
  }
}

function Get-SignedArtifact(
  # The URL to the API, e.g. https://app.signpath.io/api/.
  [Parameter()]
  [ValidateNotNullOrEmpty()]
  [string] $ApiUrl = "https://app.signpath.io/api/",

  # The API token you retrieve when adding a new CI user.
  [Parameter(Mandatory)]
  [ValidateNotNullOrEmpty()]
  [string] $CIUserToken,
  
  # The ID of the organization containing the signing policy.
  # Go to "Project > Signing policy" in the web client and copy the first ID from the url to retrieve this value (e.g. https://app.signpath.io/<OrganizationId>/SigningPolicies/<SigningPolicyId>).
  [Parameter(Mandatory)]
  [ValidateNotNullOrEmpty()]
  [string] $OrganizationId,

  # The ID of the SigningRequest that contains the desired artifact
  [Parameter(Mandatory)]
  [ValidateNotNullOrEmpty()]
  [string] $SigningRequestId,

  # Specifies the path of the downloaded signed artifact (result file).
  [Parameter(Mandatory)]
  [ValidateNotNullOrEmpty()]
  [string] $OutputArtifactPath,

  # The total time in seconds that the cmdlet will wait for a service call to succeed (across several retries). Defaults to 600 seconds.
  [Parameter()]
  [int] $ServiceUnavailableTimeoutInSeconds = 600,

  # The HTTP timeout used for upload and download HTTP requests. Defaults to 300 seconds.
  [Parameter()]
  [int] $UploadAndDownloadRequestTimeoutInSeconds = 300,
  
  # The maximum time in seconds that the cmdlet will wait for the signing request to complete (upload and download have no specific timeouts). Defaults to 60 seconds.
  [Parameter()]
  [int] $WaitForCompletionTimeoutInSeconds = 600,

  # Allows the cmdlet to overwrite the file at OutputArtifactPath.
  [Parameter()]
  [switch] $Force) {

  <#
  .SYNOPSIS
     Tries to download a signed artifact based on a SigningRequestId.
  .DESCRIPTION
     Waits for a given signing request until its processing has finished and downloads the resultiung artifact.
     If the request couldn't be downloaded in time, because the processing took to long or the request is invalid,
     this function throws exceptions.
 
     To tweak timing issues use the parameters ServiceUnavailableNumberOfRetries, ServiceUnavailableRetryTimeoutInSeconds and WaitForCompletionTimeoutInSeconds
  .EXAMPLE
     Get-SignedArtifact
        -OutputArtifactPath Program.exe
        -CIUserToken /Joe3s2m7hkhVyoba4H4weqj9UxIk6nKRXGhGbH7nv4=
        -OrganizationId 1c0ab26c-12f3-4c6e-a043-2568e133d2de
        -SigningRequestId 711960ed-bdb8-41cd-a6bf-a10d0ae3cfcd
  .OUTPUTS
     Returns void but creates a file in the given OutputArtifactPath on success.
  .NOTES
    Author: SignPath GmbH
  #>


  Set-StrictMode -Version 2.0

  $ApiUrl = GetVersionedApiUrl($ApiUrl)

  $OutputArtifactPath = PrepareOutputArtifactPath $Force $OutputArtifactPath

  CreateAndUseAuthorizedHttpClient $CIUserToken -Timeout $DefaultHttpClientTimeoutInSeconds {
    Param ([System.Net.Http.HttpClient] $defaultHttpClient)

    CreateAndUseAuthorizedHttpClient $CIUserToken -Timeout $UploadAndDownloadRequestTimeoutInSeconds {
      Param ([System.Net.Http.HttpClient] $uploadAndDownloadHttpClient)

      $expectedSigningRequestUrl = [string]::Join("/", @($ApiUrl.Trim("/"), $OrganizationId, "SigningRequests", $SigningRequestId))

      $downloadUrl = WaitForCompletion `
        -httpClient $defaultHttpClient `
        -url $expectedSigningRequestUrl `
        -WaitForCompletionTimeoutInSeconds $WaitForCompletionTimeoutInSeconds `
        -ServiceUnavailableRetryTimeoutInSeconds $ServiceUnavailableRetryTimeoutInSeconds

      DownloadArtifact `
        -HttpClient $uploadAndDownloadHttpClient `
        -Url $downloadUrl `
        -Path $OutputArtifactPath `
        -ServiceUnavailableRetryTimeoutInSeconds $ServiceUnavailableRetryTimeoutInSeconds `
        -UploadAndDownloadRequestTimeoutInSeconds $UploadAndDownloadRequestTimeoutInSeconds
    }
  }
}

function GetVersionedApiUrl([string] $ApiUrl) {
  $supportedApiVersion = "v1"
  return [string]::Join("/", @($ApiUrl.Trim("/"), $supportedApiVersion))
}

function SubmitVia(
  [System.Net.Http.HttpClient] $HttpClient, 
  [string] $Url,
  [string] $ArtifactConfigurationId,
  [string] $SigningPolicyId,
  [string] $ProjectName,
  [string] $ArtifactConfigurationName,
  [string] $SigningPolicyName,
  [string] $InputArtifactPath,
  [string] $Description) {

  # Copy variables to local scope to allow capturing them in a closure
  $local:ArtifactConfigurationId = $ArtifactConfigurationId
  $local:SigningPolicyId = $SigningPolicyId
  $local:ProjectName = $ProjectName
  $local:ArtifactConfigurationName = $ArtifactConfigurationName
  $local:SigningPolicyName = $SigningPolicyName
  $local:Description = $Description
  $local:InputArtifactPath = $InputArtifactPath

  $requestFactory = {
    $content = New-Object System.Net.Http.MultipartFormDataContent

    if($ArtifactConfigurationId) {
      $artifactConfigurationIdContent = New-Object System.Net.Http.StringContent $ArtifactConfigurationId
      $content.Add($artifactConfigurationIdContent, "ArtifactConfigurationId")
    }

    if($SigningPolicyId) {
      $signingPolicyIdContent = New-Object System.Net.Http.StringContent $SigningPolicyId
      $content.Add($signingPolicyIdContent, "SigningPolicyId")
    }

    if($ProjectName) {
      $projectNameContent = New-Object System.Net.Http.StringContent $ProjectName
      $content.Add($projectNameContent, "ProjectName")
    }

    if($ArtifactConfigurationName) {
      $artifactConfigurationNameContent = New-Object System.Net.Http.StringContent $ArtifactConfigurationName
      $content.Add($artifactConfigurationNameContent, "ArtifactConfigurationName")
    }

    if($SigningPolicyName) {
      $signingPolicyNameContent = New-Object System.Net.Http.StringContent $SigningPolicyName
      $content.Add($signingPolicyNameContent, "SigningPolicyName")
    }

    $descriptionContent = New-Object System.Net.Http.StringContent $Description
    $content.Add($descriptionContent, "Description")

    $packageFileStream = New-Object System.IO.FileStream ($InputArtifactPath, [System.IO.FileMode]::Open)
    $streamContent = New-Object System.Net.Http.StreamContent $packageFileStream
    # PLANNED SIGN-986 This shouldn't be needed anymore
    $streamContent.Headers.ContentType = New-Object System.Net.Http.Headers.MediaTypeHeaderValue "application/octet-stream"
    $fileName = [System.IO.Path]::GetFileName($InputArtifactPath)
    $content.Add($streamContent, "Artifact", $fileName)

    $request = New-Object System.Net.Http.HttpRequestMessage Post, $Url
    $request.Content = $content

    Write-Host "Requesting Url: " $Url
    return $request
  }.GetNewClosure()
  
  $submitResponse = $null
  try
  {
    $submitResponse = SendWithRetry `
      -HttpClient $HttpClient `
      -RequestFactory $requestFactory `
      -ServiceUnavailableRetryTimeoutInSeconds $ServiceUnavailableRetryTimeoutInSeconds
    CheckResponse $submitResponse

    $getUrl = $submitResponse.Headers.Location.AbsoluteUri
    Write-Host "Submitted signing request at '$getUrl'"
    return $getUrl
  }
  finally
  {
    if((Test-Path variable:submitResponse) -and $submitResponse -ne $null) {
      $submitResponse.Dispose()
    }
  } 
}

function GetWithRetry([System.Net.Http.HttpClient] $HttpClient, [string] $Url, [int] $ServiceUnavailableRetryTimeoutInSeconds) {
  $response = SendWithRetry `
    -HttpClient $HttpClient `
    -RequestFactory { New-Object System.Net.Http.HttpRequestMessage Get, $Url }.GetNewClosure() `
    -ServiceUnavailableRetryTimeoutInSeconds $ServiceUnavailableRetryTimeoutInSeconds
  return $response
}

function SendWithRetry(
  [System.Net.Http.HttpClient] $HttpClient, 
  [ScriptBlock] $RequestFactory,
  [int] $ServiceUnavailableRetryTimeoutInSeconds) {

  $sw = [System.Diagnostics.Stopwatch]::StartNew()
  $retry = 0

  while($true)
  {
    $retryReason = $null

    if($retry -gt 0) {
      Write-Host "Retry $retry..."
    }

    try 
    {
      $request = & $RequestFactory
      $response = $HttpClient.SendAsync($request).GetAwaiter().GetResult()
      
      if(503 -eq $response.StatusCode) {
        $retryReason = "SignPath REST API is temporarily unavailable. Please try again in a few moments."
      } elseif(500 -le $response.StatusCode -and 600 -gt $response.StatusCode) {
        $retryReason = "SignPath REST API returned an unexpected status code ($($response.StatusCode))"
      } else {
        return $response
      }
    } 
    catch [System.Net.Http.HttpRequestException]
    {
      $retryReason = $_
      Write-Host "URL: " $request.RequestUri
      Write-Host "EXCEPTION: " $_.Exception
    }
    catch [System.Threading.Tasks.TaskCanceledException]
    {
      $retryReason = "SignPath REST API answer time exceeded the timeout ($($HttpClient.Timeout))"
      Write-Host "URL: " $request.RequestUri
      Write-Host "EXCEPTION: " $_.Exception
    }

    if(($sw.Elapsed.TotalSeconds + $ServiceUnavailableRetryTimeoutInSeconds) -lt $ServiceUnavailableTimeoutInSeconds) {
      Write-Host "SignPath REST API call failed. Retrying in ${ServiceUnavailableRetryTimeoutInSeconds}s..."
      Start-Sleep -Seconds $ServiceUnavailableRetryTimeoutInSeconds
    } else {
      Write-Host "SignPath REST API could not be called successfully in $($retry + 1) tries. Aborting"
      throw $retryReason
    }

    $retry++
  }
}

function DownloadArtifact(
  [System.Net.Http.HttpClient] $HttpClient, 
  [string] $Url, 
  [string] $Path, 
  [int] $ServiceUnavailableRetryTimeoutInSeconds,
  [int] $UploadAndDownloadRequestTimeoutInSeconds) {

  $downloadResponse = $null
  $streamToWriteTo = $null
  try
  {
    Write-Host "Downloading signed artifact..."
    $downloadResponse = GetWithRetry `
      -HttpClient $HttpClient `
      -Url $Url `
      -ServiceUnavailableRetryTimeoutInSeconds $ServiceUnavailableRetryTimeoutInSeconds
    CheckResponse $downloadResponse

    $pathWithoutFile = [System.IO.Path]::GetDirectoryName($Path)
    [System.IO.Directory]::CreateDirectory($pathWithoutFile)

    $stream =  $downloadResponse.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
    $streamToWriteTo = [System.IO.File]::Open($path, 'Create')
    $stream.CopyToAsync($streamToWriteTo).GetAwaiter().GetResult() | Out-Null
    Write-Host "Downloaded signed artifact and saved at '$Path'"
  }
  finally
  {
    if((Test-Path variable:downloadResponse) -and $downloadResponse -ne $null) {
      $downloadResponse.Dispose()
    }

    if((Test-Path variable:stream) -and $stream -ne $null) {
      $stream.Dispose()
    }

    if((Test-Path variable:streamToWriteTo) -and $streamToWriteTo -ne $null) {
      $streamToWriteTo.Dispose()
    }
  }
}

function WaitForCompletion(
  [System.Net.Http.HttpClient] $HttpClient, 
  [string] $Url, 
  [int] $WaitForCompletionTimeoutInSeconds,
  [int] $ServiceUnavailableRetryTimeoutInSeconds) {

  $StatusComplete = "Completed"
  $StatusFailed = "Failed"
  $StatusDenied = "Denied"
  $StatusCanceled = "Canceled"

  $getResponse = $null
  try
  {
    $resultJson = $null
    $status = $null
    $sw = [System.Diagnostics.Stopwatch]::StartNew()
    do
    {
      Write-Host "Checking status... " -NoNewline

      $getResponse = GetWithRetry -HttpClient $HttpClient -Url $Url -ServiceUnavailableRetryTimeoutInSeconds $ServiceUnavailableRetryTimeoutInSeconds

      CheckResponse $getResponse
      $resultJson =  $getResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult() | ConvertFrom-Json
      $status = $resultJson.status
      Write-Host $status

      if($status -eq $StatusComplete -or $status -eq $StatusFailed -or $status -eq $StatusDenied -or $status -eq $StatusCanceled){
        break
      }
      Start-Sleep -Seconds $WaitForCompletionRetryTimeoutInSeconds
    } while($sw.Elapsed.TotalSeconds -lt $WaitForCompletionTimeoutInSeconds)

    $timeoutExpired = $sw.Elapsed.TotalSeconds -ge $WaitForCompletionTimeoutInSeconds
    if($status -ne $StatusComplete) {
      if($timeoutExpired) {
        throw "Timeout expired while waiting for signing request to complete"
      } elseif($status -eq $StatusFailed) {
        throw "Terminating because signing request failed"
      } elseif($status -eq $StatusDenied) {
        throw "Terminating because signing request was denied"
      } elseif($status -eq $StatusCanceled) {
        throw "Terminating because signing request was canceled"
      } else {
        throw "Terminating because of unexpected signing request status: $status"
      }
    }

    return $resultJson.signedArtifactLink
  }
  finally
  {
    if((Test-Path variable:getResponse) -and $getResponse -ne $null) {
      $getResponse.Dispose()
    }
  }
}

function CreateAndUseAuthorizedHttpClient([string] $CIUserToken, [int] $Timeout, [ScriptBlock] $ScriptBlock) {
  Add-Type -AssemblyName System.IO
  Add-Type -AssemblyName System.Net.Http

  $previousSecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol
  [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
  $httpClient = New-Object System.Net.Http.HttpClient
  $httpClient.Timeout = [TimeSpan]::FromSeconds($Timeout)
  $httpClient.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue @("Bearer", $CIUserToken)

  try 
  {
    & $ScriptBlock $httpClient
  }
  finally
  {
    if($httpClient -ne $null) {
      $httpClient.Dispose()
    }
    [System.Net.ServicePointManager]::SecurityProtocol = $previousSecurityProtocol
  }
}

function PrepareInputArtifactPath([string] $InputArtifactPath) {
  $InputArtifactPath = NormalizePath $InputArtifactPath

  if(-not (Test-Path -Path $InputArtifactPath)) {
    throw "The input artifact path '$InputArtifactPath' does not exist"
  }
  return $InputArtifactPath
}

function PrepareOutputArtifactPath([bool] $Force, [string] $OutputArtifactPath) {
  $OutputArtifactPath = NormalizePath $OutputArtifactPath

  if(-not $Force -and (Test-Path -Path $OutputArtifactPath)){
    throw "There is already a file at '$OutputArtifactPath'. If you want to overwrite it use the -Force switch"
  }
  return $OutputArtifactPath
}

function NormalizePath([string] $Path) {
  if(-not [System.IO.Path]::IsPathRooted($Path)) {
    return Join-Path $PWD $Path
  }
  return $Path
}

function CheckResponse([System.Net.Http.HttpResponseMessage] $Response) {
  if (-not $Response.IsSuccessStatusCode) {
    $responseBody = $Response.Content.ReadAsStringAsync().GetAwaiter().GetResult()

    $additionalReason = "";
    if(401 -eq $Response.StatusCode) {
      $additionalReason = " Did you provide the correct CIUserToken?";
    }
    elseif(403 -eq $Response.StatusCode) {
      $additionalReason = " Did you add the CI user to the list of submitters in the specified signing policy? Did you provide the correct OrganizationId?";
    }

    $serverMessage = "";
    if($responseBody -ne "") {
      $serverMessage = " (Server reported the following message: '" + $responseBody + "')";
    }

    $errorMessage = "Error {0} {1}.{2}{3}" -f $Response.StatusCode.value__, $Response.ReasonPhrase, $additionalReason, $serverMessage
    
    throw [System.Net.Http.HttpRequestException] $errorMessage
  }
}

Export-ModuleMember Submit-SigningRequest
Export-ModuleMember Get-SignedArtifact

# SIG # Begin signature block
# MIIhxQYJKoZIhvcNAQcCoIIhtjCCIbICAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAdYZIxHQtlE3b7
# IJxuHh2gEOBRcWXg1/Uk/QC+6VJhd6CCEAIwggPFMIICraADAgECAhACrFwmagtA
# m48LefKuRiV3MA0GCSqGSIb3DQEBBQUAMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV
# BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwHhcNMDYxMTEw
# MDAwMDAwWhcNMzExMTEwMDAwMDAwWjBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM
# RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQD
# EyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMIIBIjANBgkqhkiG
# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxszlc+b71LvlLS0ypt/lgT/JzSVJtnEqw9WU
# NGeiChywX2mmQLHEt7KP0JikqUFZOtPclNY823Q4pErMTSWC90qlUxI47vNJbXGR
# fmO2q6Zfw6SE+E9iUb74xezbOJLjBuUIkQzEKEFV+8taiRV+ceg1v01yCT2+OjhQ
# W3cxG42zxyRFmqesbQAUWgS3uhPrUQqYQUEiTmVhh4FBUKZ5XIneGUpX1S7mXRxT
# LH6YzRoGFqRoc9A0BBNcoXHTWnxV215k4TeHMFYE5RG0KYAS8Xk5iKICEXwnZreI
# t3jyygqoOKsKZMK/Zl2VhMGhJR6HXRpQCyASzEG7bgtROLhLywIDAQABo2MwYTAO
# BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUsT7DaQP4
# v0cB1JgmGggC72NkK8MwHwYDVR0jBBgwFoAUsT7DaQP4v0cB1JgmGggC72NkK8Mw
# DQYJKoZIhvcNAQEFBQADggEBABwaBpfc15yfPIhmBghXIdshR/gqZ6q/GDJ2QBBX
# wYrzetkRZY41+p78RbWe2UwxS7iR6EMsjrN4ztvjU3lx1uUhlAHaVYeaJGT2imbM
# 3pw3zag0sWmbI8ieeCIrcEPjVUcxYRnvWMWFL04w9qAxFiPI5+JlFjPLvxoboD34
# yl6LMYtgCIktDAZcUrfE+QqY0RVfnxK+fDZjOL1EpH/kJisKxJdpDemM4sAQV7jI
# dhKRVfJIadi8KgJbD0TUIDHb9LpwJl2QYJ68SxcJL7TLHkNoyQcnwdJc9+ohuWgS
# nDycv578gFybY83sR6olJ2egN/MAgn1U16n46S4To3foH0owggV1MIIEXaADAgEC
# AhAN1/X/PWGIuZu8TJ/zTOTxMA0GCSqGSIb3DQEBCwUAMGwxCzAJBgNVBAYTAlVT
# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
# b20xKzApBgNVBAMTIkRpZ2lDZXJ0IEVWIENvZGUgU2lnbmluZyBDQSAoU0hBMikw
# HhcNMTcxMDA0MDAwMDAwWhcNMjAxMDA4MTIwMDAwWjCBkzETMBEGCysGAQQBgjc8
# AgEDEwJBVDEdMBsGA1UEDwwUUHJpdmF0ZSBPcmdhbml6YXRpb24xETAPBgNVBAUT
# CDQ3NTUwNiB6MQswCQYDVQQGEwJBVDENMAsGA1UEBxMEV2llbjEWMBQGA1UEChMN
# U2lnblBhdGggR21iSDEWMBQGA1UEAxMNU2lnblBhdGggR21iSDCCASIwDQYJKoZI
# hvcNAQEBBQADggEPADCCAQoCggEBANUCuRw5UqO5xlTdf6eNsPSdi2Weflkqa4LT
# 14Y7Pjmq/5ROLD7Wrq5DRUOt2EiqUbXts4ws5XRit3+aaqo4Yhs51sCDomyzqKLx
# YlNaz3xNeaP9wjbQvaJkzj+FMw2EhfZmgcEO644C7wNPnKSg4M2Bpi1Sh6F8Lgjp
# l0qmemnMNwHVeMKONQeq2Wv/ejDeeRv4ZYYgHjmoxKwJtRkjKuIr9akHCWHMKWSo
# 3WS47/M3Q0FbYufa0wXe/jIwoSsqZdb1VJtlS+TUcOf4YD3OjYq7/5GMLo6hAO0c
# n95cCmASxFoDq/0Okq/kZ48RQYWxFR91GtuQFmC+k8dwbXK18qECAwEAAaOCAekw
# ggHlMB8GA1UdIwQYMBaAFI/ofvBtMmoABSPHcJdqOpD/a+rUMB0GA1UdDgQWBBS2
# QDSwXWyNi+1kzdWtsZv0aycxITAmBgNVHREEHzAdoBsGCCsGAQUFBwgDoA8wDQwL
# QVQtNDc1NTA2IHowDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMD
# MHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9FVkNv
# ZGVTaWduaW5nU0hBMi1nMS5jcmwwN6A1oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0
# LmNvbS9FVkNvZGVTaWduaW5nU0hBMi1nMS5jcmwwSwYDVR0gBEQwQjA3BglghkgB
# hv1sAwIwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQ
# UzAHBgVngQwBAzB+BggrBgEFBQcBAQRyMHAwJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBIBggrBgEFBQcwAoY8aHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0RVZDb2RlU2lnbmluZ0NBLVNIQTIuY3J0MAwGA1Ud
# EwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggEBAAwA3lVWGyFuJumCqI/xuWR8uMtN
# ZvX87eX0aigbQMyJ8TrzBEqwgZCUNOs/6PpJO87OEhoALFyFa3BIy9dV/C2ezl8E
# dpaLKds/GT7qiEFYVXiZOYpSOoj9M0qfmgyWrJQpkI79LH9FlkpO3J+or6nnxNpY
# QLh5mWEJxn8t92Z2jKktHhog8bVvE4piXb5PqHLkc/UfPYmON9qFFukyKOuGZ+20
# nlL/YWtRqmpVWONIwQZ6lhOGscXUaHU/0fVjpmOWV0+fgV2o9LNzCc8MMJ6HywyV
# rDo2yqdHMYCfQkLr3NAMmya+P+vnJ7AOshk8PyOPsVt/7xj5URYakKwJOBMwgga8
# MIIFpKADAgECAhAD8bThXzqC8RSWeLPX2EdcMA0GCSqGSIb3DQEBCwUAMGwxCzAJ
# BgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k
# aWdpY2VydC5jb20xKzApBgNVBAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVW
# IFJvb3QgQ0EwHhcNMTIwNDE4MTIwMDAwWhcNMjcwNDE4MTIwMDAwWjBsMQswCQYD
# VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGln
# aWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBFViBDb2RlIFNpZ25pbmcgQ0Eg
# KFNIQTIpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp1P6D7K1E/Fk
# z4SA/K6ANdG218ejLKwaLKzxhKw6NRI6kpG6V+TEyfMvqEg8t9Zu3JciulF5Ya9D
# Lw23m7RJMa5EWD6koZanh08jfsNsZSSQVT6hyiN8xULpxHpiRZt93mN0y55jJfiE
# mpqtRU+ufR/IE8t1m8nh4Yr4CwyY9Mo+0EWqeh6lWJM2NL4rLisxWGa0MhCfnfBS
# oe/oPtN28kBa3PpqPRtLrXawjFzuNrqD6jCoTN7xCypYQYiuAImrA9EWgiAidute
# VDgSYuHScCTb7R9w0mQJgC3itp3OH/K7IfNs29izGXuKUJ/v7DYKXJq3StMIoDl5
# /d2/PToJJQIDAQABo4IDWDCCA1QwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8B
# Af8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwfwYIKwYBBQUHAQEEczBxMCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYIKwYBBQUHMAKG
# PWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFu
# Y2VFVlJvb3RDQS5jcnQwgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDovL2NybDMu
# ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDBA
# oD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3Vy
# YW5jZUVWUm9vdENBLmNybDCCAcQGA1UdIASCAbswggG3MIIBswYJYIZIAYb9bAMC
# MIIBpDA6BggrBgEFBQcCARYuaHR0cDovL3d3dy5kaWdpY2VydC5jb20vc3NsLWNw
# cy1yZXBvc2l0b3J5Lmh0bTCCAWQGCCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1
# AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABj
# AG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBm
# ACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBk
# ACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBl
# AG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABp
# AHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBvAHIAcABvAHIAYQB0AGUAZAAg
# AGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQByAGUAbgBjAGUALjAdBgNVHQ4E
# FgQUj+h+8G0yagAFI8dwl2o6kP9r6tQwHwYDVR0jBBgwFoAUsT7DaQP4v0cB1Jgm
# GggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBABkzSgyBMzfbrTbJ5Mk6u7UbLnqi
# 4vRDQheev06hTeGx2+mB3Z8B8uSI1en+Cf0hwexdgNLw1sFDwv53K9v515EzzmzV
# shk75i7WyZNPiECOzeH1fvEPxllWcujrakG9HNVG1XxJymY4FcG/4JFwd4fcyY0x
# yQwpojPtjeKHzYmNPxv/1eAal4t82m37qMayOmZrewGzzdimNOwSAauVWKXEU1eo
# YObnAhKguSNkok27fIElZCG+z+5CGEOXu6U3Bq9N/yalTWFL7EZBuGXOuHmeCJYL
# gYyKO4/HmYyjKm6YbV5hxpa3irlhLZO46w4EQ9f1/qbwYtSZaqXBwfBklIAxghEZ
# MIIRFQIBATCBgDBsMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBF
# ViBDb2RlIFNpZ25pbmcgQ0EgKFNIQTIpAhAN1/X/PWGIuZu8TJ/zTOTxMA0GCWCG
# SAFlAwQCAQUAoIGeMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQB
# gjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCB8Ttv84eOoXHB7
# eETaDm91OLjUo4Qru+CcYl7ocsbWvjAyBgorBgEEAYI3AgEMMSQwIqAcgBoAUwBp
# AGcAbgBQAGEAdABoAC4AcABzAG0AMaECgAAwDQYJKoZIhvcNAQEBBQAEggEAeXOm
# KDychYK2pzSPaGmYYZ4LhLIdyvlCoKoX16Qq9bRPBelWCE8v0l0PfqAAuoEBN2Ug
# g8i5Horx1xHKUPKXjs9MO5lzLnP4u1sgul/HyOzqV4fb1x3iBJO6kJFLzI1F7+XN
# gnL5wmyL6BFDjRKSSrT26evTFLsHPzMSY5mRAWGLO/FJPjIkPzAevTddThe5ud87
# TS8Y0gOUfWDSHorO87dmFCMCmDxjsGf3LuKqjplHXRiCy/i77QCtWvRbZ4RDpskt
# CuHPLt82jsxvaogfISyRXxM8GfVSRbehTXKUPHRT9sL1rMGrjiKt1YuwoHZg0MFt
# nLHJ1DVFZ2gs5hXfqaGCDsgwgg7EBgorBgEEAYI3AwMBMYIOtDCCDrAGCSqGSIb3
# DQEHAqCCDqEwgg6dAgEDMQ8wDQYJYIZIAWUDBAIBBQAwdwYLKoZIhvcNAQkQAQSg
# aARmMGQCAQEGCWCGSAGG/WwHATAxMA0GCWCGSAFlAwQCAQUABCCMcyH9qg+tuejb
# LqivqfFFCeR2ZcduJcrMDfZx78d6tAIQEAK51yXJsw1HssYKMXGe7RgPMjAxOTA2
# MTkxMjM5NDBaoIILuzCCBoIwggVqoAMCAQICEAnA/EbIBEITtVmLryhPTkEwDQYJ
# KoZIhvcNAQELBQAwcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu
# YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQg
# U0hBMiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTAeFw0xNzAxMDQwMDAwMDBa
# Fw0yODAxMTgwMDAwMDBaMEwxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2Vy
# dDEqMCgGA1UEAxMhRGlnaUNlcnQgU0hBMiBUaW1lc3RhbXAgUmVzcG9uZGVyMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnpWYajQ7cxuofvzHvilpicdo
# JkZfPY1ic4eBo6Gc8LdbJDdaktT0Wdd2ieTc1Sfw1Wa8Cu60KzFnrFjFSpFZK0Ue
# CQHWZLNZ7o1mTfsjXswQDQuKZ+9SrqAIkMJS9/WotW6bLHud57U++3jNMlAYv0C1
# TIy7V/SgTxFFbEJCueWv1t/0p3wKaJYP0l8pV877HTL/9BGhEyL7Esvv11PS65fL
# oqwbHZ1YIVGCwsLe6is/LCKE0EPsOzs/R8T2VtxFN5i0a3S1Wa94V2nIDwkCeN3Y
# U8GZ22DEnequr+B+hkpcqVhhqF50igEoaHJOp4adtQJSh3BmSNOO74EkzNzYZQID
# AQABo4IDODCCAzQwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0l
# AQH/BAwwCgYIKwYBBQUHAwgwggG/BgNVHSAEggG2MIIBsjCCAaEGCWCGSAGG/WwH
# ATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw
# ggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgA
# aQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQA
# ZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcA
# aQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwA
# eQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkA
# YwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEA
# cgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIA
# eQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMB8GA1UdIwQYMBaA
# FPS24SAd/imu0uRhpbKiJbLIFzVuMB0GA1UdDgQWBBThpzJK7gEhKH1U1fIHkm60
# Bw89hzBxBgNVHR8EajBoMDKgMKAuhixodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
# c2hhMi1hc3N1cmVkLXRzLmNybDAyoDCgLoYsaHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL3NoYTItYXNzdXJlZC10cy5jcmwwgYUGCCsGAQUFBwEBBHkwdzAkBggrBgEF
# BQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tME8GCCsGAQUFBzAChkNodHRw
# Oi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEVGlt
# ZXN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4IBAQAe8EGCMq7t8bQ1E9xQ
# wtWXriIinQ4OrzPTTP18v28BEaeUZSJcxiKhyIlSa5qMc1zZXj8y3hZgTIs2/TGZ
# Cr3BhLeNHe+JJhMFVvNHzUdbrYSyOK9qI7VF4x6IMkaA0remmSL9wXjP9YvYDIwF
# Ce5E5oDVbXDMn1MeJ90qSN7ak2WtbmWjmafCQA5zzFhPj0Uo5byciOYozmBdLSVd
# i3MupQ1bUdqaTv9QBYko2vJ4u9JYeI1Ep6w6AJF4aYlkBNNdlt8qv/mlTCyT/+aK
# 3YKs8dKzooaawVWJVmpHP/rWM5VDNYkFeFo6adoiuARD029oNTZ6FD5F6Zhkhg8T
# DCZKMIIFMTCCBBmgAwIBAgIQCqEl1tYyG35B5AXaNpfCFTANBgkqhkiG9w0BAQsF
# ADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
# ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElE
# IFJvb3QgQ0EwHhcNMTYwMTA3MTIwMDAwWhcNMzEwMTA3MTIwMDAwWjByMQswCQYD
# VQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGln
# aWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGlt
# ZXN0YW1waW5nIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvdAy
# 7kvNj3/dqbqCmcU5VChXtiNKxA4HRTNREH3Q+X1NaH7ntqD0jbOI5Je/YyGQmL8T
# vFfTw+F+CNZqFAA49y4eO+7MpvYyWf5fZT/gm+vjRkcGGlV+Cyd+wKL1oODeIj8O
# /36V+/OjuiI+GKwR5PCZA207hXwJ0+5dyJoLVOOoCXFr4M8iEA91z3FyTgqt30A6
# XLdR4aF5FMZNJCMwXbzsPGBqrC8HzP3w6kfZiFBe/WZuVmEnKYmEUeaC50ZQ/ZQq
# LKfkdT66mA+Ef58xFNat1fJky3seBdCEGXIX8RcG7z3N1k3vBkL9olMqT4UdxB08
# r8/arBD13ays6Vb/kwIDAQABo4IBzjCCAcowHQYDVR0OBBYEFPS24SAd/imu0uRh
# pbKiJbLIFzVuMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMBIGA1Ud
# EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF
# BwMIMHkGCCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln
# aWNlcnQuY29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j
# b20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2
# hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290
# Q0EuY3JsMDqgOKA2hjRodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRB
# c3N1cmVkSURSb290Q0EuY3JsMFAGA1UdIARJMEcwOAYKYIZIAYb9bAACBDAqMCgG
# CCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAsGCWCGSAGG
# /WwHATANBgkqhkiG9w0BAQsFAAOCAQEAcZUS6VGHVmnN793afKpjerN4zwY3QITv
# S4S/ys8DAv3Fp8MOIEIsr3fzKx8MIVoqtwU0HWqumfgnoma/Capg33akOpMP+LLR
# 2HwZYuhegiUexLoceywh4tZbLBQ1QwRostt1AuByx5jWPGTlH0gQGF+JOGFNYkYk
# h2OMkVIsrymJ5Xgf1gsUpYDXEkdws3XVk4WTfraSZ/tTYYmo9WuWwPRYaQ18yAGx
# uSh1t5ljhSKMYcp5lH5Z/IwP42+1ASa2bKXuh1Eh5Fhgm7oMLSttosR+u8QlK0cC
# CHxJrhO24XxCQijGGFbPQTS2Zl22dHv1VjMiLyI2skuiSpXY9aaOUjGCAk0wggJJ
# AgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAX
# BgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIg
# QXNzdXJlZCBJRCBUaW1lc3RhbXBpbmcgQ0ECEAnA/EbIBEITtVmLryhPTkEwDQYJ
# YIZIAWUDBAIBBQCggZgwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqG
# SIb3DQEJBTEPFw0xOTA2MTkxMjM5NDBaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYE
# FEABkUdcmIkd66EEr0cJG1621MvLMC8GCSqGSIb3DQEJBDEiBCDgLS3lBqxyZspi
# pk4Td4wQWHbWTnBB2Xv6Oa1gaUu0zjANBgkqhkiG9w0BAQEFAASCAQA18Y/dYHZ2
# N5pHsZFflFmrAWAiE7AyMAA2lpDPbtoEywLeQDWMuBDaSbP0bCq8N2ilAvI5SLG4
# fGckOI4qHNYeXKcmvLj9cD2Y3w1g/Jw0lg5nUe8K4o3WmvSwEKC79PDjYzMsAKYD
# FY4JOB9/DEin57O/WGFAbgXyrG2xWyj3L9acU9SxQlDremHbBCLsxHHf3FD64JIZ
# +9aT6sh52dKQHxaK1WkIZ9ttRERPoIUJ9mMS6Eri9o4kvyzFXE9fIqpfZ5xombOF
# 1dBq7LOCGYTvrv61j4QT3ajpnELFmYpjNH4TWb7RpCzJfnerTd/t9QdJnpQVk8mC
# uczFDXFDCERU
# SIG # End signature block