Skip to main content

Code Signing

Applies to
Windows
MacOS
Linux

Code signing is an essential part of application distribution. On Windows, applications without code signatures are likely to be flagged as viruses. On OSX, codesigning and Notarization is required before your application can be run by users.

On both platforms, signing needs to be performed by Velopack itself, this is because the Velopack binaries (such as Update and Setup) need to be signed at different points in the package build process.

Signing on Windows

Here is the general rule of thumb for Windows Defender/Smart Screen warnings:

  • You don't sign your code. You release MyApp-v1.exe. People get smart screen warnings for a while until the reputation on that file increases. You release MyApp-v2.exe. People get warnings again until MyApp-v2.exe builds up enough reputation.
  • You sign your code with an OV certificate. You release MyApp-v1.exe. People get smart screen warnings for a while until the reputation on that certificate increases. You release MyApp-v2.exe. People don't get warnings because the certificate already has a good reputation. Eventually your certificate expires and it comes time to release MyApp-v47.exe. You renew your certificate, and people get warnings again until the new certificate builds up reputation.
  • You sign your code with an EV certificate. People never get warnings.

Acquiring a code signing certificate

Azure Trusted Signing

As of April 2024, the most effective way to code-sign your product is via Azure Trusted Signing (ATS).

  • Costs USD$10/mo (or USD$120/yr) which is on-par or cheaper than code signing certificates from other issuers.
  • Full cloud solution / Does not require any physical hardware (HSM) shipped to you.
  • Signing can be automated (eg. in your CI pipeline)
  • ATS certificates get built-in instant reputation for smart-screen and defender.

ATS can be used with by following the Microsoft documentation for using ATS with signtool.exe, and then reading our docs below on how to configure signtool.exe with Velopack.

Other certificate vendors

If you instead wish to purchase a code signing certificate from a different vendor, Microsoft lists the following trusted issuers in their documentation:

There are also resellers which may have certificates from authorised issuers for lower prices, but the added risk is that you will get no official support for those certificates and identity verification proceedures may be more convoluted.

warning

Note that since June 1, 2023 there has been a policy change, meaning it is no longer possible to download your code signing certificate. It must be either stored in an authorised cloud HSM or a USB HSM will be shipped to you. Many of these solutions will require manual steps and not be compatible with automation / CI. Please review carefully before purchasing.

Signing via signtool.exe

Usually signing is accomplished via signtool.exe. If you already use this tool to sign your application, you can just pass your sign parameters straight to Velopack (minus the 'sign' command).

For example, if your signing command before was:

signtool.exe sign /td sha256 /fd sha256 /f yourCert.pfx /tr http://timestamp.comodoca.com

Then now with --signParams it would be:

vpk pack ... --signParams "/td sha256 /fd sha256 /f yourCert.pfx /tr http://timestamp.comodoca.com"
tip

You should always pass absolute paths in your signing arguments, as there is no guarentee that signtool will be run in the same working directory as vpk.

If you are new to using signtool.exe, you can check the command line reference here. I recommend getting signing working on a single binary first, using signtool.exe, before trying to get things working with the Velopack CLI.

If you are using a USB / HSM and have the following signing error Error: SignerSign() failed." (-2147023673/0x800704c7), this means that you are missing a password / unlock token. This can be added to your signing command with the /csp /k parameters. [Read more]

warning

Take care when providing parameters with spaces. You must wrap anything containing spaces with quotes and escape with a backslash. For example, --signParams "/n \"My Cert Name\"". If you are using an MSBuild <Exec> task or similar, you will need to use &quot; instead.

By default, Velopack will sign 10 files per call to signtool.exe, to speed up signing and reduce the number of times you need to interact with the console if you are using some kind of interactive signing method. This can be disabled with the --signParallel 1 argument.

Custom signing commands and tools

If you have more advanced signing requirements, such as a custom signing tool (eg. AzureSignTool.exe), then you can provide a command template instead, where {{file}} is the binary that Velopack will substitute and sign:

vpk pack ... --signTemplate "AzureSignTool.exe sign ... {{file}}"

The above will sign one file at a time, but if your signing tool supports batch signing multiple files at a time, you can specify {{file...}} instead.

Signing & Notarizing on OSX

Codesigning and Notarization is required before your application can be run by users, therefore it is a required step before deploying your application.

Creating code signing certificates

  1. First, you will need to create an account at https://developer.apple.com, pay the annual developer fee, and accept any license agreements.
  2. Navigate to your certificates: https://developer.apple.com/account/resources/certificates
  3. Click the (+) icon to create new certificates. You need to create both a Developer ID Installer and a Developer ID Application certificate for distribution of Velopack apps outside the Mac App Store. apple certificate list
  4. Open both certificates by clicking on them, press Download, and then double click the ".cer" file to install it to your local keychain.
warning

If your certificates show as untrusted in Keychain Access, or you receive a warning like "Warning: unable to build chain to self-signed root for signer "Developer ID Application: YourName", you are probably missing a development certificate on your machine which can be downloaded from here: https://www.apple.com/certificateauthority/

