Zertifikat
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