The short answer
You can sign a Windows EXE, MSI, or DLL on Linux or macOS with osslsigncode, an OpenSSL-based Authenticode signer. Install it (apt install osslsigncode or brew install osslsigncode), then run osslsigncode sign -pkcs11module <your-token.so> -h sha256 -ts http://time.certum.pl -in app.exe -out app-signed.exe. The signature is identical to one made by signtool on Windows. The one rule the operating system can't change: for a publicly trusted certificate the private key must live on a hardware token, an HSM, or a cloud signing service (reached over PKCS#11) — a requirement in force since June 1, 2023 — never in a copyable .p12 file.
On this page
Signing your own software? Compare OV and EV code signing certificates and current prices.
Why sign Windows code on Linux at all?
Plenty of teams ship Windows binaries without owning a single Windows machine in their build path. Their CI runs on Linux, their developers are on macOS, and the release artifact is a cross-compiled EXE or an installer. Microsoft's own signing tool, signtool, runs only on Windows, so the usual advice — "just run signtool" — quietly assumes a platform half these teams don't have. osslsigncode closes that gap.
It produces a real Authenticode signature: the same structure Windows validates when a user double-clicks your installer, the same thing that swaps the "Unknown Publisher" label for your verified name. Nothing about the trust chain is weaker because you signed on Linux. What changes is only the tooling around the key, which is where the rest of this guide spends its time.
osslsigncode: the cross-platform Authenticode signer
osslsigncode is a small command-line tool built on OpenSSL that signs and verifies Authenticode files. It handles PE binaries (EXE, DLL, SYS), MSI installers, CAB and CAT files, APPX/MSIX packages, and scripts. As of July 2026 the current release is 2.9 (June 2024), which dropped the old libcurl dependency and talks to timestamp servers through OpenSSL's own HTTP client.
Install it from your package manager. On Debian or Ubuntu that's sudo apt install osslsigncode; on Fedora, sudo dnf install osslsigncode; on macOS, brew install osslsigncode. Confirm the version before you rely on it, because older 1.x builds behave differently around timestamps and PKCS#11:
# Debian / Ubuntu
sudo apt install osslsigncode
# macOS (Homebrew)
brew install osslsigncode
osslsigncode --version # expect 2.x, e.g. 2.9osslsigncode isn't the only option — jsign, a Java tool, covers the same ground and is a favourite when the key lives in a cloud KMS. The trade-offs are small; here's how the three common signers line up.
Where your key has to live (you can't just use a .p12)
This is the step that trips people up, and it has nothing to do with Linux. Since June 1, 2023, every publicly trusted code signing certificate — OV and EV alike — must have its private key generated and stored on hardware certified to at least FIPS 140-2 Level 2 or Common Criteria EAL 4+, with the key non-exportable. The soft PFX/.p12 file you used to copy onto a build server is dead for public code signing.
So before osslsigncode can sign anything the public will trust, you need a code signing certificate whose key sits on a compliant device: a CA-shipped USB token, your own HSM, or a cloud signing service. If you don't have one yet, that's the piece to sort out first — our code signing certificates are issued through Certum with the hardware or cloud key delivery the current rules require. osslsigncode reaches whichever device you have through the same interface: PKCS#11, a standard C library (a .so file) that lets a tool ask a token to sign without ever seeing the key.
osslsigncode will still accept a plain -pkcs12 cert.p12 for local testing against a self-signed or internal certificate, and that's fine for a smoke test. Just know the resulting signature won't chain to a trusted root on anyone else's machine. For anything you distribute, the key goes on hardware. If you're weighing a physical token against a cloud key, we compare them in cloud code signing vs USB tokens.
Sign an EXE with a hardware token (PKCS#11)
With a token plugged in, signing is one command. The work is telling osslsigncode which PKCS#11 library to load and which objects on the token are your certificate and key. Most Linux tokens expose an OpenSC-compatible library; SafeNet and other vendors ship their own .so. Point -pkcs11module at it, then identify the certificate and key by their PKCS#11 URIs.
# 1. Discover the objects on your token (label, ID, URI)
p11tool --provider /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so --list-all
# 2. Sign the EXE, hashing with SHA-256 and timestamping over RFC 3161
osslsigncode sign \
-pkcs11module /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so \
-pkcs11cert 'pkcs11:object=Certificate' \
-key 'pkcs11:object=Private%20Key' \
-h sha256 \
-ts http://time.certum.pl \
-in app.exe \
-out app-signed.exeA few details decide whether this works on the first try. The library path differs by distro and token vendor, so use the path p11tool confirms rather than one copied from a blog. The -in and -out files must be different; osslsigncode won't sign a file in place. And the token will prompt for its PIN — in an interactive shell that's fine, but for automation you'll want to supply it without a prompt, which is the next section's problem.
Sign with a SimplySign cloud key on Linux
A physical token is awkward on a Linux CI runner: there's no USB port on an ephemeral cloud VM, and a token shared across build agents becomes a single point of failure. A cloud signing service solves that by keeping the key in the CA's HSM and exposing it as a session. With Certum SimplySign, the desktop app opens that session and presents your certificate as a virtual smart card, which osslsigncode then reaches over PKCS#11 — same tool, same flags, different module.
# SimplySign Desktop is running and a cloud session is open.
# It exposes the certificate through the p11-kit client module.
osslsigncode sign \
-pkcs11module /usr/lib/x86_64-linux-gnu/p11-kit-client.so \
-pkcs11cert 'pkcs11:model=SimplySign%20C' \
-key 'pkcs11:model=SimplySign%20C' \
-h sha256 \
-ts http://time.certum.pl \
-in app.exe \
-out app-signed.exeThe exact PKCS#11 URI depends on how your session names the certificate; list the objects with p11tool --list-all against the p11-kit module and copy the URI you see. The session is tied to the machine where it was opened, so in CI you keep it alive on a dedicated agent rather than a fresh hosted runner. The wider pattern — where to run the session, how to gate the signing job — is exactly what we work through in code signing in CI/CD.
Timestamp and verify the signature
Timestamping isn't a nice-to-have; it's the difference between a build that keeps working and one that stops the day your certificate expires. The -ts flag in the commands above requests an RFC 3161 timestamp — use -ts, not the legacy -t — so a trusted authority records when you signed. That proof is what lets Windows keep trusting an already-shipped binary long after the certificate's own validity ends, which matters more now that certificates issued from March 1, 2026 max out at 460 days.
Once signed, verify before you ship. osslsigncode reads back the signature and its timestamp, and it's the same check you'd want as a gate in a pipeline:
osslsigncode verify -in app-signed.exe
# Look for: "Signature verification: ok"
# and a "Timestamp" block with a real date.If verification reports a signature but no timestamp, treat that as a failure and sign again — a signed-but-untimestamped release is the classic half-success that sails through to publish and then breaks on expiry day. The mechanics of why a timestamp outlives the certificate are worth the five minutes in our guide to code signing timestamps.
Errors you'll probably hit
Most first-run failures on Linux come from three places: the PKCS#11 module path, the object URIs, and the timestamp server. None of them mean the tool is broken — they mean osslsigncode couldn't find something you named. Here's how the common ones read and what fixes them.
- "Failed to load pkcs11 module" / "unable to load engine." The path after
-pkcs11moduleis wrong for your distro, or the module's own dependencies aren't installed. Find the real path withp11tool --list-tokensand install the token's middleware (OpenSC, or the vendor package) first. - "No certificate found" / empty object list. The PKCS#11 URI in
-pkcs11certor-keydoesn't match an object on the token. Runp11tool --list-alland copy the exact URI it prints, URL-encoding spaces as%20. - PIN prompt hangs in CI. An interactive PIN prompt has nothing to answer it on a runner. Supply the PIN non-interactively (osslsigncode's
-passfor the module, or the vendor's PIN-caching mechanism) and keep it in a secret, not the command line. - Timestamp step fails or hangs. The runner can't reach the timestamp URL, or you used
-twith a server that only speaks RFC 3161. Confirm outbound access to the timestamp host and prefer-tswith a SHA-256 digest. - Signature verifies for you but users still see "Unknown Publisher." You signed against a self-signed or internal certificate. Only a certificate from a publicly trusted CA, key on compliant hardware, produces a signature strangers' machines trust.
FAQ
Need the certificate osslsigncode signs with?
osslsigncode handles the signing; the trust comes from the certificate behind it. My-SSL issues standard (OV) and EV code signing certificates through Certum, a publicly trusted certificate authority, with the hardware token or SimplySign cloud key the current rules require — the same PKCS#11 devices this guide connects to. Compare the options on the code signing certificates page.
Related reading
- How to sign an EXE with SignTool — the Windows-native counterpart, flag for flag.
- Cloud code signing vs USB tokens — where your signing key should live, and the trade-offs.
- Code signing in CI/CD — automating signatures on Linux runners without exposing the key.
Sources worth checking directly
- osslsigncode (mtrojnar/osslsigncode) — tool documentation, options, and releases (PKCS#11,
-ts, supported file types) - CA/Browser Forum — Baseline Requirements for Code Signing (hardware key rule since June 2023; 460-day validity from March 2026)
- Microsoft Learn — Authenticode Digital Signatures (what a valid PE signature must contain)