Saturday, November 29, 2025

Signing ClickOnce Office/Excel Plugin using Azure Trusted Signing

Task

I have an Microsoft Office Excel plugin application that historically has been deployed using ClickOnce publishing. The app that has been running for several years without changes. Now some changes were requested by the customer, and I had to figure out how to sign the ClickOnce application considering the changes the industry has made over the last couple of years for certificates.

I was no longer able to use the UI in Visual Studio IDE for ClickOnce Signing. 


CodeSigning certificates are not cheap, so I wanted to explore Azure Trusted Signing and if it could be used to sign ClickOnce Office plugin applications.

I managed in the end, but it was not as simple as I thought at all.


Why the Code Signing Process Changed

The primary motivation was to enhance security and prevent the widespread misuse of compromised private keys, which were often easily stolen when stored as software files (like PFX) on potentially insecure development machines. The new requirements became effective on June 1, 2023. All major Certificate Authorities (CAs), such as DigiCert, Sectigo, and GlobalSign, agreed to adhere to these new rules. They now issue all new standard (OV/IV) and EV code signing certificates with the private keys generated and stored on secure hardware devices, such as USB tokens or Hardware Security Modules (HSMs), that meet specific security standards (FIPS 140-2 Level 2 or Common Criteria EAL 4+).

See GlobalSign's notice here 


Prerequisites: Azure Trusted Signing setup

Follow steps 1-7 from https://melatonin.dev/blog/code-signing-on-windows-with-azure-trusted-signing/.

I also reviewed the following blog posts that had some details that were helpful.

Proces for Signing ClickOnce Office/Excel Plugin using Azure Trusted Signing 


None of the above blog posts gave me a fully working solution since they could not fully sign the ClickOnce application's manifest files (XML files), so I spent some more time searching on Google.

I ended up finding this issue "Added support for Trusted Signing" on Github - Sign CLI. Reviewing the files lead to the below steps.

  • Open Powershell as Administrator
  • Log in on the relevant Azure account where you have created the above subscription and Trusted Signing via the powershell command line

    az login


  • My output on the command line was something like the below

    Select the account you want to log in with. For more information on login with Azure CLI, see https://go.microsoft.com/fwlink/?linkid=2271136

    Retrieving tenants and subscriptions for the selection...

    [Tenant and subscription selection]

    No     Subscription name               Subscription ID                       Tenant
    -----  ------------------------------  ------------------------------------  -------------
    [1] *  ABCCodeSigningSubscription      12345678-1234-1234-1234-123456789012  ABC

    The default is marked with an *; the default tenant is 'ABC' and subscription is 'ABCCodeSigningSubscription' (12345678-1234-1234-1234-123456789012).

    Select a subscription and tenant (Type a number or Enter for no changes): 1

    Tenant: ABCTenant
    Subscription: ABCCodeSigningSubscription (12345678-1234-1234-1234-123456789012)

    [Announcements]
    With the new Azure CLI login experience, you can select the subscription you want to use more easily. Learn more about it and its configuration at https://go.microsoft.com/fwlink/?linkid=2271236

    If you encounter any problem, please open an issue at https://aka.ms/azclibug

    [Warning] The login output has been updated. Please be aware that it no longer displays the full list of available subscriptions by default.

  • Install the Sign CLI (for this blog post I used https://github.com/dotnet/sign version 0.9.1-beta.25379.1+ba6e717abf74a693f0f9c5e891c0e3ef624956b3)

    dotnet tool install --tool-path . --prerelease sign 

  • By reviewing the codechanges in https://github.com/dotnet/sign/pull/716/files and using the commandline help for trusted signing


    Description:
      Use Trusted Signing.

    Usage:
      sign code trusted-signing <file(s)>... [options]

    Arguments:
      <file(s)>  File(s) to sign.

    Options:
      -tse, --trusted-signing-endpoint <trusted-signing-endpoint> (REQUIRED)                         The Trusted Signing Account endpoint. The value must be a URI that aligns to the region that your Trusted Signing Account and Certificate Profile were created in.
      -tsa, --trusted-signing-account <trusted-signing-account> (REQUIRED)                           The Trusted Signing Account name.
      -tscp, --trusted-signing-certificate-profile <trusted-signing-certificate-profile> (REQUIRED)  The Certificate Profile name.
      -act, --azure-credential-type <azure-cli|azure-powershell|managed-identity|workload-identity>  Azure credential type that will be used. This defaults to DefaultAzureCredential.
      -mici, --managed-identity-client-id <managed-identity-client-id>                               The client id of a user assigned ManagedIdentity.
      -miri, --managed-identity-resource-id <managed-identity-resource-id>                           The resource id of a user assigned ManagedIdentity.
      -an, --application-name <application-name>                                                     Application name (ClickOnce).
      -d, --description <description>                                                                Description of the signing certificate.
      -u, --description-url <description-url>                                                        Description URL of the signing certificate.
      -b, --base-directory <base-directory>                                                          Base directory for files.  Overrides the current working directory. [default: D:\]
      -o, --output <output>                                                                          Output file or directory. If omitted, input files will be overwritten.
      -pn, --publisher-name <publisher-name>                                                         Publisher name (ClickOnce).
      -fl, --file-list <file-list>                                                                   Path to file containing paths of files to sign or to exclude from signing.
      -rc, --recurse-containers                                                                      Sign container contents. [default: True]
      -fd, --file-digest <file-digest>                                                               Digest algorithm to hash files with. Allowed values are 'sha256', 'sha384', and 'sha512'. [default: SHA256]
      -t, --timestamp-url <timestamp-url>                                                            RFC 3161 timestamp server URL. [default: http://timestamp.acs.microsoft.com/]
      -td, --timestamp-digest <timestamp-digest>                                                     Digest algorithm for the RFC 3161 timestamp server. Allowed values are sha256, sha384, and sha512. [default: SHA256]
      -m, --max-concurrency <max-concurrency>                                                        Maximum concurrency. [default: 4]
      -v, --verbosity <Critical|Debug|Error|Information|None|Trace|Warning>                          Sets the verbosity level. Allowed values are 'none', 'critical', 'error', 'warning', 'information', 'debug', and 'trace'.
                                                                                                     [default: Warning]
      -?, -h, --help                                                                                 Show help and usage information

  • I ended up with the following command

    & ./sign code trusted-signing "D:\ClickOncePublishDirectory\ABCApp.vsto" -tse https://weu.codesigning.azure.net -tsa ABCCodeSigning -tscp ABCCodeSigningCertificate -an ABCApp -pn ABC -t "http://timestamp.acs.microsoft.com" -v debug 

  • I had problems signing the ClickOnce initially due to having quite a few older versions under the "Application Files" directory, once I removed the older versions and only had the lastest build it worked fine

I  hope this helps other people that may be facing a similar task.