kdk_handler: Enumerate KDKs from Apple developer portal

This commit is contained in:
Dhinak G
2022-10-03 16:00:23 -04:00
parent 41ebf7ce32
commit fa352fbc0c

View File

@@ -1,19 +1,37 @@
# Kernel Debug Kit downloader # Kernel Debug Kit downloader
import datetime
from typing import cast
import urllib.parse
from pathlib import Path from pathlib import Path
import requests, urllib
import packaging.version
import requests
from resources import utilities from resources import utilities
from resources.constants import Constants
SESSION = requests.Session() SESSION = requests.Session()
class kernel_debug_kit_handler: class kernel_debug_kit_handler:
def __init__(self, constants: Constants):
def __init__(self, constants):
self.constants = constants self.constants = constants
def get_available_kdks(self):
KDK_API_LINK = "https://kdk-api.dhinak.net/v1"
def get_closest_match(self, host_version: str, host_build: str): print("- Fetching available KDKs")
results = SESSION.get(KDK_API_LINK, headers={"User-Agent": f"OCLP/{self.constants.patcher_version}"})
if results.status_code != 200:
print(" - Could not fetch KDK list")
return None
return results.json().sort(key=lambda x: (packaging.version.parse(x["version"]), datetime.datetime.fromisoformat(x["date"])), reversed=True)
def get_closest_match_legacy(self, host_version: str, host_build: str):
# Get the closest match to the provided version # Get the closest match to the provided version
# KDKs are generally a few days late, so we'll rely on N-1 matching # KDKs are generally a few days late, so we'll rely on N-1 matching
@@ -21,12 +39,7 @@ class kernel_debug_kit_handler:
OS_DATABASE_LINK = "https://api.appledb.dev/main.json" OS_DATABASE_LINK = "https://api.appledb.dev/main.json"
newest_build = { parsed_host_version = cast(packaging.version.Version, packaging.version.parse(host_version))
"name": "",
"version": "",
"build": "",
"date": "",
}
print(f"- Checking closest match for: {host_version} build {host_build}") print(f"- Checking closest match for: {host_version} build {host_build}")
@@ -36,65 +49,27 @@ class kernel_debug_kit_handler:
print(" - Could not fetch database") print(" - Could not fetch database")
return None, "" return None, ""
results = results.json() macos_builds = [i for i in results.json()["ios"] if i["osType"] == "macOS"]
macos_builds.sort(key=lambda x: (packaging.version.parse(x["version"]), datetime.datetime.fromisoformat(x["released"])), reverse=True)
# Iterate through, find build that is closest to the host version # Iterate through, find build that is closest to the host version
# Use date to determine which is closest # Use date to determine which is closest
if "ios" in results: for build_info in macos_builds:
for variant in results["ios"]: if build_info["osType"] == "macOS":
if variant["osStr"] == "macOS": version = cast(packaging.version.Version, packaging.version.parse(build_info["version"]))
if version == parsed_host_version:
name = variant["version"] # Skip, as we want the next closest match
version = name.split(" ")[0] continue
build = variant["build"] elif version <= parsed_host_version and version.major == parsed_host_version.major and version.minor == parsed_host_version.minor:
date = variant["released"] # The KDK list is already sorted by date then version, so the first match is the closest
print(f"- Closest match: {build_info['version']} build {build_info['build']}")
if version != host_version: return self.generate_kdk_link(build_info["version"], build_info["build"]), build_info["build"]
# Check if this is a security update (ie. 13.0.1)
version_split = version.split(".")
host_version_split = host_version.split(".")
if len(version_split) >= 2 and len(host_version_split) >= 2:
if not (version_split[0] == host_version_split[0] and version_split[1] == host_version_split[1]):
continue
else:
continue
if build == host_build:
# Skip, as we want the next closest match
continue
# Check if this is the newest build
if newest_build["date"] == "":
newest_build = {
"name": name,
"version": version,
"build": build,
"date": date,
}
else:
if date > newest_build["date"]:
newest_build = {
"name": name,
"version": version,
"build": build,
"date": date,
}
if newest_build["date"] != "":
print(f"- Closest match: {newest_build['version']} build {newest_build['build']}")
return self.generate_kdk_link(newest_build["version"], newest_build["build"])
print(" - Could not find a match") print(" - Could not find a match")
return None, "" return None, ""
def generate_kdk_link(self, version: str, build: str): def generate_kdk_link(self, version: str, build: str):
# Note: cannot do lazy matching as we don't store old version/build numbers nor can we enumerate KDKs from the portal return f"https://download.developer.apple.com/macOS/Kernel_Debug_Kit_{version}_build_{build}/Kernel_Debug_Kit_{version}_build_{build}.dmg"
URL_TEMPLATE = f"https://download.developer.apple.com/macOS/Kernel_Debug_Kit_{version}_build_{build}/Kernel_Debug_Kit_{version}_build_{build}.dmg"
return URL_TEMPLATE, build
def verify_apple_developer_portal(self, link): def verify_apple_developer_portal(self, link):
# Determine whether Apple Developer Portal is up # Determine whether Apple Developer Portal is up
@@ -105,7 +80,7 @@ class kernel_debug_kit_handler:
# 1: Portal is up but file is not available # 1: Portal is up but file is not available
# 2: Portal is down # 2: Portal is down
TOKEN_URL_BASE = "https://developerservices2.apple.com/services/download?path=" TOKEN_URL_BASE = "https://developerservices2.apple.com/services/download"
remote_path = urllib.parse.urlparse(link).path remote_path = urllib.parse.urlparse(link).path
token_url = urllib.parse.urlunparse(urllib.parse.urlparse(TOKEN_URL_BASE)._replace(query=urllib.parse.urlencode({"path": remote_path}))) token_url = urllib.parse.urlunparse(urllib.parse.urlparse(TOKEN_URL_BASE)._replace(query=urllib.parse.urlencode({"path": remote_path})))
@@ -126,7 +101,6 @@ class kernel_debug_kit_handler:
return 2 return 2
return 0 return 0
def download_kdk(self, version: str, build: str): def download_kdk(self, version: str, build: str):
error_msg = "" error_msg = ""
detected_build = build detected_build = build
@@ -135,37 +109,60 @@ class kernel_debug_kit_handler:
print(" - KDK is already installed") print(" - KDK is already installed")
return True, error_msg, detected_build return True, error_msg, detected_build
# Note: cannot do lazy matching as we don't store old version/build numbers nor can we enumerate KDKs from the portal download_link = None
URL_TEMPLATE, detected_build = self.generate_kdk_link(version, build) closest_match_download_link = None
closest_build = ""
kdk_list = self.get_available_kdks()
parsed_version = cast(packaging.version.Version, packaging.version.parse(version))
if kdk_list:
for kdk in kdk_list:
kdk_version = cast(packaging.version.Version, packaging.version.parse(kdk["version"]))
if kdk["build"] == build:
download_link = kdk["download_url"]
elif not closest_match_download_link and kdk_version <= parsed_version and kdk_version.major == parsed_version.major and kdk_version.minor == parsed_version.minor:
# The KDK list is already sorted by date then version, so the first match is the closest
closest_match_download_link = kdk["download_url"]
closest_build = kdk["build"]
else:
print(" - Could not fetch KDK list, falling back to brute force")
download_link = self.generate_kdk_link(version, build)
closest_match_download_link, closest_build = self.get_closest_match_legacy(version, build)
print(f"- Downloading Apple KDK for macOS {version} build {build}") print(f"- Downloading Apple KDK for macOS {version} build {build}")
result = self.verify_apple_developer_portal(URL_TEMPLATE) # download_link is None if no matching KDK is found, so we'll fall back to the closest match
if result == 2: result = self.verify_apple_developer_portal(download_link) if download_link else 1
error_msg = "Could not contact Apple download servers" if result == 0:
return False, error_msg, ""
elif result == 0:
print(" - Downloading KDK") print(" - Downloading KDK")
elif result == 1: elif result == 1:
print(" - Could not find KDK, finding closest match") print(" - Could not find KDK, finding closest match")
URL_TEMPLATE, detected_build = self.get_closest_match(version, build)
if self.is_kdk_installed(detected_build) is True: if self.is_kdk_installed(closest_build) is True:
return True, error_msg, detected_build return True, error_msg, closest_build
if URL_TEMPLATE is None:
if closest_match_download_link is None:
error_msg = "Could not find KDK for host, nor closest match" error_msg = "Could not find KDK for host, nor closest match"
return False, error_msg, "" return False, error_msg, ""
result = self.verify_apple_developer_portal(URL_TEMPLATE)
if result == 2: result = self.verify_apple_developer_portal(closest_match_download_link)
error_msg = "Could not contact Apple download servers"
return False, error_msg, "" if result == 0:
elif result == 0:
print(" - Downloading KDK") print(" - Downloading KDK")
download_link = closest_match_download_link
elif result == 1: elif result == 1:
print(" - Could not find KDK") print(" - Could not find KDK")
error_msg = "Could not find KDK for host on Apple's servers, nor closest match" error_msg = "Could not find KDK for host on Apple's servers, nor closest match"
return False, error_msg, "" return False, error_msg, ""
elif result == 2:
error_msg = "Could not contact Apple download servers"
return False, error_msg, ""
elif result == 2:
error_msg = "Could not contact Apple download servers"
return False, error_msg, ""
if utilities.download_apple_developer_portal(URL_TEMPLATE, self.constants.kdk_download_path): if utilities.download_apple_developer_portal(download_link, self.constants.kdk_download_path):
return True, error_msg, detected_build return True, error_msg, detected_build
error_msg = "Failed to download KDK" error_msg = "Failed to download KDK"
return False, error_msg, "" return False, error_msg, ""
@@ -176,4 +173,4 @@ class kernel_debug_kit_handler:
if file.is_dir(): if file.is_dir():
if file.name.endswith(f"{build}.kdk"): if file.name.endswith(f"{build}.kdk"):
return True return True
return False return False