Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide codesigned stub exe's #5252

Merged
merged 7 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified conda_build/cli-32.exe
Binary file not shown.
Binary file modified conda_build/cli-64.exe
Binary file not shown.
Binary file modified conda_build/gui-32.exe
Binary file not shown.
Binary file modified conda_build/gui-64.exe
Binary file not shown.
19 changes: 19 additions & 0 deletions news/5252-sign-stubs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
### Enhancements

* For Windows users, the stub-exe's that are used for Python entrypoints in packages are now codesigned. (#5252)
jezdez marked this conversation as resolved.
Show resolved Hide resolved

### Bug fixes

* <news item>

### Deprecations

* <news item>

### Docs

* <news item>

### Other

* <news item>
97 changes: 97 additions & 0 deletions tests/test_codesigned.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Copyright (C) 2014 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations

import os
from functools import lru_cache
from pathlib import Path
from shutil import which
from subprocess import CalledProcessError, check_output, run

import pytest

from conda_build.utils import on_win

HERE = os.path.abspath(os.path.dirname(__file__))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be mixing pathlib and os.path calls in the same file? Or do we want to be consistent in usage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no Pathlib variant of shutil.which and other tests don't seem to use Pathlib much at first glance, but I'm a big proponent of Pathlib.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've been slowly moving paths in conda over to pathlib, but rest assured it backfired in messy code where paths are handed around and assumed strings.

REPO_ROOT = (Path(HERE) / "..").resolve().absolute()
STUB_FOLDER = REPO_ROOT / "conda_build"
jezdez marked this conversation as resolved.
Show resolved Hide resolved


@lru_cache(maxsize=None)
def find_signtool() -> str | None:
"""Tries to find signtool

Prefers signtool on PATH otherwise searches system.
Ref:
- https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
- https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool
- https://learn.microsoft.com/en-us/windows/win32/seccrypto/using-signtool-to-verify-a-file-signature
"""
signtool_path = which("signtool")
if signtool_path:
return signtool_path

# Common installation directories where signtool might be located
common_paths = [
"C:\\Program Files (x86)\\Windows Kits\\10\\bin",
"C:\\Program Files\\Windows Kits\\10\\bin",
"C:\\Windows\\System32",
]

signtool_path = None
# Search for signtool in common paths
for path in common_paths:
if signtool_path:
# We found one already
return signtool_path
if not os.path.exists(path):
continue
signtool_path = os.path.join(path, "signtool.exe")
if os.path.exists(signtool_path):
return signtool_path
elif "Windows Kits" in path:
signtool_path = None
max_version = 0
for dirname in os.listdir(path):
# Use most recent signtool version
if not dirname.endswith(".0"):
continue # next dirname
if int(dirname.replace(".", "")) < max_version:
continue # next dirname

maybe_signtool_path = os.path.join(path, dirname, "x64", "signtool.exe")
if os.path.exists(maybe_signtool_path):
signtool_path = maybe_signtool_path
return signtool_path


@lru_cache(maxsize=None)
def signtool_unsupported_because() -> str:
reason = ""
if not on_win:
reason = "Only verifying signatures of stub exe's on windows"
return reason
signtool = find_signtool()
if not signtool:
reason = "signtool: unable to locate signtool.exe"
try:
check_output([signtool, "verify", "/?"])
except CalledProcessError as exc:
reason = f"signtool: something went wrong while running 'signtool verify /?', output:\n{exc.output}\n"
return reason


def signtool_unsupported() -> bool:
return bool(signtool_unsupported_because())


@pytest.mark.skipif(signtool_unsupported(), reason=signtool_unsupported_because())
@pytest.mark.parametrize(
"stub_file_name", ["cli-32.exe", "cli-64.exe", "gui-32.exe", "gui-64.exe"]
)
def test_stub_exe_signatures(stub_file_name: str) -> None:
"""Verify that signtool verifies the signature of the stub exes"""
stub_file = STUB_FOLDER / stub_file_name
signtool_exe = find_signtool()
completed_process = run([signtool_exe, "verify", "/pa", "/v", stub_file])
assert completed_process.returncode == 0
Loading