Sign dotnetup executable for Windows and Mac#54059
Sign dotnetup executable for Windows and Mac#54059dsplaisted wants to merge 16 commits intodotnet:release/dnupfrom
Conversation
There was a problem hiding this comment.
I think copilot mostly used the existing signing implementation for the library package and copied it.
The executable production only happens in the official ci, so you'll need to run the branches in the official CI to see whether this is working or not. Here's an example signing run from the CI! https://dev.azure.com/dnceng/internal/_build/results?buildId=2958051&view=logs&j=3d5d6410-622d-51c4-8220-9d539bbf3309&t=26ffafe4-f146-5df5-f784-50f2fd9186bb
Add code signing infrastructure for the dotnetup NativeAOT executable: - Create src/Installer/dotnetup.sign.proj that uses Arcade's SignToolTask to Authenticode-sign dotnetup.exe with the Microsoft400 certificate - Add FileSignInfo entries in eng/Signing.props for dotnetup.exe (Windows) and dotnetup (macOS MacDeveloperHarden, for future use) - Restructure dotnetup-executables.yml Windows steps to sign the executable after publish but before copying to artifact staging directories, ensuring both the full publish artifact and standalone binary artifact are signed - The signing step generates a binlog for diagnostics macOS signing requires enableMicrobuildForMacAndLinux pipeline support and will be addressed in a follow-up change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Enable MicroBuild for Mac and Linux in the executable pipeline (enableMicrobuildForMacAndLinux: true) - Add macOS signing step conditioned on osx-* RIDs that invokes dotnetup.sign.proj with TargetOS=osx and MacDeveloperHarden cert - Update dotnetup.sign.proj to handle both platforms: - Windows: Authenticode via Microsoft400 for dotnetup.exe - macOS: MacDeveloperHarden for extensionless dotnetup binary - Cross-platform DotNetPath default (dotnet.exe vs dotnet) - Split Unix pipeline steps into publish → sign → stage (matching the Windows structure) so signing happens before artifact copy Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Merge dotnetup executable signing into existing Installer.sign.proj instead of a separate dotnetup.sign.proj (per nagilson's review) - Add DotnetupPublishDir-conditioned ItemGroup for exe/macOS signing - Add cross-platform DotNetPath defaults (Windows/Unix) - Delete the separate dotnetup.sign.proj - Remove dotnetup FileSignInfo from Signing.props (dotnetup is not in the SDK redist — signing only happens via Installer.sign.proj in the standalone dotnetup pipeline) - Update dotnetup-executables.yml to invoke Installer.sign.proj - Add SignCheck verification steps: signtool verify (Windows), codesign --verify (macOS) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generate .sha512 companion files for dotnetup executables on all platforms (Windows via Get-FileHash, Unix via sha512sum). The checksum files are published alongside the standalone binary artifacts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
signtool.exe is not available on the PATH in AzDO build agents. Replace with PowerShell's built-in Get-AuthenticodeSignature which is always available on Windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
304991a to
3d3a137
Compare
The "Install .NET 8.0 SDK for MicroBuild Plugin" task in
eng/common/core-templates/steps/install-microbuild.yml referenced an
undeclared microBuildOutputFolder parameter, so the SDK installation
path resolved to /.dotnet on Linux/macOS - a read-only location that
caused the task to fail with EROFS in cross-build containers.
Backport the upstream arcade fix:
- Declare microBuildOutputFolder parameter defaulting to
Agent.TempDirectory/MicroBuild.
- Install the SDK at microBuildOutputFolder/.dotnet-microbuild.
Also restrict enableMicrobuildForMacAndLinux on the dotnetup
executables job to osx-* RIDs, since the Linux jobs do not sign and
do not need the MicroBuild plugin at all.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MicroBuildSigningPlugin@4 on macOS requires a pipeline variable literally named TeamName, not just the arcade _TeamName convention or the env var set by install-microbuild.yml. Add it alongside _TeamName so osx signing jobs no longer fail with "TeamName variable is required to use MicroBuild". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Microsoft.DotNet.SignTool unconditionally requires PkgToolPath when running on macOS, even when only signing a single Mach-O binary (no .pkg/.app bundles). Match the pattern used by arcade Sign.proj: - Add a PackageDownload for Microsoft.DotNet.MacOsPkg.Cli (osx-only) using the same version as the SignTool package, since both ship from the same arcade build. - Wire MacOSPkgCliItem into the SignToolTask via PkgToolPath. - Add an explicit restore step in the osx sign job, since Installer.sign.proj is not part of dotnetup.slnf and dotnetup signing is the only consumer of Microsoft.DotNet.MacOsPkg.Cli. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MicroBuild signing process spawns a child MSBuild on Round0-Sign.proj whose binlog is written to artifacts/log/Sign/. Publish that directory as a pipeline artifact for osx jobs so the actual signing failure is diagnosable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MicroBuild ESRP signing plugin needs SYSTEM_ACCESSTOKEN at runtime to acquire a federated OIDC token. Without it, SignFiles fails with 'Access token is not available' from ESRPCliDll.EncryptAccessToken / GetFederatedTokenData. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nagilson
left a comment
There was a problem hiding this comment.
I will verify the Mac signature on Monday, but the changes look great! Thank you for including a verification of the authenticode, I was also able to verify the signature.
One question I have is if we could try using Microsoft402 for Windows
<FileSignInfo Include="dotnetup.exe" CertificateName="Microsoft402" />
<FileExtensionSignInfo Include=".exe" CertificateName="Microsoft402" />The dotnetup executable is still being rejected by windows defender smart screen because microsoft 400 is an internal EKU (this is my bad for suggesting to use the library package signature) but I think Microsoft402 is an EV Authenticode which will pass defender. MicrosoftDotNet500 is also a standard we use but this does not have automatic reputation which means it will still get blocked by defender until it has enough downloads, to my knowledge.
|
|
||
| <!-- SignTool requires explicit dotnet path; default to repo-local SDK. --> | ||
| <DotNetPath Condition="'$(DotNetPath)' == ''">$(RepoRoot).dotnet\dotnet.exe</DotNetPath> | ||
| <DotNetPath Condition="'$(DotNetPath)' == '' and '$(OS)' == 'Windows_NT'">$(RepoRoot).dotnet\dotnet.exe</DotNetPath> |
There was a problem hiding this comment.
Good catch to fix this executable ending!
nagilson
left a comment
There was a problem hiding this comment.
The signature fails on Mac 'dotnetup cannot be opened because apple cannot check it for malicious software.'
I believe we need to use MacDeveloperWithNotarization as a starting point.
I also think this may be a potential concern: Distributing a bare Mach-O binary in a .zip can lose the notarization ticket staple (which is stored as an xattr). Apple recommends distributing notarized binaries inside a .dmg, .pkg, or using stapler staple on the binary before zipping (the ticket gets embedded in the binary's code directory).
We should consider making the pipeline run xcrun stapler staple dotnetup after notarization succeeds and before archiving. You may want to see how others are using it in our org or if dnceng can do it for us: https://github.com/search?q=org%3Adotnet+xcrun&type=code
…on macOS - Switch Windows dotnetup.exe signing cert from Microsoft400 to Microsoft402. Microsoft400 is an internal EKU; Defender SmartScreen still blocks the binary. Microsoft402 is EV Authenticode and has automatic reputation, which lets the executable pass SmartScreen. - Switch macOS dotnetup signing cert from MacDeveloperHarden to MacDeveloperWithNotarization so the binary is both signed and notarized by Apple. Without notarization, Gatekeeper blocks execution with ''dotnetup cannot be opened because Apple cannot check it for malicious software''. - Add a pipeline step that runs `xcrun stapler staple` on the notarized binary before it is archived. Distributing a bare Mach-O inside a .zip can lose the notarization ticket (it is otherwise stored as an xattr). Stapling embeds the ticket into the binary''s code directory so it survives zipping. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The MicroBuild signing plugin reports ''Could not find the specified Authenticode cert MacDeveloperWithNotarization''. The correct cert name in MicroBuild is `MacDeveloperHardenWithNotarization` (matches the pattern used by dotnet/cli-lab). Also publish the per-RID signing logs (artifacts/log/Sign) for Windows jobs in addition to macOS. The Windows sign step is also failing in Round0-Sign.proj exit 1, but the underlying SignFiles error from the child MSBuild is only captured in those logs and is not currently published, so we cannot see why Microsoft402 is being rejected (or if it is even what is failing). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
MacDeveloperHardenWithNotarization is a *virtual* cert name handled by Microsoft.DotNet.SignTool: the task translates it to the real underlying MacCertificate (MacDeveloperHarden) plus a notarize step. The mapping is not built in — it must be supplied via the SignToolTask.CertificatesSignInfo parameter (an item group with MacCertificate and MacNotarizationAppName metadata). Without this, MicroBuild''s SignFiles plugin sees the virtual name as a literal cert name, fails to find it, and reports''Could not find the specified Authenticode cert MacDeveloperHardenWithNotarization''. This matches how dotnet/arcade''s own Sign.props declares the mapping for the full set of MacDeveloper*WithNotarization virtual certs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Microsoft402 (EV) is documented in arcade but not actually provisioned in MicroBuild.Plugins.Signing 1.1.1271 (no ESRPCerts/402.json), causing the Windows sign step to fail. MicrosoftDotNet500 is the .NET-specific Authenticode cert documented for cases where Microsoft400 is blocked by Defender SmartScreen. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
NativeAOT publish on osx produces dotnetup.dSYM next to the binary. The dSYM bundle contains an unsigned Mach-O DWARF file. When the sign+notarize archive is submitted to Apple, the unsigned dSYM Mach-O fails notarization with 'Archive contains critical validation errors', causing the whole submission to be rejected. dSYMs are debug data not needed by end users; remove them before signing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Instead of deleting the NativeAOT-produced dotnetup.dSYM bundle before Mac signing, move it into a sibling staging directory and publish it as a separate 'dotnetup-executable-dsym-<rid>' pipeline artifact. The dSYM contains DWARF debug info useful for symbolicating native crash dumps later. We don't want it in the notarization archive (it's an unsigned Mach-O that causes Apple's notarytool to reject the whole submission), but there's no reason to discard it entirely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
I've updated this to use Microsoft500 for Windows as it looks like MicroBuild doesn't support Microsoft402. I've also updated the Mac signing to do the notarization and stapling. However, there seems to be a signing outage, so I'm not sure if it works all the way. |
There was a problem hiding this comment.
Pull request overview
This PR updates the dotnetup build pipeline and signing project to produce signed dotnetup executables on Windows (Authenticode) and macOS (codesign + notarization), and publishes related artifacts (logs, binlogs, dSYM, checksums) for diagnostics and distribution.
Changes:
- Extend
Installer.sign.projto signdotnetup/dotnetup.exe, including macOS notarization support viaCertificatesSignInfoand macOS pkg tooling wiring. - Update the dotnetup executable pipeline job to run signing/verification steps for Windows and macOS, publish signing logs, and generate SHA-512 checksums (plus dSYM handling on macOS).
- Adjust MicroBuild installation on non-Windows agents and set required pipeline variables for MicroBuildSigningPlugin usage.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Installer/Installer.sign.proj |
Adds dotnetup executable signing rules for Windows and macOS, plus macOS SignTool prerequisites (PkgToolPath + cert mapping). |
eng/pipelines/templates/jobs/dotnetup/dotnetup-executables.yml |
Adds signing, signature verification, signing-log publishing, dSYM staging (macOS), and checksum generation to dotnetup executable jobs. |
eng/common/core-templates/steps/install-microbuild.yml |
Changes where the MicroBuild helper .NET SDK is installed on Mac/Linux and introduces a parameterized install folder. |
.vsts-dnup-ci.yml |
Defines TeamName pipeline variable required by MicroBuildSigningPlugin on non-Windows agents. |
| <FileExtensionSignInfo Include=".exe" CertificateName="MicrosoftDotNet500" /> | ||
| <FileSignInfo Include="dotnetup.exe" CertificateName="MicrosoftDotNet500" /> | ||
| <ItemsToSign Condition="'$(OS)' == 'Windows_NT'" Include="$(DotnetupPublishDir)dotnetup.exe" /> | ||
| <FileSignInfo Include="dotnetup" CertificateName="MacDeveloperHardenWithNotarization" /> | ||
| <ItemsToSign Condition="'$(TargetOS)' == 'osx'" Include="$(DotnetupPublishDir)dotnetup" /> | ||
| <CertificatesSignInfo Condition="'$(TargetOS)' == 'osx'" Include="MacDeveloperHardenWithNotarization" MacCertificate="MacDeveloperHarden" MacNotarizationAppName="dotnet" /> |
| displayName: Install .NET 8.0 SDK for MicroBuild Plugin | ||
| inputs: | ||
| packageType: sdk | ||
| version: 8.0.x | ||
| installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet | ||
| installationPath: ${{ parameters.microBuildOutputFolder }}/.dotnet-microbuild | ||
| workingDirectory: ${{ parameters.microBuildOutputFolder }} |
No description provided.