Add Checksum verification to InstallAssistant.pkg download

This commit is contained in:
Mykola Grymalyuk
2022-05-10 22:46:49 -06:00
parent 60fab3986e
commit 2ddb679554
4 changed files with 180 additions and 20 deletions

1
.gitignore vendored
View File

@@ -28,3 +28,4 @@ __pycache__/
/payloads/AutoPkg-Assets.pkg.zip /payloads/AutoPkg-Assets.pkg.zip
/payloads/Universal-Binaries /payloads/Universal-Binaries
/payloads/OpenCore-Legacy-Patcher /payloads/OpenCore-Legacy-Patcher
/payloads/InstallAssistant.pkg.integrityDataV1

View File

@@ -20,8 +20,10 @@
- Add Serial Number Spoofing - Add Serial Number Spoofing
- For recycled machines where MDM was mistakenly left on - For recycled machines where MDM was mistakenly left on
- Add sys_patch file validation during CI - Add sys_patch file validation during CI
- Add GUI Prompt for booting mismatched OpenCore configs - GUI Enhancements:
- ex. Booting MacBookPro8,1 config on MacBookPro11,1 - Add GUI Prompt for booting mismatched OpenCore configs
- ex. Booting MacBookPro8,1 config on MacBookPro11,1
- Add Checksum verification to InstallAssistant.pkg download
## 0.4.4 ## 0.4.4
- Lower SIP requirement for Root Patching - Lower SIP requirement for Root Patching

View File

@@ -13,10 +13,13 @@ import wx.adv
from wx.lib.agw import hyperlink from wx.lib.agw import hyperlink
import threading import threading
from pathlib import Path from pathlib import Path
import binascii
import hashlib
from resources import constants, defaults, build, install, installer, sys_patch_download, utilities, sys_patch_detect, sys_patch, run, generate_smbios, updates from resources import constants, defaults, build, install, installer, sys_patch_download, utilities, sys_patch_detect, sys_patch, run, generate_smbios, updates, integrity_verification
from data import model_array, os_data, smbios_data, sip_data from data import model_array, os_data, smbios_data, sip_data
from gui import menu_redirect from gui import menu_redirect
class wx_python_gui: class wx_python_gui:
def __init__(self, versions): def __init__(self, versions):
@@ -1202,7 +1205,7 @@ class wx_python_gui:
self.subheader.GetPosition().y + self.subheader.GetSize().height + i self.subheader.GetPosition().y + self.subheader.GetSize().height + i
) )
) )
self.install_selection.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.download_macos_click(f"macOS {available_installers[temp]['Version']} ({available_installers[temp]['Build']})", available_installers[temp]['Link'])) self.install_selection.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.download_macos_click(available_installers[temp]))
self.install_selection.Centre(wx.HORIZONTAL) self.install_selection.Centre(wx.HORIZONTAL)
else: else:
self.install_selection = wx.StaticText(self.frame, label="No installers available") self.install_selection = wx.StaticText(self.frame, label="No installers available")
@@ -1243,8 +1246,9 @@ class wx_python_gui:
def reload_macos_installer_catalog(self, event=None, ias=None): def reload_macos_installer_catalog(self, event=None, ias=None):
self.grab_installer_data(ias=ias) self.grab_installer_data(ias=ias)
def download_macos_click(self, installer_name, installer_link): def download_macos_click(self, app_dict):
self.frame.DestroyChildren() self.frame.DestroyChildren()
installer_name = f"macOS {app_dict['Version']} ({app_dict['Build']})"
# Header # Header
self.header = wx.StaticText(self.frame, label=f"Downloading {installer_name}") self.header = wx.StaticText(self.frame, label=f"Downloading {installer_name}")
@@ -1277,31 +1281,117 @@ class wx_python_gui:
self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40)
# Download macOS install data # Download macOS install data
if installer.download_install_assistant(self.constants.payload_path, installer_link): if installer.download_install_assistant(self.constants.payload_path, app_dict['Link']):
# Fix stdout # Fix stdout
sys.stdout = self.stock_stdout sys.stdout = self.stock_stdout
self.download_label.SetLabel(f"Finished Downloading {installer_name}") self.download_label.SetLabel(f"Finished Downloading {installer_name}")
self.download_label.Centre(wx.HORIZONTAL) self.download_label.Centre(wx.HORIZONTAL)
wx.App.Get().Yield() wx.App.Get().Yield()
# Update Label: self.installer_validation(apple_integrity_file_link= app_dict['integrity'])
sys.stdout=menu_redirect.RedirectLabelAll(self.download_label)
sys.stderr=menu_redirect.RedirectLabelAll(self.download_label)
installer.install_macOS_installer(self.constants.payload_path)
sys.stdout = self.stock_stdout
sys.stderr = self.stock_stderr
# Update Label:
self.download_label.SetLabel(f"Finished Installing {installer_name}")
self.download_label.Centre(wx.HORIZONTAL)
# Set Return to Main Menu into flash_installer_menu
self.return_to_main_menu.SetLabel("Flash Installer")
self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.flash_installer_menu)
self.return_to_main_menu.Centre(wx.HORIZONTAL)
else: else:
sys.stdout = self.stock_stdout sys.stdout = self.stock_stdout
self.download_label.SetLabel(f"Failed to download {installer_name}") self.download_label.SetLabel(f"Failed to download {installer_name}")
self.download_label.Centre(wx.HORIZONTAL) self.download_label.Centre(wx.HORIZONTAL)
def installer_validation(self, event=None, apple_integrity_file_link=""):
self.frame.DestroyChildren()
# Header: Verifying InstallAssistant.pkg
self.header = wx.StaticText(self.frame, label="Verifying InstallAssistant.pkg")
self.header.SetFont(wx.Font(18, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD))
self.header.Centre(wx.HORIZONTAL)
# Label: Verifying Chunk 0 of 1200
self.verifying_chunk_label = wx.StaticText(self.frame, label="Verifying Chunk 0 of 1200")
self.verifying_chunk_label.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.BOLD))
self.verifying_chunk_label.SetPosition(
wx.Point(
self.header.GetPosition().x,
self.header.GetPosition().y + self.header.GetSize().height + 10
)
)
self.verifying_chunk_label.Centre(wx.HORIZONTAL)
# Progress Bar
self.progress_bar = wx.Gauge(self.frame, range=1200, size=(300, 25))
self.progress_bar.SetPosition(
wx.Point(
self.verifying_chunk_label.GetPosition().x,
self.verifying_chunk_label.GetPosition().y + self.verifying_chunk_label.GetSize().height + 10
)
)
self.progress_bar.Centre(wx.HORIZONTAL)
# Button: Return to Main Menu
self.return_to_main_menu = wx.Button(self.frame, label="Return to Main Menu")
self.return_to_main_menu.SetPosition(
wx.Point(
self.progress_bar.GetPosition().x,
self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 20
)
)
self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu)
self.return_to_main_menu.Centre(wx.HORIZONTAL)
self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40)
wx.App.Get().Yield()
integrity_path = Path(Path(self.constants.payload_path) / Path(apple_integrity_file_link.split("/")[-1]))
if utilities.download_file(apple_integrity_file_link, integrity_path, verify_checksum=False):
# If we're unable to download the integrity file immediately after downloading the IA, there's a legitmate issue
# on Apple's end.
# Fail gracefully and just head to installing the IA.
apple_integrity_file = str(integrity_path)
chunks = integrity_verification.generate_chunklist_dict(str(apple_integrity_file))
max_progress = len(chunks)
self.progress_bar.SetValue(0)
self.progress_bar.SetRange(max_progress)
wx.App.Get().Yield()
# See integrity_verification.py for more information on the integrity verification process
with Path(self.constants.payload_path / Path("InstallAssistant.pkg")).open("rb") as f:
for chunk in chunks:
status = hashlib.sha256(f.read(chunk["length"])).digest()
if not status == chunk["checksum"]:
print(f"Chunk {chunks.index(chunk) + 1} checksum status FAIL: chunk sum {binascii.hexlify(chunk['checksum']).decode()}, calculated sum {binascii.hexlify(status).decode()}")
self.popup = wx.MessageDialog(
self.frame,
f"We've found that Chunk {chunks.index(chunk) + 1} of {len(chunks)} has failed the integrity check.\n\nThis generally happens when downloading on unstable connections such as WiFi or cellular.\n\nPlease try redownloading again on a stable connection (ie. Ethernet)",
"Corrupted Installer!",
style = wx.OK | wx.ICON_EXCLAMATION
)
self.popup.ShowModal()
self.main_menu()
break
else:
self.progress_bar.SetValue(self.progress_bar.GetValue() + 1)
self.verifying_chunk_label.SetLabel(f"Verifying Chunk {self.progress_bar.GetValue()} of {max_progress}")
wx.App.Get().Yield()
else:
print("Failed to download integrity file, skipping integrity check.")
wx.App.Get().Yield()
self.header.SetLabel("Installing InstallAssistant.pkg")
self.header.Centre(wx.HORIZONTAL)
self.verifying_chunk_label.SetLabel("Installing into Applications folder")
self.verifying_chunk_label.Centre(wx.HORIZONTAL)
thread_install = threading.Thread(target=installer.install_macOS_installer, args=(self.constants.payload_path,))
thread_install.start()
while thread_install.is_alive():
self.progress_bar.Pulse()
wx.App.Get().Yield()
self.progress_bar.SetValue(self.progress_bar.GetRange())
self.return_to_main_menu.SetLabel("Flash Installer")
self.verifying_chunk_label.SetLabel("Finished extracting to Applications folder!")
self.verifying_chunk_label.Centre(wx.HORIZONTAL)
self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.flash_installer_menu)
self.return_to_main_menu.Centre(wx.HORIZONTAL)
def flash_installer_menu(self, event=None): def flash_installer_menu(self, event=None):
self.frame.DestroyChildren() self.frame.DestroyChildren()
self.frame.SetSize(self.WINDOW_WIDTH_MAIN, self.WINDOW_HEIGHT_MAIN) self.frame.SetSize(self.WINDOW_WIDTH_MAIN, self.WINDOW_HEIGHT_MAIN)

