# osslsigncode: Authenticode signing for PE and MSI files without Windows

> osslsigncode is a small C tool that implements the Authenticode signing and timestamping part of Microsoft's signtool.exe on top of OpenSSL and cURL, so binaries can be signed from Linux or macOS. It suits a release pipeline that builds on Linux but signs for Windows, and its PKCS#11 and cloud HSM paths are what make it usable in CI.

**mtrojnar/osslsigncode** — OpenSSL-based Authenticode signing for PE, CAB, CAT, MSI, APPX, and script file

- Repository: https://github.com/mtrojnar/osslsigncode
- Stars: 1,116 · Forks: 154
- Language: C
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mtrojnar-osslsigncode

## Why the tool exists at all

osslsigncode is a small tool that implements part of the functionality of the Microsoft tool signtool.exe, more exactly the Authenticode signing and timestamping, and it is based on OpenSSL and cURL, so it should be able to compile on most platforms where those exist.

The reason for its existence is stated in the README with unusual bluntness. signtool.exe makes good use of the CryptoAPI in Windows, and those APIs are not yet fully implemented in Wine, so the author could compile and build binaries using Wine on Linux but could not sign them, because signtool.exe would fail. So osslsigncode was born.

That is the whole design brief, and it explains the scope. This is a signing tool, not a general purpose Authenticode toolkit. It signs and timestamps, and it also supports signature verification, removal and extraction, which is what you need for a build pipeline that signs on one machine and verifies on another.

The formats it handles are the ones Windows users actually receive. PE files, meaning EXE, SYS and DLL, plus CAB, CAT, MSI and APPX. It also signs script files, with the extensions .ps1, .ps1xml, .psc1, .psd1, .psm1, .cdxml, .mof and .js, which is the set of files that PowerShell execution policy treats as signed, so a script that Windows will run with reduced restrictions needs the right signature.

One specific compatibility case is called out. It supports the equivalent of signtool.exe's -j javasign.dll -jp low option, which adds a valid signature for a CAB file containing Java files, and only the low parameter is currently supported.

## Building on Linux and macOS with CMake

The build section covers Unix-like systems, with a separate INSTALL.W32.md for Windows notes, and the README recommends downloading a release tarball rather than cloning from git.

The prerequisites on a Debian based distribution are short.

```bash
sudo apt update && sudo apt install cmake libssl-dev libcurl4-openssl-dev zlib1g-dev python3
```

On macOS the README uses Homebrew, and it pins the OpenSSL version, which matters because the build has to find the right libraries.

```bash
brew install cmake pkg-config openssl@1.1
export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"
```

CMake 3.17 or newer is required, and the README notes that on some systems you need cmake3 instead of cmake.

The configure and build sequence is the standard CMake one, with a list of optional parameters you can pass to it.

```bash
mkdir build && cd build && cmake -S ..
```

```bash
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_C_COMPILER=clang
-DCMAKE_PREFIX_PATH=[openssl directory];[curl directory]
-DCMAKE_INSTALL_PREFIX=[installation directory]
-DBASH_COMPLETION_USER_DIR=[bash completion installation directory]
```

```bash
cmake --build .
ctest -C Release
sudo cmake --install .
```

The test step runs ctest in the Release configuration, and there is a source tarball target that simulates the autotools make dist.

```bash
cmake --build . --target package_source
```

## Signing a PE file needs a certificate and a key

Before you can sign anything you need a Software Publishing Certificate and the corresponding private key, and the README points at an article on code signing as a starting point for the whole procedure.

The key formats are worth listing because they are where the friction usually is. The certificate file can be in SPC or PEM format. The private key must be a key file in DER or PEM format, and, if osslsigncode was compiled against OpenSSL 1.0.0 or later, it can also be in PVK format. There is also a PKCS#12 container path, where the certificate and key travel together behind a single password.

The basic sign invocation takes the certificate, the key, a name, a URL, an input and an output.

```bash
osslsigncode sign -certs <cert-file> -key <der-key-file> \
  -n "Your Application" -i http://www.yourwebsite.com/ \
  -in yourapp.exe -out yourapp-signed.exe
```

With a password protected key, the -pass option supplies it.

