Improve windows signature script to allow EV automatic signing

This commit is contained in:
Orsiris de Jong 2024-09-08 18:33:47 +02:00
parent cbade449b2
commit bb6a68798f
5 changed files with 161 additions and 51 deletions

View file

@ -262,10 +262,13 @@ The file `customization.py` also contains OEM strings that can be safely changed
In order to fully protect the AES key that is needed to support NPBackup, one can compile the program with Nuitka.
Compiling needs restic binary for the target platform in `RESTIC_SOURCE_FILES` folder, files must be named `restic_{version}_{platform}_{arch}[.extension]` like provided by restic.net or [github](https://github.com/restic/restic)
Linux binaries need to be made executable in the `RESTIC_SOURCE_FILES` folder.
Linux binaries need to be made executable in the `RESTIC_SOURCE_FILES` folder.
You can use `update_restic.sh` script in `RESTIC_SOURCE_FILES` to download binaries.
You'll need to change the default AES key in `secrets.py`, see the documentation in the file itself.
One you're setup, you need to install compilation tools, see `requirements-compilation.txt` file.
Compile options are available in `compile.py`.
We maintain a special 32 bit binary for Windows 7 which allows to backup those old machines until they get replaced.
@ -282,7 +285,7 @@ Official binaries for Windows provided by NetInvent are signed with a certificat
Also, official binaries are compiled using Nuitka Commercial grade, which is more secure in storing secrets.
Pre-compiled builds for Windows have been code signed with NetInvent's EV certificate, using [windows_tools.signtool](github.com/netinvent/windows_tools)
Signing on a Windows machine with Windows SDK installed can be done via `bin\sign_windows.py` script.
Signing on a Windows machine with Windows SDK installed can be done via `windows\sign_windows.py` script.
Alternatively, you can sign executables via:
```
from windows_tools.signtool import SignTool

View file

@ -7,8 +7,8 @@ __intname__ = "npbackup.compile"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2024090301"
__version__ = "2.0.1"
__build__ = "2024090801"
__version__ = "2.1.0"
"""
@ -18,6 +18,9 @@ Nuitka compilation script tested for
- Linux i386
- Linux i686
- Linux armv71
Also optionally signs windows executables
"""
@ -28,6 +31,8 @@ import argparse
import atexit
from command_runner import command_runner
from ofunctions.platform import python_arch, get_os
if os.name == "nt":
from npbackup.windows.sign_windows import sign
AUDIENCES = ["public", "private"]
BUILD_TYPES = ["cli", "gui", "viewer"]
@ -177,7 +182,7 @@ def have_nuitka_commercial():
return False
def compile(arch: str, audience: str, build_type: str, onefile: bool, create_tar_only: bool):
def compile(arch: str, audience: str, build_type: str, onefile: bool, create_tar_only: bool, ev_cert_data: str = None):
if build_type not in BUILD_TYPES:
print("CANNOT BUILD BOGUS BUILD TYPE")
sys.exit(1)
@ -327,6 +332,9 @@ def compile(arch: str, audience: str, build_type: str, onefile: bool, create_tar
fh.write(npbackup_version)
print(f"COMPILED {'WITH SUCCESS' if not errors else 'WITH ERRORS'}")
if os.name == "nt" and ev_cert_data:
sign(ev_cert_data=ev_cert_data, dry_run=args.dry_run)
if not onefile:
if not create_archive(
platform=platform,
@ -417,6 +425,15 @@ if __name__ == "__main__":
help="Build single file executable (more prone to AV detection)",
)
parser.add_argument(
"--sign",
type=str,
dest="ev_cert_data",
default=False,
required=False,
help="Digitally sign windows executables",
)
parser.add_argument(
"--create-tar-only",
action="store_true",
@ -472,7 +489,8 @@ if __name__ == "__main__":
audience=audience,
build_type=build_type,
onefile=args.onefile,
create_tar_only=create_tar_only
create_tar_only=create_tar_only,
ev_cert_data=args.ev_cert_data
)
if not create_tar_only:
audience_build = "private" if private_build else "public"

View file

@ -1,45 +0,0 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of npbackup
__intname__ = "npbackup.sign_windows"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2024060401"
__version__ = "1.1.2"
import os
try:
from windows_tools.signtool import SignTool
except ImportError:
print("This tool needs windows_tools.signtool >= 0.3.1")
basepath = r"C:\GIT\npbackup\BUILDS"
audiences = ["private", "public"]
arches = ["x86", "x64"]
binaries = ["npbackup-cli", "npbackup-gui", "npbackup-viewer"]
signer = SignTool()
for audience in audiences:
for arch in arches:
for binary in binaries:
one_file_exe_path = exe_path = os.path.join(
basepath, audience, "windows", arch, binary + f"-{arch}.exe"
)
standalone_exe_path = os.path.join(
basepath, audience, "windows", arch, binary + ".dist", binary + f".exe"
)
for exe_file in (one_file_exe_path, standalone_exe_path):
if os.path.isfile(exe_file):
print(f"Signing {exe_file}")
result = signer.sign(exe_file, bitness=arch)
if not result:
raise EnvironmentError(
"Could not sign executable ! Is the PKI key connected ?"
)

View file

@ -0,0 +1,2 @@
nuitka>=2.4.8
windows_tools.signtool>0.5.0

View file

@ -0,0 +1,132 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of npbackup
__intname__ = "npbackup.sign_windows"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2024090801"
__version__ = "1.2.0"
import os
import sys
import argparse
from cryptidy.symmetric_encryption import decrypt_message
try:
from windows_tools.signtool import SignTool
except ImportError:
print("This tool needs windows_tools.signtool >= 0.4.0")
basepath = r"C:\GIT\npbackup\BUILDS"
audiences = ["private", "public"]
arches = ["x86", "x64"]
binaries = ["npbackup-cli", "npbackup-gui", "npbackup-viewer"]
def check_private_ev():
"""
Test if we have private ev data
"""
try:
from PRIVATE._private_ev_data import AES_EV_KEY
from PRIVATE._private_obfuscation import obfuscation
print("We have private EV certifcate DATA")
return obfuscation(AES_EV_KEY)
except ImportError as exc:
print("ERROR: Cannot load private EV certificate DATA: {}".format(exc))
sys.exit()
def get_ev_data(cert_data_path):
"""
This retrieves specific data for crypto env
"""
aes_key = check_private_ev()
with open(cert_data_path, "rb") as fp:
ev_cert_data = fp.read()
try:
timestamp, ev_cert = decrypt_message(ev_cert_data, aes_key=aes_key)
(
pkcs12_certificate,
pkcs12_password,
container_name,
cryptographic_provider,
) = ev_cert
except Exception as exc:
print("EV Cert data is corrupt")
sys.exit(1)
return pkcs12_certificate, pkcs12_password, container_name, cryptographic_provider
def sign(ev_cert_data: str = None, dry_run: bool = False):
if ev_cert_data:
(
pkcs12_certificate,
pkcs12_password,
container_name,
cryptographic_provider,
) = get_ev_data(ev_cert_data)
signer = SignTool(
certificate=pkcs12_certificate,
pkcs12_password=pkcs12_password,
container_name=container_name,
cryptographic_provider=cryptographic_provider,
)
else:
signer = SignTool()
for audience in audiences:
for arch in arches:
for binary in binaries:
one_file_exe_path = os.path.join(
basepath, audience, "windows", arch, binary + f"-{arch}.exe"
)
standalone_exe_path = os.path.join(
basepath,
audience,
"windows",
arch,
binary + ".dist",
binary + f".exe",
)
for exe_file in (one_file_exe_path, standalone_exe_path):
if os.path.isfile(exe_file):
print(f"Signing {exe_file}")
result = signer.sign(exe_file, bitness=arch, dry_run=dry_run)
if not result:
# IMPORTANT: If using an automated crypto USB EV token, we need to stop on error so we don't lock ourselves out of the token with bad password attempts
raise EnvironmentError(
"Could not sign executable ! Is the PKI key connected ?"
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog="npbackup sign_windows.py",
description="Windows executable signer for NPBackup",
)
parser.add_argument(
"--dry-run",
action="store_true",
default=False,
required=False,
help="Don't actually sign anything, just test command",
)
parser.add_argument(
"--ev-cert-data",
type=str,
default=None,
required=False,
help="Path to EV certificate data",
)
args = parser.parse_args()
sign(ev_cert_data=args.ev_cert_data, dry_run=args.dry_run)