Add test WebListener module and tests for Web Cmdlet Certificate Authentication (#4622)

Introduce new test module 'WebListener.psm1'.
Now web HTTPS tests can use it to exclude using external sites.

PowerShell/PowerShell#4609

* [Feature] Add Tests for Web Cmdlet Certificate Authentication

PowerShell/PowerShell#4609

* [feature] Add new app to Publish-PSTestTools refactor tests

also add ASP.NET to .spelling

* [feature] spelling fix

* [feature] revert badssl changes

* [feature] Impliment suggestions

* [feature] Spelling, var rename, port 8443 to 8083

rebase fix conflict

* [feature] Rename to HttpsListener and Module-ize

.

* [feature] password protect ClientCert to fix macOS import issue

* [feature] Rename to WebListener 

* Rename HttpsListener to WebListener
* Switch Listener from Razor pages to MVC
* Address PR feedback
* Adjust tests

* [feature] Address PR feedback

* [feature] Replace missing smeicolons

* [feature] Address PR Feedback

* [feature] Cleanup and minor fix

* Enum was not used
* GetStatus() was not accessing the correct property chain
* Added -Test param to make URL generation smoother in test code and to fix double / issues

* [feature] More minor fixes

* Https when it matters.
* Expand property... not exclude..
* Remove superfluous and outdated ToString() override

* [Feature] Move ClientCeret.pfx to WebListener Module

* Move the cert
* Adjust Get-WebListenerClientCertificate 
* Remove cert from csproj
* ActionResult -> JsonResult (was mistakenly left as ActionResult during testing)..

* [Feature] Move ServerCert.pfx to Module

* Move cert
* Upate csproj
* Update module
* Add/Update README.md's

CI Retest.
This commit is contained in:
Mark Kraus 2017-08-31 04:30:35 -05:00 committed by Ilya
parent bd42fe95b4
commit 3b2d169c5e
22 changed files with 511 additions and 130 deletions

View file

@ -13,6 +13,7 @@ AppImage
AppVeyor
artifact
artifacts
ASP.NET
AssemblyLoadContext
behavior
behaviors
@ -920,6 +921,7 @@ v0.5.0
v0.6.0
v6.0.0
ValidateNotNullOrEmpty
WebListener
WebRequest
win7-x86
WindowsVersion

View file

@ -779,6 +779,7 @@ function Publish-PSTestTools {
$tools = @(
@{Path="${PSScriptRoot}/test/tools/TestExe";Output="testexe"}
@{Path="${PSScriptRoot}/test/tools/WebListener";Output="WebListener"}
)
if ($null -eq $Options)
{

View file

@ -296,95 +296,6 @@ function ExecuteWebRequest
return $result
}
function GetSelfSignedCert {
<#
.NOTES
This certificate is not issued for any specific Key Usage
It cannot be used for any service that requires a specific key usage
It can be used for SSL/TLS Client Authentication
#>
$PfxBase64 = @'
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
hqPP3QQIlSXpfTECuB4CAggA
'@
$Bytes = [System.Convert]::FromBase64String($PfxBase64)
[System.Security.Cryptography.X509Certificates.X509Certificate2]::new($Bytes)
}
<#
Defines the list of redirect codes to test as well as the
expected Method when the redirection is handled.
@ -412,6 +323,7 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {
BeforeAll {
$response = Start-HttpListener -Port 8080
$WebListener = Start-WebListener
}
AfterAll {
@ -919,31 +831,6 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {
#endregion SkipHeaderVerification Tests
#region Certificate Authentication Tests
# Test pending creation of native test solution
# https://github.com/PowerShell/PowerShell/issues/4609
It "Verifies Invoke-WebRequest Certificate Authentication Fails without -Certificate" -Pending {
$command = 'Invoke-WebRequest https://prod.idrix.eu/secure/'
$result = ExecuteWebCommand -command $command
ValidateResponse -response $result
$result.Output | Should Match ([regex]::Escape('Error: No SSL client certificate presented'))
}
# Test pending creation of native test solution
# https://github.com/PowerShell/PowerShell/issues/4609
It "Verifies Invoke-WebRequest Certificate Authentication Successful with -Certificate" -Pending {
$Certificate = GetSelfSignedCert
$command = 'Invoke-WebRequest https://prod.idrix.eu/secure/ -Certificate $Certificate'
$result = ExecuteWebCommand -command $command
ValidateResponse -response $result
$result.Output.Content | Should Match ([regex]::Escape('SSL Authentication OK!'))
}
#endregion Certificate Authentication Tests
#region charset encoding tests
Context "BasicHtmlWebResponseObject Encoding tests" {
@ -1224,6 +1111,32 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {
#endregion Content Header Inclusion
#region Client Certificate Authentication
It "Verifies Invoke-WebRequest Certificate Authentication Fails without -Certificate" {
$uri = Get-WebListenerUrl -Https -Test 'Cert'
$result = Invoke-WebRequest -Uri $uri -SkipCertificateCheck |
Select-Object -ExpandProperty Content |
ConvertFrom-Json
$result.Status | Should Be 'FAILED'
}
# Test skipped on macOS pending support for Client Certificate Authentication
# https://github.com/PowerShell/PowerShell/issues/4650
It "Verifies Invoke-WebRequest Certificate Authentication Successful with -Certificate" -skip:$IsOSX {
$uri = Get-WebListenerUrl -Https -Test 'Cert'
$certificate = Get-WebListenerClientCertificate
$result = Invoke-WebRequest -Uri $uri -Certificate $certificate -SkipCertificateCheck |
Select-Object -ExpandProperty Content |
ConvertFrom-Json
$result.Status | Should Be 'OK'
$result.Thumbprint | Should Be $certificate.Thumbprint
}
#endregion Client Certificate Authentication
BeforeEach {
if ($env:http_proxy) {
$savedHttpProxy = $env:http_proxy
@ -1255,6 +1168,7 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" {
BeforeAll {
$response = Start-HttpListener -Port 8081
$WebListener = Start-WebListener
}
AfterAll {
@ -1742,28 +1656,27 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" {
#endregion SkipHeaderVerification tests
#region Certificate Authentication Tests
#region Client Certificate Authentication
# Test pending creation of native test solution
# https://github.com/PowerShell/PowerShell/issues/4609
It "Verifies Invoke-RestMethod Certificate Authentication Fails without -Certificate" -Pending {
$command = 'Invoke-RestMethod https://prod.idrix.eu/secure/'
$result = ExecuteWebCommand -command $command
$result.Output | Should Match ([regex]::Escape('Error: No SSL client certificate presented'))
It "Verifies Invoke-RestMethod Certificate Authentication Fails without -Certificate" {
$uri = Get-WebListenerUrl -Https -Test 'Cert'
$result = Invoke-RestMethod -Uri $uri -SkipCertificateCheck
$result.Status | Should Be 'FAILED'
}
# Test pending creation of native test solution
# https://github.com/PowerShell/PowerShell/issues/4609
It "Verifies Invoke-RestMethod Certificate Authentication Successful with -Certificate" -Pending {
$Certificate = GetSelfSignedCert
$command = 'Invoke-RestMethod https://prod.idrix.eu/secure/ -Certificate $Certificate'
$result = ExecuteWebCommand -command $command
# Test skipped on macOS pending support for Client Certificate Authentication
# https://github.com/PowerShell/PowerShell/issues/4650
It "Verifies Invoke-RestMethod Certificate Authentication Successful with -Certificate" -skip:$IsOSX {
$uri = Get-WebListenerUrl -Https -Test 'Cert'
$certificate = Get-WebListenerClientCertificate
$result = Invoke-RestMethod -uri $uri -Certificate $certificate -SkipCertificateCheck
$result.Output | Should Match ([regex]::Escape('SSL Authentication OK!'))
$result.Status | Should Be 'OK'
$result.Thumbprint | Should Be $certificate.Thumbprint
}
#endregion Certificate Authentication Tests
#endregion Client Certificate Authentication
BeforeEach {
if ($env:http_proxy) {

Binary file not shown.

View file

@ -0,0 +1,17 @@
# 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.
# Running WebListener
```powershell
Import-Module .\build.psm1
Publish-PSTestTools
$Listener = Start-WebListener -HttpPort 8083 -HttpsPort 8084
```
# Stopping WebListener
```powershell
Stop-WebListener
```

Binary file not shown.

View file

@ -0,0 +1,14 @@
@{
ModuleVersion = '1.0.0'
GUID = '90572e25-3f15-49b0-8f25-fb717d3ef46a'
Author = 'Mark Kraus'
Description = 'An HTTP and HTTPS Listener for testing purposes'
RootModule = 'WebListener.psm1'
FunctionsToExport = @(
'Get-WebListener'
'Get-WebListenerClientCertificate'
'Get-WebListenerUrl'
'Start-WebListener'
'Stop-WebListener'
)
}

View file

@ -0,0 +1,135 @@
Class WebListener
{
[int]$HttpPort
[int]$HttpsPort
[System.Management.Automation.Job]$Job
WebListener () { }
[String] GetStatus()
{
return $This.Job.JobStateInfo.State
}
}
[WebListener]$WebListener
function Get-WebListener
{
[CmdletBinding(ConfirmImpact = 'Low')]
[OutputType([WebListener])]
param()
process
{
return [WebListener]$Script:WebListener
}
}
function Start-WebListener
{
[CmdletBinding(ConfirmImpact = 'Low')]
[OutputType([WebListener])]
param
(
[ValidateRange(1,65535)]
[int]$HttpPort = 8083,
[ValidateRange(1,65535)]
[int]$HttpsPort = 8084
)
process
{
$runningListener = Get-WebListener
if ($null -ne $runningListener -and $runningListener.GetStatus() -eq 'Running')
{
return $runningListener
}
$initTimeoutSeconds = 15
$appDll = 'WebListener.dll'
$serverPfx = 'ServerCert.pfx'
$serverPfxPassword = 'password'
$initCompleteMessage = 'Now listening on'
$serverPfxPath = Join-Path $MyInvocation.MyCommand.Module.ModuleBase $serverPfx
$timeOut = (get-date).AddSeconds($initTimeoutSeconds)
$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
}
$Script:WebListener = [WebListener]@{
HttpPort = $HttpPort
HttpsPort = $HttpsPort
Job = $Job
}
# Wait until the app is running or until the initTimeoutSeconds have been reached
do
{
Start-Sleep -Milliseconds 100
$initStatus = $Job.ChildJobs[0].Output | Out-String
$isRunning = $initStatus -match $initCompleteMessage
}
while (-not $isRunning -and (get-date) -lt $timeOut)
if (-not $isRunning)
{
$Job | Stop-Job -PassThru | Receive-Job
$Job | Remove-Job
throw 'WebListener did not start before the timeout was reached.'
}
return $Script:WebListener
}
}
function Stop-WebListener
{
[CmdletBinding(ConfirmImpact = 'Low')]
[OutputType([Void])]
param()
process
{
$Script:WebListener.job | Stop-Job -PassThru | Remove-Job
$Script:WebListener = $null
}
}
function Get-WebListenerClientCertificate {
[CmdletBinding(ConfirmImpact = 'Low')]
[OutputType([System.Security.Cryptography.X509Certificates.X509Certificate2])]
param()
process {
$pfxPath = Join-Path $MyInvocation.MyCommand.Module.ModuleBase 'ClientCert.pfx'
[System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pfxPath,'password')
}
}
function Get-WebListenerUrl {
[CmdletBinding()]
[OutputType([Uri])]
param (
[switch]$Https,
[String]$Test
)
process {
$runningListener = Get-WebListener
if ($null -eq $runningListener -or $runningListener.GetStatus() -ne 'Running')
{
return $null
}
$Uri = [System.UriBuilder]::new()
$Uri.Host = 'localhost'
$Uri.Port = $runningListener.HttpPort
$Uri.Scheme = 'Http'
if ($Https.IsPresent)
{
$Uri.Port = $runningListener.HttpsPort
$Uri.Scheme = 'Https'
}
$Uri.Path = $Test
return [Uri]$Uri.ToString()
}
}

View file

@ -0,0 +1,43 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using mvc.Models;
namespace mvc.Controllers
{
public class CertController : Controller
{
public JsonResult Index()
{
// X509Certificate2 objects do not serialize as JSON. Create a HashTable instead
Hashtable output = new Hashtable
{
{"Status", "FAILED"}
};
if (null != HttpContext.Connection.ClientCertificate)
{
output = new Hashtable
{
{"Status" , "OK"},
{"Thumbprint" , HttpContext.Connection.ClientCertificate.Thumbprint},
{"Subject" , HttpContext.Connection.ClientCertificate.Subject},
{"SubjectName" , HttpContext.Connection.ClientCertificate.SubjectName.Name},
{"Issuer" , HttpContext.Connection.ClientCertificate.Issuer},
{"IssuerName" , HttpContext.Connection.ClientCertificate.IssuerName.Name},
{"NotAfter" , HttpContext.Connection.ClientCertificate.NotAfter},
{"NotBefore" , HttpContext.Connection.ClientCertificate.NotBefore}
};
}
return Json(output);
}
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using mvc.Models;
namespace mvc.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace mvc.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace mvc
{
public class Program
{
public static void Main(string[] args)
{
if (args.Count() != 4)
{
System.Console.WriteLine("Required: <CertificatePath> <CertificatePassword> <HTTPPortNumber> <HTTPSPortNumber>");
Environment.Exit(1);
}
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder()
.UseStartup<Startup>().UseKestrel(options =>
{
options.Listen(IPAddress.Loopback, int.Parse(args[2]));
options.Listen(IPAddress.Loopback, int.Parse(args[3]), listenOptions =>
{
var certificate = new X509Certificate2(args[0], args[1]);
HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions();
httpsOption.SslProtocols = SslProtocols.Tls12;
httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => {return true;};
httpsOption.CheckCertificateRevocation = false;
httpsOption.ServerCertificate = certificate;
listenOptions.UseHttps(httpsOption);
});
})
.Build();
}
}

View file

@ -0,0 +1,56 @@
# WebListener App
ASP.NET Core 2.0 app for testing HTTP and HTTPS Requests. The default page will return a list of available tests.
# Run with `dotnet`
```
dotnet restore
dotnet publish --output bin --configuration Release
cd bin
dotnet WebListener.dll ServerCert.pfx password 8083 8084
```
The test site can then be accessed via `http://localhost:8083/` or `https://localhost:8084/`.
The `WebListener.dll` takes 4 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
# Run With WebListener Module
```powershell
Import-Module .\build.psm1
Publish-PSTestTools
$Listener = Start-WebListener -HttpPort 8083 -HttpsPort 8084
```
# Tests
## /Cert/
Returns a JSON object containing the details of the Client Certificate if one is provided in the request.
Response when certificate is provided in request:
```json
{
"Status": "OK",
"IssuerName": "E=randd@adatum.com, CN=adatum.com, OU=R&D, O=A. Datum Corporation, L=Redmond, S=Washington, C=US",
"SubjectName": "E=randd@adatum.com, CN=adatum.com, OU=R&D, O=A. Datum Corporation, L=Redmond, S=Washington, C=US",
"NotAfter": "2044-12-26T12:16:46-06:00",
"Issuer": "E=randd@adatum.com, CN=adatum.com, OU=R&D, O=A. Datum Corporation, L=Redmond, S=Washington, C=US",
"Subject": "E=randd@adatum.com, CN=adatum.com, OU=R&D, O=A. Datum Corporation, L=Redmond, S=Washington, C=US",
"NotBefore": "2017-08-10T13:16:46-05:00",
"Thumbprint": "2DECF1348FF21B780F45D316A039B5EB4C6312F7"
}
```
Response when certificate is not provided in request:
```json
{
"Status": "FAILED"
}
```

View file

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace mvc
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

View file

@ -0,0 +1,5 @@
<h1>Available Tests</h1>
<ul>
<li><a href="/">/</a> - This page</li>
<li><a href="/Cert/">/Cert/</a> - Client Certificate Details</li>
</ul>

View file

@ -0,0 +1,22 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
</p>

View file

@ -0,0 +1 @@
@RenderBody()

View file

@ -0,0 +1,3 @@
@using mvc
@using mvc.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View file

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View file

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<Description>A simple ASP.NET Core 2.0 MVC app to provide an HTTP and HTTPS server for testing.</Description>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.0" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View file

@ -0,0 +1,8 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}