Signing, Notarization & OS Trust

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

A command-line tool that someone downloads runs with the privileges of the person who starts it, so each operating system tries to answer two questions before or while it runs: has the file been altered since the publisher built it, and does anyone vouch for the publisher? The answers differ a lot. macOS ties them together with a signature, an Apple notarization ticket and Gatekeeper. Windows uses an Authenticode signature, a timestamp and SmartScreen reputation. Linux has no operating-system gate for a downloaded executable at all; trust comes from the package manager and from signatures and checksums that the user or a script checks.

This page covers the three, with the Bookshelf shelf tool as the running example. It was written against the Apple documentation for notarization and the hardened runtime, the SignTool reference (page updated 2026-10-05), the Microsoft Defender SmartScreen overview, the Sigstore cosign documentation, the SLSA 1.0 specification, the Debian Secure APT wiki page and the DNF configuration reference, all read on 2026-10-25. The macOS commands that need no paid account (ad hoc signing, codesign inspection, pkgbuild, hdiutil, stapler on an unnotarized file, the quarantine attribute) were run on macOS with Apple clang and the Xcode command line tools, and their output is shown. The commands that need a Developer ID certificate, an Apple notary account, a Windows code-signing certificate or a GitHub Actions run (Developer ID signing, notarytool, productsign, signtool, cosign in CI) were not run while the page was written; they follow the official documentation linked after each example.

The app-bundle side of macOS (Developer ID certificates, entitlements, Xcode archives, DMG layout) is on Distributing Mac Apps with Developer ID and Notarization and is not repeated here. The Windows side of packaged apps (MSIX, certificate choices, Artifact Signing in GitHub Actions) is on MSIX Packaging & Signing. Checksums, cosign and GitHub artifact attestations as a release practice are introduced in Packaging & Distribution and the verification recipe is in the "Signed releases" section of Security. This page adds what is specific to command-line binaries. The build side (universal binaries, cross-compilation, the pipeline that produces the files to sign) is on Native Binaries & Cross-Compilation, and the packaging channels that consume signed files are on Native Packages & Installers.

The trust chain on each platform

Trust chain of a downloaded shelf binary: build, sign, notarize or timestamp, ship and the client check on macOS, Windows and Linux

The heavy-outlined box in each row is the check that happens on the user’s machine. On macOS and Windows it is done by the operating system without being asked; on Linux the equivalent is done by the package manager, or by the user.

Question macOS Windows Linux

Who vouches for the publisher?

A Developer ID certificate issued by Apple to a Developer Program member

A code-signing certificate from a public CA, or a certificate issued through Microsoft Artifact Signing

A GPG key (distribution repositories) or a Sigstore identity (cosign); nothing is built into the kernel

Does a second party scan the file?

Yes: Apple’s notary service, which returns a ticket

Reputation service: SmartScreen judges the file hash and the signer certificate

No, apart from whatever the distribution’s maintainers do

What survives going offline?

A stapled ticket (not possible for a bare binary); the signature itself

The signature and its timestamp

Whatever the user verified at download time

Who enforces it at first launch?

Gatekeeper, for files that carry the quarantine attribute

SmartScreen, for files downloaded from the internet

Nobody; the package manager enforces it at install time

Three words are easy to mix up, and the rest of the page depends on them.

  • A checksum (SHA-256) shows that a file is identical to the one the publisher listed. It says nothing about who the publisher is unless the list is itself signed or fetched over a channel you trust.

  • A signature binds the file contents to a key or certificate, so a change to the file is detected and the signer is named. It does not say the software is harmless.

  • Notarization and reputation are judgments by a third party (Apple’s notary service, Microsoft’s SmartScreen) about a signed file. They are an addition to a signature, never a replacement for one.

macOS

What the system checks

Gatekeeper checks software that came from outside the App Store the first time it is opened. By default, downloaded software must be signed by a registered developer and notarized by Apple, and Gatekeeper asks for user approval before downloaded software opens for the first time. Apple’s platform security guide says Gatekeeper "tracks the provenance of files written by downloaded software": a quarantine-aware application such as a browser marks what it downloads with the com.apple.quarantine extended attribute, and that mark is what makes the first-launch check apply. A person who is sure of the source can override a block in System Settings, Privacy & Security, with the Open Anyway button.

