mirror of
https://github.com/netinvent/npbackup.git
synced 2024-09-20 06:46:13 +08:00
Improve windows signature script to allow EV automatic signing
This commit is contained in:
parent
cbade449b2
commit
bb6a68798f
|
@ -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.
|
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)
|
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.
|
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`.
|
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.
|
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.
|
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)
|
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:
|
Alternatively, you can sign executables via:
|
||||||
```
|
```
|
||||||
from windows_tools.signtool import SignTool
|
from windows_tools.signtool import SignTool
|
||||||
|
|
|
@ -7,8 +7,8 @@ __intname__ = "npbackup.compile"
|
||||||
__author__ = "Orsiris de Jong"
|
__author__ = "Orsiris de Jong"
|
||||||
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
|
__copyright__ = "Copyright (C) 2023-2024 NetInvent"
|
||||||
__license__ = "GPL-3.0-only"
|
__license__ = "GPL-3.0-only"
|
||||||
__build__ = "2024090301"
|
__build__ = "2024090801"
|
||||||
__version__ = "2.0.1"
|
__version__ = "2.1.0"
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -18,6 +18,9 @@ Nuitka compilation script tested for
|
||||||
- Linux i386
|
- Linux i386
|
||||||
- Linux i686
|
- Linux i686
|
||||||
- Linux armv71
|
- Linux armv71
|
||||||
|
|
||||||
|
Also optionally signs windows executables
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +31,8 @@ import argparse
|
||||||
import atexit
|
import atexit
|
||||||
from command_runner import command_runner
|
from command_runner import command_runner
|
||||||
from ofunctions.platform import python_arch, get_os
|
from ofunctions.platform import python_arch, get_os
|
||||||
|
if os.name == "nt":
|
||||||
|
from npbackup.windows.sign_windows import sign
|
||||||
|
|
||||||
AUDIENCES = ["public", "private"]
|
AUDIENCES = ["public", "private"]
|
||||||
BUILD_TYPES = ["cli", "gui", "viewer"]
|
BUILD_TYPES = ["cli", "gui", "viewer"]
|
||||||
|
@ -177,7 +182,7 @@ def have_nuitka_commercial():
|
||||||
return False
|
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:
|
if build_type not in BUILD_TYPES:
|
||||||
print("CANNOT BUILD BOGUS BUILD TYPE")
|
print("CANNOT BUILD BOGUS BUILD TYPE")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -327,6 +332,9 @@ def compile(arch: str, audience: str, build_type: str, onefile: bool, create_tar
|
||||||
fh.write(npbackup_version)
|
fh.write(npbackup_version)
|
||||||
print(f"COMPILED {'WITH SUCCESS' if not errors else 'WITH ERRORS'}")
|
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 onefile:
|
||||||
if not create_archive(
|
if not create_archive(
|
||||||
platform=platform,
|
platform=platform,
|
||||||
|
@ -417,6 +425,15 @@ if __name__ == "__main__":
|
||||||
help="Build single file executable (more prone to AV detection)",
|
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(
|
parser.add_argument(
|
||||||
"--create-tar-only",
|
"--create-tar-only",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
|
@ -472,7 +489,8 @@ if __name__ == "__main__":
|
||||||
audience=audience,
|
audience=audience,
|
||||||
build_type=build_type,
|
build_type=build_type,
|
||||||
onefile=args.onefile,
|
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:
|
if not create_tar_only:
|
||||||
audience_build = "private" if private_build else "public"
|
audience_build = "private" if private_build else "public"
|
||||||
|
|
|
@ -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 ?"
|
|
||||||
)
|
|
2
npbackup/requirements-compilation.txt
Normal file
2
npbackup/requirements-compilation.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
nuitka>=2.4.8
|
||||||
|
windows_tools.signtool>0.5.0
|
132
npbackup/windows/sign_windows.py
Normal file
132
npbackup/windows/sign_windows.py
Normal 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)
|
Loading…
Reference in a new issue