View File

@@ -0,0 +1,67 @@
# Validate the integrity of Apple downloaded files via .chunklist and .integrityDataV1 files
# Based off of chunklist.py:
# - https://gist.github.com/dhinakg/cbe30edf31ddc153fd0b0c0570c9b041
# Copyright (C) 2021-2022, Dhinak G, Mykola Grymalyuk
import binascii
import hashlib
import struct
from pathlib import Path
CHUNK_LENGTH = 4 + 32
def hexswap(i):
return struct.unpack("<I", struct.pack(">I", i))[0]
def generate_chunklist_dict(chunklist):
chunklist = Path(chunklist).read_bytes() if isinstance(chunklist, str) else chunklist
header = {
"magic": chunklist[:4],
"length": int.from_bytes(chunklist[4:8], "little"),
"fileVersion": chunklist[8],
"chunkMethod": chunklist[9],
"sigMethod": chunklist[10],
"chunkCount": int.from_bytes(chunklist[12:20], "little"),
"chunkOffset": int.from_bytes(chunklist[20:28], "little"),
"sigOffset": int.from_bytes(chunklist[28:36], "little")
}
all_chunks = chunklist[header["chunkOffset"]:header["chunkOffset"]+header["chunkCount"]*CHUNK_LENGTH]
chunks = [{"length": int.from_bytes(all_chunks[i:i+4], "little"), "checksum": all_chunks[i+4:i+CHUNK_LENGTH]} for i in range(0, len(all_chunks), CHUNK_LENGTH)]
return chunks
def chunk(file_path, chunklist, verbose):
chunklist = Path(chunklist).read_bytes() if isinstance(
chunklist, str) else chunklist
header = {
"magic": chunklist[:4],
"length": int.from_bytes(chunklist[4:8], "little"),
"fileVersion": chunklist[8],
"chunkMethod": chunklist[9],
"sigMethod": chunklist[10],
"chunkCount": int.from_bytes(chunklist[12:20], "little"),
"chunkOffset": int.from_bytes(chunklist[20:28], "little"),
"sigOffset": int.from_bytes(chunklist[28:36], "little")
}
all_chunks = chunklist[header["chunkOffset"]:header["chunkOffset"]+header["chunkCount"]*CHUNK_LENGTH]
chunks = [{"length": int.from_bytes(all_chunks[i:i+4], "little"), "checksum": all_chunks[i+4:i+CHUNK_LENGTH]}
for i in range(0, len(all_chunks), CHUNK_LENGTH)]
with Path(file_path).open("rb") as f:
for chunk in chunks:
status = hashlib.sha256(f.read(chunk["length"])).digest()
if not status == chunk["checksum"]:
print(
f"Chunk {chunks.index(chunk) + 1} checksum status FAIL: chunk sum {binascii.hexlify(chunk['checksum']).decode()}, calculated sum {binascii.hexlify(status).decode()}")
return False
elif verbose:
print(
f"Chunk {chunks.index(chunk) + 1} checksum status success")
return True