Skip to content

Instantly share code, notes, and snippets.

@flozz
Created March 2, 2026 13:30
Show Gist options
  • Select an option

  • Save flozz/2c0c25d885bf6b735d4545f06998bd6b to your computer and use it in GitHub Desktop.

Select an option

Save flozz/2c0c25d885bf6b735d4545f06998bd6b to your computer and use it in GitHub Desktop.
Signs given Windows executable or DLL using Microsoft™ signtool.
#!/usr/bin/env python3
"""Signs given Windows executable or DLL using Microsoft™ signtool."""
# USAGE:
#
# ./signexe.py --help
#
# LICENSE:
#
# Copyright © 2026 Fabien LOISON <https://blog.flozz.fr>
# This work is free. You can redistribute it and/or modify it under the
# terms of the Do What The Fuck You Want To Public License, Version 2,
# as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
import sys
import argparse
import subprocess
from pathlib import Path
from collections.abc import Sequence
def locate_signtool_exe() -> Path:
"""Locates Microsoft™'s signtool.exe.
:raise FileNotFoundError: if the signtool cannot be found.
:return: The path of signtool.exe.
"""
path = Path("C:\\Program Files (x86)\\Windows Kits\\")
signtools = list(path.glob("**/signtool.exe", case_sensitive=False))
if not signtools:
raise FileNotFoundError("Signtool not found on the system!")
return signtools[0]
def sign_exe(
exe_path: Path,
cert_name: str | None = None,
cert_sha1: str | None = None,
file_digest: str = "SHA256",
timestamp_server: str | None = None,
timestamp_digest: str = "SHA256",
) -> None:
"""Signs the given executable or library.
:param exe_path: The path of the exe or DLL file.
:param cert_name: The common name (or a part of it) of the certificate to use.
:param cert_sha1: The sha1 thumbprint of the certificate to use.
:param file_digest: The digest algorythm used for file signature.
:param timestamp_server: URL of the RFC 3161 compliant trusted time stamp server.
:param timestamp_digest: The digest algorythm used for the timestamp signature.
.. IMPORTANT::
At least one of the ``cert_*`` parameters must be provided.
"""
if not cert_name and not cert_sha1:
raise ValueError("Either cert_name or cert_sha1 is required")
signtool = locate_signtool_exe()
cli = [str(signtool), "sign"]
if cert_name:
cli += ["/n", cert_name]
if cert_sha1:
cli += ["/sha1", cert_sha1]
cli += ["/fd", file_digest]
if timestamp_server:
cli += ["/tr", timestamp_server, "/td", timestamp_digest]
cli += [str(exe_path.absolute())]
print("Running: %s" % str(cli))
subprocess.run(cli)
def main(args: Sequence[str] = sys.argv[1:]) -> None:
parser = argparse.ArgumentParser(description=__doc__)
cert_id_group = parser.add_argument_group(
"Certificat Identification",
"At least one of the identification option must be provided",
)
cert_id_group.add_argument(
"--cert-name",
help="the common name of the certificate to use",
type=str,
)
cert_id_group.add_argument(
"--cert-sha1",
help="the sha1 thumbprint of the certificate to use",
type=str,
)
parser.add_argument(
"--trusted-timestamp-server",
help="an RFC 3161 compliant trusted timestamp server",
type=str,
)
parser.add_argument(
"EXE_PATH",
help="path to the executable or DLL file to sign",
type=argparse.FileType("rb"),
)
parsed_args = parser.parse_args(args)
sign_exe(
Path(parsed_args.EXE_PATH.name),
cert_name=parsed_args.cert_name,
cert_sha1=parsed_args.cert_sha1,
timestamp_server=parsed_args.trusted_timestamp_server,
)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment