diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
index 5e8055572a3..30792f3938b 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs
@@ -164,6 +164,12 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet
[Parameter]
public virtual SwitchParameter SkipCertificateCheck { get; set; }
+ ///
+ /// gets or sets the CertificateValidationScript property. This will be ignored if SkipCertificateCheck is set.
+ ///
+ [Parameter]
+ public virtual ScriptBlock CertificateValidationScript { get; set; }
+
///
/// Gets or sets the TLS/SSL protocol used by the Web Cmdlet
///
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs
index 0163b5f0958..acfad4388d5 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs
@@ -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;
@@ -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 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)
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
index dc3bdce61ed..5e5b5ba8669 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
@@ -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
@@ -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