Skip to content
Merged
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 @@ -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
Expand Down Expand Up @@ -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.
/// <summary>
/// The valid values for the -SslProtocol parameter for Invoke-RestMethod and Invoke-WebRequest
/// </summary>
[Flags]
public enum WebSslProtocol
{
/// <summary>
/// No SSL protocol will be set and the system defaults will be used.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is "system default"? In .Net? Os?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is environment specific and subject to implementation changes by CoreFX, thus the comment is not specific on purpose. It is SslProtocol.None... which is terribly named for PowerShell users as it implies no SSL/TLS would be used (that's a C# specific terminology for the 0 field on flag enums).

/// </summary>
Default = 0,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be exactly Default = SslProtocols.None.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how well that works with Flags. We have to define 0 for the default here and if CoreFX decides none is no longer 0 we lose our Default option.

Copy link
Collaborator

@iSazonov iSazonov Nov 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see handler.SslProtocols = (SslProtocols)SslProtocol; so I believe Default = SslProtocols.None is more accurate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SslProtocols will always have a 0 too since it is a Flag as well. I'm weary of changing this one. Wehn I was researching I saw multiple asks for the SslProtocols to be cleaned up an None removed. it seems to be universally detested as a field name that is better defined as SystemDefault. Looking at the implementation of SecurityProtocolType they, too, erred on the side of using 0.

https://github.com/dotnet/corefx/blob/ba0e14d957cd2fed3e1dec54d2c5f2a92a746c74/src/System.Net.ServicePoint/src/System/Net/SecurityProtocolType.cs#L12


/// <summary>
/// Specifies the TLS 1.0 security protocol. The TLS protocol is defined in IETF RFC 2246.
/// </summary>
Tls = SslProtocols.Tls,

/// <summary>
/// Specifies the TLS 1.1 security protocol. The TLS protocol is defined in IETF RFC 4346.
/// </summary>
Tls11 = SslProtocols.Tls11,

/// <summary>
/// Specifies the TLS 1.2 security protocol. The TLS protocol is defined in IETF RFC 5246
/// </summary>
Tls12 = SslProtocols.Tls12
}

/// <summary>
/// Base class for Invoke-RestMethod and Invoke-WebRequest commands.
/// </summary>
Expand Down Expand Up @@ -137,6 +167,12 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet
[Parameter]
public virtual SwitchParameter SkipCertificateCheck { get; set; }

/// <summary>
/// Gets or sets the TLS/SSL protocol used by the Web Cmdlet
/// </summary>
[Parameter]
public virtual WebSslProtocol SslProtocol { get; set; } = WebSslProtocol.Default;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should ask: maybe use WebSslProtocol for parameter name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Web is kind of redundant in the parameter name. I was just continuing with the convention of prefixing Web in front of Enums used for web cmdlet parameter options. Technically, it is setting the SslProtocol, we are just limiting the options that the user can use to those it actually supports.


/// <summary>
/// Gets or sets the Token property. Token is required by Authentication OAuth and Bearer.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,70 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {
}
}

Context "Invoke-WebRequest -SslProtocol Test" {
It "Verifies Invoke-WebRequest -SslProtocol <SslProtocol> works on <ActualProtocol>" -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 <IntendedProtocol> fails on a <ActualProtocol> only connection" -TestCases @(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have some tests were intended is multiple protocols. that should be allowed since you used a flags enum.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

@{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
Expand Down Expand Up @@ -2332,6 +2396,69 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" {
}
}

Context "Invoke-RestMethod -SslProtocol Test" {
It "Verifies Invoke-RestMethod -SslProtocol <SslProtocol> works on <ActualProtocol>" -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 <IntendedProtocol> fails on a <ActualProtocol> 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
Expand Down
4 changes: 2 additions & 2 deletions test/tools/Modules/WebListener/README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
27 changes: 24 additions & 3 deletions test/tools/Modules/WebListener/WebListener.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ Class WebListener
{
[int]$HttpPort
[int]$HttpsPort
[int]$Tls11Port
[int]$TlsPort
[System.Management.Automation.Job]$Job

WebListener () { }
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -112,6 +122,10 @@ function Get-WebListenerUrl {
[OutputType([Uri])]
param (
[switch]$Https,

[ValidateSet('Default', 'Tls12', 'Tls11', 'Tls')]
[string]$SslProtocol = 'Default',

[ValidateSet(
'Cert',
'Compression',
Expand Down Expand Up @@ -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'
}

Expand Down
26 changes: 24 additions & 2 deletions test/tools/WebListener/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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: <CertificatePath> <CertificatePassword> <HTTPPortNumber> <HTTPSPortNumber>");
System.Console.WriteLine("Required: <CertificatePath> <CertificatePassword> <HTTPPortNumber> <HTTPSPortNumberTls2> <HTTPSPortNumberTls11> <HTTPSPortNumberTls>");
Environment.Exit(1);
}
BuildWebHost(args).Run();
Expand All @@ -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();
}
Expand Down
12 changes: 7 additions & 5 deletions test/tools/WebListener/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down