Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet
[Parameter]
public virtual SwitchParameter SkipCertificateCheck { get; set; }

/// <summary>
/// gets or sets the CertificateValidationScript property. This will be ignored if SkipCertificateCheck is set.
/// </summary>
[Parameter]
public virtual ScriptBlock CertificateValidationScript { get; set; }

/// <summary>
/// Gets or sets the TLS/SSL protocol used by the Web Cmdlet
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.IO;
using System.Text;
using System.Collections;
using System.Globalization;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Xml;
using System.Collections.Generic;
Expand Down Expand Up @@ -173,6 +176,45 @@ internal virtual HttpClient GetHttpClient(bool handleRedirect)
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
}
else if (CertificateValidationScript != null)
{
// validationCallBackWrapper wraps the CertificateValidationScript ScriptBlock so the async callback properly execute ScriptBlock
Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,bool> validationCallBackWrapper =
delegate(HttpRequestMessage httpRequestMessage, X509Certificate2 x509Certificate2, X509Chain x509Chain, SslPolicyErrors sslPolicyErrors)
{
InitialSessionState iss = InitialSessionState.CreateDefault();

// Add $_ variable with the Delegate parameters as members
PSObject dollarUnderbar = new PSObject();
dollarUnderbar.Members.Add(new PSNoteProperty("Request", httpRequestMessage));
dollarUnderbar.Members.Add(new PSNoteProperty("Certificate", x509Certificate2));
dollarUnderbar.Members.Add(new PSNoteProperty("CertificateChain", x509Chain));
dollarUnderbar.Members.Add(new PSNoteProperty("SslErrors", sslPolicyErrors));
iss.Variables.Add(new SessionStateVariableEntry(name: "_", value: dollarUnderbar, description: String.Empty));

Boolean result = false;
try
{
using (Runspace rs = RunspaceFactory.CreateRunspace(iss))
using (var ps = System.Management.Automation.PowerShell.Create())
{
ps.Runspace = rs;
rs.Open();
ps.AddScript(CertificateValidationScript.ToString());

result = LanguagePrimitives.IsTrue(ps.Invoke().First());
}
}
catch // Treat all exceptions as Certificate failures.
{
result = false;
}

return result;
};

handler.ServerCertificateCustomValidationCallback = validationCallBackWrapper;
}

// This indicates GetResponse will handle redirects.
if (handleRedirect)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1557,6 +1557,58 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {

}

Context "Invoke-WebRequest CertificateValidationScript Tests" {
It "Verifies Invoke-WebRequest -CertificateValidationScript can accept all certificates" -Pending:$PendingCertificateTest {
$params = @{
Uri = Get-WebListenerUrl -Test 'Get' -Https
ErrorAction = 'Stop'
CertificateValidationScript = { $true }
}
# WebListener uses a self-signed cert. Without -SkipCertificateCheck this would normally fail
$result = Invoke-WebRequest @Params
$jsonResult = $result.Content | ConvertFrom-Json
$jsonResult.Headers.Host | Should BeExactly $params.Uri.Authority
}

It "Verifies Invoke-WebRequest -CertificateValidationScript is ignored when -SkipCertificateCheck is present" {
$params = @{
Uri = Get-WebListenerUrl -Test 'Get' -Https
ErrorAction = 'Stop'
SkipCertificateCheck = $true
# This script would fail all certificates
CertificateValidationScript = { $false }
}
$result = Invoke-WebRequest @Params
$jsonResult = $result.Content | ConvertFrom-Json
$jsonResult.Headers.Host | Should BeExactly $params.Uri.Authority
}

It 'Verifies Invoke-WebRequest -CertificateValidationScript script has a $_' -Pending:$PendingCertificateTest {
$params = @{
Uri = Get-WebListenerUrl -Test 'Get' -Https
ErrorAction = 'Stop'
CertificateValidationScript = {
$_.Certificate.Thumbprint -eq 'C8747A1C4A46E52EEC688A6766967010F86C58E3' -and
$_.CertificateChain.ChainElements[0].Certificate.Thumbprint -eq 'C8747A1C4A46E52EEC688A6766967010F86C58E3' -and
$_.SslErrors -eq 'RemoteCertificateChainErrors' -and
$_.Request.Method.Method -eq 'GET'
}
}
$result = Invoke-WebRequest @Params
$jsonResult = $result.Content | ConvertFrom-Json
$jsonResult.Headers.Host | Should BeExactly $params.Uri.Authority
}

It "Verifies Invoke-WebRequest -CertificateValidationScript treats exceptions as Certificate failures" -Pending:$PendingCertificateTest {
$params = @{
Uri = Get-WebListenerUrl -Test 'Get' -Https
ErrorAction = 'Stop'
CertificateValidationScript = { throw 'Bad Cert' }
}
{ Invoke-WebRequest @Params } | ShouldBeErrorId 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand'
}
}

BeforeEach {
if ($env:http_proxy) {
$savedHttpProxy = $env:http_proxy
Expand Down Expand Up @@ -2629,6 +2681,56 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" {
}
}

Context "Invoke-RestMethod CertificateValidationScript Tests" {
It "Verifies Invoke-RestMethod -CertificateValidationScript can accept all certificates" -Pending:$PendingCertificateTest {
$params = @{
Uri = Get-WebListenerUrl -Test 'Get' -Https
ErrorAction = 'Stop'
CertificateValidationScript = { $true }
}
# WebListener uses a self-signed cert. Without -SkipCertificateCheck this would normally fail
$result = Invoke-RestMethod @Params
$result.Headers.Host | Should BeExactly $params.Uri.Authority
}

It "Verifies Invoke-RestMethod -CertificateValidationScript is ignored when -SkipCertificateCheck is present" {
$params = @{
Uri = Get-WebListenerUrl -Test 'Get' -Https
ErrorAction = 'Stop'
SkipCertificateCheck = $true
# This script would fail all certificates
CertificateValidationScript = { $false }
}
$result = Invoke-RestMethod @Params
$result.Headers.Host | Should BeExactly $params.Uri.Authority
}

It 'Verifies Invoke-RestMethod -CertificateValidationScript script has a $_' -Pending:$PendingCertificateTest {
$params = @{
Uri = Get-WebListenerUrl -Test 'Get' -Https
ErrorAction = 'Stop'
CertificateValidationScript = {
$WebListenerThumbprint = 'C8747A1C4A46E52EEC688A6766967010F86C58E3'
$_.Certificate.Thumbprint -eq $WebListenerThumbprint -and
$_.CertificateChain.ChainElements[0].Certificate.Thumbprint -eq $WebListenerThumbprint -and
$_.SslErrors -eq 'RemoteCertificateChainErrors' -and
$_.Request.Method.Method -eq 'GET'
}
}
$result = Invoke-RestMethod @Params
$result.Headers.Host | Should BeExactly $params.Uri.Authority
}

It "Verifies Invoke-RestMethod -CertificateValidationScript treats exceptions as Certificate failures" -Pending:$PendingCertificateTest {
$params = @{
Uri = Get-WebListenerUrl -Test 'Get' -Https
ErrorAction = 'Stop'
CertificateValidationScript = { throw 'Bad Cert' }
}
{ Invoke-RestMethod @Params } | ShouldBeErrorId 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand'
}
}

BeforeEach {
if ($env:http_proxy) {
$savedHttpProxy = $env:http_proxy
Expand Down