Skip to main content

    How to Sign a Windows EXE on Linux or macOS (osslsigncode)

    Sign Windows EXE, MSI, and DLL files from Linux or macOS with osslsigncode. Connect a hardware token or SimplySign cloud key over PKCS#11, then timestamp.

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

    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.

    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.

    How osslsigncode signs a Windows file without the key leaving its hardwareA flow diagram. On a Linux or macOS build machine, osslsigncode connects over PKCS#11 to a private key that stays inside a USB token or the Certum SimplySign cloud HSM. It sends only the file hash to be signed, requests an RFC 3161 timestamp from a timestamp authority, and writes out a signed Windows EXE. The key itself never reaches the build machine.Linux / macOS buildosslsigncodePKCS#11hash in, signature outCertified hardwareprivate key never leavesUSB tokenSimplySign cloud HSM(no token to plug in)Timestamp authorityRFC 3161 (time.certum.pl)-tsapp-signed.exe
    The operating system you build on is a detail; the part that must not change is the key staying inside its hardware while osslsigncode only ever sends it a hash to sign.

    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:

    Install and check the version
    # Debian / Ubuntu
    sudo apt install osslsigncode
    
    # macOS (Homebrew)
    brew install osslsigncode
    
    osslsigncode --version   # expect 2.x, e.g. 2.9

    osslsigncode 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.

    signtool, osslsigncode and jsign compared for cross-platform signingA comparison panel across three Authenticode signers. Only osslsigncode and jsign run on Linux and macOS; signtool is Windows-only. All three sign PE and MSI files and reach a hardware key over PKCS#11. The row for running on Linux or macOS is highlighted as the reason to choose osslsigncode or jsign.signtoolosslsigncodejsignRuns on Linux / macOSNoYesYesSigns PE, MSI, CABYesYesYesHardware key via PKCS#11YesYesYesRuntimeWindows SDKNative CJava (JVM)Best fitWindows dev boxShell / CI on LinuxCloud KMS + JVM
    The three tools produce the same kind of signature; the highlighted row is the only one that decides whether you can run it on the machine you actually build on.

    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.

    Sign with a USB token
    # 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.exe

    A 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.

    Anatomy of an osslsigncode sign commandA labeled breakdown of the osslsigncode sign command. -pkcs11module points at the PKCS#11 library for your token or cloud session, -h sha256 sets the digest, -ts requests an RFC 3161 timestamp so the signature outlives the certificate, and -in and -out name the input and signed output files. The timestamp flag is highlighted as the one people forget.osslsigncode sign -pkcs11module … -h sha256 -ts … -in app.exe -out app-signed.exe-pkcs11modulepath to your token / cloud .so-h sha256digest algorithm-ts <RFC 3161 URL>signature outlives the cert —the flag people forget-in / -outunsigned input and signed output (never the same file)
    If you strip the command to essentials, four flags carry the weight — and the highlighted timestamp is the one whose absence you won't notice until your certificate expires.

    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.

    Sign against an open SimplySign session
    # 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.exe

    The 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:

    Verify the signed file
    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 -pkcs11module is wrong for your distro, or the module's own dependencies aren't installed. Find the real path with p11tool --list-tokens and install the token's middleware (OpenSC, or the vendor package) first.
    • "No certificate found" / empty object list. The PKCS#11 URI in -pkcs11cert or -key doesn't match an object on the token. Run p11tool --list-all and 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 -pass for 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 -t with a server that only speaks RFC 3161. Confirm outbound access to the timestamp host and prefer -ts with 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

    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)