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:
parent
bd42fe95b4
commit
3b2d169c5e
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 = @'
|
||||
MIIQwQIBAzCCEIcGCSqGSIb3DQEHAaCCEHgEghB0MIIQcDCCBqcGCSqGSIb3DQEHBqCCBpgwggaU
|
||||
AgEAMIIGjQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIfGLU4iludG8CAggAgIIGYA2q8iyw
|
||||
roL/uN2zcGKynxniSCwn7nCRi5zPs8f7l/ar1YvNjRaPmCZstGpfy/XVHddgPzUp1C8Jj999Z9DX
|
||||
XtWILi4D53845NLHnDz8hDsgsyCGkp7GLa8Mi9Mf1dB3BTStJ30nz+qAbkXoedCWnfkkFT7N/g8j
|
||||
K+yxvikbDzAB5PLgwACVX4KWqMVoU0VWhK8XyQe2FK05gx2ek789WfX924FfsZ7lDkncMRU0gwk8
|
||||
W+PX5qPgvi1k5+0H3afiykS53Of8+SWjJQr6dWCgErYt0SsfiUIkFIgzVR6xJI4kSxYMIX4W7Hjr
|
||||
KXXID+51MTiLvC/QBa0cjWIqKFz/ru//P8vEjPH1CxNf/P7q2rMV0Sr2lhH50xp+Tk1M+75BCMZ5
|
||||
TroimUciF3HT01MUBxPnQt8Ad9QDBahlpJQXCckVXIONvw/80c0eY/5qYPhuKt3fZmOdBIUcjS35
|
||||
xGpPlioTfjzRdTEZRZEv6pgtmtgrI2JVqwxwKooFHI5qmIQDGFtvwEFtb0OIl6WoKNMFTF0OWIRc
|
||||
9E9Zjjbth4m9pCbKdw/bRg5DDwMzTxQFT5CKigPojGCQjUZinUHSEHOd5ttuBy2wbJA5z43IHE2s
|
||||
chEhGf9YRh3QIjWW38Bn+K1l8ev+2kbvVJqaUFI7sy0NJ4O2I1rCEJhDmmU1ib6OwHX4ONP/qwtg
|
||||
weJV2+qvtwt0P/Dfhs2E9/lJu4BvsOXUmVPjtVJbzA2DAAvUbYWyQ0nbUL7fGVHqMN3W+yPRGWlY
|
||||
aMLhhgE5+xU/m5yv43NexWYKHigpKwg5Yhx1dTi+vrgECXe8QoENgWVVC5zBANcr2qONE6BHAJMm
|
||||
Fhx9EhvaRIndTo4a2Pq5DOMfevNexsJwcnFdcre/CuzmN7bLkzjumA/a9yOYhMMSfIpapZE0KDk1
|
||||
+uQIXQCCzyicyNYDtgKUNK1DYP+quw02NAe3csR2YiwDrKqzsA0hbIrsmW6umz96KvIiAtyUhCEk
|
||||
4MrQrrv3cA6nYPljeIM5snUmaO2izTcVUFpoGvmJvWtkVRx17QeFaJgiUF4lbnNeVgJjLDe3w3gm
|
||||
3IkziXYHwK2s+Hn19QCio5tyHtmsXDVVpghAMeo3HfZpDQP1pydCw4mnSTtuWE+ebe/nLNYiSdEp
|
||||
oU7LGdMjUGWsCQgNhJVjEfCdyBeBzAAJqSd98yN4jGdztx0ksCqU7EcOMtMzxu4pHIvKxhdi6LVN
|
||||
aTeZN3W4rsaAg3dfI+touOmhcUEvbv/6w6PRd5f7VwIbr+0K7R1Tu13Wok8OLrpUGt5ijSiYpdQx
|
||||
pYPBZ3OsFcfYylb9BrSmQGHmfXv0Gm4DP/VPifB1l12GEKTshD5nVoKOic7OJPzcY7385rY+UV7v
|
||||
KXthpWTI+T64ewZ8fAf0x48ATmhIDm/HhUV+vrVfZCc7lk5v2BO+EGm+WjcmUbNMN/FwtnGDR+rq
|
||||
ivi1XdSOKfanUw4wCSfHJ1NZgCmPGQ14QUtbhpnlE9C0MkKvHNz8i8yXGLIdGpicqsI5m6xqwJfk
|
||||
a1DIP2mCrp0wH8zORG+zqNzMBcZ00FXyPBOcqmdK+V2X35azgldmryu1lyc8SJtwWfv6v5/8Ebzb
|
||||
ObbwQA+Cnj+H7wfuhmo/6CBoSP5bhhgUBNF6fkoFtf4JMis+1TwOT/WrUgVo6jA0uyYEE7bXBjvi
|
||||
eByDXT/nm30YlJ3FOwvjJXuXJM5e1TqHM8s8P5yHOE5ZsGxEc1zD48hXk0+LImou1hgYHAWggxrK
|
||||
NBdeF9tpmkUIJQfQrTg6L4fw6Xn505tP4Q6kGyxRAVwASkO9ty2NoBuCExB8mzKsrFPiDzJBeEBX
|
||||
Ai90BFu9zu9fHY9WfC/SIfb0MYL5Iw+S13OScV/iJRnTFVMxm+RxT0EyYKPl1w4LbtItYIQu60Yr
|
||||
YVt3Kz6fKMXR9qlEdNgiLkqO10GzAnbR96876srHD7iIepvGuJFT67AwpP/nnvSre5ltzG4mcz6B
|
||||
s18cOyUOcuKT5muAS4QCyQnDm4oiuRjK73fmup8ssFVF5DahsuWCA5J7KFppl4Uecug+4y18ssHs
|
||||
KT71+rQC1ghZwOOTdHi4bOIzO+RHUKxp49Cb55dYtBNaPC5uxfC+YhAoeJYOqZjsZFDe+alplH9Q
|
||||
v22+mZ3xdFI3+v3thNZ00tt/LXXGOXsdOeyEP8zZHTCCCcEGCSqGSIb3DQEHAaCCCbIEggmuMIIJ
|
||||
qjCCCaYGCyqGSIb3DQEMCgECoIIJbjCCCWowHAYKKoZIhvcNAQwBAzAOBAhyG7OVfzoYtgICCAAE
|
||||
gglIRMB+P1KxL/yawhmV0d+kd5sg6rJuOi0Zf4h/nn4ehaVRBFY8ZTRao39SCmfzxyRen5z22oqh
|
||||
gV9rA2bC73KC3Z0mApZQCoU1gYXOXPTMmeuHoF16a42KB/gOVMxiOZC+5spDjiBlGyOZgG3cwtvq
|
||||
KwRTGGy/XtWOSLKZyl0hTkrX7lagbp5kourrBhuHfEBYtr5BEP/9PGNFcV15bKvtLorx4VixbR3W
|
||||
OjfE6ziHVThDxKIDfqtirZsjCiUqQ6uH3pHhjAddW6zm1pr+hpQoda0D6mNu83tzFuZrGJJ+sxAt
|
||||
sApUc6u+U5zT1k5pd+e+1qttz7U/OUXA1m3noT15b7Krmh02kgn65jOi7pU2p0dOZniF1/K71oQD
|
||||
hutZYar9SmFPkNTv3nA+iTEgJqiVx7JH26X2qGcgubo3rpKRE2W8BwVcDvQJb7BWxYubZ4QS9zal
|
||||
qy2YYgDZlN3RW4N3Zrs0ipDm/d3LWHNlLQZ2ONdTqt7n964wtGdUgq+rhwtzh5wMCmOSnF707jaU
|
||||
KfsNUqKWlMM5+v2qUzUr4eiVgyF4LTGMawGxRqynNTWmzp/EsRNOTNRWMoyEvj2KQ4Sb/EddPYiH
|
||||
8W0Oa9RgTQWE6dwm89p7stpGv96deqXw5H7z5ELW2W6qFIiHRaZ/o+QjS0BQyKaWsBHVdYkApjnU
|
||||
3kO2/pLHNB+V4fNd+b19hmOhUnU+n2N4qOkTdChl+1km7UDtUXvBqCTfXpZGohjYyGPDGglZQUlC
|
||||
YU8fyzooaN7CaT6084Rdzp0Zx69doEHlFe3DHZ6fYhCuS9wTiGdzz1ay8dyE280j2aK0JY0qqXev
|
||||
ppBfM/IZFyltI4R5rCxSxc7ztcooNynos5/QX1RjQloaSM+rcCAxDPdH1LAJ9ENppHNlbspocERI
|
||||
FSP2GMjvOr/x4F5XS1mGTVL2wKL2VAtzcU9Fg1YiwWpw+i4FirOEbc6FItT8gfX19yxu9MBk1VsQ
|
||||
5H4xejBTOSlCvt7gA4W59ly3b6HS1ZEvC+TqFqsetRq6jsjI4XOMNp7DJzoSn/qjHPRF2FD15jw6
|
||||
VF8buIXUezxlgd5sgwzSvK9znSK2lj4KmbBMbm2TnQmEnRanxYZN1dId1cCbB0oVkOv01tLCHayX
|
||||
ENYwNueHJ6Y3Qp82+Ervtr5+iyO7O/8BmfuHzIpAirQqNah9OiP377oLJtvsJHuHhAd5+xMF5PVR
|
||||
lq8ufzdwjekidgM2KhDX37s2Xn25gp+yuG+mgA8YiDGX4JIGsZ4u+ZRD1As4/SbrbqlCISq0NCXz
|
||||
yOZXZK+BQjPAc3jFrVmLnVeTqgX7qPG4tLnHjEM6iXdupeREcoer2nmkTMR0cxnjlgOUiiWbIREm
|
||||
hqB/+qgWH5zQVnifZNxFEbKCTnS6bJO5Rla51RKOX7YY/b3mJV7dTUB8mj5RvO0a4f0khmJHeELx
|
||||
wpRImawkJd3xOwpOfQBO0As/LTxV0dzz/NyPZkP8hzXW18Js2i9HW7rX2oobZEtM/1jx5IMs7/Ql
|
||||
gUoH6rCA/4Y+3BLQphK5/B/j4Kqb7AkuGhMYxefYuLdicxIhAYwGpoPrkUpYX5sh4UlWn6lDByx9
|
||||
S6NTtdq9wzjEc6d7LLrQhrEyIppaerESfG/gcyz7odCN3PxxZh4xAM+uNtCRBxRfI51qEIw8aNxJ
|
||||
HxhjNuCDxmmG2LC4G7j1ry3kc6zkU5yInp2WuGife2dRaNQPeATAUqTlJY343oh0LY56uZ75wBUK
|
||||
8Q2zJ0I25CujnY+SnCpz1thdIlSXLsRC+/AQ1XZSM2i3koiocqZZKFZJWEm2ggNjT8OuUly1WMkN
|
||||
9dhaTsbAoHBJJ3hPlaEG+EXhyhtTcEjsWu6TbeP8yKt6YeyAwFAsDl/ONSfc/xnVuoyBHAswcrp2
|
||||
/FFkYn5w9kD/wU4RwaXSmFEtbVtK9jPgwVhYjhuGiWXoo+JM7Ve6mnMGjs+fxoDv4DQ5+GT+U+29
|
||||
Ip2BKYQDdzf2IiGgCkTMa2X1Zc/KcL5AuM47HnlcnsXRF6DiiVpCgqRezBhcxAsYkRgV8YVCsiWH
|
||||
sqA3Xzd0f/aVhZgus2yBHHIKuLVR8xkjjPzIH+IJZRLD7J+V3KtuwgmkNrAMDNUkCWGet52CrTs/
|
||||
6/mESQv+3aM2nlplWVAEYAMlGt8QlIq0ZHtcdOTA+60RNfxIAvqQ0Go8gbJtmTc/XCupzuXQUgmR
|
||||
rr6z+yu9cdT2JfpgDC4coJs4KR3/1MXr80FErIsQ6/ECMdpr9JUWwKG1gujwulyDXJZDjHK9Nj1q
|
||||
JcBXAyeuMqNVw94SOUllsvQjQUr0SwzFaVMwon5YIvlMbW32JIMa2MvzsSm7/wBsUL8yBVuuOIcE
|
||||
XsgXLXscPj16IxQy6x6gflKDdtIu9fiy/bs0DccmQU1uT7eaFOd5BqL+ijcJTTt9SU6wpv+E0uRt
|
||||
C9JfoZ8F09C6b8Rp/8bXpaSahW5Omo5v0shRor0cJrskDdGESn4cLPUoFPX94LTmpDz9sH2ETQAh
|
||||
w6Laka1o/i17qaYr684nc9Xfw5lBqoAz0PquAB4xq38jKem0dxUxt4g62Vqpomd1wSBM7lrAlbep
|
||||
6gTJQHJ5cfbdXhnh71CF0SXnwm0zV7mhKIYAdz3H6SVOguiyjSNsyinNkvSq5+e5ip4Qt49jnMBI
|
||||
/7SRk3BgkrEm0RKAV4aF7LwjwuoVOOfrzZ5paAMXFu6b9tUW4lAdv65xOyaDNWpjKb2WtXE3KFRt
|
||||
mVqr+QCh1pMTDsLhD9LNQ0jH0Xvq5mnDmHc3D8YTsJJhxedJZIlLMCNeRF9/9vPUt52NyA2pKX4U
|
||||
7eP+BACyJhfK3sfMF+q5GGi77Q6NWk08Us7fn8Z48sNm8XN5A73Hbx+TEhaQUbb/skEXEOwNDShB
|
||||
wYtsd+Cloip4xKdN0tgEFgahkoKYNFtgJyuOFAEEPanol1PET9otbv8Gmqpn0tXQyEfbSZ1ch4Uy
|
||||
otpJ40ETB3pclTFk3ARupg84CxveuXeI0SdA3sNe4DlTVA4cZ4Y8vMtsFJStPMU0ca15L9Ii2yVr
|
||||
YJX20neZhIGnsT36bd8e38Mj+7hrVhvV/G2x0aS+lB2lD0HIvRNW02+UxRsZ+S+TtBXnlTHFLAm5
|
||||
+IBnXcKWBVnaEvBjwyMIo/bI8C0fhFOt+W88XyoIuPeRYSKVRmg2vjyqMSUwIwYJKoZIhvcNAQkV
|
||||
MRYEFC3s8TSP8ht4D0XTFqA5tetMYxL3MDEwITAJBgUrDgMCGgUABBS39FfrA3N6RIvd2k2XO1rY
|
||||
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) {
|
||||
|
BIN
test/tools/Modules/WebListener/ClientCert.pfx
Normal file
BIN
test/tools/Modules/WebListener/ClientCert.pfx
Normal file
Binary file not shown.
17
test/tools/Modules/WebListener/README.md
Normal file
17
test/tools/Modules/WebListener/README.md
Normal 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
|
||||
```
|
BIN
test/tools/Modules/WebListener/ServerCert.pfx
Normal file
BIN
test/tools/Modules/WebListener/ServerCert.pfx
Normal file
Binary file not shown.
14
test/tools/Modules/WebListener/WebListener.psd1
Normal file
14
test/tools/Modules/WebListener/WebListener.psd1
Normal 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'
|
||||
)
|
||||
}
|
135
test/tools/Modules/WebListener/WebListener.psm1
Normal file
135
test/tools/Modules/WebListener/WebListener.psm1
Normal 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()
|
||||
}
|
||||
}
|
43
test/tools/WebListener/Controllers/CertController.cs
Normal file
43
test/tools/WebListener/Controllers/CertController.cs
Normal 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 });
|
||||
}
|
||||
}
|
||||
}
|
22
test/tools/WebListener/Controllers/HomeController.cs
Normal file
22
test/tools/WebListener/Controllers/HomeController.cs
Normal 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 });
|
||||
}
|
||||
}
|
||||
}
|
11
test/tools/WebListener/Models/ErrorViewModel.cs
Normal file
11
test/tools/WebListener/Models/ErrorViewModel.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace mvc.Models
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
}
|
||||
}
|
50
test/tools/WebListener/Program.cs
Normal file
50
test/tools/WebListener/Program.cs
Normal 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();
|
||||
}
|
||||
}
|
56
test/tools/WebListener/README.md
Normal file
56
test/tools/WebListener/README.md
Normal 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"
|
||||
}
|
||||
```
|
49
test/tools/WebListener/Startup.cs
Normal file
49
test/tools/WebListener/Startup.cs
Normal 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?}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
5
test/tools/WebListener/Views/Home/Index.cshtml
Normal file
5
test/tools/WebListener/Views/Home/Index.cshtml
Normal 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>
|
22
test/tools/WebListener/Views/Shared/Error.cshtml
Normal file
22
test/tools/WebListener/Views/Shared/Error.cshtml
Normal 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>
|
1
test/tools/WebListener/Views/Shared/_Layout.cshtml
Normal file
1
test/tools/WebListener/Views/Shared/_Layout.cshtml
Normal file
@ -0,0 +1 @@
|
||||
@RenderBody()
|
3
test/tools/WebListener/Views/_ViewImports.cshtml
Normal file
3
test/tools/WebListener/Views/_ViewImports.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@using mvc
|
||||
@using mvc.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
3
test/tools/WebListener/Views/_ViewStart.cshtml
Normal file
3
test/tools/WebListener/Views/_ViewStart.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
16
test/tools/WebListener/WebListener.csproj
Normal file
16
test/tools/WebListener/WebListener.csproj
Normal 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>
|
10
test/tools/WebListener/appsettings.Development.json
Normal file
10
test/tools/WebListener/appsettings.Development.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
8
test/tools/WebListener/appsettings.json
Normal file
8
test/tools/WebListener/appsettings.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user