```bash
osslsigncode sign -certs <cert-file> \
  -key <key-file> -pass <key-password> \
  -n "Your Application" -i http://www.yourwebsite.com/ \
  -in yourapp.exe -out yourapp-signed.exe
```

And a PKCS#12 container collapses the two into one file and one password.

```bash
osslsigncode sign -pkcs12 <pkcs12-file> -pass <pkcs12-password> \
  -n "Your Application" -i http://www.yourwebsite.com/ \
  -in yourapp.exe -out yourapp-signed.exe
```

The -n and -i options are the values that end up in the signature's description, and the -i URL is what a user sees when they inspect the file's properties, so it is worth setting to something meaningful rather than leaving it as a placeholder.

## Timestamps and PKCS#11 tokens, the two things CI needs

Adding a timestamp is a single extra option, and it is the option that makes a signature survive certificate expiry.

```bash
osslsigncode sign -certs <cert-file> -key <key-file> \
  -n "Your Application" -i http://www.yourwebsite.com/ \
  -t http://timestamp.digicert.com \
  -in yourapp.exe -out yourapp-signed.exe
```

For a CI pipeline the more interesting capability is key handling without a file on disk. The PKCS#11 engine path takes an engine module, a PKCS#11 module and two object URIs, one for the certificate and one for the key, and the README shows it with SoftHSM.

```bash
osslsigncode sign \
  -engine /usr/lib64/engines-1.1/pkcs11.so \
  -pkcs11module /usr/lib64/pkcs11/libsofthsm2.so \
  -pkcs11cert 'pkcs11:token=softhsm-token;object=cert' \
  -key 'pkcs11:token=softhsm-token;object=key' \
  -in yourapp.exe -out yourapp-signed.exe
```

OpenSSL 3.0 brought a provider based architecture, and the README documents the equivalent provider route for OpenSSL 3.x only, using OpenSC as the example.

```bash
osslsigncode sign \
  -provider /usr/lib64/ossl-modules/pkcs11prov.so \
  -pkcs11module /usr/lib64/opensc-pkcs11.so \
  -pkcs11cert 'pkcs11:token=my-token;object=cert' \
  -key 'pkcs11:token=my-token;object=key' \
  -in yourapp.exe -out yourapp-signed.exe
```

The cloud case is the newest part of the documentation. Some code signing services keep the private key in their own HSM and publish a PKCS#11 module that talks to it over the network, so there is no token to plug in and no vendor desktop application to run, and osslsigncode drives such a module exactly like a local token, which makes headless signing on Linux and in CI possible. The example given is ssign-pkcs11, described as an independent third party project that is not affiliated with or endorsed by osslsigncode, exposing a Certum SimplySign cloud certificate as a PKCS#11 module, with the certificate chain supplied separately and the timestamp pointing at a Certum endpoint.

```bash
export CERTUM_EMAIL=you@example.com CERTUM_OTP=<your-TOTP-seed>
osslsigncode sign \
  -provider /usr/lib64/ossl-modules/pkcs11prov.so \
  -pkcs11module ./libssign_pkcs11.so \
  -pkcs11cert 'pkcs11:type=cert' \
  -key 'pkcs11:type=private' \
  -ac certum-code-signing-2021-ca.pem \
  -h sha256 -t http://time.certum.pl/ \
  -in yourapp.exe -out yourapp-signed.exe
```

## The source tree shows one file per format

The repository layout is a good map of what the tool actually implements. osslsigncode.c and osslsigncode.h hold the core, and there is one source file per format: pe.c for PE files, msi.c for MSI, cab.c for CAB, cat.c for CAT, appx.c for APPX and script.c for the script formats. That is the clearest statement of the scope in the repository, since each file is a container format with its own directory structure and its own rules about where the signature goes.

The shared parts are in helpers.c and helpers.h, and utf.c and utf.h for text handling. There is also get_code_signing_ca.py, which fetches a certificate authority file, and code_signing_ca.pem in the tree, which suggests the tool needs a chain of trust to attach to a signature.

The build and configuration files are modern. CMakeLists.txt and CMakeSettings.json cover the build, cmake/ holds the modules, Config.h.in is the configure template, and vcpkg.json is there for the Windows dependency route, alongside INSTALL.W32.md. The Dockerfile is a two stage Alpine build that compiles the tool and then copies only the binary into a clean image with libcrypto3 and a /workdir volume for the files to sign.

