Dieser Artikel betrifft die Nintendo Wii

Zertifikat

Aus WiiDatabase Wiki
Zur Navigation springenZur Suche springen

Zertifikate enthalten die öffentlichen Schlüssel, mit denen die Wii Tickets, TMDs und weitere Zertifikate prüft. Sie hängen als Kette zusammen: der Root-Schlüssel, der fest in der Konsole steckt, signiert das CA-Zertifikat, dieses signiert das CP-Zertifikat (für TMDs), das XS-Zertifikat (für Tickets) und die MS-Zertifikate (für Gerätezertifikate).

Die Kette für Tickets und TMDs liegt im NAND unter /sys/cert.sys, 2560 Byte groß mit CA00000001, CP00000004 und XS00000003. In welcher Reihenfolge die drei in der Datei stehen, ist nicht festgelegt, denn ES schreibt jedes Zertifikat dazu, das es beim Import zum ersten Mal sieht. Geprüft wird damit alles, was im NAND liegt, denn die dort gespeicherten TMDs und Tickets enthalten selbst keine Zertifikate.

Kommt ein Titel von außen, wird die Kette mitgeliefert:

  • Beim NUS hängt sie hinter der Datei. Hinter der TMD folgen CP00000004 und CA00000001, hinter dem Ticket XS00000003 und CA00000001.
  • In einer WAD ist sie ein eigener Block direkt hinter dem Header, also vor Ticket und TMD.
  • Bei Speicherdaten wird die Kette nicht gespeichert sondern direkt ausgelesen.

Jede Konsole hat zusätzlich ein eigenes Gerätezertifikat (NG) mit einem ECC-B233-Schlüssel, das von einem MS-Zertifikat signiert ist. Mit dessen Schlüssel signiert ES Daten im Auftrag von Titeln und stellt dazu AP-Zertifikate aus, deren Name aus AP und der Title ID besteht (beim Systemmenü also AP0000000100000002). Damit sind die Speicherdaten signiert, die auf die SD-Karte kopiert werden; die Kette dafür besteht nur aus NG- und AP-Zertifikat. Die beiden MS-Zertifikate stecken im Systemmenü selbst: in dessen Haupt-DOL liegen MS00000002 und MS00000003 hintereinander, je 0x240 Byte.

Zertifikate

Name Signatur Schlüssel Aussteller Zweck
Root RSA-4096 Steckt fest im Sicherheitskernel von IOS und ist der Anfang jeder Kette
CA00000001 RSA-4096 RSA-2048 Root Prüft die XS-, CP- und MS-Zertifikate für Endkundenkonsolen
CA00000002 RSA-4096 RSA-2048 Root Dasselbe für Entwicklungskonsolen
XS00000003 RSA-2048 RSA-2048 Root-CA00000001 Signiert Tickets
XS00000006 RSA-2048 RSA-2048 Root-CA00000002 Signiert Tickets für Entwicklungskonsolen
CP00000004 RSA-2048 RSA-2048 Root-CA00000001 Signiert TMDs
CP00000007 RSA-2048 RSA-2048 Root-CA00000002 Signiert TMDs für Entwicklungskonsolen
MS00000002 RSA-2048 ECC-B233 Root-CA00000001 Signiert die Gerätezertifikate der Endkundenkonsolen
MS00000003 RSA-2048 ECC-B233 Root-CA00000002 Signiert die Gerätezertifikate der Entwicklungskonsolen
NGxxxxxxxx ECC-B233 ECC-B233 Root-CA00000001-MS00000002 Gerätezertifikat, xxxxxxxx ist die Geräte-ID
APxxxxxxxxxxxxxxxx ECC-B233 ECC-B233 Root-CA00000001-MS00000002-NGxxxxxxxx Wird von der Konsole ausgestellt und signiert Speicherdaten, x ist die Title ID

Für die Revolution Arcade gibt es eigene CA-, CP- und XS-Zertifikate mit Nummern ab 0x10000000. Nintendos interne Test- und Beta-Ketten benutzen wieder andere Nummern, z.B. CP00000001 oder XS0000001F.

Datenstruktur

Ein Zertifikat beginnt mit dem gleichen Signaturblock wie TMD und Ticket: Signatur-Typ, Signatur, Padding und Aussteller. Wie dort deckt die Signatur alles ab dem Aussteller ab. Weil die Signatur je nach Verfahren unterschiedlich lang ist, beginnen die eigentlichen Zertifikatsdaten nicht immer an derselben Stelle. Die Gesamtlänge ist durch Padding immer ein Vielfaches von 64 Byte.

Start Länge in Byte Beschreibung
0x0000 4 Signatur-Typ
0x0004 256, 512 oder 64 Signatur des Ausstellers (Länge je nach Signatur-Typ)
60 Padding (immer 0)
64 Aussteller, z.B. Root-CA00000001
Zertifikatsdaten

Signatur-Typ

Wert Verfahren Länge der Signatur Zertifikatsdaten ab
0x00010000 RSA-4096 512 0x0280
0x00010001 RSA-2048 256 0x0180
0x00010002 ECC-B233 60 + 4 Byte Padding 0x00C0