Setting up a NotaryTool profile

  1. Create an app-specific password: https://support.apple.com/en-us/102654. You will only be shown this password once, so save or write it down somewhere.
  2. Find your apple team ID: https://developer.apple.com/account#MembershipDetailsCard
  3. Store your Apple account credentials to a new NotaryTool profile:
    xcrun notarytool store-credentials \
    --apple-id "yourapple@account.com" \
    --team-id "your-located-team-id" \
    --password "your-generated-app-specific-password" \
    "your-local-profile-name-here"

Putting it all together

Now that you have your NotaryTool profile and code signing certificates installed, you can add the following parameters to your pack command:

vpk pack \
...
--signAppIdentity "Developer ID Application: Your Name" \
--signInstallIdentity "Developer ID Installer: Your Name" \
--notaryProfile "your-local-profile-name-here" \

When these parameters are specified and valid, Velopack will automatically code sign and notarize your application and installer packages.

tip

Velopack will use codesign and xcrun notarytool to sign and notarize your application. By default they will use the default keychain on your device to discover certificates and the notary profile. If you would like to use a different keychain, you must install your certificates and create your notary profile on that keychain and use the Velopack --keychain {path} argument to specify that Velopack should discover certificates from a non-default keychain.


If you do not provide an entitlements file for hardened runtime, one suitable for most dotnet apps will be provided for you automatically. If your app requires special entitlements and the defaults are not suitable, you should supply your own signing entitlements using --signEntitlements {path}


Even though your certificate may appear as Developer ID Application: Your Name (TeamName) in Keychain Access, you should not specify the team name as a part of your --signAppIdentity or --signInstallIdentity arguments. Your certificate should just be Developer ID Application: Your Name with no team.

Automate signing in CI/CD (Github Actions)

It is also posible to store your certificates and notary credentials as Action Secrets and sign your code during CI builds.

  1. Launch Keychain Access and open the "My Certificates" pane.

  2. Select each certificate (one at a time), right click and select "Export". Save as a p12 file and make note of the password. You can use the same password for both certificates.

    tip

    If you can't see the export option, or exporting as a .p12 is disabled, you may need to change which keychain or tab you are viewing. You should be on the "My Certificates" tab. See https://stackoverflow.com/questions/15662377/unable-to-export-apple-production-push-ssl-certificate-in-p12-format for more information.

  3. Copy the contents of the certificate to clipboard as base64, example:

    base64 -i CERT.p12 | pbcopy
  4. Create 7 Github Secrets for your Actions workflows

    • BUILD_CERTIFICATE_BASE64 (b64 of your app cert)
    • INSTALLER_CERTIFICATE_BASE64 (b64 of your installer cert)
    • P12_PASSWORD (password for the certificates)
    • APPLE_ID (your apple username)
    • APPLE_PASSWORD (your app-specific password from the notary step above)
    • APPLE_TEAM (your team id from the notary step above)
    • KEYCHAIN_PASSWORD (can be any random string, will be used to create a new keychain)
  5. Add a step to your workflow which installs the certificates and keychain profile. Here is an example:

    name: App build & sign
    on: push
    jobs:
    build_with_signing:
    runs-on: macos-latest
    steps:
    - name: Checkout repository
    uses: actions/checkout@v4

    - name: Install Apple certificates and notary profile
    env:
    BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
    INSTALLER_CERTIFICATE_BASE64: ${{ secrets.INSTALLER_CERTIFICATE_BASE64 }}
    P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
    APPLE_ID: ${{ secrets.APPLE_ID }}
    APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
    APPLE_TEAM: ${{ secrets.APPLE_TEAM }}
    KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
    run: |
    # create variables for file paths
    CERT_BUILD_PATH=$RUNNER_TEMP/build_certificate.p12
    CERT_INSTALLER_PATH=$RUNNER_TEMP/installer_certificate.p12
    KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db

    # import certificates from secrets
    echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERT_BUILD_PATH
    echo -n "$INSTALLER_CERTIFICATE_BASE64" | base64 --decode -o $CERT_INSTALLER_PATH

    # create temporary keychain
    security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
    security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
    security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

    # import certificates to keychain
    security import $CERT_BUILD_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
    security import $CERT_INSTALLER_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
    security list-keychain -d user -s $KEYCHAIN_PATH

    # create notarytool profile
    xcrun notarytool store-credentials --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM" --password "$APPLE_PASSWORD" velopack-profile

    - name: Compile your app
    ...

    - name: Create Velopack Release
    run: |
    dotnet tool install -g vpk
    vpk pack ... \
    --signAppIdentity "Developer ID Application: Your Name" \
    --signInstallIdentity "Developer ID Installer: Your Name" \
    --notaryProfile "velopack-profile" \
    --keychain $RUNNER_TEMP/app-signing.keychain-db

    - name: Clean up keychain
    if: ${{ always() }}
    run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db