There is also osslsigncode.bash for shell completion, a TODO.md, a NEWS.md, and a COPYING.txt next to the LICENSE.txt. The project publishes 2.14 on 2026-07-20, 2.13 on 2026-02-10 and 2.12 on 2026-02-02, with the last push on 2026-10-15.

## Where osslsigncode is the wrong tool

Three cases argue against it. The first is a Windows only release pipeline with a code signing HSM attached to the signing machine. The original motivation for this tool was to avoid needing Windows at all, and if you have Windows and CryptoAPI then signtool.exe is the vendor supported path with full feature coverage, and the README is explicit that osslsigncode implements part of signtool.exe's functionality rather than all of it.

The second case is a project that needs the Windows native PKCS#11 stack, where the vendor ships only a Windows CSP or KSP. The tool's PKCS#11 support is a real strength on Linux with OpenSC or SoftHSM, but a vendor module that only works on Windows is not going to work here, and the engine and provider paths assume a Unix style module layout.

The third case is a team that wants a signing service rather than a signing tool. osslsigncode signs files on the machine it runs on, with the key in a local file, a local token or a cloud module the tool drives. If your requirement is an audit trail, an approval workflow and a central key custody, that is a different product.

There is a specific operational point about the trust chain that the repository layout hints at. The presence of a certificate authority file and the get_code_signing_ca.py script means the tool attaches a chain to the signature, and the examples supply the chain explicitly in the cloud case with the -ac option. A signature with the wrong chain looks signed to a casual check and fails to validate in Windows, which is a confusing failure mode worth testing explicitly.

On the state of the project, the release cadence through 2026 is regular, with 2.12, 2.13 and 2.14 landing in February, February and July, and the last push on 2026-10-15. The licence is a custom one that GitHub could not classify, so a team embedding this in a distribution should read the LICENSE.txt rather than assume a standard permissive grant.

## Conclusion

osslsigncode suits a release engineer who builds Windows binaries on a Linux or macOS machine, or who signs in CI where a Windows agent with CryptoAPI is not available. It does not suit someone who needs the full signtool.exe feature set, since the project describes itself as implementing part of that functionality, and it does not suit a build where the signing key sits on a Windows HSM with a vendor driver. Before you adopt it, test the sign and verify round trip on one binary with your own certificate, confirm the timestamp service you plan to use accepts the request, and note that the last push was on 2026-10-15 with the newest release 2.14 from 2026-07-20.

## FAQ

### What file types can osslsigncode sign?

PE files such as EXE, SYS and DLL, plus CAB, CAT, MSI and APPX, and script files with the .ps1, .ps1xml, .psc1, .psd1, .psm1, .cdxml, .mof and .js extensions. It also supports the equivalent of signtool.exe's -j javasign.dll -jp low option for CAB files containing Java files, with only the low parameter supported.

### How do I build osslsigncode on Linux?

Install cmake, libssl-dev, libcurl4-openssl-dev, zlib1g-dev and python3, then configure with cmake -S .. in a build directory, build with cmake --build ., test with ctest -C Release and install with sudo cmake --install .. CMake 3.17 or newer is required, and the README recommends downloading a release tarball rather than cloning.

### Can osslsigncode sign using a key held in a PKCS#11 token or cloud HSM?

Yes. The PKCS#11 engine path takes an engine, a PKCS#11 module and object URIs, and OpenSSL 3.x users can use the provider path instead. For cloud HSMs, the tool drives a network PKCS#11 module the same way, which is what makes headless signing in CI possible.

### What certificate and key formats does osslsigncode accept?

The certificate file can be in SPC or PEM format, the private key in DER or PEM format, and in PVK format if it was compiled against OpenSSL 1.0.0 or later, and a PKCS#12 container can hold both behind a single password supplied with the -pass option.

## Sources

- [Issues](https://github.com/mtrojnar/osslsigncode/issues)
- [mtrojnar/osslsigncode on GitHub](https://github.com/mtrojnar/osslsigncode)
- [README](https://github.com/mtrojnar/osslsigncode/blob/master/README.md)
- [Releases](https://github.com/mtrojnar/osslsigncode/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mtrojnar-osslsigncode
