From f1c5eb29667301705d12cf74c7391a27bab9e85a Mon Sep 17 00:00:00 2001 From: markekraus Date: Sun, 1 Oct 2017 15:59:41 -0500 Subject: [PATCH 1/5] Add CertificateValidationScript Parameter to Web Cmdlets --- .../Common/WebRequestPSCmdlet.Common.cs | 6 + .../CoreCLR/WebRequestPSCmdlet.CoreClr.cs | 47 ++++++ .../WebCmdlets.Tests.ps1 | 140 ++++++++++++++++++ 3 files changed, 193 insertions(+) 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..20ea95f9957 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 + /// + [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..abc5517a5dc 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,50 @@ internal virtual HttpClient GetHttpClient(bool handleRedirect) handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; handler.ClientCertificateOptions = ClientCertificateOption.Manual; } + else if (CertificateValidationScript != null) + { + // validationCallBackWrapper wraps the certificateValidationClosure ScriptBlock so the async callback can set the default + // PowerShell Runspace in the async thread to the Runspace of the current thread. This provides the ScriptBlock access to + // the current scope. + Runspace currentRunspace = Runspace.DefaultRunspace; + ScriptBlock certificateValidationClosure = CertificateValidationScript.GetNewClosure(); + Func validationCallBackWrapper = + delegate(HttpRequestMessage httpRequestMessage, X509Certificate2 x509Certificate2, X509Chain x509Chain, SslPolicyErrors sslPolicyErrors) + { + Runspace previousRunspace = Runspace.DefaultRunspace; + Runspace.DefaultRunspace = currentRunspace; + + // Set Script scope automatic variables for the closure. + PSVariableIntrinsics closureVi = certificateValidationClosure.Module.SessionState.PSVariable; + closureVi.Set("HttpRequestMessage", httpRequestMessage); + closureVi.Set("X509Certificate2", x509Certificate2); + closureVi.Set("X509Chain", x509Chain); + closureVi.Set("SslPolicyErrors", sslPolicyErrors); + closureVi.Set("ErrorActionPreference", "Stop"); + + Boolean result = false; + try + { + result = LanguagePrimitives.IsTrue( + certificateValidationClosure.InvokeReturnAsIs( + httpRequestMessage, + x509Certificate2, + x509Chain, + sslPolicyErrors + ) + ); + } + catch // Treat all exceptions as Certificate failures. + { + result = false; + } + + Runspace.DefaultRunspace = previousRunspace; + 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..529ffded56e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1557,6 +1557,78 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { } + Context "Invoke-WebRequest CertificateValidationScript Tests" { + It "Verifies Invoke-WebRequest -CertificateValidationScript can accept all certificates" { + $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 access to the calling scope" { + # WebListener's Certificate Thumbprint + $thumbprint = 'C8747A1C4A46E52EEC688A6766967010F86C58E3' + $scriptHash = @{Subject = $null} + $params = @{ + Uri = Get-WebListenerUrl -Test 'Get' -Https + ErrorAction = 'Stop' + CertificateValidationScript = { + # $args[1] is the remote server's X509Certificate2 certificate + $scriptHash['Subject'] = $args[1].Subject + return ($args[1].Thumbprint -eq $thumbprint) + } + } + $result = Invoke-WebRequest @Params + $jsonResult = $result.Content | ConvertFrom-Json + $jsonResult.Headers.Host | Should BeExactly $params.Uri.Authority + $scriptHash.Subject | Should BeExactly 'CN=localhost' + } + + It "Verifies Invoke-WebRequest -CertificateValidationScript treats exceptions as Certificate failures" { + $params = @{ + Uri = Get-WebListenerUrl -Test 'Get' -Https + ErrorAction = 'Stop' + CertificateValidationScript = { throw 'Bad Cert' } + } + { Invoke-WebRequest @Params } | ShouldBeErrorId 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand' + } + + It "Verifies Invoke-WebRequest -CertificateValidationScript Creates the automatic variable" -TestCases @( + @{ Name = "HttpRequestMessage"; Type = "System.Net.Http.HttpRequestMessage" } + @{ Name = "X509Certificate2"; Type = "System.Security.Cryptography.X509Certificates.X509Certificate2" } + @{ Name = "X509Chain"; Type = "System.Security.Cryptography.X509Certificates.X509Chain" } + @{ Name = "SslPolicyErrors"; Type = "System.Net.Security.SslPolicyErrors" } + ) { + param($name, $type) + $params = @{ + Uri = Get-WebListenerUrl -Test 'Get' -Https + ErrorAction = 'Stop' + CertificateValidationScript = { (Get-Variable -Name $name -ValueOnly) -is $type } + } + $result = Invoke-WebRequest @Params + $jsonResult = $result.Content | ConvertFrom-Json + $jsonResult.Headers.Host | Should BeExactly $params.Uri.Authority + } + } + BeforeEach { if ($env:http_proxy) { $savedHttpProxy = $env:http_proxy @@ -2629,6 +2701,74 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { } } + Context "Invoke-RestMethod CertificateValidationScript Tests" { + It "Verifies Invoke-RestMethod -CertificateValidationScript can accept all certificates" { + $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 access to the calling scope" { + # WebListener's Certificate Thumbprint + $thumbprint = 'C8747A1C4A46E52EEC688A6766967010F86C58E3' + $scriptHash = @{Subject = $null} + $params = @{ + Uri = Get-WebListenerUrl -Test 'Get' -Https + ErrorAction = 'Stop' + CertificateValidationScript = { + # $args[1] is the remote server's X509Certificate2 certificate + $scriptHash['Subject'] = $args[1].Subject + return ($args[1].Thumbprint -eq $thumbprint) + } + } + $result = Invoke-RestMethod @Params + $result.Headers.Host | Should BeExactly $params.Uri.Authority + $scriptHash.Subject | Should BeExactly 'CN=localhost' + } + + It "Verifies Invoke-RestMethod -CertificateValidationScript treats exceptions as Certificate failures" { + $params = @{ + Uri = Get-WebListenerUrl -Test 'Get' -Https + ErrorAction = 'Stop' + CertificateValidationScript = { throw 'Bad Cert' } + } + { Invoke-RestMethod @Params } | ShouldBeErrorId 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand' + } + + It "Verifies Invoke-RestMethod -CertificateValidationScript Creates the automatic variable" -TestCases @( + @{ Name = "HttpRequestMessage"; Type = "System.Net.Http.HttpRequestMessage" } + @{ Name = "X509Certificate2"; Type = "System.Security.Cryptography.X509Certificates.X509Certificate2" } + @{ Name = "X509Chain"; Type = "System.Security.Cryptography.X509Certificates.X509Chain" } + @{ Name = "SslPolicyErrors"; Type = "System.Net.Security.SslPolicyErrors" } + ) { + param($name, $type) + $params = @{ + Uri = Get-WebListenerUrl -Test 'Get' -Https + ErrorAction = 'Stop' + CertificateValidationScript = { (Get-Variable -Name $name -ValueOnly) -is $type } + } + $result = Invoke-RestMethod @Params + $result.Headers.Host | Should BeExactly $params.Uri.Authority + } + } + BeforeEach { if ($env:http_proxy) { $savedHttpProxy = $env:http_proxy From e1c1cedcc2c0671357d35f52b9cc47aaa941dca3 Mon Sep 17 00:00:00 2001 From: Mark Kraus Date: Mon, 2 Oct 2017 06:36:33 -0500 Subject: [PATCH 2/5] Address PR Feedback --- .../utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 20ea95f9957..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 @@ -165,7 +165,7 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet public virtual SwitchParameter SkipCertificateCheck { get; set; } /// - /// gets or sets the CertificateValidationScript property + /// gets or sets the CertificateValidationScript property. This will be ignored if SkipCertificateCheck is set. /// [Parameter] public virtual ScriptBlock CertificateValidationScript { get; set; } From c49ab67835bf22f46affe90c63aed92128f58f44 Mon Sep 17 00:00:00 2001 From: Mark Kraus Date: Thu, 12 Oct 2017 06:47:14 -0500 Subject: [PATCH 3/5] [Feature] Remove Automatic Variables and DefaultRunspace assignment --- .../CoreCLR/WebRequestPSCmdlet.CoreClr.cs | 20 ++--------- .../WebCmdlets.Tests.ps1 | 33 ------------------- 2 files changed, 2 insertions(+), 51 deletions(-) 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 abc5517a5dc..2cb7fe0a7e9 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 @@ -178,30 +178,15 @@ internal virtual HttpClient GetHttpClient(bool handleRedirect) } else if (CertificateValidationScript != null) { - // validationCallBackWrapper wraps the certificateValidationClosure ScriptBlock so the async callback can set the default - // PowerShell Runspace in the async thread to the Runspace of the current thread. This provides the ScriptBlock access to - // the current scope. - Runspace currentRunspace = Runspace.DefaultRunspace; - ScriptBlock certificateValidationClosure = CertificateValidationScript.GetNewClosure(); + // validationCallBackWrapper wraps the CertificateValidationScript ScriptBlock so the async callback properly execute ScriptBlock Func validationCallBackWrapper = delegate(HttpRequestMessage httpRequestMessage, X509Certificate2 x509Certificate2, X509Chain x509Chain, SslPolicyErrors sslPolicyErrors) { - Runspace previousRunspace = Runspace.DefaultRunspace; - Runspace.DefaultRunspace = currentRunspace; - - // Set Script scope automatic variables for the closure. - PSVariableIntrinsics closureVi = certificateValidationClosure.Module.SessionState.PSVariable; - closureVi.Set("HttpRequestMessage", httpRequestMessage); - closureVi.Set("X509Certificate2", x509Certificate2); - closureVi.Set("X509Chain", x509Chain); - closureVi.Set("SslPolicyErrors", sslPolicyErrors); - closureVi.Set("ErrorActionPreference", "Stop"); - Boolean result = false; try { result = LanguagePrimitives.IsTrue( - certificateValidationClosure.InvokeReturnAsIs( + CertificateValidationScript.InvokeReturnAsIs( httpRequestMessage, x509Certificate2, x509Chain, @@ -214,7 +199,6 @@ internal virtual HttpClient GetHttpClient(bool handleRedirect) result = false; } - Runspace.DefaultRunspace = previousRunspace; return result; }; diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 529ffded56e..1b46ccefc3e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1610,23 +1610,6 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { } { Invoke-WebRequest @Params } | ShouldBeErrorId 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand' } - - It "Verifies Invoke-WebRequest -CertificateValidationScript Creates the automatic variable" -TestCases @( - @{ Name = "HttpRequestMessage"; Type = "System.Net.Http.HttpRequestMessage" } - @{ Name = "X509Certificate2"; Type = "System.Security.Cryptography.X509Certificates.X509Certificate2" } - @{ Name = "X509Chain"; Type = "System.Security.Cryptography.X509Certificates.X509Chain" } - @{ Name = "SslPolicyErrors"; Type = "System.Net.Security.SslPolicyErrors" } - ) { - param($name, $type) - $params = @{ - Uri = Get-WebListenerUrl -Test 'Get' -Https - ErrorAction = 'Stop' - CertificateValidationScript = { (Get-Variable -Name $name -ValueOnly) -is $type } - } - $result = Invoke-WebRequest @Params - $jsonResult = $result.Content | ConvertFrom-Json - $jsonResult.Headers.Host | Should BeExactly $params.Uri.Authority - } } BeforeEach { @@ -2751,22 +2734,6 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { } { Invoke-RestMethod @Params } | ShouldBeErrorId 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand' } - - It "Verifies Invoke-RestMethod -CertificateValidationScript Creates the automatic variable" -TestCases @( - @{ Name = "HttpRequestMessage"; Type = "System.Net.Http.HttpRequestMessage" } - @{ Name = "X509Certificate2"; Type = "System.Security.Cryptography.X509Certificates.X509Certificate2" } - @{ Name = "X509Chain"; Type = "System.Security.Cryptography.X509Certificates.X509Chain" } - @{ Name = "SslPolicyErrors"; Type = "System.Net.Security.SslPolicyErrors" } - ) { - param($name, $type) - $params = @{ - Uri = Get-WebListenerUrl -Test 'Get' -Https - ErrorAction = 'Stop' - CertificateValidationScript = { (Get-Variable -Name $name -ValueOnly) -is $type } - } - $result = Invoke-RestMethod @Params - $result.Headers.Host | Should BeExactly $params.Uri.Authority - } } BeforeEach { From a56050e84e9d6311aca31db0422cdf3643ee00cc Mon Sep 17 00:00:00 2001 From: Mark Kraus Date: Thu, 12 Oct 2017 12:49:54 -0500 Subject: [PATCH 4/5] [Feature] Mark tests as pending for currently unsupported OSes --- .../WebCmdlets.Tests.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 1b46ccefc3e..477cc9d0576 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1558,7 +1558,7 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { } Context "Invoke-WebRequest CertificateValidationScript Tests" { - It "Verifies Invoke-WebRequest -CertificateValidationScript can accept all certificates" { + It "Verifies Invoke-WebRequest -CertificateValidationScript can accept all certificates" -Pending:$PendingCertificateTest { $params = @{ Uri = Get-WebListenerUrl -Test 'Get' -Https ErrorAction = 'Stop' @@ -1583,7 +1583,7 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { $jsonResult.Headers.Host | Should BeExactly $params.Uri.Authority } - It "Verifies Invoke-WebRequest -CertificateValidationScript script has access to the calling scope" { + It "Verifies Invoke-WebRequest -CertificateValidationScript script has access to the calling scope" -Pending:$PendingCertificateTest { # WebListener's Certificate Thumbprint $thumbprint = 'C8747A1C4A46E52EEC688A6766967010F86C58E3' $scriptHash = @{Subject = $null} @@ -1602,7 +1602,7 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { $scriptHash.Subject | Should BeExactly 'CN=localhost' } - It "Verifies Invoke-WebRequest -CertificateValidationScript treats exceptions as Certificate failures" { + It "Verifies Invoke-WebRequest -CertificateValidationScript treats exceptions as Certificate failures" -Pending:$PendingCertificateTest { $params = @{ Uri = Get-WebListenerUrl -Test 'Get' -Https ErrorAction = 'Stop' @@ -2685,7 +2685,7 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { } Context "Invoke-RestMethod CertificateValidationScript Tests" { - It "Verifies Invoke-RestMethod -CertificateValidationScript can accept all certificates" { + It "Verifies Invoke-RestMethod -CertificateValidationScript can accept all certificates" -Pending:$PendingCertificateTest { $params = @{ Uri = Get-WebListenerUrl -Test 'Get' -Https ErrorAction = 'Stop' @@ -2708,7 +2708,7 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { $result.Headers.Host | Should BeExactly $params.Uri.Authority } - It "Verifies Invoke-RestMethod -CertificateValidationScript script has access to the calling scope" { + It "Verifies Invoke-RestMethod -CertificateValidationScript script has access to the calling scope" -Pending:$PendingCertificateTest { # WebListener's Certificate Thumbprint $thumbprint = 'C8747A1C4A46E52EEC688A6766967010F86C58E3' $scriptHash = @{Subject = $null} @@ -2726,7 +2726,7 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { $scriptHash.Subject | Should BeExactly 'CN=localhost' } - It "Verifies Invoke-RestMethod -CertificateValidationScript treats exceptions as Certificate failures" { + It "Verifies Invoke-RestMethod -CertificateValidationScript treats exceptions as Certificate failures" -Pending:$PendingCertificateTest { $params = @{ Uri = Get-WebListenerUrl -Test 'Get' -Https ErrorAction = 'Stop' From 9bece06f9ff37f37e835c71330b0079e07f06d32 Mon Sep 17 00:00:00 2001 From: Mark Kraus Date: Sat, 28 Oct 2017 07:18:25 -0500 Subject: [PATCH 5/5] [Feature] Use seperate run space and $_ --- .../CoreCLR/WebRequestPSCmdlet.CoreClr.cs | 27 +++++++++++----- .../WebCmdlets.Tests.ps1 | 31 ++++++++----------- 2 files changed, 32 insertions(+), 26 deletions(-) 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 2cb7fe0a7e9..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 @@ -182,17 +182,28 @@ internal virtual HttpClient GetHttpClient(bool handleRedirect) 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 { - result = LanguagePrimitives.IsTrue( - CertificateValidationScript.InvokeReturnAsIs( - httpRequestMessage, - x509Certificate2, - x509Chain, - sslPolicyErrors - ) - ); + 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. { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 477cc9d0576..5e5b5ba8669 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1583,23 +1583,20 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" { $jsonResult.Headers.Host | Should BeExactly $params.Uri.Authority } - It "Verifies Invoke-WebRequest -CertificateValidationScript script has access to the calling scope" -Pending:$PendingCertificateTest { - # WebListener's Certificate Thumbprint - $thumbprint = 'C8747A1C4A46E52EEC688A6766967010F86C58E3' - $scriptHash = @{Subject = $null} + It 'Verifies Invoke-WebRequest -CertificateValidationScript script has a $_' -Pending:$PendingCertificateTest { $params = @{ Uri = Get-WebListenerUrl -Test 'Get' -Https ErrorAction = 'Stop' CertificateValidationScript = { - # $args[1] is the remote server's X509Certificate2 certificate - $scriptHash['Subject'] = $args[1].Subject - return ($args[1].Thumbprint -eq $thumbprint) + $_.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 - $scriptHash.Subject | Should BeExactly 'CN=localhost' } It "Verifies Invoke-WebRequest -CertificateValidationScript treats exceptions as Certificate failures" -Pending:$PendingCertificateTest { @@ -2708,22 +2705,20 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" { $result.Headers.Host | Should BeExactly $params.Uri.Authority } - It "Verifies Invoke-RestMethod -CertificateValidationScript script has access to the calling scope" -Pending:$PendingCertificateTest { - # WebListener's Certificate Thumbprint - $thumbprint = 'C8747A1C4A46E52EEC688A6766967010F86C58E3' - $scriptHash = @{Subject = $null} + It 'Verifies Invoke-RestMethod -CertificateValidationScript script has a $_' -Pending:$PendingCertificateTest { $params = @{ Uri = Get-WebListenerUrl -Test 'Get' -Https ErrorAction = 'Stop' - CertificateValidationScript = { - # $args[1] is the remote server's X509Certificate2 certificate - $scriptHash['Subject'] = $args[1].Subject - return ($args[1].Thumbprint -eq $thumbprint) + 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 = Invoke-RestMethod @Params $result.Headers.Host | Should BeExactly $params.Uri.Authority - $scriptHash.Subject | Should BeExactly 'CN=localhost' } It "Verifies Invoke-RestMethod -CertificateValidationScript treats exceptions as Certificate failures" -Pending:$PendingCertificateTest {