Skip to main content

    How to Sign a ClickOnce Application (Step by Step)

    Sign ClickOnce manifests with mage and a hardware-token key, timestamp them, and avoid the certificate-renewal trap that forces users to reinstall the app.

    MS
    My-SSL Team
    ·
    12 min read
    ·
    Published July 31, 2026
    ·
    Last updated July 31, 2026

    In short

    Signing a ClickOnce application means signing its two manifests — the application manifest and the deployment manifest — with a code signing certificate, using mage (or dotnet-mage for .NET 5 and later). Sign the application manifest, update the deployment manifest to reference it, then sign the deployment manifest — adding an RFC 3161 timestamp to each. The catch that trips most publishers: a ClickOnce install trusts one specific public key, so renewing to a certificate with a new key breaks automatic updates and forces every user to uninstall and reinstall.

    ClickOnce signing looks like it should be a SignTool job and isn't. The thing you sign isn't the EXE — it's a pair of XML manifests, signed with a different tool (mage), and the identity baked into that signature is what ClickOnce checks every time it installs or updates the app. Two things catch people out: reaching a private key that now lives on a hardware token instead of in a PFX file, and discovering — usually at renewal time — that a new certificate quietly severs the update path to everyone who already installed. This guide walks the signing steps, then the two problems, so the first signed build isn't the one that strands your users.

    What's different about ClickOnce signing

    A ClickOnce deployment isn't signed by signing the EXE. It's signed by signing two XML manifests — the application manifest (MyApp.exe.manifest) and the deployment manifest (MyApp.application) — with a code signing certificate through mage. That manifest signature carries the publisher identity ClickOnce validates on install and on every update. The compiled EXE is a separate, optional Authenticode signature.

    What gets signed in a ClickOnce deploymentA layered diagram. The application manifest (MyApp.exe.manifest) lists and hashes the program files and is signed with the code signing certificate. The deployment manifest (MyApp.application) references the signed application manifest and is signed with the same certificate; it carries the publisher identity, highlighted, that ClickOnce trusts. Separately, the compiled EXE and the setup.exe bootstrapper are Authenticode-signed with SignTool, which SmartScreen reads but which does not sign the manifests.Two signatures, two toolsSigned with mage — this is "signing a ClickOnce app"MyApp.exe.manifestApplication manifest — lists and hashes every program file.Sign this first.referenced by hashMyApp.applicationDeployment manifest — version, update rules, and thepublisher identity (public key) ClickOnce trusts.Signed with SignToolMyApp.exeAuthenticode on the binarysetup.exebootstrapper — read bySmartScreen + UACGood practice, but doesnot sign the manifests.
    The signature ClickOnce actually enforces lives on the manifests, not the EXE — which is why a perfectly SignTool-signed executable can still deploy as an untrusted ClickOnce app.

    The two manifests do different jobs. The application manifest lists every file in the app and stores a hash of each, so a tampered file is detected at launch. The deployment manifest sits above it: it holds the version, the update location and rules, and — the part that matters most for this guide — the publisherIdentity, which records the public key of the signing certificate. Change that key and you've changed who ClickOnce thinks the publisher is.

    You should still Authenticode-sign the compiled EXE and the setup.exe bootstrapper with SignTool — that's what the browser download, SmartScreen, and the UAC prompt read. But SignTool never touches the manifests, and a beautifully signed EXE inside an unsigned-manifest deployment still installs as an untrusted application. The two signatures are complementary, not interchangeable; the mage half is the one unique to ClickOnce.

    What you need before you sign

    You need three things: a code signing certificate whose private key lives on certified hardware, the right mage for your target framework, and an RFC 3161 timestamp URL. mage ships with the Windows SDK and .NET Framework; for apps on .NET 5 and later you use dotnet-mage, installed as a global .NET tool. Both take the same signing flags.

    • A code signing certificate on hardware. Standard (OV) or EV both work for ClickOnce; the difference is validation depth and SmartScreen reputation speed, not signing ability. Since June 1, 2023, publicly trusted code signing keys must be generated and stored on a FIPS 140-2 Level 2 / Common Criteria EAL 4+ device — a USB token, HSM, or cloud signing service — so there's no software PFX to point mage at. The Certum code signing certificates My-SSL sells ship on a token or via SimplySign cloud signing and work with the mage commands below.
    • The matching mage. Classic desktop apps on .NET Framework use mage.exe from the Windows SDK. Apps on .NET 5+ use dotnet tool install -g Microsoft.DotNet.Mage and then dotnet-mage. Use a current version — the mage bug that silently signed with SHA-1 even when the certificate was SHA-256 was fixed in Visual Studio 2022 17.3.
    • A timestamp server. Any RFC 3161 endpoint, for example http://time.certum.pl for a Certum certificate. Timestamping isn't optional here: an untimestamped manifest signature dies with the certificate, and ClickOnce then refuses to install or update the deployment.

    One decision to make up front, because it shapes every command: where the key physically lives. That's the difference between a plain -CertHash and the extra provider flags in the hardware-token section below. If you're still choosing between a token and cloud signing, our token-vs-cloud comparison lays out the trade-offs for teams and CI.

    Sign the manifests with mage

    Signing is three commands in a fixed order: sign the application manifest, update the deployment manifest so it references the freshly signed application manifest, then sign the deployment manifest. The middle step is the one people skip — re-signing the application manifest changes its hash, and the deployment manifest has to be re-pointed at the new hash before it is signed, or the install fails with a mismatch error.

    The three-command ClickOnce signing orderA left-to-right flow of three steps. Step one: mage -Sign the application manifest. Step two: mage -Update the deployment manifest so it points at the freshly signed application manifest. Step three, highlighted: mage -Sign the deployment manifest. A note explains that skipping the update step in the middle is the most common mistake, because re-signing the app manifest changes its hash.Order matters: sign, update, sign1. Sign app manifestmage -SignMyApp.exe.manifest2. Update deploymentmage -Update-AppManifest ...3. Sign deploymentmage -SignMyApp.applicationSkip step 2 and the deployment manifest still points at the old hash — install fails with a"manifest does not match" / SHA mismatch error.
    Re-signing the application manifest changes its hash, so the deployment manifest must be re-pointed at it before it, too, is signed — that middle step is the one people forget.
    Sign both manifests (certificate in the Windows store)
    :: Thumbprint (SHA-1) of your code signing cert, no spaces:
    set HASH=1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d
    set TSA=http://time.certum.pl
    
    :: 1. Sign the application manifest
    mage -Sign MyApp.exe.manifest -CertHash %HASH% -TimestampUri %TSA%
    
    :: 2. Re-point the deployment manifest at it
    mage -Update MyApp.application -AppManifest MyApp.exe.manifest -CertHash %HASH%
    
    :: 3. Sign the deployment manifest
    mage -Sign MyApp.application -CertHash %HASH% -TimestampUri %TSA%
    
    :: On .NET 5+ replace "mage" with "dotnet-mage" — same flags.

    A few notes on the flags. -CertHash takes the SHA-1 thumbprint of the certificate as it appears in your Windows certificate store — copy it from certmgr.msc and strip the spaces. Using the thumbprint rather than a PFX path is what lets you sign against a key that can't be exported, which is every publicly trusted code signing key today. If Visual Studio publishes the app for you, its Publish tool runs the equivalent of these commands using the certificate on the Signing tab — but a scripted build wants mage directly so signing lives in the pipeline, not on someone's machine.

    Re-sign the deployment manifest after any change

    Anything that edits a manifest after signing — bumping the version, changing the update URL, editing the config — invalidates the signature. That's exactly the case when you promote the same build from a test URL to production: change the deployment provider URL with mage -Update, then sign the deployment manifest again. Never hand-edit a signed .application file and ship it unsigned.

    Signing with a key on a hardware token

    Since the hardware-key mandate, the private key sits on a token, HSM, or cloud signing service and can't be exported to a file. mage handles this by signing against the certificate in your Windows store by thumbprint. When the key is held by a smart-card key storage provider, add -CryptoProvider and -KeyContainer so mage can reach it through the token's middleware. The exact provider name comes from that middleware.

    Which key locations mage can sign ClickOnce manifests fromA four-row panel. A USB token or on-prem HSM reached through a CSP or KSP works and is highlighted as the practical path. A CA cloud signing service that exposes a CSP or KSP also works. A key in Azure Key Vault commonly fails because mage expects an exportable private key. Microsoft Trusted Signing is not supported for ClickOnce manifests as of mid-2026.Where the key can live for mage signingUSB token / on-prem HSM (CSP or KSP)-CertHash, plus -CryptoProvider / -KeyContainerWorks — the practical path for most publishersCA cloud signing (KSP library)e.g. a provider's KSP exposed to mageWorks when the service ships a Windows CSP/KSPAzure Key Vault (direct)mage wants an exportable keyCommonly fails: "does not contain a private key"Microsoft Trusted SigningEXE / MSI / MSIX onlyNot supported for ClickOnce manifests (mid-2026)
    The rule of thumb: mage can sign wherever a Windows CSP or KSP exposes the key — a token or HSM — but not from a service that only offers a cloud-signing API without one.
    Sign against a token-held key through its CSP
    :: The provider name and container come from your token's middleware,
    :: e.g. SafeNet: "eToken Base Cryptographic Provider".
    mage -Sign MyApp.application ^
      -CertHash %HASH% ^
      -CryptoProvider "eToken Base Cryptographic Provider" ^
      -KeyContainer "<your key container>" ^
      -TimestampUri http://time.certum.pl

    Two failure modes are worth knowing before you hit them. First, a key that lives only in Azure Key Vault, accessed directly, tends to fail with "this certificate does not contain a private key" — mage expects an exportable key it can hand off, which Key Vault deliberately won't provide. The way around it is a signing tool or KSP library that talks to the vault on mage's behalf, rather than pointing mage at the vault. Second, Microsoft Trusted Signing (formerly Azure Code Signing) does not, as of mid-2026, sign ClickOnce manifests at all — it covers the SignTool file types. Confirm the current state in the Trusted Signing docs before you design a pipeline around it.

    The reliable pattern for automated ClickOnce signing is a token or HSM on a dedicated signing agent, reached through its CSP or KSP, or a CA cloud signing service that ships a Windows KSP library mage can load. If you're wiring this into a build server, the same key-isolation rules that apply to any code signing in CI apply here — our CI/CD signing guide covers keeping the key off the runner.

    The certificate-renewal trap

    A ClickOnce installation trusts the specific public key that signed its deployment. When you renew a publicly trusted code signing certificate, you almost always get a new key pair — the hardware rules make the old key non-exportable, so it can't be carried over. ClickOnce then reads the update as coming from a different publisher and refuses to apply it, and the only way onto the new certificate is for each user to uninstall and reinstall.

    Why renewing the certificate breaks ClickOnce updatesTwo timelines. On top, the same key across a renewal: the old and new certificates share public key A, so an installed app keeps updating normally. On the bottom, and highlighted as the real-world case, a renewed publicly trusted certificate carries a new key B on non-exportable hardware; ClickOnce sees a different publisher identity and refuses the update, so users must uninstall and reinstall.The public key is the identity ClickOnce trustsSame key on renewal (rare now)Old cert · key Asigned the installNew cert · key Asame public keyUpdate appliesno user actionNew key on renewal (the norm with hardware keys)Old cert · key Asigned the installNew cert · key Bdifferent public keyUpdate refuseduninstall + reinstallNon-exportable hardware keys can't be carried across a renewal, so the bottom path is the default one.
    This is unique to ClickOnce: SignTool-signed EXEs and MSIs don't care that a renewed certificate has a new key, but a ClickOnce install ties its whole update chain to the original public key.

    This used to be a once-every-few-years problem. It isn't anymore. Under CA/Browser Forum ballot CSC-31, code signing certificates issued on or after March 1, 2026 max out at 460 days — so the renewal that breaks ClickOnce updates now comes around roughly every 15 months instead of every three years. The old escape hatch (renewing with the same key pair, or the community "RenewCert" trick) doesn't apply to a publicly trusted certificate, because that key legitimately cannot leave the hardware. What changed and why is covered in our 460-day validity explainer.

    There is no way to make ClickOnce silently accept a new key on an existing install, so plan the cutover instead of being surprised by it:

    • Time the switch, don't stack it. Sign with the new certificate as a clean version bump and expect the reinstall. Doing it on a scheduled release — not mid-cycle — means one planned interruption rather than a mystery failure.
    • Ship a migration build first. A common pattern: while the old certificate is still valid, push an update (signed with the old key, so it installs normally) that uninstalls the app and relaunches the installer from the location signed with the new certificate. Users move across without hunting for an uninstall dialog.
    • Tell users in advance. If a manual uninstall/reinstall is unavoidable — internal line-of-business apps often just accept this — a short heads-up and a link to the fresh installer turns a support spike into a non-event.
    • Consider whether ClickOnce is still the right channel. For apps that ship often, MSIX with an auto-update source, or a plain signed installer, sidestep the public-key-identity coupling entirely. That's a bigger decision, but the 460-day cap is a fair prompt to make it.

    Verify, ship, and the VSTO case

    Before you publish, check the signatures on both manifests and confirm the timestamp took. Run mage -Verify MyApp.application and mage -Verify MyApp.exe.manifest; for the compiled binaries, verify the Authenticode signature with signtool verify /pa /v. Then do one real install from the published location on a clean machine — the surest test that the chain resolves and the publisher shows correctly.

    Verify manifests and binaries
    mage -Verify MyApp.exe.manifest
    mage -Verify MyApp.application
    
    :: The compiled EXE and bootstrapper (Authenticode):
    signtool verify /pa /v MyApp.exe
    signtool verify /pa /v setup.exe

    A valid manifest signature still doesn't silence SmartScreen on its own. A ClickOnce app from a brand-new certificate can show the "Windows protected your PC" prompt until download reputation builds against your identity — the same reputation model that applies to any signed download, walked through in our SmartScreen reputation guide. Signing is what makes the reputation you earn stick to your name across builds.

    VSTO add-ins sign the same way. A Visual Studio Tools for Office add-in — for Excel, Word, or Outlook — deploys through ClickOnce and carries the same two manifests, signed with the same mage commands. That also means it inherits the same renewal trap: change the signing key and installed add-ins won't take the update until they're removed and reinstalled. The one extra wrinkle is trust — VSTO add-ins from outside the Office store need the publisher certificate trusted on the target machine (via the Trusted Publishers store or an inclusion list policy) before the add-in loads without a prompt.

    Common ClickOnce signing errors

    ClickOnce signing fails in a handful of recognizable ways: a hash mismatch from skipping the update step, a missing-private-key error when mage can't reach a hardware key, an expiry failure from a missing timestamp, and the update that silently stops after a certificate change. Match your symptom below.

    "Reference in the deployment does not match the identity..." / manifest hash mismatch

    You re-signed the application manifest but didn't re-point the deployment manifest at it. Run mage -Update MyApp.application -AppManifest MyApp.exe.manifest before signing the deployment manifest. The order is always: sign app manifest, update deployment manifest, sign deployment manifest.

    "This certificate does not contain a private key"

    mage found the certificate but can't reach its key. With a token or HSM, add -CryptoProvider and -KeyContainer for the middleware holding the key. With a key in Azure Key Vault, mage can't sign directly — use a KSP library or signing tool that fronts the vault. Confirm the token is plugged in and its middleware is installed for the user running the build.

    The deployment installs today but fails once the certificate expires

    The manifests weren't timestamped, so the signatures expired with the certificate. Re-sign both manifests with -TimestampUri pointing at an RFC 3161 server and republish. Going forward, treat the timestamp flag as mandatory on every signing command.

    Updates stopped reaching users after I renewed the certificate

    Expected behavior, not a bug: the renewed certificate has a new public key, so ClickOnce treats the deployment as a different publisher. Affected users must uninstall and reinstall. The renewal-trap section above covers the migration-build pattern that moves them across with the least friction.

    mage signed with SHA-1 even though my certificate is SHA-256

    An older mage bug. Update to the mage that ships with Visual Studio 2022 17.3 or later (or a current dotnet-mage), which honors the certificate's hash algorithm. Re-sign the manifests after upgrading.

    Frequently Asked Questions

    Get instant answers to common questions about SSL certificates and our services.

    Still Have Questions?

    Our SSL experts are available 24/7 to help with any questions about certificates, installation, or technical issues.

    The bottom line

    Sign the application manifest, update the deployment manifest, sign the deployment manifest — all with mage, all timestamped, all against a key on certified hardware. Authenticode-sign the EXE and bootstrapper too, and plan the certificate renewal as a deliberate cutover, because a new key means a reinstall. If the missing piece is the certificate, My-SSL carries Certum code signing certificates from $99/year, and the document checklist shows what validation will ask for.