Two consequences matter for a command-line tool. First, the check is attached to the way a file arrived, not to its type: a binary fetched with curl is not quarantined, because curl does not set the attribute. The attribute is just an extended attribute, and the example below shows it by setting it by hand.

$ curl -s file://$PWD/shelf.c -o got.c
$ xattr -l got.c
com.apple.provenance:
$ xattr -w com.apple.quarantine "0083;5f000000;Safari;" shelf
$ xattr -l shelf
com.apple.provenance:
com.apple.quarantine: 0083;5f000000;Safari;
$ xattr -d com.apple.quarantine shelf

The file written by curl carries no com.apple.quarantine attribute (it shows only com.apple.provenance, an attribute that Apple’s public documentation does not describe); the attribute appears on shelf only because the example sets it by hand to the kind of value a browser would write. Do not mark files this way as a test of the real flow: download a build with Safari or Chrome on a clean Mac, as described under Verifying what a user sees. The Apple documentation describes the quarantine mechanism only in general terms and does not document how Gatekeeper treats a bare command-line tool that a user starts from a shell. Second, whatever the details, Apple’s rules for Developer ID software apply to command-line tools too: the notarization requirements below name "app and command line targets".

Every Mach-O is signed, but not trusted

On Apple silicon, the linker signs what it produces. Compiling the shelf example produced a valid, ad hoc, linker-signed binary with no certificate behind it.

$ clang -o shelf shelf.c
$ codesign -dv --verbose=2 shelf
Executable=/work/shelf
Identifier=shelf
Format=Mach-O thin (arm64)
CodeDirectory v=20400 size=382 flags=0x20002(adhoc,linker-signed) hashes=9+0 location=embedded
Signature=adhoc
Info.plist=not bound
TeamIdentifier=not set
Sealed Resources=none
Internal requirements=none

That signature lets the kernel run the file, and it detects changes, but TeamIdentifier=not set shows no identity vouches for it. Gatekeeper’s policy assessment agrees:

$ spctl --assess --type execute -vv shelf
shelf: rejected

An ad hoc build is correct for development and for swift build output that you run yourself. It is not something to ship to other people. man codesign and man spctl are the reference; Apple’s public documentation pages read for this page do not describe the linker signature.

Signing for distribution

Notarization imposes a short list of requirements on the software you submit. For a command-line tool they are: a valid code signature on every executable, a Developer ID Application certificate (not a Mac Distribution, ad hoc, Apple Development or local certificate), the hardened runtime enabled for "app and command line targets", a secure timestamp in the signature, no com.apple.security.get-task-allow entitlement set to true, and linking against the macOS 10.9 or later SDK.

$ codesign --force --options runtime --timestamp \
    --identifier com.example.shelf \
    --sign "Developer ID Application: Example Corp (TEAMID1234)" \
    shelf

Source: Apple — Notarizing macOS software before distribution and Apple — Customizing the notarization workflow. This command needs a Developer ID certificate in the keychain and was not run while the page was written.

The same command with --sign - (ad hoc) was run, to show what each option changes. Without --identifier, codesign derives an identifier from the file name and a hash (shelf-55554944ed39c0e65496385ea1dadb0b279bf190); with it, the identifier is stable across builds, which is what a requirement or an entitlement lookup should rely on.

$ codesign --force --options runtime --identifier com.example.shelf --sign - shelf
$ codesign -dv shelf 2>&1 | grep -i "identifier\|flags"
Identifier=com.example.shelf
CodeDirectory v=20500 size=274 flags=0x10002(adhoc,runtime) hashes=3+2 location=embedded
$ codesign --verify --strict --verbose=2 shelf
shelf: valid on disk
shelf: satisfies its Designated Requirement

The runtime in flags confirms that the hardened runtime is on. On a universal binary, sign after lipo has produced the final file (see Command-Line Tools in Swift for a universal build), because any later change invalidates the signature.

codesign option Effect

--sign IDENTITY

The signing identity; - signs ad hoc. For distribution, a Developer ID Application certificate.

--options runtime

Enables the hardened runtime, required for notarization.

--timestamp

Adds the secure timestamp that notarization requires, so the signature stays valid after the certificate expires.

--identifier ID

Sets the signing identifier instead of deriving one.

--entitlements FILE

Embeds an entitlements property list (only the exceptions below).

--force

Replaces an existing signature, for example the linker signature.

The options table summarizes the flags used on this page; man codesign lists the others.

The hardened runtime and what a tool may need to switch off

The hardened runtime, together with System Integrity Protection, protects the runtime integrity of a process against code injection, library hijacking and memory tampering. It does not affect most tools. The few things it disallows can be re-enabled one at a time with entitlements, which apply to executables; shared libraries and in-process plug-ins inherit the entitlements of their host. Apple’s advice is to use only the entitlements that are absolutely necessary.

Entitlement When a command-line tool needs it

com.apple.security.cs.allow-jit

The tool generates machine code at run time (a JIT such as a JavaScript or WebAssembly engine inside a CLI).

com.apple.security.cs.allow-unsigned-executable-memory

The tool makes writable and executable memory without MAP_JIT; broader than allow-jit.

com.apple.security.cs.disable-library-validation

The tool loads plug-ins or libraries that are not signed by the same team.

com.apple.security.cs.allow-dyld-environment-variables

The tool must honour the DYLD_* variables.

com.apple.security.cs.debugger

The tool is a debugger that attaches to other processes.

com.apple.security.cs.disable-executable-page-protection

Disables all code-signing protections while launching and running; avoid it.

A plain shelf needs none of them. A tool built on a runtime that generates code (the JVM, Node.js, a Python with a JIT) needs at least allow-jit, and the entitlements file is passed with --entitlements.

Hardened-runtime protections also change how a tool behaves under a debugger and with injection tools, and com.apple.security.get-task-allow must not be set to true in a notarized build. The Apple notarization page names it as a reason for rejection.

Notarizing

The notary service accepts disk images (UDIF), signed flat installer packages and ZIP archives. It does not accept a bare executable file, so a standalone tool is archived first. ditto is the archiver the Apple examples use for bundles; for a single file it produces a ZIP that notarytool accepts.

$ ditto -c -k --keepParent shelf shelf.zip
$ xcrun notarytool store-credentials shelf-notary --key AuthKey_ABC123.p8 \
    --key-id ABC123 --issuer 12345678-90ab-cdef-1234-567890abcdef
$ xcrun notarytool submit shelf.zip --keychain-profile shelf-notary --wait
$ xcrun notarytool log <submission-id> --keychain-profile shelf-notary notary-log.json

Source: Apple — Customizing the notarization workflow. These commands need a paid Apple Developer Program membership and network access to Apple’s notary service and were not run while the page was written. The page above also describes app-specific passwords and the store-credentials keychain item; the API-key form shown here is the one that CI uses, and the credential options are explained on Distributing Mac Apps with Developer ID and Notarization. The installed notarytool was version 1.1.3 (42).

The service generates a ticket for the top-level file and for each nested file. Apple says to check the log even when notarization succeeds, because it can contain warnings to fix before the next submission. Notarization completes for most software within minutes. An Invalid result is resolved from the log; Apple’s Resolving common notarization issues page lists the usual causes (missing hardened runtime, missing secure timestamp, an unsigned nested binary, a debug entitlement).

Why a bare tool cannot be stapled, and what to do instead

Stapling attaches the notarization ticket to a file, so Gatekeeper can find it with no network connection and future copies carry it. Apple’s documentation states the limit directly: you can staple a ticket to an app, bundle, disk image or flat installer package; "while you can notarize a ZIP archive, you can’t staple to it directly"; and "although tickets are created for standalone binaries, it’s not currently possible to staple tickets to them."

The run below used an unnotarized shelf, so the failure shown is the "record not found" one; it is not a demonstration of the stapling limit, which comes from the documentation and not from this output.

$ xcrun stapler staple shelf
Processing: /work/shelf
CloudKit query for shelf (2/f64e720dd58f9704453be928a85e2745ba417da9) failed due to "Record not found".
Could not find base64 encoded ticket in response for 2/f64e720dd58f9704453be928a85e2745ba417da9
The staple and validate action failed! Error 65.

There are three honest options, in order of how well they behave offline.

Distribute as How What the user gets

A signed, notarized and stapled installer package (.pkg)

Install the signed tool to /usr/local/bin (or another path) with pkgbuild, sign the package with a Developer ID Installer certificate, notarize it, staple it

The ticket travels in the file; Gatekeeper needs no network. This is the right choice when the tool installs system-wide, runs scripts or ships a launch daemon (see shelfd)

A signed, notarized and stapled disk image (.dmg)

Put the signed tool in an image, sign the image with the Developer ID Application certificate, notarize it, staple it

The ticket travels with the image. The user drags the tool out; the copy outside the image is a bare binary again, so its own signature is what remains

A signed, notarized ZIP or tarball of the bare binary

Sign the binary, submit the archive, ship the archive without stapling

Gatekeeper looks the ticket up online, as Apple’s documentation describes for notarized software without a stapled ticket. There is no ticket in the file for an offline machine

The reasoning behind the table is in Apple’s own text, which says that Gatekeeper finds the ticket online for any notarized software, and that you "should also attach the ticket" with stapler so that "Gatekeeper can find the ticket even when a network connection isn’t available". For a bare binary only the first half is possible.

Whichever container you ship, sign the tool inside it with Developer ID Application and the hardened runtime before you build the container. Apple’s guidance for a custom third-party installer is two rounds: notarize and staple the payload, then package it and notarize the installer.

A signed installer package, step by step

pkgbuild builds a flat component package from a directory that mirrors the install location. The commands below (except productsign and notarytool) were run with an ad hoc signed tool, and the package was verified as unsigned.

$ mkdir -p root/usr/local/bin && cp shelf root/usr/local/bin/
$ codesign --force --options runtime --timestamp --identifier com.example.shelf \
    --sign "Developer ID Application: Example Corp (TEAMID1234)" root/usr/local/bin/shelf
$ pkgbuild --root root --identifier com.example.shelf --version 1.0.0 \
    --install-location / shelf-1.0.0.pkg
pkgbuild: Inferring bundle components from contents of root
pkgbuild: Wrote package to shelf-1.0.0.pkg
$ pkgutil --check-signature shelf-1.0.0.pkg
Package "shelf-1.0.0.pkg":
   Status: no signature
$ productsign --sign "Developer ID Installer: Example Corp (TEAMID1234)" \
    shelf-1.0.0.pkg shelf-1.0.0-signed.pkg
$ xcrun notarytool submit shelf-1.0.0-signed.pkg --keychain-profile shelf-notary --wait
$ xcrun stapler staple shelf-1.0.0-signed.pkg
$ xcrun stapler validate shelf-1.0.0-signed.pkg

Source: Apple — Customizing the notarization workflow. pkgbuild and pkgutil ran locally with the output shown; the Developer ID signing, productsign, notarization and stapling steps need certificates and an account and were not run. A disk image is built the same way (the hdiutil create command with -volname shelf, -srcfolder root/usr/local/bin and -format UDZO ran), then signed with codesign and a Developer ID Application identity, and submitted and stapled like the package. Which certificate signs which container (Application for apps and images, Installer for packages) is described on the Apple distribution page linked at the top.

Verifying what a user sees

Three commands check a release before you publish it, and a final manual test confirms it.

$ codesign --verify --strict --verbose=2 shelf
$ codesign -dv --verbose=4 shelf 2>&1 | grep -i "flags\|TeamIdentifier\|Authority"
$ xcrun stapler validate shelf-1.0.0-signed.pkg

The spctl --assess command assesses the policy; for a notarized, correctly signed file it reports an accepted source, and for the ad hoc shelf it reported rejected, as shown earlier. The only real test is the user’s path: upload the container, download it with a browser onto a Mac that has never seen the file, and open it.

Homebrew, curl | sh and the quarantine mark

How a file reaches the disk decides whether Gatekeeper applies. A browser download is quarantined; a file written by curl, tar or a package manager is not, and an installer script that downloads with curl leaves a tool that is not subject to the first-launch check. That is not a reason to skip signing: the signature still identifies the build and can be verified by codesign, and the same file is the one that passes Gatekeeper when someone later downloads it with a browser. The packaging channels and their signing requirements are compared on Native Packages & Installers.

Windows

Authenticode and signtool

Windows code signing is called Authenticode. signtool.exe is installed with the Windows SDK in the Bin folder of the SDK path (for example C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe). It has four commands (catdb, sign, timestamp, verify) plus remove, and returns exit code 0 for success, 1 for failure and 2 for completion with warnings. Newer SDK builds require the digest algorithm explicitly: SignTool’s sign command needs /fd (the file digest) and, when timestamping with /tr, /td (the timestamp digest). Microsoft recommends SHA256.

PS> signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 `
      /n "Example Corp" shelf.exe
PS> signtool verify /pa /v shelf.exe

Source: Microsoft — SignTool. These commands need a code-signing certificate and a Windows machine and were not run while the page was written. The timestamp URL is the one used in the Microsoft examples; use your certificate authority’s server.

signtool sign option Meaning

/fd alg

File digest algorithm; required. /fd certHash uses the algorithm of the certificate.

/tr URL and /td alg

RFC 3161 timestamp server and its digest algorithm. /t URL is the older form and cannot be combined with /tr.

/n subject

Selects the certificate by subject name (a substring); /sha1 hash selects by thumbprint when several match.

/a

Automatically selects the best certificate; without it, signtool expects exactly one valid certificate.

/f file and /p password

Signs with a PFX file. A password on the command line ends up in history and in CI logs; prefer a certificate in a store or a cloud signer.

/as

Appends a signature, so a file can carry several (for example SHA-1 and SHA-256).

/dlib DLL

Signs through a DLL that implements AuthenticodeDigestSign; this is how remote signing services plug in. Equivalent to using /dg, /ds and /di.

/ph and /nph

Generate or suppress page hashes for executables.

For verification, signtool verify /pa uses the Default Authentication Verification Policy; without /pa it uses the Windows Driver Verification Policy, which is the wrong one for an ordinary program. /tw warns when a signature has no timestamp, and /all checks every signature of a file that has more than one. signtool remove /s strips a signature.

Timestamps

Without a timestamp, a signature is valid only while the signing certificate is. A timestamp from an RFC 3161 server proves that the signature existed while the certificate was valid, so the file keeps verifying after the certificate expires. SignTool does not timestamp unless /tr or /t is given, and if the timestamp request fails it produces only a warning, so a CI job must check the result with signtool verify /pa /tw. The timestamp rules are the same for installers; see Timestamping in the MSIX page.

SmartScreen and reputation

Microsoft Defender SmartScreen checks downloaded files against a list of reported malicious programs and against a list of files that are well known and downloaded frequently; if the file is not on that list, it shows a warning that advises caution. It also checks the digital signature used to sign a file. The page states the rule that matters for a new tool: if a URL, file, app or certificate has an established reputation, users do not see warnings; if there is no reputation, the item is treated as higher risk and the user sees one. SmartScreen protects against malicious files from the internet, not against files on network shares addressed with UNC paths.

What this means for shelf.exe:

  • A signature is necessary but does not make the first download quiet: reputation attaches to the file hash and to the signing certificate and builds with use. How reputation builds, the end of the EV bypass in 2024 and Smart App Control are covered, with their Microsoft sources, in Choosing a Certificate: OV, EV and SmartScreen and in Security & App Isolation.

  • Sign every release with the same identity so reputation carries from one version to the next, and sign every executable inside an archive or installer, not only the installer.

  • A false warning or block can be reported to Microsoft for review by submitting the file and selecting Microsoft Defender SmartScreen as the product.

  • Console tools launched from a terminal by a user who already has the file are mostly affected through the download step (the browser and the file’s origin), not through the command line itself. The Microsoft pages do not document a separate rule for console executables.

Cloud signing

Microsoft’s Artifact Signing (formerly called Trusted Signing) is a managed signing service. It provides certificate lifecycle management inside FIPS 140-3 level 3 certified HSMs, integrates with developer toolsets, supports external timestamping services and public-trust signing, and signs a digest so that your file never leaves your machine. An account contains an identity validation and a certificate profile, and comes as a Basic or a Premium SKU. For signtool it is used through the /dlib option. The GitHub Actions workflow, the prices and the regions in which it is available are covered on the Azure Artifact Signing section of the MSIX page; this page does not repeat them. Other vendors' cloud-HSM and token-based services work through /dlib or through their own tools; the SignTool documentation lists the mechanism and not the vendors.

Installers, archives and managers

Sign the executable, then sign the container: an MSI or EXE installer with signtool, an MSIX with signtool and a certificate whose subject matches the manifest publisher (see the MSIX page), and a ZIP of loose files cannot carry a signature of its own, so the files inside must be signed. A winget manifest records the installer’s SHA-256 (InstallerSha256), so sign first and hash afterwards; the policy on whether signing is required is on Distribution Channels. Cross-compiling a Windows tool from Linux or macOS is covered on Native Binaries & Cross-Compilation; signtool itself runs only on Windows, and the SignTool documentation describes no other signer.

Linux

No operating-system gate

A downloaded ELF file starts when its execute bit is set, with no signature check. The kernel does not know about publishers. Linux distributions put trust in two places instead: the package manager checks signed repository metadata and package signatures when installing, and the user (or an install script) checks a signature or checksum that the project publishes. Everything on this page for Linux is therefore a convention a user must apply, not an enforcement that happens automatically, which is why the way you publish matters: the more of the chain a package manager checks, the fewer users skip it.

Checksums

Publish a SHA256SUMS file with every release and sign it, not the archives one by one, so that a user verifies one signature and one file.

$ sha256sum shelf-1.0.0-linux-amd64.tar.gz shelf-1.0.0-linux-arm64.tar.gz > SHA256SUMS
$ sha256sum --check SHA256SUMS --ignore-missing
shelf-1.0.0-linux-amd64.tar.gz: OK

A checksum proves the download is intact; it proves who built it only when the checksum file is itself signed or served over a trusted path. The same file feeds package manager manifests, such as the Homebrew sha256 and the winget InstallerSha256. The tool sha256sum is from GNU coreutils and was not run on this macOS host; macOS ships shasum -a 256 for the same job.

GPG detached signatures

GPG remains what distribution repositories use. A detached, ASCII-armored signature sits next to the file.

$ gpg --armor --detach-sign SHA256SUMS
$ gpg --verify SHA256SUMS.asc SHA256SUMS

Source: Debian Wiki — SecureApt (the gpg -abs form for signing a Release file). The gpg commands were not run while the page was written. The project key’s fingerprint must be published on a channel separate from the download (the project site, a signed commit or tag), otherwise a verified signature only shows that the file matches a key that came from the same place.

Sigstore cosign

Sigstore’s cosign sign-blob signs a file. The documentation recommends keyless signing, in which an ephemeral key is tied to an OpenID Connect identity, and recommends a bundle (--bundle), which carries the verification metadata and must be stored for verification. --yes bypasses confirmation prompts for unattended runs.

$ cosign sign-blob SHA256SUMS --bundle SHA256SUMS.sigstore.json --yes
$ cosign verify-blob SHA256SUMS --bundle SHA256SUMS.sigstore.json \
    --certificate-identity "https://github.com/OWNER/shelf/.github/workflows/release.yml@refs/tags/v1.0.0" \
    --certificate-oidc-issuer "https://token.actions.githubusercontent.com"

Source: Sigstore — Signing blobs and Sigstore — Verifying signatures. These commands were not run while the page was written. The identity and issuer values in the verify command are an example for a GitHub Actions workflow and must be replaced; the cosign pages document the identity and issuer flags in general terms, and the GitHub-specific values come from the workflow’s own identity, as on the Security page.

Keyless verification is only as strong as the identity you ask for. Accepting any identity that Sigstore will certify is the same as accepting no publisher at all, so scripts must pin both the identity and the issuer.

Provenance with SLSA

The SLSA 1.0 specification describes provenance as a record of what entity built an artifact, the process it used and its inputs. Its build track has levels: L0 (none), L1 (provenance exists, but is easy to bypass or forge), L2 (a hosted build platform generates and signs the provenance) and L3 (a hardened platform isolates builds and protects the signing secrets). GitHub artifact attestations, which gh attestation verify checks, are the practical way to publish provenance for a GitHub release; the attestation links an artifact to its source and build and is not a certificate that the artifact is safe. The commands are on Packaging & Distribution.

Signed repositories: apt and dnf

For a .deb, apt checks a chain: the signed Release file lists the checksums of the Packages files, and those list the checksum of every package. The Release file is signed with an OpenPGP key, and apt aborts when a check fails. Trusted keys are kept as files in /etc/apt/trusted.gpg.d/ (the debian-archive-keyring package manages Debian’s) and apt-key has been removed in Debian 13. For a third-party repository, the key is converted to binary with gpg --dearmor and stored, for example under /usr/share/keyrings/; verifying that key is left to the administrator. A publisher builds the metadata with apt-ftparchive and signs it:

$ apt-ftparchive release dists/stable > dists/stable/Release
$ gpg -abs -o dists/stable/Release.gpg dists/stable/Release

Source: Debian Wiki — SecureApt. The page is partly dated: it does not describe the inline-signed InRelease file or the signed-by option for source entries, so check the current apt-secure(8) and sources.list(5) manual pages before publishing a repository. The commands were not run while the page was written.

For RPM repositories, DNF has separate switches, all defaulting to False:

DNF option Effect

gpgcheck

Checks GPG signatures on packages from the repository.

repo_gpgcheck

Checks GPG signatures on the repository metadata; its keys are stored separately for each repository.

localpkg_gpgcheck

Checks signatures on package files installed from a local file.

gpgkey

URLs of the key files used for those checks.

Source: DNF Configuration Reference. A published repository that ships gpgcheck=0 or omits the key gives users no verification, so the repository file that your documentation tells users to install should set the checks on explicitly. Building .deb and .rpm files with nfpm, hosting repositories and the Snap, AppImage and Nix channels are on Native Packages & Installers.

A release pipeline for shelf

The three signing paths run on three different machines (a Mac, a Windows host and any Linux runner), which is why most projects use a release matrix. The shape is: build per target, sign on the host that can sign, collect the signed outputs, checksum and sign the checksum file, and publish.

# .github/workflows/release.yml (outline: not run while this page was written)
jobs:
  macos:
    runs-on: macos-latest
    steps:
      - run: swift build -c release --arch arm64 --arch x86_64
      - run: codesign --force --options runtime --timestamp --identifier com.example.shelf
             --sign "$DEVELOPER_ID_APP" .build/apple/Products/Release/shelf
      - run: ditto -c -k --keepParent .build/apple/Products/Release/shelf shelf-macos.zip
      - run: xcrun notarytool submit shelf-macos.zip --keychain-profile shelf-notary --wait
  windows:
    runs-on: windows-latest
    steps:
      - run: cargo build --release
      - run: signtool sign /fd SHA256 /tr $env:TIMESTAMP_URL /td SHA256 /n "Example Corp" target\release\shelf.exe
  publish:
    needs: [macos, windows]
    runs-on: ubuntu-latest
    permissions: { id-token: write, contents: write }
    steps:
      - run: sha256sum shelf-* > SHA256SUMS
      - run: cosign sign-blob SHA256SUMS --bundle SHA256SUMS.sigstore.json --yes

Keep the certificates and the notary API key in the CI system’s secret store and give the job that uses them the smallest scope that works; the build, sign, package and publish pipeline as a whole is drawn on Native Binaries & Cross-Compilation. The workflow is an outline: the keychain import for the Developer ID certificate on a hosted macOS runner and the runner’s access to a Windows certificate are steps that each provider documents separately, and they are not shown here.

Pitfalls

Pitfall What to do

Expecting stapler staple shelf to work after notarizing a bare Mach-O

It is documented as not possible. Staple a .pkg or .dmg, or accept the online ticket lookup for a ZIP.

Shipping the ad hoc linker signature

flags=0x20002(adhoc,linker-signed) and TeamIdentifier=not set mean no identity; re-sign with Developer ID before notarizing.

Signing, then modifying the file

lipo, strip and install_name_tool after signing break the signature. Sign last.

Forgetting --timestamp or /tr

Notarization requires the secure timestamp on macOS; on Windows the signature expires with the certificate.

A debug entitlement in the release build

com.apple.security.get-task-allow set to true is a notarization failure.

Passing a PFX password or notary key on the command line

It is visible in process lists and logs. Use the keychain profile, a certificate store or a cloud signer.

Verifying with signtool verify and no /pa

The default policy is the driver policy; ordinary programs need /pa.

A new signing identity every release

SmartScreen reputation attaches to the file and certificate and restarts when the identity changes.

gpgcheck=0 or an unpublished key fingerprint

Users cannot verify what they install; publish the fingerprint out of band.

Verifying a keyless signature without pinning the identity and issuer

Anyone who can obtain a Sigstore certificate would pass.

Treating a signature or an attestation as a safety certificate

It identifies the publisher and the build. It does not say the code is harmless.

Summary

  • macOS: sign with Developer ID Application, the hardened runtime and --timestamp, notarize a ZIP, .pkg or .dmg, and staple the .pkg or .dmg. A bare Mach-O tool cannot be stapled; ship it inside a stapled container, or rely on the online ticket.

  • Windows: signtool sign with /fd SHA256, /tr and /td SHA256, verify with /pa, sign every executable and installer, and expect a SmartScreen warning until reputation builds.

  • Linux: publish and sign a SHA256SUMS file with cosign or GPG, publish provenance, and distribute through repositories whose metadata is signed and checked by apt or dnf.