aboutsummaryrefslogtreecommitdiffstats
path: root/packaging-tools/sign_windows_installer.py
blob: f9e044bf38b5f74a884eb79601577baec08a7306 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#############################################################################
#
# Copyright (C) 2023 The Qt Company Ltd.
# Contact: https://www.qt.io/licensing/
#
# This file is part of the release tools of the Qt Toolkit.
#
# $QT_BEGIN_LICENSE:GPL-EXCEPT$
# Commercial License Usage
# Licensees holding valid commercial Qt licenses may use this file in
# accordance with the commercial license agreement provided with the
# Software or, alternatively, in accordance with the terms contained in
# a written agreement between you and The Qt Company. For licensing terms
# and conditions see https://www.qt.io/terms-conditions. For further
# information use the contact form at https://www.qt.io/contact-us.
#
# GNU General Public License Usage
# Alternatively, this file may be used under the terms of the GNU
# General Public License version 3 as published by the Free Software
# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
# included in the packaging of this file. Please review the following
# information to ensure the GNU General Public License requirements will
# be met: https://www.gnu.org/licenses/gpl-3.0.html.
#
# $QT_END_LICENSE$
#
#############################################################################

import argparse
import os
import subprocess
import sys
from datetime import datetime
from pathlib import Path
from subprocess import DEVNULL
from time import time
from typing import List

import pysftp  # type: ignore

from installer_utils import PackagingError
from logging_util import init_logger
from read_remote_config import download_remote_file_sftp, get_pkg_value

log = init_logger(__name__, debug_mode=False)
timestamp = datetime.fromtimestamp(time()).strftime('%Y-%m-%d--%H:%M:%S')


def _handle_signing(file_path: str, verify_signtool: str) -> None:
    """
    Sign executable from file_path using AzureSignTool with the configured options
    Verify the signing with the verify_signtool specified

    Args:
        file_path: A string path to the file to be signed
        verify_signtool: Name of the signtool executable used for verification

    Raises:
        PackagingError: When signing or verification is unsuccessful
    """
    remote_config = Path(os.environ["WINDOWS_SIGNKEYS_PATH"])
    kvu = get_pkg_value("kvu", "", remote_config)
    kvi = get_pkg_value("kvi", "", remote_config)
    kvs = get_pkg_value("kvs", "", remote_config)
    kvc = get_pkg_value("kvc", "", remote_config)
    tr_sect = get_pkg_value("tr", "", remote_config)
    cmd_args_sign = ["AzureSignTool.exe", "sign", "-kvu", kvu, "-kvi", kvi, "-kvs", kvs, "-kvc", kvc, "-tr", tr_sect, "-v", file_path]
    log_entry = cmd_args_sign[:]
    log_entry[3] = "****"
    log_entry[5] = "****"
    log_entry[7] = "****"
    log_entry[9] = "****"
    log_entry[11] = "****"
    log.info("Calling: %s", ' '.join(log_entry))
    sign_result = subprocess.run(cmd_args_sign, stdout=DEVNULL, stderr=DEVNULL, check=False)
    if sign_result.returncode != 0:
        raise PackagingError(f"Package {file_path} signing with error {sign_result.returncode}")
    log.info("Successfully signed: %s", file_path)
    cmd_args_verify: List[str] = [verify_signtool, "verify", "-pa", file_path]
    verify_result = subprocess.run(cmd_args_verify, stdout=DEVNULL, stderr=DEVNULL, check=False)
    if verify_result.returncode != 0:
        raise PackagingError(f"Failed to verify {file_path} with error {verify_result.returncode}")
    log.info("Successfully verified: %s", file_path)


def download_signing_tools(signtool: Path) -> None:
    try:
        cnopts = pysftp.CnOpts()
        cnopts.hostkeys = None
        download_remote_file_sftp(remote_path=signtool)
    except PackagingError:
        raise PackagingError("Failed to download signing tools!") from None


def sign_executable(file_path: str) -> None:
    log.info("Signing: %s", file_path)
    try:
        signtool = os.environ["WINDOWS_SIGNTOOL_X64_PATH"]
    except KeyError as err:
        raise PackagingError("Signtool path not found from env") from err
    try:
        download_signing_tools(Path(signtool))
        path = Path(file_path)
        if path.is_dir():
            for subpath in path.rglob('*'):
                if subpath.is_file() and subpath.suffix in ['.exe', '.dll', '.pyd']:
                    _handle_signing(str(subpath), Path(signtool).name)
        else:
            _handle_signing(file_path, Path(signtool).name)
    finally:
        Path(Path.cwd() / Path(signtool).name).unlink()


def main() -> None:
    """Main"""
    parser = argparse.ArgumentParser(prog="Codesign Windows executables and DLLs")
    parser.add_argument("--file", dest="file_to_sign", required=True, help="File or directory to sign")
    args = parser.parse_args(sys.argv[1:])
    sign_executable(args.file_to_sign)


if __name__ == "__main__":
    main()