diff --git a/build.psm1 b/build.psm1 index e8e2f6c9ef..9296d98b0e 100644 --- a/build.psm1 +++ b/build.psm1 @@ -5,6 +5,10 @@ # On Windows paths is separated by semicolon $script:TestModulePathSeparator = [System.IO.Path]::PathSeparator +# Path to a directory to store modules we temporarily need to test PowerShell +$script:TestModuleDirPath = Join-Path ([System.IO.Path]::GetTempPath()) 'PwshTestModules' +$null = New-Item -Force -ItemType Directory -Path $script:TestModuleDirPath + $dotnetCLIChannel = "release" $dotnetCLIRequiredVersion = $(Get-Content $PSScriptRoot/global.json | ConvertFrom-Json).Sdk.Version @@ -1035,6 +1039,9 @@ function Publish-PSTestTools { Pop-Location } } + + # Get the SelfSignedCertificate module so the web listener can use it + Save-Module -Name SelfSignedCertificate -Path $script:TestModuleDirPath -Repository "PSGallery" -MinimumVersion '0.0.2' -Force -Confirm:$false } function Get-ExperimentalFeatureTests { @@ -1139,7 +1146,8 @@ function Start-PSPester { } # Autoload (in subprocess) temporary modules used in our tests - $command += '$env:PSModulePath = '+"'$TestModulePath$TestModulePathSeparator'" + '+$($env:PSModulePath);' + $newPathFragment = $TestModulePath + $TestModulePathSeparator + $script:TestModuleDirPath + $TestModulePathSeparator + $command += '$env:PSModulePath = '+"'$newPathFragment'" + '+$env:PSModulePath;' # Windows needs the execution policy adjusted if ($Environment.IsWindows) { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 index 0865032b85..7dc805be35 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 @@ -28,9 +28,8 @@ Describe "CmsMessage cmdlets and Get-PfxCertificate basic tests" -Tags "CI" { } It "Verify Get-PfxCertificate right password" { - #[SuppressMessage("Microsoft.Security", "CS002:SecretInNextLine", Justification="Test secret.")] - $pass = ConvertTo-SecureString "password" -AsPlainText -Force - $cert = Get-PfxCertificate $protectedCertLocation -Password $pass + $password = Get-CertificatePassword + $cert = Get-PfxCertificate $protectedCertLocation -Password $password $cert.Subject | Should -Be "CN=localhost" } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 index a58c3ba83b..f390082193 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 @@ -68,6 +68,21 @@ OksttXT1kXf+aez9EzDlsgQU4ck78h0WTy01zHLwSKNWK4wFFQM= return $certLocation } +Function New-CertificatePassword +{ + $script:protectedCertPassword = ConvertTo-SecureString -Force -AsPlainText (New-RandomHexString) + return $script:protectedCertPassword +} + +Function Get-CertificatePassword +{ + if ($null -eq $script:protectedCertPassword) + { + throw [System.InvalidOperationException] "`$script:protectedCertPassword is not defined. Call New-CertificatePassword first." + } + return $script:protectedCertPassword +} + Function New-ProtectedCertificate { <# @@ -78,7 +93,15 @@ Function New-ProtectedCertificate Password: "password" #> - $certLocation = ".\test\tools\Modules\WebListener\ServerCert.pfx" + $certLocation = Join-Path ([System.IO.Path]::GetTempPath()) 'protectedCert.pfx' + + $password = New-CertificatePassword + + $null = SelfSignedCertificate\New-SelfSignedCertificate ` + -CommonName 'localhost' ` + -OutCertPath $certLocation ` + -Passphrase $password ` + -Force return $certLocation } diff --git a/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 b/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 index 6bf4370f88..0dea69c59d 100644 --- a/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 +++ b/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 @@ -21,6 +21,7 @@ FunctionsToExport = @( 'Disable-Testhook' 'Enable-Testhook' 'Get-RandomFileName' + 'New-RandomHexString' 'Send-VstsLogFile' 'Set-TesthookResult' 'Start-NativeExecution' diff --git a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 index 3a25101f6f..f3ca73a1fe 100644 --- a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 +++ b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 @@ -313,3 +313,13 @@ function Start-NativeExecution $script:ErrorActionPreference = $backupEAP } } + +# Creates a new random hex string for use with things like test certificate passwords +function New-RandomHexString +{ + param([int]$Length = 10) + + $random = [Random]::new() + return ((1..$Length).ForEach{ '{0:x}' -f $random.Next(0xf) }) -join '' +} + diff --git a/test/tools/Modules/WebListener/ClientCert.pfx b/test/tools/Modules/WebListener/ClientCert.pfx deleted file mode 100644 index 3303943fb7..0000000000 Binary files a/test/tools/Modules/WebListener/ClientCert.pfx and /dev/null differ diff --git a/test/tools/Modules/WebListener/README.md b/test/tools/Modules/WebListener/README.md index 0bb257d8f0..43222d7519 100644 --- a/test/tools/Modules/WebListener/README.md +++ b/test/tools/Modules/WebListener/README.md @@ -1,6 +1,19 @@ # 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. The port used for `-HttpsPort` will use TLS 1.2. +A PowerShell module for managing the WebListener App. + +When the WebListener is started from this module, +it will automatically generate two fresh certificates, +`ClientCert.pfx` and `ServerCert.pfx` using the `SelfSignedCertificate` module. + +The generated Self-Signed Certificate `ServerCert.pfx` has a randomly generated password +and is issued for the Client and Server Authentication key usages. +This certificate is used by the WebListener App for SSL/TLS. + +The generated Self-Signed Certificate `ClientCert.pfx` has a randomly generated password +and is not 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 diff --git a/test/tools/Modules/WebListener/ServerCert.pfx b/test/tools/Modules/WebListener/ServerCert.pfx deleted file mode 100644 index dd1cf6a9a8..0000000000 Binary files a/test/tools/Modules/WebListener/ServerCert.pfx and /dev/null differ diff --git a/test/tools/Modules/WebListener/WebListener.psd1 b/test/tools/Modules/WebListener/WebListener.psd1 index 91021b45e5..5d90958c08 100644 --- a/test/tools/Modules/WebListener/WebListener.psd1 +++ b/test/tools/Modules/WebListener/WebListener.psd1 @@ -4,6 +4,9 @@ Author = 'Mark Kraus' Description = 'An HTTP and HTTPS Listener for testing purposes' RootModule = 'WebListener.psm1' + RequiredModules = @( + 'SelfSignedCertificate' + ) FunctionsToExport = @( 'Get-WebListener' 'Get-WebListenerClientCertificate' diff --git a/test/tools/Modules/WebListener/WebListener.psm1 b/test/tools/Modules/WebListener/WebListener.psm1 index c58db23ada..d400c768ef 100644 --- a/test/tools/Modules/WebListener/WebListener.psm1 +++ b/test/tools/Modules/WebListener/WebListener.psm1 @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. + Class WebListener { [int]$HttpPort @@ -18,6 +19,67 @@ Class WebListener [WebListener]$WebListener +function New-ClientCertificate +{ + param([string]$CertificatePath, [string]$Password) + + if ($Password) + { + $Passphrase = ConvertTo-SecureString -Force -AsPlainText $Password + } + + $distinguishedName = @{ + CN = 'adatum.com' + C = 'US' + S = 'Washington' + L = 'Redmond' + O = 'A. Datum Corporation' + OU = 'R&D' + E = 'randd@adatum.com' + } + + $certificateParameters = @{ + OutCertPath = $CertificatePath + StartDate = [datetime]::Now.Subtract([timespan]::FromDays(30)) + Duration = [timespan]::FromDays(365) + Passphrase = $Passphrase + CertificateFormat = 'Pfx' + KeyLength = 4096 + ForCertificateAuthority = $true + Force = $true + } + $distinguishedName + + SelfSignedCertificate\New-SelfSignedCertificate @certificateParameters +} + +function New-ServerCertificate +{ + param([string]$CertificatePath, [string]$Password) + + if ($Password) + { + $Passphrase = ConvertTo-SecureString -Force -AsPlainText $Password + } + + $distinguishedName = @{ + CN = 'localhost' + } + + $certificateParameters = @{ + OutCertPath = $CertificatePath + StartDate = [datetime]::Now.Subtract([timespan]::FromDays(30)) + Duration = [timespan]::FromDays(1000) + Passphrase = $Passphrase + KeyUsage = 'DigitalSignature','KeyEncipherment' + EnhancedKeyUsage = 'ServerAuthentication','ClientAuthentication' + CertificateFormat = 'Pfx' + KeyLength = 2048 + Force = $true + } + $distinguishedName + + SelfSignedCertificate\New-SelfSignedCertificate @certificateParameters +} + function Get-WebListener { [CmdletBinding(ConfirmImpact = 'Low')] @@ -60,11 +122,17 @@ function Start-WebListener $initTimeoutSeconds = 15 $appDll = 'WebListener.dll' $serverPfx = 'ServerCert.pfx' - $serverPfxPassword = 'password' + $serverPfxPassword = New-RandomHexString + $clientPfx = 'ClientCert.pfx' $initCompleteMessage = 'Now listening on' $sleepMilliseconds = 100 - $serverPfxPath = Join-Path $MyInvocation.MyCommand.Module.ModuleBase $serverPfx + $serverPfxPath = Join-Path ([System.IO.Path]::GetTempPath()) $serverPfx + $Script:ClientPfxPath = Join-Path ([System.IO.Path]::GetTempPath()) $clientPfx + $Script:ClientPfxPassword = New-RandomHexString + New-ServerCertificate -CertificatePath $serverPfxPath -Password $serverPfxPassword + New-ClientCertificate -CertificatePath $Script:ClientPfxPath -Password $Script:ClientPfxPassword + $Job = Start-Job { $path = Split-Path -parent (get-command WebListener).Path -Verbose Push-Location $path -Verbose @@ -130,8 +198,7 @@ function Get-WebListenerClientCertificate { [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') + [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($Script:ClientPfxPath, $Script:ClientPfxPassword) } } diff --git a/test/tools/WebListener/README.md b/test/tools/WebListener/README.md index 705bf62b05..2d270f55a3 100644 --- a/test/tools/WebListener/README.md +++ b/test/tools/WebListener/README.md @@ -11,6 +11,10 @@ cd bin dotnet WebListener.dll ServerCert.pfx password 8083 8084 8085 8086 ``` +**NOTE**: `ServerCert.pfx` is no longer a static asset +and you will need to create your own certificate for this purpose. +The `SelfSignedCertificate` module in the PowerShell Gallery provides this functionality. + 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 6 arguments: