From ded207dec26b442ca8a21ee0eba528ce7b79413a Mon Sep 17 00:00:00 2001 From: Jesus Zarate Date: Wed, 27 May 2026 15:33:02 -0600 Subject: [PATCH] Add catalog signing for non-PE XML content files Non-PE files (XML docs, templates) cannot carry Authenticode signatures. This adds catalog signing infrastructure: 1. eng/Signing.props: Add FileExtensionSignInfo for .cat so Arcade signs the generated catalog with Microsoft400 2. eng/generate-catalog.ps1: Script to generate CDF and run makecat.exe 3. VisualFSharpDebug.csproj: GenerateCatalogFiles target that runs after VSIX assembly to produce a .cat covering xmlfile.xml Fixes VS signing scan violations for xmlfile.xml (VisualFSharpDebug VSIX). The fsharp.core_13.xml fix for the SDK Swix package is tracked separately (requires VS-repo catalog signing). Bug: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/3008042 --- eng/Signing.props | 7 + eng/generate-catalog.ps1 | 157 ++++++++++++++++++ .../VisualFSharpFull/VisualFSharpDebug.csproj | 19 +++ 3 files changed, 183 insertions(+) create mode 100644 eng/generate-catalog.ps1 diff --git a/eng/Signing.props b/eng/Signing.props index 222ad3dc47a..22ef88fff52 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -5,6 +5,13 @@ + + + + + true diff --git a/eng/generate-catalog.ps1 b/eng/generate-catalog.ps1 new file mode 100644 index 00000000000..70dd8bda448 --- /dev/null +++ b/eng/generate-catalog.ps1 @@ -0,0 +1,157 @@ +<# +.SYNOPSIS + Generates a Catalog Definition File (.cdf) and optionally runs makecat.exe to + produce a signed catalog (.cat) file. +.DESCRIPTION + Recursively scans a directory for files matching a filter and produces a .cdf + file suitable for makecat.exe. Optionally invokes makecat.exe to generate the + .cat file directly. + + Used in component team pipelines (Arcade SDK, MicroBuild, or custom) to + catalog-sign customer-modifiable or non-PE files that cannot use direct + Authenticode signing. + + If -RunMakecat is specified, the script finds and runs makecat.exe automatically. + Otherwise, it prints the commands to run manually. +.PARAMETER RootPath + The directory containing files to include in the catalog. +.PARAMETER CdfPath + The output path for the .cdf file. If not specified and CatOutputPath is set, + defaults to the CatOutputPath with a .cdf extension. +.PARAMETER CatOutputPath + The path where makecat.exe will create the .cat file. Defaults to the CDF + path with a .cat extension. +.PARAMETER Filter + File filter pattern (e.g., '*.js', '*.xml', '*.ttf'). Default: '*.*' (all files). +.PARAMETER RunMakecat + If specified, finds and runs makecat.exe to produce the .cat file. +.PARAMETER WindowsSdkDir + Optional path to the Windows SDK. Used to locate makecat.exe when -RunMakecat is set. +.PARAMETER ErrorIfMakecatNotFound + If specified with -RunMakecat, throws an error when makecat.exe is not found + instead of warning and skipping. Use in CI/official builds. +.EXAMPLE + # Generate CDF only (print manual steps): + .\New-CatalogDefinitionFile.ps1 -RootPath ".\content" -CdfPath ".\obj\my-files.cdf" +.EXAMPLE + # Generate CDF and run makecat.exe: + .\New-CatalogDefinitionFile.ps1 -RootPath ".\content" -CatOutputPath ".\obj\my-files.cat" -Filter "*.js" -RunMakecat +.EXAMPLE + # CI usage (error if makecat.exe not found): + .\New-CatalogDefinitionFile.ps1 -RootPath "$(_ContentRoot)" -CatOutputPath "$(_CatOutputPath)" -Filter "*.js" -RunMakecat -ErrorIfMakecatNotFound +#> +[CmdletBinding()] +param( + [Parameter(Mandatory)] + [string]$RootPath, + + [string]$CdfPath, + + [string]$CatOutputPath, + + [string]$Filter = '*.*', + + [string]$WindowsSdkDir = '', + + [switch]$RunMakecat, + + [switch]$ErrorIfMakecatNotFound +) + +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path $RootPath)) { + Write-Error "Root path not found: $RootPath" + return +} + +# Resolve paths: need at least one of CdfPath or CatOutputPath +if (-not $CdfPath -and -not $CatOutputPath) { + Write-Error "Specify at least one of -CdfPath or -CatOutputPath." + return +} +if (-not $CatOutputPath) { + $CatOutputPath = [System.IO.Path]::ChangeExtension($CdfPath, '.cat') +} +if (-not $CdfPath) { + $CdfPath = [System.IO.Path]::ChangeExtension($CatOutputPath, '.cdf') +} + +# Ensure output directories exist +$cdfDir = Split-Path $CdfPath -Parent +if ($cdfDir -and -not (Test-Path $cdfDir)) { + New-Item -ItemType Directory -Path $cdfDir -Force | Out-Null +} +$catDir = Split-Path $CatOutputPath -Parent +if ($catDir -and -not (Test-Path $catDir)) { + New-Item -ItemType Directory -Path $catDir -Force | Out-Null +} + +$files = Get-ChildItem -Path $RootPath -Recurse -Filter $Filter -File +if ($files.Count -eq 0) { + Write-Warning "No files matching '$Filter' found under $RootPath - skipping catalog generation." + return +} + +$cdfContent = @() +$cdfContent += "[CatalogHeader]" +$cdfContent += "Name=$CatOutputPath" +$cdfContent += "CatalogVersion=2" +$cdfContent += "HashAlgorithms=SHA256" +$cdfContent += "" +$cdfContent += "[CatalogFiles]" + +$i = 0 +foreach ($f in $files) { + $ext = $f.Extension.TrimStart('.').ToLower() + $label = "${ext}_${i}_" + ($f.Name -replace '[^\w\.-]', '_') + $cdfContent += "$label=$($f.FullName)" + $i++ +} + +$cdfContent | Set-Content -Path $CdfPath -Encoding ASCII + +Write-Host "Generated CDF with $($files.Count) file(s) matching '$Filter' at $CdfPath" + +if ($RunMakecat) { + # Find makecat.exe — ships with the Windows SDK + $makecat = $null + if ($WindowsSdkDir -and (Test-Path $WindowsSdkDir)) { + $makecat = Get-ChildItem -Path (Join-Path $WindowsSdkDir 'bin') -Recurse -Filter 'makecat.exe' -File | + Where-Object { $_.DirectoryName -match 'x64' } | + Sort-Object DirectoryName -Descending | + Select-Object -First 1 + } + if (-not $makecat) { $makecat = Get-Command makecat.exe -ErrorAction SilentlyContinue } + if (-not $makecat) { + $sdkRoot = "${env:ProgramFiles(x86)}\Windows Kits\10\bin" + if (Test-Path $sdkRoot) { + $makecat = Get-ChildItem -Path $sdkRoot -Recurse -Filter 'makecat.exe' -File | + Where-Object { $_.DirectoryName -match 'x64' } | + Sort-Object DirectoryName -Descending | + Select-Object -First 1 + } + } + if (-not $makecat) { + if ($ErrorIfMakecatNotFound) { + throw "makecat.exe not found. Catalog signing requires the Windows SDK." + } + Write-Warning "makecat.exe not found - skipping catalog generation. Install Windows SDK for catalog signing." + return + } + + $makecatPath = if ($makecat -is [System.Management.Automation.CommandInfo]) { $makecat.Source } else { $makecat.FullName } + Write-Host "Using makecat.exe at: $makecatPath" + + & $makecatPath $CdfPath + if ($LASTEXITCODE -ne 0) { + throw "makecat.exe failed with exit code $LASTEXITCODE" + } + + Write-Host "Generated catalog file: $CatOutputPath" +} else { + Write-Host "" + Write-Host "Next steps:" + Write-Host " 1. Run: makecat.exe `"$CdfPath`"" + Write-Host " 2. Sign: dotnet ddsignfiles.dll -- /file:`"$CatOutputPath`" /certs:Microsoft400" +} diff --git a/vsintegration/Vsix/VisualFSharpFull/VisualFSharpDebug.csproj b/vsintegration/Vsix/VisualFSharpFull/VisualFSharpDebug.csproj index 5a6671c4d24..f2f6b8ab992 100644 --- a/vsintegration/Vsix/VisualFSharpFull/VisualFSharpDebug.csproj +++ b/vsintegration/Vsix/VisualFSharpFull/VisualFSharpDebug.csproj @@ -84,4 +84,23 @@ + + + + + <_ContentRoot>$(TargetVsixContainerIntermediateOutputPath) + <_CatOutputPath>$(TargetVsixContainerIntermediateOutputPath)FSharpDebugContent.cat + <_ErrorFlag Condition="'$(ContinuousIntegrationBuild)' == 'true' or '$(OfficialBuild)' == 'true'">-ErrorIfMakecatNotFound + + + +