3
3
4
4
from __future__ import annotations
5
5
6
- import hashlib
7
6
import io
8
7
import logging
9
8
import shutil
15
14
from zipfile import ZipFile
16
15
17
16
from PySide6 import QtCore , QtNetwork , QtWidgets
17
+ from signify .authenticode import SignedPEFile
18
+ from signify .exceptions import SignifyError
18
19
from __feature__ import snake_case , true_property # noqa: F401
19
20
20
21
from auto_neutron .constants import VERSION
27
28
from auto_neutron .utils .utils import get_application
28
29
from auto_neutron .windows import UpdateErrorWindow , VersionDownloadConfirmDialog
29
30
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
+
30
56
log = logging .getLogger (__name__ )
31
57
32
58
LATEST_RELEASE_URL = (
@@ -152,46 +178,15 @@ def _download_new_release(self, release_json: dict[str, t.Any]) -> None:
152
178
self ._show_error_window (_ ("Unable to find appropriate new release." ))
153
179
else :
154
180
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 ),
179
186
)
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
+ )
191
188
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 :
195
190
"""
196
191
Create the new executable/directory from the reply data and start it.
197
192
@@ -217,14 +212,15 @@ def _create_new_and_restart(
217
212
finally :
218
213
reply .delete_later ()
219
214
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
-
227
215
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
+
228
224
temp_path = EXECUTABLE_PATH .with_stem (TEMP_NAME )
229
225
try :
230
226
EXECUTABLE_PATH .rename (temp_path )
@@ -238,6 +234,19 @@ def _create_new_and_restart(
238
234
return
239
235
240
236
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
+
241
250
dir_path = EXECUTABLE_PATH .parent
242
251
temp_path = dir_path / TEMP_NAME
243
252
@@ -259,7 +268,7 @@ def _create_new_and_restart(
259
268
return
260
269
261
270
try :
262
- ZipFile ( io . BytesIO ( download_bytes )) .extractall (path = dir_path )
271
+ zip_file .extractall (path = dir_path )
263
272
except OSError as e :
264
273
self ._show_error_window (
265
274
_ ("Unable to extract new release files: " ) + str (e )
0 commit comments