Handle cert chain data in a title as a CertificateChain instead of bytes

This commit is contained in:
Campbell 2024-12-16 22:14:07 -05:00
parent ece19177c4
commit 8a15b1e82e
Signed by: NinjaCheetah
GPG Key ID: 670C282B3291D63D
2 changed files with 42 additions and 22 deletions

View File

@ -40,10 +40,10 @@ def download_title(title_id: str, title_version: int = None, wiiu_endpoint: bool
""" """
# First, create the new title. # First, create the new title.
title = Title() title = Title()
# Download and load the TMD, Ticket, and certs. # Download and load the certificate chain, TMD, and Ticket.
title.load_cert_chain(download_cert_chain(wiiu_endpoint, endpoint_override))
title.load_tmd(download_tmd(title_id, title_version, wiiu_endpoint, endpoint_override)) title.load_tmd(download_tmd(title_id, title_version, wiiu_endpoint, endpoint_override))
title.load_ticket(download_ticket(title_id, wiiu_endpoint, endpoint_override)) title.load_ticket(download_ticket(title_id, wiiu_endpoint, endpoint_override))
title.wad.set_cert_data(download_cert(wiiu_endpoint, endpoint_override))
# Download all contents # Download all contents
title.load_content_records() title.load_content_records()
title.content.content_list = download_contents(title_id, title.tmd, wiiu_endpoint, endpoint_override) title.content.content_list = download_contents(title_id, title.tmd, wiiu_endpoint, endpoint_override)
@ -146,9 +146,9 @@ def download_ticket(title_id: str, wiiu_endpoint: bool = False, endpoint_overrid
return ticket return ticket
def download_cert(wiiu_endpoint: bool = False, endpoint_override: str = None) -> bytes: def download_cert_chain(wiiu_endpoint: bool = False, endpoint_override: str = None) -> bytes:
""" """
Downloads the signing certificate used by all WADs. This uses System Menu 4.3U as the source. Downloads the signing certificate chain used by all WADs. This uses System Menu 4.3U as the source.
Parameters Parameters
---------- ----------
@ -163,7 +163,7 @@ def download_cert(wiiu_endpoint: bool = False, endpoint_override: str = None) ->
bytes bytes
The cert file. The cert file.
""" """
# Download the TMD and cetk for the System Menu 4.3U. # Download the TMD and cetk for System Menu 4.3U (v513).
if endpoint_override is not None: if endpoint_override is not None:
endpoint_url = _validate_endpoint(endpoint_override) endpoint_url = _validate_endpoint(endpoint_override)
else: else:
@ -175,18 +175,18 @@ def download_cert(wiiu_endpoint: bool = False, endpoint_override: str = None) ->
cetk_url = endpoint_url + "0000000100000002/cetk" cetk_url = endpoint_url + "0000000100000002/cetk"
tmd = requests.get(url=tmd_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content tmd = requests.get(url=tmd_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content
cetk = requests.get(url=cetk_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content cetk = requests.get(url=cetk_url, headers={'User-Agent': 'wii libnup/1.0'}, stream=True).content
# Assemble the certificate. # Assemble the certificate chain.
cert = b'' cert_chain = b''
# Certificate Authority data. # Certificate Authority data.
cert += cetk[0x2A4 + 768:] cert_chain += cetk[0x2A4 + 768:]
# Certificate Policy data. # Certificate Policy (TMD certificate) data.
cert += tmd[0x328:0x328 + 768] cert_chain += tmd[0x328:0x328 + 768]
# XS data. # XS (Ticket certificate) data.
cert += cetk[0x2A4:0x2A4 + 768] cert_chain += cetk[0x2A4:0x2A4 + 768]
# Since the cert is always the same, check the hash to make sure nothing went wildly wrong. # Since the cert chain is always the same, check the hash to make sure nothing went wildly wrong.
if hashlib.sha1(cert).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0": if hashlib.sha1(cert_chain).hexdigest() != "ace0f15d2a851c383fe4657afc3840d6ffe30ad0":
raise Exception("An unknown error has occurred downloading and creating the certificate.") raise Exception("An unknown error has occurred downloading and creating the certificate.")
return cert return cert_chain
def download_content(title_id: str, content_id: int, wiiu_endpoint: bool = False, def download_content(title_id: str, content_id: int, wiiu_endpoint: bool = False,

View File

@ -4,6 +4,7 @@
# See https://wiibrew.org/wiki/Title for details about how titles are formatted # See https://wiibrew.org/wiki/Title for details about how titles are formatted
import math import math
from .cert import CertificateChain
from .content import ContentRegion from .content import ContentRegion
from .ticket import Ticket from .ticket import Ticket
from .tmd import TMD from .tmd import TMD
@ -19,17 +20,20 @@ class Title:
Attributes Attributes
---------- ----------
wad : WAD wad: WAD
A WAD object of a WAD containing the title's data. A WAD object of a WAD containing the title's data.
tmd : TMD cert_chain: CertificateChain
The chain of certificates used to verify the contents of a title.
tmd: TMD
A TMD object of the title's TMD. A TMD object of the title's TMD.
ticket : Ticket ticket: Ticket
A Ticket object of the title's Ticket. A Ticket object of the title's Ticket.
content: ContentRegion content: ContentRegion
A ContentRegion object containing the title's contents. A ContentRegion object containing the title's contents.
""" """
def __init__(self): def __init__(self):
self.wad: WAD = WAD() self.wad: WAD = WAD()
self.cert_chain: CertificateChain = CertificateChain()
self.tmd: TMD = TMD() self.tmd: TMD = TMD()
self.ticket: Ticket = Ticket() self.ticket: Ticket = Ticket()
self.content: ContentRegion = ContentRegion() self.content: ContentRegion = ContentRegion()
@ -47,6 +51,9 @@ class Title:
# Create a new WAD object based on the WAD data provided. # Create a new WAD object based on the WAD data provided.
self.wad = WAD() self.wad = WAD()
self.wad.load(wad) self.wad.load(wad)
# Load the certificate chain.
self.cert_chain = CertificateChain()
self.cert_chain.load(self.wad.get_cert_data())
# Load the TMD. # Load the TMD.
self.tmd = TMD() self.tmd = TMD()
self.tmd.load(self.wad.get_tmd_data()) self.tmd.load(self.wad.get_tmd_data())
@ -75,6 +82,8 @@ class Title:
# Set WAD type to ib if the title being packed is boot2. # Set WAD type to ib if the title being packed is boot2.
if self.tmd.title_id == "0000000100000001": if self.tmd.title_id == "0000000100000001":
self.wad.wad_type = "ib" self.wad.wad_type = "ib"
# Dump the certificate chain and set it in the WAD.
self.wad.set_cert_data(self.cert_chain.dump())
# Dump the TMD and set it in the WAD. # Dump the TMD and set it in the WAD.
# This requires updating the content records and number of contents in the TMD first. # This requires updating the content records and number of contents in the TMD first.
self.tmd.content_records = self.content.content_records # This may not be needed because it's a ref already self.tmd.content_records = self.content.content_records # This may not be needed because it's a ref already
@ -87,6 +96,19 @@ class Title:
self.wad.set_content_data(content_data, content_size) self.wad.set_content_data(content_data, content_size)
return self.wad.dump() return self.wad.dump()
def load_cert_chain(self, cert_chain: bytes) -> None:
"""
Load an existing certificate chain into the title. Note that this will overwrite any existing certificate chain
data for this title.
Parameters
----------
cert_chain: bytes
The data for the certificate chain to load.
"""
self.cert_chain.load(cert_chain)
def load_tmd(self, tmd: bytes) -> None: def load_tmd(self, tmd: bytes) -> None:
""" """
Load existing TMD data into the title. Note that this will overwrite any existing TMD data for this title. Load existing TMD data into the title. Note that this will overwrite any existing TMD data for this title.
@ -94,9 +116,8 @@ class Title:
Parameters Parameters
---------- ----------
tmd : bytes tmd : bytes
The data for the WAD you wish to load. The data for the TMD to load.
""" """
# Load TMD.
self.tmd.load(tmd) self.tmd.load(tmd)
def load_ticket(self, ticket: bytes) -> None: def load_ticket(self, ticket: bytes) -> None:
@ -107,9 +128,8 @@ class Title:
Parameters Parameters
---------- ----------
ticket : bytes ticket : bytes
The data for the WAD you wish to load. The data for the Ticket to load.
""" """
# Load Ticket.
self.ticket.load(ticket) self.ticket.load(ticket)
def load_content_records(self) -> None: def load_content_records(self) -> None: