Skip to content

Commit 041762f

Browse files
committed
verify authenticode signature on download
1 parent 9767bc4 commit 041762f

File tree

1 file changed

+56
-47
lines changed

1 file changed

+56
-47
lines changed

auto_neutron/self_updater.py

Lines changed: 56 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
from __future__ import annotations
55

6-
import hashlib
76
import io
87
import logging
98
import shutil
@@ -15,6 +14,8 @@
1514
from zipfile import ZipFile
1615

1716
from PySide6 import QtCore, QtNetwork, QtWidgets
17+
from signify.authenticode import SignedPEFile
18+
from signify.exceptions import SignifyError
1819
from __feature__ import snake_case, true_property # noqa: F401
1920

2021
from auto_neutron.constants import VERSION
@@ -27,6 +28,31 @@
2728
from auto_neutron.utils.utils import get_application
2829
from auto_neutron.windows import UpdateErrorWindow, VersionDownloadConfirmDialog
2930

31+
IGNORE_UNSIGNED_DIR_FILES = {
32+
"_internal/babel/locale-data/en_001.dat",
33+
"_internal/babel/locale-data/en_150.dat",
34+
"_internal/babel/locale-data/en.dat",
35+
"_internal/babel/locale-data/root.dat",
36+
"_internal/babel/global.dat",
37+
"_internal/babel/py.typed",
38+
"_internal/locale/en/LC_MESSAGES/auto_neutron.mo",
39+
"_internal/locale/en/LC_MESSAGES/auto_neutron.po",
40+
"_internal/locale/auto_neutron.pot",
41+
"_internal/locale/README.md",
42+
"_internal/resources/icon.svg",
43+
"_internal/resources/icons_library.ico",
44+
"_internal/resources/refresh-dark.svg",
45+
"_internal/resources/refresh.svg",
46+
"_internal/third_party_licenses/LICENSE_babel.md",
47+
"_internal/third_party_licenses/LICENSE_breeze-icons.md",
48+
"_internal/third_party_licenses/LICENSE_more-itertools.md",
49+
"_internal/third_party_licenses/LICENSE_PySide6.md",
50+
"_internal/third_party_licenses/LICENSE_Python.md",
51+
"_internal/third_party_licenses/LICENSE_tomli-w.md",
52+
"_internal/base_library.zip",
53+
"_internal/LICENSE.md",
54+
}
55+
3056
log = logging.getLogger(__name__)
3157

3258
LATEST_RELEASE_URL = (
@@ -152,46 +178,15 @@ def _download_new_release(self, release_json: dict[str, t.Any]) -> None:
152178
self._show_error_window(_("Unable to find appropriate new release."))
153179
else:
154180
download_url = asset_json["browser_download_url"]
155-
hash_download_url = download_url + ".signature.txt"
156-
log.info(f"Downloading hash from {download_url}.")
157-
make_network_request(
158-
hash_download_url,
159-
finished_callback=partial(
160-
self._set_hash_and_download, asset_download_url=download_url
161-
),
162-
)
163-
164-
def _set_hash_and_download(
165-
self, network_reply: QtNetwork.QNetworkReply, asset_download_url: str
166-
) -> None:
167-
"""Schedule the file to be downloaded and pass it the checksum hash from the reply."""
168-
try:
169-
if network_reply.error() is QtNetwork.QNetworkReply.NetworkError.NoError:
170-
hash_ = network_reply.read_all().data().decode().strip()
171-
log.info(f"Downloading release from {asset_download_url}.")
172-
self._download_started.emit(
173-
make_network_request(
174-
asset_download_url,
175-
finished_callback=partial(
176-
self._create_new_and_restart, hash_to_check=hash_
177-
),
178-
)
181+
log.info(f"Downloading release from {download_url}.")
182+
self._download_started.emit(
183+
make_network_request(
184+
download_url,
185+
finished_callback=partial(self._create_new_and_restart),
179186
)
180-
elif (
181-
network_reply.error()
182-
is QtNetwork.QNetworkReply.NetworkError.OperationCanceledError
183-
):
184-
return
185-
else:
186-
self._show_error_window(network_reply.error_string())
187-
return
188-
189-
finally:
190-
network_reply.delete_later()
187+
)
191188

192-
def _create_new_and_restart(
193-
self, reply: QtNetwork.QNetworkReply, hash_to_check: str
194-
) -> None:
189+
def _create_new_and_restart(self, reply: QtNetwork.QNetworkReply) -> None:
195190
"""
196191
Create the new executable/directory from the reply data and start it.
197192
@@ -217,14 +212,15 @@ def _create_new_and_restart(
217212
finally:
218213
reply.delete_later()
219214

220-
downloaded_hash = hashlib.sha256(download_bytes)
221-
if downloaded_hash.hexdigest() != hash_to_check:
222-
self._show_error_window(
223-
_("Downloaded file does not match expected checksum.")
224-
)
225-
return
226-
227215
if IS_ONEFILE:
216+
try:
217+
SignedPEFile(io.BytesIO(download_bytes)).verify()
218+
except SignifyError as e:
219+
self._show_error_window(
220+
_("Unable to verify downloaded file signature: " + str(e))
221+
)
222+
return
223+
228224
temp_path = EXECUTABLE_PATH.with_stem(TEMP_NAME)
229225
try:
230226
EXECUTABLE_PATH.rename(temp_path)
@@ -238,6 +234,19 @@ def _create_new_and_restart(
238234
return
239235

240236
else:
237+
zip_file = ZipFile(io.BytesIO(download_bytes))
238+
for file in zip_file.namelist():
239+
if file not in IGNORE_UNSIGNED_DIR_FILES:
240+
try:
241+
SignedPEFile(io.BytesIO(zip_file.read(file))).verify()
242+
except SignifyError as e:
243+
self._show_error_window(
244+
_(
245+
"Unable to verify downloaded file signature for file {}: {}"
246+
).format(file, str(e))
247+
)
248+
return
249+
241250
dir_path = EXECUTABLE_PATH.parent
242251
temp_path = dir_path / TEMP_NAME
243252

@@ -259,7 +268,7 @@ def _create_new_and_restart(
259268
return
260269

261270
try:
262-
ZipFile(io.BytesIO(download_bytes)).extractall(path=dir_path)
271+
zip_file.extractall(path=dir_path)
263272
except OSError as e:
264273
self._show_error_window(
265274
_("Unable to extract new release files: ") + str(e)

0 commit comments

Comments
 (0)