Zertifikatsdaten

Die folgenden Offsets sind relativ zum Beginn der Zertifikatsdaten. Bei einem RSA-2048-signierten Zertifikat (CP, XS, MS) sind also 0x0180 dazuzurechnen.

Start Ende Länge in Byte Beschreibung
0x0000 0x0003 4 Schlüssel-Typ
0x0004 0x0043 64 Name des Zertifikats, z.B. CP00000004. Aussteller und Name zusammen ergeben den Aussteller-Eintrag der damit signierten Datei, also Root-CA00000001-CP00000004
0x0044 0x0047 4 Datum. Bei Gerätezertifikaten das Herstellungsdatum der Konsole. ES wertet das Feld nicht aus
0x0048 512, 256 oder 60 Öffentlicher Schlüssel (Länge je nach Schlüssel-Typ)
4 Exponent, nur bei RSA-Schlüsseln und immer 0x00010001
52 bzw. 60 Padding

Schlüssel-Typ

Wert Verfahren Länge des Schlüssels
0 RSA-4096 512
1 RSA-2048 256
2 ECC-B233 60

Größen

Signatur- und Schlüssel-Typ zusammen ergeben eine feste Gesamtlänge. Damit lässt sich eine angehängte Kette durchlaufen, ohne sie zu parsen.

Zertifikat Signatur Schlüssel Länge
CA00000001 RSA-4096 RSA-2048 0x0400
CP00000004, XS00000003 RSA-2048 RSA-2048 0x0300
MS-Zertifikate RSA-2048 ECC-B233 0x0240
Gerätezertifikat (NG) und die davon signierten AP-Zertifikate ECC-B233 ECC-B233 0x0180

Ein Root-Zertifikat gibt es auf der Wii nicht als Datei, denn der Root-Schlüssel liegt in der Konsole selbst.

Prüfung

Der Aussteller-Eintrag ist der ganze Weg durch die Kette, z.B. Root-CA00000001-CP00000004. Der letzte Teil ist der Name des Zertifikats, das die Datei signiert hat; alles davor ist dessen eigener Aussteller. So hangelt sich ES von der TMD oder dem Ticket über CP bzw. XS und CA bis zum Root-Schlüssel, und weil jede Stufe die nächste prüft, hängt am Ende alles am Root-Schlüssel.

Dabei schaut ES auch nach, wofür ein Zertifikat gedacht ist: bei einer TMD muss der Name mit CP beginnen, bei einem Ticket mit XS und bei einem Gerätezertifikat mit MS. Ein Ticket, das mit dem CP-Zertifikat signiert wurde, wird also abgelehnt, auch wenn die Signatur selbst stimmt.

Anwendungsbeispiel

Dieses Code-Beispiel in Python 3 liest ein Zertifikat und prüft damit die Signatur einer TMD.

from hashlib import sha1
from struct import unpack

# Länge der Signatur je Signatur-Typ und des Schlüssels je Schlüssel-Typ
SIG_SIZES = {0x10000: 512, 0x10001: 256, 0x10002: 64}
KEY_SIZES = {0: 512, 1: 256, 2: 60}


def parse_cert(cert):
    # Signatur-Typ, danach Signatur, 60 Byte Padding und 64 Byte Aussteller
    sig_type, = unpack(">I", cert[:4])
    data = 4 + SIG_SIZES[sig_type] + 60 + 64  # hier beginnen die Zertifikatsdaten

    key_type, = unpack(">I", cert[data:data + 4])
    key_size = KEY_SIZES[key_type]
    name = cert[data + 4:data + 0x44].split(b"\0")[0].decode()
    key = cert[data + 0x48:data + 0x48 + key_size]
    exponent, = unpack(">I", cert[data + 0x48 + key_size:data + 0x4C + key_size])

    length = data + 0x48 + key_size + (56 if key_type != 2 else 60)
    return name, key, exponent, length


def verify_tmd(tmd):
    """Prüft die Signatur einer TMD mit dem angehängten CP-Zertifikat."""
    count, = unpack(">H", tmd[0x1DE:0x1E0])
    end = 0x1E4 + 36 * count  # Ende der TMD, danach beginnt die Zertifikatskette
    name, key, exponent, length = parse_cert(tmd[end:])

    signature = int.from_bytes(tmd[0x04:0x104], "big")
    decrypted = pow(signature, exponent, int.from_bytes(key, "big"))
    return decrypted.to_bytes(256, "big")[-20:] == sha1(tmd[0x140:end]).digest()


tmd = open("tmd", "rb").read()
print(verify_tmd(tmd))

Gibt für die TMD des IOS58 True aus. Mit der Länge aus parse_cert lässt sich auch die ganze Kette durchlaufen:

offset = 0x1E4 + 36 * unpack(">H", tmd[0x1DE:0x1E0])[0]
while offset < len(tmd):
    name, key, exponent, length = parse_cert(tmd[offset:])
    print(hex(offset), name, hex(length))
    offset += length
0x490 CP00000004 0x300
0x790 CA00000001 0x400

Siehe auch