From 9ae059b79716890e69fc303d6babe9a5db2d8268 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Sun, 13 Oct 2024 21:39:52 -0400 Subject: [PATCH] Add support for extracting/packing/otherwise handling dev WADs --- pyproject.toml | 2 +- src/libWiiPy/title/commonkeys.py | 14 +++++++++++--- src/libWiiPy/title/crypto.py | 12 ++++++++---- src/libWiiPy/title/ticket.py | 12 +++++++++++- src/libWiiPy/title/title.py | 3 ++- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ae83e53..8f06d06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "libWiiPy" -version = "0.5.1" +version = "0.5.2" authors = [ { name="NinjaCheetah", email="ninjacheetah@ncxprogramming.com" }, { name="Lillian Skinner", email="lillian@randommeaninglesscharacters.com" } diff --git a/src/libWiiPy/title/commonkeys.py b/src/libWiiPy/title/commonkeys.py index 66cd8be..10d4903 100644 --- a/src/libWiiPy/title/commonkeys.py +++ b/src/libWiiPy/title/commonkeys.py @@ -7,11 +7,14 @@ common_key = 'ebe42a225e8593e448d9c5457381aaf7' korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e' vwii_key = '30bfc76e7c19afbb23163330ced7c28d' +development_key = 'a1604a6a7123b529ae8bec32c816fcaa' -def get_common_key(common_key_index) -> bytes: + +def get_common_key(common_key_index, dev=False) -> bytes: """ Gets the specified Wii Common Key based on the index provided. If an invalid common key index is provided, this - function falls back on always returning key 0 (the Common Key). + function falls back on always returning key 0 (the Common Key). If the kwarg "dev" is specified, then key 0 will + point to the development common key rather than the retail one. Keys 1 and 2 are unaffected by this argument. Possible values for common_key_index: 0: Common Key, 1: Korean Key, 2: vWii Key @@ -19,6 +22,8 @@ def get_common_key(common_key_index) -> bytes: ---------- common_key_index : int The index of the common key to be returned. + dev : bool + If the dev keys should be used in place of the retail keys. Only affects key 0. Returns ------- @@ -27,7 +32,10 @@ def get_common_key(common_key_index) -> bytes: """ match common_key_index: case 0: - common_key_bin = binascii.unhexlify(common_key) + if dev: + common_key_bin = binascii.unhexlify(development_key) + else: + common_key_bin = binascii.unhexlify(common_key) case 1: common_key_bin = binascii.unhexlify(korean_key) case 2: diff --git a/src/libWiiPy/title/crypto.py b/src/libWiiPy/title/crypto.py index 35e2e9e..ef02c22 100644 --- a/src/libWiiPy/title/crypto.py +++ b/src/libWiiPy/title/crypto.py @@ -30,7 +30,7 @@ def _convert_tid_to_iv(title_id: str | bytes) -> bytes: return title_key_iv -def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: bytes | str) -> bytes: +def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: bytes | str, dev=False) -> bytes: """ Gets the decrypted version of the encrypted Title Key provided. @@ -44,6 +44,8 @@ def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: byt The index of the common key used to encrypt the Title Key. title_id : bytes, str The Title ID of the title that the key is for. + dev : bool + Whether the Title Key is encrypted with the development key or not. Returns ------- @@ -51,7 +53,7 @@ def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: byt The decrypted Title Key. """ # Load the correct common key for the title. - common_key = get_common_key(common_key_index) + common_key = get_common_key(common_key_index, dev) # Convert the IV into the correct format based on the type provided. title_key_iv = _convert_tid_to_iv(title_id) # The IV will always be in the same format by this point, so add the last 8 bytes. @@ -63,7 +65,7 @@ def decrypt_title_key(title_key_enc: bytes, common_key_index: int, title_id: byt return title_key -def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: bytes | str) -> bytes: +def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: bytes | str, dev=False) -> bytes: """ Encrypts the provided Title Key with the selected common key. @@ -77,6 +79,8 @@ def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: byt The index of the common key used to encrypt the Title Key. title_id : bytes, str The Title ID of the title that the key is for. + dev : bool + Whether the Title Key is encrypted with the development key or not. Returns ------- @@ -84,7 +88,7 @@ def encrypt_title_key(title_key_dec: bytes, common_key_index: int, title_id: byt An encrypted Title Key. """ # Load the correct common key for the title. - common_key = get_common_key(common_key_index) + common_key = get_common_key(common_key_index, dev) # Convert the IV into the correct format based on the type provided. title_key_iv = _convert_tid_to_iv(title_id) # The IV will always be in the same format by this point, so add the last 8 bytes. diff --git a/src/libWiiPy/title/ticket.py b/src/libWiiPy/title/ticket.py index 8266bb7..b1f4ed9 100644 --- a/src/libWiiPy/title/ticket.py +++ b/src/libWiiPy/title/ticket.py @@ -40,6 +40,9 @@ class Ticket: Attributes ---------- + is_dev : bool + Whether this Ticket is signed for development or not, and whether the Title Key is encrypted for development + or not. signature : bytes The signature applied to the ticket. ticket_version : int @@ -56,6 +59,8 @@ class Ticket: The index of the common key required to decrypt this ticket's Title Key. """ def __init__(self): + # If this is a dev ticket + self.is_dev: bool = False # Defaults to false, set to true during load if this ticket is using dev certs. # Signature blob header self.signature_type: bytes = b'' # Type of signature, always 0x10001 for RSA-2048 self.signature: bytes = b'' # Actual signature data @@ -155,6 +160,11 @@ class Ticket: limit_type = int.from_bytes(ticket_data.read(4)) limit_value = int.from_bytes(ticket_data.read(4)) self.title_limits_list.append(_TitleLimit(limit_type, limit_value)) + # Check certs to see if this is a retail or dev ticket. Treats unknown certs as being retail for now. + if self.signature_issuer.find("Root-CA00000002-XS00000006") != -1: + self.is_dev = True + else: + self.is_dev = False def dump(self) -> bytes: """ @@ -315,7 +325,7 @@ class Ticket: bytes The decrypted title key. """ - title_key = decrypt_title_key(self.title_key_enc, self.common_key_index, self.title_id) + title_key = decrypt_title_key(self.title_key_enc, self.common_key_index, self.title_id, self.is_dev) return title_key def set_title_id(self, title_id) -> None: diff --git a/src/libWiiPy/title/title.py b/src/libWiiPy/title/title.py index e2702a0..aedb3f6 100644 --- a/src/libWiiPy/title/title.py +++ b/src/libWiiPy/title/title.py @@ -137,7 +137,8 @@ class Title: self.tmd.set_title_id(title_id) title_key_decrypted = self.ticket.get_title_key() self.ticket.set_title_id(title_id) - title_key_encrypted = encrypt_title_key(title_key_decrypted, self.ticket.common_key_index, title_id) + title_key_encrypted = encrypt_title_key(title_key_decrypted, self.ticket.common_key_index, title_id, + self.ticket.is_dev) self.ticket.title_key_enc = title_key_encrypted def set_title_version(self, title_version: str | int) -> None: