diff --git a/requirements.txt b/requirements.txt index 378eac2..1fca23a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ build +pycryptodome diff --git a/src/libWiiPy/commonkeys.py b/src/libWiiPy/commonkeys.py index f6eee50..97f436d 100644 --- a/src/libWiiPy/commonkeys.py +++ b/src/libWiiPy/commonkeys.py @@ -1,21 +1,25 @@ # "commonkeys.py" from libWiiPy by NinjaCheetah & Contributors # https://github.com/NinjaCheetah/libWiiPy -default_key = 'ebe42a225e8593e448d9c5457381aaf7' +import binascii + +common_key = 'ebe42a225e8593e448d9c5457381aaf7' korean_key = '63b82bb4f4614e2e13f2fefbba4c9b7e' vwii_key = '30bfc76e7c19afbb23163330ced7c28d' -def get_default_key(): - """Returns the regular Wii Common Key used to encrypt most content.""" - return default_key - - -def get_korean_key(): - """Returns the Korean Wii Common Key used to encrypt Korean content.""" - return korean_key - - -def get_vwii_key(): - """Returns the vWii Common Key used to encrypt vWii-specific content.""" - return vwii_key +def get_common_key(common_key_index): + """ + Returns the specified Wii Common Key based on the index provided. + Possible values for common_key_index: 0: Common Key, 1: Korean Key, 2: vWii Key + """ + match common_key_index: + case 0: + common_key_bin = binascii.unhexlify(common_key) + case 1: + common_key_bin = binascii.unhexlify(korean_key) + case 2: + common_key_bin = binascii.unhexlify(vwii_key) + case _: + raise ValueError("The common key index provided, " + str(common_key_index + ", does not exist.")) + return common_key_bin diff --git a/src/libWiiPy/crypto.py b/src/libWiiPy/crypto.py index 6c214d7..d8a7451 100644 --- a/src/libWiiPy/crypto.py +++ b/src/libWiiPy/crypto.py @@ -2,3 +2,22 @@ # https://github.com/NinjaCheetah/libWiiPy # # See https://wiibrew.org/wiki/Ticket for details about the TMD format + +from .commonkeys import get_common_key +from Crypto.Cipher import AES + + +def decrypt_title_key(title_key_enc, common_key_index, title_id): + """ + Returns the decrypted version of the encrypted Title Key provided. + Requires the index of the common key to use, and the Title ID of the title that the Title Key is for. + """ + # Load the correct common key for the title. + common_key = get_common_key(common_key_index) + # Calculate the IV by adding 8 bytes to the end of the Title ID. + title_key_iv = title_id + (b'\x00' * 8) + # Create a new AES object with the values provided. + aes = AES.new(common_key, AES.MODE_CBC, title_key_iv) + # Decrypt the Title Key using the AES object. + title_key = aes.decrypt(title_key_enc) + return title_key diff --git a/src/libWiiPy/ticket.py b/src/libWiiPy/ticket.py index ddf1084..cf31c8f 100644 --- a/src/libWiiPy/ticket.py +++ b/src/libWiiPy/ticket.py @@ -4,6 +4,20 @@ # See https://wiibrew.org/wiki/Ticket for details about the TMD format import io +from .crypto import decrypt_title_key +from dataclasses import dataclass +from typing import List + + +@dataclass +class TitleLimit: + """Creates a TitleLimit object that contains the type of restriction and the limit.""" + # The type of play limit applied. The following types exist: + # 0 = None, 1 = Time Limit, 3 = None, 4 = Launch Count + limit_type: int + # The maximum value of the limit applied. + # This is either the number of minutes for a time limit, or the number of launches for a launch limit. + maximum_usage: int class Ticket: @@ -28,9 +42,7 @@ class Ticket: self.title_export_allowed: int # Whether title export is allowed with a PRNG key or not. self.common_key_index: int # Which common key should be used. 0 = Common Key, 1 = Korean Key, 2 = vWii Key self.content_access_permissions: bytes # "Content access permissions (one bit for each content)" - #self.limit_type: int # Type of play limit applied to the title. - # 0 = None, 1 = Time Limit, 3 = None, 4 = Launch Count - #self.maximum_launches: int # Maximum for the selected limit type, being either minutes or launches. + self.title_limits_list: List[TitleLimit] = [] # List of play limits applied to the title. # v1 ticket data # TODO: Figure out v1 ticket stuff with io.BytesIO(self.ticket) as ticketdata: @@ -85,6 +97,12 @@ class Ticket: # Content access permissions ticketdata.seek(0x222) self.content_access_permissions = ticketdata.read(64) + # Content limits + ticketdata.seek(0x264) + for limit in range(0, 8): + limit_type = int.from_bytes(ticketdata.read(4)) + limit_value = int.from_bytes(ticketdata.read(4)) + self.title_limits_list.append(TitleLimit(limit_type, limit_value)) def get_signature(self): """Returns the signature of the ticket.""" @@ -131,6 +149,6 @@ class Ticket: def get_title_key(self): """Returns the decrypted title key contained in the ticket.""" - # TODO - return b'\x00' + title_key = decrypt_title_key(self.title_key_enc, self.common_key_index, self.title_id) + return title_key diff --git a/src/libWiiPy/tmd.py b/src/libWiiPy/tmd.py index 5a8e838..e626356 100644 --- a/src/libWiiPy/tmd.py +++ b/src/libWiiPy/tmd.py @@ -29,17 +29,17 @@ class TMD: self.version: int # This seems to always be 0 no matter what? self.ca_crl_version: int self.signer_crl_version: int - self.vwii: int - self.ios_tid: str - self.ios_version: int - self.title_id: str - self.content_type: str - self.group_id: int # Publisher of the title - self.region: int + self.vwii: int # Whether the title is for the vWii. 0 = No, 1 = Yes + self.ios_tid: str # The Title ID of the IOS version the associated title runs on. + self.ios_version: int # The IOS version the associated title runs on. + self.title_id: str # The Title ID of the associated title. + self.content_type: str # The type of content contained within the associated title. + self.group_id: int # The ID of the publisher of the associated title. + self.region: int # The ID of the region of the associated title. self.ratings: int self.access_rights: int - self.title_version: int - self.num_contents: int + self.title_version: int # The version of the associated title. + self.num_contents: int # The number of contents contained in the associated title. self.boot_index: int self.content_record: List[ContentRecord] # Load data from TMD file