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 00828fec4b9..a79e40f492c 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
@@ -11,6 +11,7 @@
using System.Collections;
using System.Globalization;
using System.Security;
+using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
#if !CORECLR
@@ -46,6 +47,35 @@ public enum WebAuthenticationType
OAuth,
}
+ // WebSslProtocol is used because not all SslProtocols are supported by HttpClientHandler.
+ // Also SslProtocols.Default is not the "default" for HttpClientHandler as SslProtocols.Ssl3 is not supported.
+ ///
+ /// The valid values for the -SslProtocol parameter for Invoke-RestMethod and Invoke-WebRequest
+ ///
+ [Flags]
+ public enum WebSslProtocol
+ {
+ ///
+ /// No SSL protocol will be set and the system defaults will be used.
+ ///
+ Default = 0,
+
+ ///
+ /// Specifies the TLS 1.0 security protocol. The TLS protocol is defined in IETF RFC 2246.
+ ///
+ Tls = SslProtocols.Tls,
+
+ ///
+ /// Specifies the TLS 1.1 security protocol. The TLS protocol is defined in IETF RFC 4346.
+ ///
+ Tls11 = SslProtocols.Tls11,
+
+ ///
+ /// Specifies the TLS 1.2 security protocol. The TLS protocol is defined in IETF RFC 5246
+ ///
+ Tls12 = SslProtocols.Tls12
+ }
+
///
/// Base class for Invoke-RestMethod and Invoke-WebRequest commands.
///
@@ -137,6 +167,12 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet
[Parameter]
public virtual SwitchParameter SkipCertificateCheck { get; set; }
+ ///
+ /// Gets or sets the TLS/SSL protocol used by the Web Cmdlet
+ ///
+ [Parameter]
+ public virtual WebSslProtocol SslProtocol { get; set; } = WebSslProtocol.Default;
+
///
/// Gets or sets the Token property. Token is required by Authentication OAuth and Bearer.
///
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 f5322ddc129..630f66804c6 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
@@ -13,6 +13,7 @@
using System.Text;
using System.Collections;
using System.Globalization;
+using System.Security.Authentication;
using System.Security.Cryptography;
using System.Threading;
using System.Xml;
@@ -192,6 +193,9 @@ internal virtual HttpClient GetHttpClient(bool handleRedirect)
}
}
+ handler.SslProtocols = (SslProtocols)SslProtocol;
+
+
HttpClient httpClient = new HttpClient(handler);
// check timeout setting (in seconds instead of milliseconds as in HttpWebRequest)
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
index 8dc1b1aaaef..fa5b15e8c24 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1
@@ -1414,6 +1414,70 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {
}
}
+ Context "Invoke-WebRequest -SslProtocol Test" {
+ It "Verifies Invoke-WebRequest -SslProtocol works on " -TestCases @(
+ @{SslProtocol = 'Default'; ActualProtocol = 'Default'}
+ @{SslProtocol = 'Tls'; ActualProtocol = 'Tls'}
+ @{SslProtocol = 'Tls11'; ActualProtocol = 'Tls11'}
+ @{SslProtocol = 'Tls12'; ActualProtocol = 'Tls12'}
+ # macOS does not support multiple SslProtocols
+ if (-not $IsMacOS)
+ {
+ @{SslProtocol = 'Tls, Tls11, Tls12'; ActualProtocol = 'Tls12'}
+ @{SslProtocol = 'Tls11, Tls12'; ActualProtocol = 'Tls12'}
+ @{SslProtocol = 'Tls, Tls11, Tls12'; ActualProtocol = 'Tls11'}
+ @{SslProtocol = 'Tls11, Tls12'; ActualProtocol = 'Tls11'}
+ @{SslProtocol = 'Tls, Tls11'; ActualProtocol = 'Tls11'}
+ @{SslProtocol = 'Tls, Tls11, Tls12'; ActualProtocol = 'Tls'}
+ @{SslProtocol = 'Tls, Tls11'; ActualProtocol = 'Tls'}
+ @{SslProtocol = 'Tls, Tls12'; ActualProtocol = 'Tls'}
+ }
+ # macOS does not support multiple SslProtocols and possible CoreFX for this combo on Linux
+ if($IsWindows)
+ {
+
+ @{SslProtocol = 'Tls, Tls12'; ActualProtocol = 'Tls12'}
+ }
+ ) {
+ param($SslProtocol, $ActualProtocol)
+ $params = @{
+ Uri = Get-WebListenerUrl -Test 'Get' -Https -SslProtocol $ActualProtocol
+ SslProtocol = $SslProtocol
+ SkipCertificateCheck = $true
+ }
+ $response = Invoke-WebRequest @params
+ $result = $Response.Content | ConvertFrom-Json
+
+ $result.headers.Host | Should Be $params.Uri.Authority
+ }
+
+ It "Verifies Invoke-WebRequest -SslProtocol -SslProtocol fails on a only connection" -TestCases @(
+ @{IntendedProtocol = 'Tls'; ActualProtocol = 'Tls12'}
+ @{IntendedProtocol = 'Tls'; ActualProtocol = 'Tls11'}
+ @{IntendedProtocol = 'Tls11'; ActualProtocol = 'Tls12'}
+ @{IntendedProtocol = 'Tls11'; ActualProtocol = 'Tls'}
+ @{IntendedProtocol = 'Tls12'; ActualProtocol = 'Tls'}
+ @{IntendedProtocol = 'Tls12'; ActualProtocol = 'Tls11'}
+ # macOS does not support multiple SslProtocols
+ if (-not $IsMacOS)
+ {
+ @{IntendedProtocol = 'Tls11, Tls12'; ActualProtocol = 'Tls'}
+ @{IntendedProtocol = 'Tls, Tls12'; ActualProtocol = 'Tls11'}
+ @{IntendedProtocol = 'Tls, Tls11'; ActualProtocol = 'Tls12'}
+ }
+ ) {
+ param( $IntendedProtocol, $ActualProtocol)
+ $params = @{
+ Uri = Get-WebListenerUrl -Test 'Get' -Https -SslProtocol $ActualProtocol
+ SslProtocol = $IntendedProtocol
+ SkipCertificateCheck = $true
+ ErrorAction = 'Stop'
+ }
+ { Invoke-WebRequest @params } | ShouldBeErrorId 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand'
+ }
+
+ }
+
BeforeEach {
if ($env:http_proxy) {
$savedHttpProxy = $env:http_proxy
@@ -2332,6 +2396,69 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" {
}
}
+ Context "Invoke-RestMethod -SslProtocol Test" {
+ It "Verifies Invoke-RestMethod -SslProtocol works on " -TestCases @(
+ @{SslProtocol = 'Default'; ActualProtocol = 'Default'}
+ @{SslProtocol = 'Tls'; ActualProtocol = 'Tls'}
+ @{SslProtocol = 'Tls11'; ActualProtocol = 'Tls11'}
+ @{SslProtocol = 'Tls12'; ActualProtocol = 'Tls12'}
+ # macOS does not support multiple SslProtocols
+ if (-not $IsMacOS)
+ {
+ @{SslProtocol = 'Tls, Tls11, Tls12'; ActualProtocol = 'Tls12'}
+ @{SslProtocol = 'Tls11, Tls12'; ActualProtocol = 'Tls12'}
+ @{SslProtocol = 'Tls, Tls11, Tls12'; ActualProtocol = 'Tls11'}
+ @{SslProtocol = 'Tls11, Tls12'; ActualProtocol = 'Tls11'}
+ @{SslProtocol = 'Tls, Tls11'; ActualProtocol = 'Tls11'}
+ @{SslProtocol = 'Tls, Tls11, Tls12'; ActualProtocol = 'Tls'}
+ @{SslProtocol = 'Tls, Tls11'; ActualProtocol = 'Tls'}
+ @{SslProtocol = 'Tls, Tls12'; ActualProtocol = 'Tls'}
+ }
+ # macOS does not support multiple SslProtocols and possible CoreFX for this combo on Linux
+ if($IsWindows)
+ {
+ @{SslProtocol = 'Tls, Tls12'; ActualProtocol = 'Tls12'}
+ }
+ ) {
+ param($SslProtocol, $ActualProtocol)
+ $params = @{
+ Uri = Get-WebListenerUrl -Test 'Get' -Https -SslProtocol $ActualProtocol
+ SslProtocol = $SslProtocol
+ SkipCertificateCheck = $true
+ }
+ $result = Invoke-RestMethod @params
+
+ $result.headers.Host | Should Be $params.Uri.Authority
+ }
+
+ It "Verifies Invoke-RestMethod -SslProtocol fails on a only connection" -TestCases @(
+ @{IntendedProtocol = 'Tls'; ActualProtocol = 'Tls12'}
+ @{IntendedProtocol = 'Tls'; ActualProtocol = 'Tls11'}
+ @{IntendedProtocol = 'Tls11'; ActualProtocol = 'Tls12'}
+ @{IntendedProtocol = 'Tls11'; ActualProtocol = 'Tls'}
+ @{IntendedProtocol = 'Tls12'; ActualProtocol = 'Tls'}
+ @{IntendedProtocol = 'Tls12'; ActualProtocol = 'Tls11'}
+ # macOS does not support multiple SslProtocols
+ if (-not $IsMacOS)
+ {
+ @{IntendedProtocol = 'Tls11, Tls12'; ActualProtocol = 'Tls'}
+ @{IntendedProtocol = 'Tls, Tls12'; ActualProtocol = 'Tls11'}
+ @{IntendedProtocol = 'Tls, Tls11'; ActualProtocol = 'Tls12'}
+ }
+ ) {
+ param( $IntendedProtocol, $ActualProtocol)
+ $params = @{
+ Uri = Get-WebListenerUrl -Test 'Get' -Https -SslProtocol $ActualProtocol
+ SslProtocol = $IntendedProtocol
+ SkipCertificateCheck = $true
+ ErrorAction = 'Stop'
+ }
+ { Invoke-RestMethod @params } | ShouldBeErrorId 'WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand'
+ }
+
+
+ }
+
BeforeEach {
if ($env:http_proxy) {
$savedHttpProxy = $env:http_proxy
diff --git a/test/tools/Modules/WebListener/README.md b/test/tools/Modules/WebListener/README.md
index f66d94b2c16..0bb257d8f09 100644
--- a/test/tools/Modules/WebListener/README.md
+++ b/test/tools/Modules/WebListener/README.md
@@ -1,13 +1,13 @@
# WebListener Module
-A PowerShell module for managing the WebListener App. The included SelF-Signed Certificate `ServerCert.pfx` has the password set to `password` and is issued for the Client and Server Authentication key usages. This certificate is used by the WebListener App for SSL/TLS. The included SelF-Signed Certificate `ClientCert.pfx` has the password set to `password` and has not been issued for any specific key usage. This Certificate is used for Client Certificate Authentication with the WebListener App.
+A PowerShell module for managing the WebListener App. The included SelF-Signed Certificate `ServerCert.pfx` has the password set to `password` and is issued for the Client and Server Authentication key usages. This certificate is used by the WebListener App for SSL/TLS. The included SelF-Signed Certificate `ClientCert.pfx` has the password set to `password` and has not been issued for any specific key usage. This Certificate is used for Client Certificate Authentication with the WebListener App. The port used for `-HttpsPort` will use TLS 1.2.
# Running WebListener
```powershell
Import-Module .\build.psm1
Publish-PSTestTools
-$Listener = Start-WebListener -HttpPort 8083 -HttpsPort 8084
+$Listener = Start-WebListener -HttpPort 8083 -HttpsPort 8084 -Tls11Port 8085 -TlsPort 8086
```
# Stopping WebListener
diff --git a/test/tools/Modules/WebListener/WebListener.psm1 b/test/tools/Modules/WebListener/WebListener.psm1
index a3125d07c6b..1e4ea444bf2 100644
--- a/test/tools/Modules/WebListener/WebListener.psm1
+++ b/test/tools/Modules/WebListener/WebListener.psm1
@@ -2,6 +2,8 @@ Class WebListener
{
[int]$HttpPort
[int]$HttpsPort
+ [int]$Tls11Port
+ [int]$TlsPort
[System.Management.Automation.Job]$Job
WebListener () { }
@@ -36,7 +38,13 @@ function Start-WebListener
[int]$HttpPort = 8083,
[ValidateRange(1,65535)]
- [int]$HttpsPort = 8084
+ [int]$HttpsPort = 8084,
+
+ [ValidateRange(1,65535)]
+ [int]$Tls11Port = 8085,
+
+ [ValidateRange(1,65535)]
+ [int]$TlsPort = 8086
)
process
@@ -58,11 +66,13 @@ function Start-WebListener
$Job = Start-Job {
$path = Split-Path -parent (get-command WebListener).Path
Push-Location $path
- dotnet $using:appDll $using:serverPfxPath $using:serverPfxPassword $using:HttpPort $using:HttpsPort
+ dotnet $using:appDll $using:serverPfxPath $using:serverPfxPassword $using:HttpPort $using:HttpsPort $using:Tls11Port $using:TlsPort
}
$Script:WebListener = [WebListener]@{
HttpPort = $HttpPort
HttpsPort = $HttpsPort
+ Tls11Port = $Tls11Port
+ TlsPort = $TlsPort
Job = $Job
}
# Wait until the app is running or until the initTimeoutSeconds have been reached
@@ -112,6 +122,10 @@ function Get-WebListenerUrl {
[OutputType([Uri])]
param (
[switch]$Https,
+
+ [ValidateSet('Default', 'Tls12', 'Tls11', 'Tls')]
+ [string]$SslProtocol = 'Default',
+
[ValidateSet(
'Cert',
'Compression',
@@ -140,9 +154,16 @@ function Get-WebListenerUrl {
$Uri.Host = 'localhost'
$Uri.Port = $runningListener.HttpPort
$Uri.Scheme = 'Http'
+
if ($Https.IsPresent)
{
- $Uri.Port = $runningListener.HttpsPort
+ switch ($SslProtocol)
+ {
+ 'Tls11' { $Uri.Port = $runningListener.Tls11Port }
+ 'Tls' { $Uri.Port = $runningListener.TlsPort }
+ # The base HTTPs port is configured for Tls12 only
+ default { $Uri.Port = $runningListener.HttpsPort }
+ }
$Uri.Scheme = 'Https'
}
diff --git a/test/tools/WebListener/Program.cs b/test/tools/WebListener/Program.cs
index 124f28f7e8d..e0706c9a374 100644
--- a/test/tools/WebListener/Program.cs
+++ b/test/tools/WebListener/Program.cs
@@ -20,9 +20,9 @@ public class Program
{
public static void Main(string[] args)
{
- if (args.Count() != 4)
+ if (args.Count() != 6)
{
- System.Console.WriteLine("Required: ");
+ System.Console.WriteLine("Required: ");
Environment.Exit(1);
}
BuildWebHost(args).Run();
@@ -44,6 +44,28 @@ public static IWebHost BuildWebHost(string[] args) =>
httpsOption.ServerCertificate = certificate;
listenOptions.UseHttps(httpsOption);
});
+ options.Listen(IPAddress.Loopback, int.Parse(args[4]), listenOptions =>
+ {
+ var certificate = new X509Certificate2(args[0], args[1]);
+ HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions();
+ httpsOption.SslProtocols = SslProtocols.Tls11;
+ httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
+ httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => {return true;};
+ httpsOption.CheckCertificateRevocation = false;
+ httpsOption.ServerCertificate = certificate;
+ listenOptions.UseHttps(httpsOption);
+ });
+ options.Listen(IPAddress.Loopback, int.Parse(args[5]), listenOptions =>
+ {
+ var certificate = new X509Certificate2(args[0], args[1]);
+ HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions();
+ httpsOption.SslProtocols = SslProtocols.Tls;
+ httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
+ httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => {return true;};
+ httpsOption.CheckCertificateRevocation = false;
+ httpsOption.ServerCertificate = certificate;
+ listenOptions.UseHttps(httpsOption);
+ });
})
.Build();
}
diff --git a/test/tools/WebListener/README.md b/test/tools/WebListener/README.md
index 04dbd20ccb5..2a26200b826 100644
--- a/test/tools/WebListener/README.md
+++ b/test/tools/WebListener/README.md
@@ -8,24 +8,26 @@ ASP.NET Core 2.0 app for testing HTTP and HTTPS Requests.
dotnet restore
dotnet publish --output bin --configuration Release
cd bin
-dotnet WebListener.dll ServerCert.pfx password 8083 8084
+dotnet WebListener.dll ServerCert.pfx password 8083 8084 8085 8086
```
-The test site can then be accessed via `http://localhost:8083/` or `https://localhost:8084/`.
+The test site can then be accessed via `http://localhost:8083/`, `https://localhost:8084/`, `https://localhost:8085/`, or `https://localhost:8086/`.
-The `WebListener.dll` takes 4 arguments:
+The `WebListener.dll` takes 6 arguments:
* The path to the Server Certificate
* The Server Certificate Password
* The TCP Port to bind on for HTTP
-* The TCP Port to bind on for HTTPS
+* The TCP Port to bind on for HTTPS using TLS 1.2
+* The TCP Port to bind on for HTTPS using TLS 1.1
+* The TCP Port to bind on for HTTPS using TLS 1.0
# Run With WebListener Module
```powershell
Import-Module .\build.psm1
Publish-PSTestTools
-$Listener = Start-WebListener -HttpPort 8083 -HttpsPort 8084
+$Listener = Start-WebListener -HttpPort 8083 -HttpsPort 8084 -Tls11Port 8085 -TlsPort = 8086
```
# Tests