/* This file is part of libWiiSharp * Copyright (C) 2009 Leathl * * libWiiSharp is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libWiiSharp is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.IO; using System.Security.Cryptography; namespace libWiiSharp { public class CertificateChain : IDisposable { private const string certCaHash = "5B7D3EE28706AD8DA2CBD5A6B75C15D0F9B6F318"; private const string certCpHash = "6824D6DA4C25184F0D6DAF6EDB9C0FC57522A41C"; private const string certXsHash = "09787045037121477824BC6A3E5E076156573F8A"; private SHA1 sha = SHA1.Create(); private bool[] certsComplete = new bool[3]; private byte[] certCa = new byte[0x400]; private byte[] certCp = new byte[0x300]; private byte[] certXs = new byte[0x300]; /// /// If false, the Certificate Chain is not complete (i.e. at least one certificate is missing). /// public bool CertsComplete { get { return (certsComplete[0] && certsComplete[1] && certsComplete[2]); } } #region IDisposable Members private bool isDisposed = false; ~CertificateChain() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing && !isDisposed) { sha.Clear(); sha = null; certsComplete = null; certCa = null; certCp = null; certXs = null; } isDisposed = true; } #endregion #region Public Functions /// /// Loads a cert file. /// /// /// public static CertificateChain Load(string pathToCert) { return Load(File.ReadAllBytes(pathToCert)); } /// /// Loads a cert file. /// /// /// public static CertificateChain Load(byte[] certFile) { CertificateChain c = new CertificateChain(); MemoryStream ms = new MemoryStream(certFile); try { c.parseCert(ms); } catch { ms.Dispose(); throw; } ms.Dispose(); return c; } /// /// Loads a cert file. /// /// /// public static CertificateChain Load(Stream cert) { CertificateChain c = new CertificateChain(); c.parseCert(cert); return c; } /// /// Grabs certificates from Ticket and Tmd. /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!) /// /// /// /// public static CertificateChain FromTikTmd(string pathToTik, string pathToTmd) { return FromTikTmd(File.ReadAllBytes(pathToTik), File.ReadAllBytes(pathToTmd)); } /// /// Grabs certificates from Ticket and Tmd. /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!) /// /// /// /// public static CertificateChain FromTikTmd(string pathToTik, byte[] tmdFile) { return FromTikTmd(File.ReadAllBytes(pathToTik), tmdFile); } /// /// Grabs certificates from Ticket and Tmd. /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!) /// /// /// /// public static CertificateChain FromTikTmd(byte[] tikFile, byte[] tmdFile) { CertificateChain c = new CertificateChain(); MemoryStream ms = new MemoryStream(tikFile); try { c.grabFromTik(ms); } catch { ms.Dispose(); throw; } ms = new MemoryStream(tmdFile); try { c.grabFromTmd(ms); } catch { ms.Dispose(); throw; } ms.Dispose(); if (!c.CertsComplete) throw new Exception("Couldn't locate all certs!"); return c; } /// /// Grabs certificates from Ticket and Tmd. /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!) /// /// /// /// public static CertificateChain FromTikTmd(Stream tik, Stream tmd) { CertificateChain c = new CertificateChain(); c.grabFromTik(tik); c.grabFromTmd(tmd); return c; } /// /// Loads a cert file. /// /// public void LoadFile(string pathToCert) { LoadFile(File.ReadAllBytes(pathToCert)); } /// /// Loads a cert file. /// /// public void LoadFile(byte[] certFile) { MemoryStream ms = new MemoryStream(certFile); try { parseCert(ms); } catch { ms.Dispose(); throw; } ms.Dispose(); } /// /// Loads a cert file. /// /// public void LoadFile(Stream cert) { parseCert(cert); } /// /// Grabs certificates from Ticket and Tmd. /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!) /// /// /// /// public void LoadFromTikTmd(string pathToTik, string pathToTmd) { LoadFromTikTmd(File.ReadAllBytes(pathToTik), File.ReadAllBytes(pathToTmd)); } /// /// Grabs certificates from Ticket and Tmd. /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!) /// /// /// /// public void LoadFromTikTmd(string pathToTik, byte[] tmdFile) { LoadFromTikTmd(File.ReadAllBytes(pathToTik), tmdFile); } /// /// Grabs certificates from Ticket and Tmd. /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!) /// /// /// public void LoadFromTikTmd(byte[] tikFile, byte[] tmdFile) { MemoryStream ms = new MemoryStream(tikFile); try { grabFromTik(ms); } catch { ms.Dispose(); throw; } ms = new MemoryStream(tmdFile); try { grabFromTmd(ms); } catch { ms.Dispose(); throw; } ms.Dispose(); if (!CertsComplete) throw new Exception("Couldn't locate all certs!"); } /// /// Grabs certificates from Ticket and Tmd. /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!) /// /// /// public void LoadFromTikTmd(Stream tik, Stream tmd) { grabFromTik(tik); grabFromTmd(tmd); } /// /// Saves the Certificate Chain. /// /// public void Save(string savePath) { if (File.Exists(savePath)) File.Delete(savePath); using (FileStream fs = new FileStream(savePath, FileMode.Create)) writeToStream(fs); } /// /// Returns the Certificate Chain as a memory stream. /// /// public MemoryStream ToMemoryStream() { MemoryStream ms = new MemoryStream(); try { writeToStream(ms); } catch { ms.Dispose(); throw; } return ms; } /// /// Returns the Certificate Chain as a byte array. /// /// public byte[] ToByteArray() { MemoryStream ms = new MemoryStream(); try { writeToStream(ms); } catch { ms.Dispose(); throw; } byte[] res = ms.ToArray(); ms.Dispose(); return res; } #endregion #region Private Functions private void writeToStream(Stream writeStream) { fireDebug("Writing Certificate Chain..."); if (!CertsComplete) { fireDebug(" Certificate Chain incomplete..."); throw new Exception("At least one certificate is missing!"); } writeStream.Seek(0, SeekOrigin.Begin); fireDebug(" Writing Certificate CA... (Offset: 0x{0})", writeStream.Position.ToString("x8")); writeStream.Write(certCa, 0, certCa.Length); fireDebug(" Writing Certificate CP... (Offset: 0x{0})", writeStream.Position.ToString("x8")); writeStream.Write(certCp, 0, certCp.Length); fireDebug(" Writing Certificate XS... (Offset: 0x{0})", writeStream.Position.ToString("x8")); writeStream.Write(certXs, 0, certXs.Length); fireDebug("Writing Certificate Chain Finished..."); } private void parseCert(Stream certFile) { fireDebug("Parsing Certificate Chain..."); int off = 0; for (int i = 0; i < 3; i++) { fireDebug(" Scanning at Offset 0x{0}:", off.ToString("x8")); try { certFile.Seek(off, SeekOrigin.Begin); byte[] temp = new byte[0x400]; certFile.Read(temp, 0, temp.Length); fireDebug(" Checking for Certificate CA..."); if (isCertCa(temp) && !certsComplete[1]) { fireDebug(" Certificate CA detected..."); certCa = temp; certsComplete[1] = true; off += 0x400; continue; } fireDebug(" Checking for Certificate CP..."); if (isCertCp(temp) && !certsComplete[2]) { fireDebug(" Certificate CP detected..."); Array.Resize(ref temp, 0x300); certCp = temp; certsComplete[2] = true; off += 0x300; continue; } fireDebug(" Checking for Certificate XS..."); if (isCertXs(temp) && !certsComplete[0]) { fireDebug(" Certificate XS detected..."); Array.Resize(ref temp, 0x300); certXs = temp; certsComplete[0] = true; off += 0x300; continue; } } catch (Exception ex) { fireDebug("Error: {0}", ex.Message); } off += 0x300; } if (!CertsComplete) { fireDebug(" Couldn't locate all Certificates..."); throw new Exception("Couldn't locate all certs!"); } fireDebug("Parsing Certificate Chain Finished..."); } private void grabFromTik(Stream tik) { fireDebug("Scanning Ticket for Certificates..."); int off = 676; for (int i = 0; i < 3; i++) { fireDebug(" Scanning at Offset 0x{0}:", off.ToString("x8")); try { tik.Seek(off, SeekOrigin.Begin); byte[] temp = new byte[0x400]; tik.Read(temp, 0, temp.Length); fireDebug(" Checking for Certificate CA..."); if (isCertCa(temp) && !certsComplete[1]) { fireDebug(" Certificate CA detected..."); certCa = temp; certsComplete[1] = true; off += 0x400; continue; } fireDebug(" Checking for Certificate CP..."); if (isCertCp(temp) && !certsComplete[2]) { fireDebug(" Certificate CP detected..."); Array.Resize(ref temp, 0x300); certCp = temp; certsComplete[2] = true; off += 0x300; continue; } fireDebug(" Checking for Certificate XS..."); if (isCertXs(temp) && !certsComplete[0]) { fireDebug(" Certificate XS detected..."); Array.Resize(ref temp, 0x300); certXs = temp; certsComplete[0] = true; off += 0x300; continue; } } catch { } off += 0x300; } fireDebug("Scanning Ticket for Certificates Finished..."); } private void grabFromTmd(Stream tmd) { fireDebug("Scanning TMD for Certificates..."); byte[] temp = new byte[2]; tmd.Seek(478, SeekOrigin.Begin); tmd.Read(temp, 0, 2); int numContents = Shared.Swap(BitConverter.ToUInt16(temp, 0)); int off = 484 + numContents * 36; for (int i = 0; i < 3; i++) { fireDebug(" Scanning at Offset 0x{0}:", off.ToString("x8")); try { tmd.Seek(off, SeekOrigin.Begin); temp = new byte[0x400]; tmd.Read(temp, 0, temp.Length); fireDebug(" Checking for Certificate CA..."); if (isCertCa(temp) && !certsComplete[1]) { fireDebug(" Certificate CA detected..."); certCa = temp; certsComplete[1] = true; off += 0x400; continue; } fireDebug(" Checking for Certificate CP..."); if (isCertCp(temp) && !certsComplete[2]) { fireDebug(" Certificate CP detected..."); Array.Resize(ref temp, 0x300); certCp = temp; certsComplete[2] = true; off += 0x300; continue; } fireDebug(" Checking for Certificate XS..."); if (isCertXs(temp) && !certsComplete[0]) { fireDebug(" Certificate XS detected..."); Array.Resize(ref temp, 0x300); certXs = temp; certsComplete[0] = true; off += 0x300; continue; } } catch { } off += 0x300; } fireDebug("Scanning TMD for Certificates Finished..."); } private bool isCertXs(byte[] part) { if (part.Length < 0x300) return false; else if (part.Length > 0x300) Array.Resize(ref part, 0x300); if (part[0x184] == 'X' && part[0x185] == 'S') { byte[] newHash = sha.ComputeHash(part); byte[] oldHash = Shared.HexStringToByteArray(certXsHash); if (Shared.CompareByteArrays(newHash, oldHash)) return true; } return false; } private bool isCertCa(byte[] part) { if (part.Length < 0x400) return false; else if (part.Length > 0x400) Array.Resize(ref part, 0x400); if (part[0x284] == 'C' && part[0x285] == 'A') { byte[] newHash = sha.ComputeHash(part); byte[] oldHash = Shared.HexStringToByteArray(certCaHash); if (Shared.CompareByteArrays(newHash, oldHash)) return true; } return false; } private bool isCertCp(byte[] part) { if (part.Length < 0x300) return false; else if (part.Length > 0x300) Array.Resize(ref part, 0x300); if (part[0x184] == 'C' && part[0x185] == 'P') { byte[] newHash = sha.ComputeHash(part); byte[] oldHash = Shared.HexStringToByteArray(certCpHash); if (Shared.CompareByteArrays(newHash, oldHash)) return true; } return false; } #endregion #region Events /// /// Fires debugging messages. You may write them into a log file or log textbox. /// public event EventHandler Debug; private void fireDebug(string debugMessage, params object[] args) { EventHandler debug = Debug; if (debug != null) debug(new object(), new MessageEventArgs(string.Format(debugMessage, args))); } #endregion } }