winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 57c22c5ff20657c7c19619b831b8885c87f619b8
parent d010e249786b3d4e95ae5aaef4aa52a7b9fb89b8
Author: Flor Chacón <14323496+florelis@users.noreply.github.com>
Date:   Thu, 22 Aug 2024 10:31:34 -0700

Generate test certificates in pipeline (#4747)

The tests use test certificates for codesigning and for HTTPS with the
localhost server. These test certificates were stored as secure files in
the pipelines.

This PR changes the test pipeline to generate the certificates on each
run so that we don't need to create new certificates when they expire,
or if we want to recreate the pipelines in a different ADO account.

* Added pipeline steps to create/install the test certificates for
codesigning and HTTPS. The scripts export the path and password (a GUID)
as pipeline variables for use in subsequent tasks
* Removed unnecessary BinSkim task (already included in official
pipelines)
* Removed unused certificates from the code (unrelated to the ones being
replaced)
* Updated hash tests that were using the existing certificates to use a
different file, as the cert is no longer constant
* Added collection of the logs from the localhost server and index
creation; this requires stopping the server after it is used

Diffstat:
M.github/actions/spelling/expect.txt | 3---
Mazure-pipelines.yml | 45+++++++++++++++++----------------------------
Msrc/AppInstallerCLIE2ETests/Constants.cs | 3---
Msrc/AppInstallerCLIE2ETests/HashCommand.cs | 8++++----
Msrc/AppInstallerCLIE2ETests/SetUpFixture.cs | 4----
Dsrc/AppInstallerCLIE2ETests/TestData/AppInstallerTest.cer | 0
Dsrc/AppInstallerCLIE2ETests/TestData/IndexPackageIntRoot.cer | 0
Msrc/AppInstallerCLIE2ETests/TestData/localsource.json | 3++-
Dsrc/LocalhostWebServer/InstallDevCert.ps1 | 16----------------
Msrc/LocalhostWebServer/Program.cs | 2+-
Msrc/LocalhostWebServer/Run-LocalhostWebServer.ps1 | 13++++++++++++-
Mtemplates/e2e-setup.yml | 55++++++++++++++++++++++++++++++++++++-------------------
12 files changed, 72 insertions(+), 80 deletions(-)

diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -30,7 +30,6 @@ appname appshutdown APPTERMINATION archs -argumentlist ARMNT arp arphelper @@ -390,7 +389,6 @@ Peet peetdev PEGI pfn -pfxpath pgp Pherson pid @@ -469,7 +467,6 @@ savepoint schematab Scm sddl -SECUREFILEPATH secureobject securestring seof diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -259,7 +259,8 @@ jobs: - template: templates/e2e-setup.yml parameters: sourceDir: $(Build.SourcesDirectory) - localhostWebServerArgs: '-BuildRoot $(artifactsDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(Agent.TempDirectory)\TestLocalIndex -LocalSourceJson $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\localsource.json -TestDataPath $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData -SourceCert $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\AppInstallerTest.cer -ExitBeforeRun' + localhostWebServerArgs: '-BuildRoot $(artifactsDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(Agent.TempDirectory)\TestLocalIndex -LocalSourceJson $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\localsource.json -TestDataPath $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData -ExitBeforeRun' + signingCertOutDir: $(artifactsDir)\E2ETests - task: CopyFiles@2 displayName: 'Copy TestLocalIndex' @@ -302,28 +303,6 @@ jobs: verbosity: 'Verbose' alertWarningLevel: 'High' - # Run BimSkim for all the binaries - - task: BinSkim@4 - displayName: 'Run BinSkim ' - inputs: - arguments: 'analyze - "$(buildOutDir)\AppInstallerCLI\winget.exe" - "$(buildOutDir)\WinGetUtil\WinGetUtil.dll" - "$(buildOutDir)\WindowsPackageManager\WindowsPackageManager.dll" - "$(buildOutDir)\Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.InProc.dll" - "$(Build.SourcesDirectory)\src\WinGetUtilInterop\bin\WinGetUtil*Interop.dll" - "$(buildOutDir)\UndockedRegFreeWinRT\winrtact.dll" - "$(buildOutDir)\Microsoft.WinGet.Client.Cmdlets\Microsoft.WinGet.Client*.dll" - "$(buildOutDir)\ConfigurationRemotingServer\ConfigurationRemoting*Server.dll" - "$(buildOutDir)\ConfigurationRemotingServer\ConfigurationRemoting*Server.exe" - "$(buildOutDir)\ConfigurationRemotingServer\Microsoft.Management.Configuration*.dll" - "$(buildOutDir)\Microsoft.Management.Configuration\Microsoft.Management.Configuration*.dll" - "$(buildOutDir)\Microsoft.Management.Configuration.OutOfProc\Microsoft.Management.Configuration*.dll" - --config default --recurse' - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@3 - displayName: 'Publish Security Analysis Logs' - # Test job runs tests using build artifacts - job: 'Test' @@ -431,7 +410,7 @@ jobs: - template: templates/e2e-setup.yml parameters: sourceDir: $(Build.SourcesDirectory) - localhostWebServerArgs: '-BuildRoot $(buildOutDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(buildOutDir)\E2ETests\TestLocalIndex -SourceCert $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\AppInstallerTest.cer' + localhostWebServerArgs: '-BuildRoot $(buildOutDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(buildOutDir)\E2ETests\TestLocalIndex -SourceCert $(buildOutDir)\E2ETests\TestSigningCert.cer' - template: templates/e2e-test.template.yml parameters: @@ -511,6 +490,10 @@ jobs: arguments: '-TargetLocation $(artifactsDir)\ConfigOOPTestsLog' condition: succeededOrFailed() + - powershell: Get-Process LocalhostWebServer | Stop-Process + displayName: Stop LocalhostWebServer + condition: succeededOrFailed() + - task: PublishPipelineArtifact@1 displayName: Publish Pipeline Artifacts inputs: @@ -524,6 +507,8 @@ jobs: timeoutInMinutes: 120 dependsOn: 'Build' condition: succeeded('Build') + variables: + buildOutDir: $(Pipeline.Workspace)\Build.x64Release steps: - task: DownloadPipelineArtifact@2 @@ -532,7 +517,7 @@ jobs: - task: CopyFiles@2 displayName: 'Copy x64 PowerShell Binaries to Output' inputs: - SourceFolder: '$(Pipeline.Workspace)\Build.x64release\PowerShell' + SourceFolder: '$(buildOutDir)\PowerShell' Contents: '**\*' TargetFolder: '$(Build.ArtifactStagingDirectory)' @@ -566,14 +551,14 @@ jobs: targetType: 'inline' script: | Get-ChildItem AppxPackages\AppInstallerCLIPackage_0.0.2.0_Test\Dependencies\x64 -Filter *.appx | %{ Add-AppxPackage $_.FullName } - workingDirectory: $(Pipeline.Workspace)\Build.x64release\ + workingDirectory: $(buildOutDir) - template: templates/e2e-setup.yml parameters: sourceDir: $(Build.SourcesDirectory) - localhostWebServerArgs: '-BuildRoot $(Pipeline.Workspace)\Build.x64release\E2ETests\LocalhostWebServer -StaticFileRoot $(Pipeline.Workspace)\Build.x64release\E2ETests\TestLocalIndex -SourceCert $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\AppInstallerTest.cer' + localhostWebServerArgs: '-BuildRoot $(buildOutDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(buildOutDir)\E2ETests\TestLocalIndex -SourceCert $(buildOutDir)\E2ETests\TestSigningCert.cer' - - pwsh: .\RunTests.ps1 -testModulesPath $(Build.ArtifactStagingDirectory) -outputPath $(Pipeline.Workspace)\PesterTest -packageLayoutPath $(Pipeline.Workspace)\Build.x64release\DevPackage + - pwsh: .\RunTests.ps1 -testModulesPath $(Build.ArtifactStagingDirectory) -outputPath $(Pipeline.Workspace)\PesterTest -packageLayoutPath $(buildOutDir)\DevPackage workingDirectory: $(Build.SourcesDirectory)\src\PowerShell\tests\ displayName: Run PowerShell 7 Tests @@ -582,6 +567,10 @@ jobs: displayName: Run Windows PowerShell Tests condition: succeededOrFailed() + - powershell: Get-Process LocalhostWebServer | Stop-Process + displayName: Stop LocalhostWebServer + condition: succeededOrFailed() + - task: PublishTestResults@2 displayName: Publish Pester Test Results PowerShell 7 inputs: diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs @@ -45,9 +45,6 @@ namespace AppInstallerCLIE2ETests public const string TestSourceType = "Microsoft.PreIndexed.Package"; public const string TestSourceIdentifier = @"WingetE2E.Tests_8wekyb3d8bbwe"; - public const string AppInstallerTestCert = "AppInstallerTest.cer"; - public const string AppInstallerTestCertThumbprint = "d03e7a688b388b1edde8476a627531c49db88017"; - public const string AICLIPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; public const string AICLIPackageName = "WinGetDevCLI"; public const string AICLIPackagePublisherHash = "8wekyb3d8bbwe"; diff --git a/src/AppInstallerCLIE2ETests/HashCommand.cs b/src/AppInstallerCLIE2ETests/HashCommand.cs @@ -21,9 +21,9 @@ namespace AppInstallerCLIE2ETests [Test] public void HashFile() { - var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTest.cer")); + var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTestMsiInstaller.msi")); Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("9b4c49ad7e47afd97d2e666e93347745e1647c55f1a7ebba6d31b7dd5f69ee68")); + Assert.True(result.StdOut.Contains("21d90ee9b3569590c624836ef50bf39791c7184869c227eedc00585e1f39b4de")); } /// <summary> @@ -44,9 +44,9 @@ namespace AppInstallerCLIE2ETests [Test] public void HashInvalidMSIX() { - var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTest.cer") + " -m"); + var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTestMsiInstaller.msi") + " -m"); Assert.AreEqual(Constants.ErrorCode.OPC_E_ZIP_MISSING_END_OF_CENTRAL_DIRECTORY, result.ExitCode); - Assert.True(result.StdOut.Contains("9b4c49ad7e47afd97d2e666e93347745e1647c55f1a7ebba6d31b7dd5f69ee68")); + Assert.True(result.StdOut.Contains("21d90ee9b3569590c624836ef50bf39791c7184869c227eedc00585e1f39b4de")); Assert.True(result.StdOut.Contains("Please verify that the input file is a valid, signed MSIX.")); } diff --git a/src/AppInstallerCLIE2ETests/SetUpFixture.cs b/src/AppInstallerCLIE2ETests/SetUpFixture.cs @@ -41,8 +41,6 @@ namespace AppInstallerCLIE2ETests shouldRevertDefaultFileTypeRiskOnExit = this.DecreaseFileTypeRisk(".exe;.msi", false); - Assert.True(TestCommon.RunCommand("certutil.exe", "-addstore -f \"TRUSTEDPEOPLE\" " + TestCommon.GetTestDataFile(Constants.AppInstallerTestCert)), "Add AppInstallerTestCert"); - if (testParams.PackagedContext) { if (testParams.LooseFileRegistration) @@ -83,8 +81,6 @@ namespace AppInstallerCLIE2ETests this.DecreaseFileTypeRisk(defaultFileTypes, true); } - TestCommon.RunCommand("certutil.exe", $"-delstore \"TRUSTEDPEOPLE\" {Constants.AppInstallerTestCertThumbprint}"); - TestCommon.PublishE2ETestLogs(); if (TestSetup.Parameters.PackagedContext) diff --git a/src/AppInstallerCLIE2ETests/TestData/AppInstallerTest.cer b/src/AppInstallerCLIE2ETests/TestData/AppInstallerTest.cer Binary files differ. diff --git a/src/AppInstallerCLIE2ETests/TestData/IndexPackageIntRoot.cer b/src/AppInstallerCLIE2ETests/TestData/IndexPackageIntRoot.cer Binary files differ. diff --git a/src/AppInstallerCLIE2ETests/TestData/localsource.json b/src/AppInstallerCLIE2ETests/TestData/localsource.json @@ -44,6 +44,7 @@ } ], "Signature": { - "CertFile": "%APPINSTALLERTEST_SECUREFILEPATH%" + "CertFile": "%TestSigningCert_PfxPath%", + "Password": "%TestSigningCert_Password%" } } diff --git a/src/LocalhostWebServer/InstallDevCert.ps1 b/src/LocalhostWebServer/InstallDevCert.ps1 @@ -1,15 +0,0 @@ -param( - [Parameter(Mandatory=$true)] - [string]$pfxpath, - - [Parameter(Mandatory=$true)] - [string]$password -) - -Add-Type -AssemblyName System.Security -$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -$cert.Import($pfxpath, $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet") -$store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "Root", LocalMachine -$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]"ReadWrite") -$store.Add($cert) -$store.Close()- \ No newline at end of file diff --git a/src/LocalhostWebServer/Program.cs b/src/LocalhostWebServer/Program.cs @@ -164,7 +164,7 @@ namespace LocalhostWebServer foreach (string file in files) { string dest = Path.Combine(destDir, Path.GetFileName(file)); - File.Copy(file, dest); + File.Copy(file, dest, overwrite: true); } string[] directories = Directory.GetDirectories(sourceDir); diff --git a/src/LocalhostWebServer/Run-LocalhostWebServer.ps1 b/src/LocalhostWebServer/Run-LocalhostWebServer.ps1 @@ -54,8 +54,19 @@ if (-not [System.String]::IsNullOrEmpty($sourceCert)) Push-Location $BuildRoot -$Local:process = Start-Process -FilePath "LocalhostWebServer.exe" -ArgumentList "StaticFileRoot=$StaticFileRoot CertPath=$CertPath CertPassword=$CertPassword OutCertFile=$OutCertFile LocalSourceJson=$LocalSourceJson TestDataPath=$TestDataPath ExitBeforeRun=$ExitBeforeRun" -PassThru +$startProcessArguments = @{ + FilePath = Join-Path $BuildRoot "LocalhostWebServer.exe" + ArgumentList = "StaticFileRoot=$StaticFileRoot CertPath=$CertPath CertPassword=$CertPassword OutCertFile=$OutCertFile LocalSourceJson=$LocalSourceJson TestDataPath=$TestDataPath ExitBeforeRun=$ExitBeforeRun" + PassThru = $true +} + +if (-not [System.string]::IsNullOrEmpty($env:artifactsDir)) +{ + $startProcessArguments.RedirectStandardOutput = Join-Path $env:artifactsDir "LocalhostWebServer.out" + $startProcessArguments.RedirectStandardError = Join-Path $env:artifactsDir "LocalhostWebServer.err" +} +$Local:process = Start-Process @startProcessArguments if ($ExitBeforeRun) { diff --git a/templates/e2e-setup.yml b/templates/e2e-setup.yml @@ -4,31 +4,48 @@ parameters: type: string - name: localhostWebServerArgs type: string +- name: signingCertOutDir + type: string + default: $(Agent.TempDirectory) steps: - - task: DownloadSecureFile@1 - name: AppInstallerTest - displayName: 'Download Source Package Certificate' - inputs: - secureFile: 'AppInstallerTest.pfx' + - pwsh: | + $newCertArguments = @{ + Type = "Custom" + Subject = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" + KeyUsage = "DigitalSignature" + TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") + CertStoreLocation = "Cert:\CurrentUser\My" + } + $cert = New-SelfSignedCertificate @newCertArguments - - task: DownloadSecureFile@1 - name: HTTPSDevCert - displayName: 'Download Kestrel Certificate' - inputs: - secureFile: 'HTTPSDevCertV2.pfx' + $certPfxPath = Join-Path $(Agent.TempDirectory) TestSigningCert.pfx + $certCerPath = Join-Path ${{ parameters.signingCertOutDir }} TestSigningCert.cer + $certPassword = (New-Guid).ToString() + $certSecurePassword = ConvertTo-SecureString $certPassword -AsPlainText - - task: PowerShell@2 - displayName: Install Root Certificate - inputs: - filePath: '${{ parameters.sourceDir }}\src\LocalhostWebServer\InstallDevCert.ps1' - arguments: '-pfxpath $(HTTPSDevCert.secureFilePath) -password microsoft' + Export-PfxCertificate -Cert $cert -FilePath $certPfxPath -Password $certSecurePassword + Export-Certificate -Cert $cert -FilePath $certCerPath - - task: PowerShell@2 + Write-Host "##vso[task.setvariable variable=TestSigningCert.PfxPath;]$certPfxPath" + Write-Host "##vso[task.setvariable variable=TestSigningCert.CerPath;]$certCerPath" + Write-Host "##vso[task.setvariable variable=TestSigningCert.Password;]$certPassword" + displayName: Create test codesigning cert + + - pwsh: | + $httpsCertPath = Join-Path $(Agent.TempDirectory) HttpsCert.pfx + $httpsCertPassword = (New-Guid).ToString() + dotnet dev-certs https --export-path $httpsCertPath --password $httpsCertPassword + + $securePassword = ConvertTo-SecureString $httpsCertPassword -AsPlainText + Import-PfxCertificate -FilePath $httpsCertPath -Password $securePassword -CertStoreLocation Cert:\LocalMachine\Root + + Write-Host "##vso[task.setvariable variable=HttpsCert.Path;]$httpsCertPath" + Write-Host "##vso[task.setvariable variable=HttpsCert.Password;]$httpsCertPassword" + displayName: Create and install localhost HTTPS cert + + - pwsh: ${{ parameters.sourceDir }}\src\LocalhostWebServer\Run-LocalhostWebServer.ps1 -CertPath $(HttpsCert.Path) -CertPassword $(HttpsCert.Password) -OutCertFile $(Agent.TempDirectory)\servercert.cer ${{ parameters.localhostWebServerArgs }} displayName: Launch LocalhostWebServer - inputs: - filePath: '${{ parameters.sourceDir }}\src\LocalhostWebServer\Run-LocalhostWebServer.ps1' - arguments: '-CertPath $(HTTPSDevCert.secureFilePath) -CertPassword microsoft -OutCertFile $(Agent.TempDirectory)\servercert.cer ${{ parameters.localhostWebServerArgs }}' - task: PowerShell@2 displayName: Setup Local PS Repository