diff --git a/LibHac/Crypto.cs b/LibHac/Crypto.cs index a059e277..0eca4aae 100644 --- a/LibHac/Crypto.cs +++ b/LibHac/Crypto.cs @@ -88,6 +88,10 @@ namespace LibHac { DecryptEcb(srcKek, keySeed, dest, Aes128Size); } + else + { + Array.Copy(srcKek, dest, Aes128Size); + } } private static BigInteger GetBigInteger(byte[] bytes) diff --git a/LibHac/Keyset.cs b/LibHac/Keyset.cs index 44def255..afe105a1 100644 --- a/LibHac/Keyset.cs +++ b/LibHac/Keyset.cs @@ -27,6 +27,8 @@ namespace LibHac public byte[] KeyAreaKeyApplicationSource { get; set; } = new byte[0x10]; public byte[] KeyAreaKeyOceanSource { get; set; } = new byte[0x10]; public byte[] KeyAreaKeySystemSource { get; set; } = new byte[0x10]; + public byte[] SaveMacKekSource { get; set; } = new byte[0x10]; + public byte[] SaveMacKeySource { get; set; } = new byte[0x10]; public byte[] TitlekekSource { get; set; } = new byte[0x10]; public byte[] HeaderKekSource { get; set; } = new byte[0x10]; public byte[] SdCardKekSource { get; set; } = new byte[0x10]; @@ -37,6 +39,7 @@ namespace LibHac public byte[] XciHeaderKey { get; set; } = new byte[0x10]; public byte[][] Titlekeks { get; set; } = Util.CreateJaggedArray(0x20, 0x10); public byte[][][] KeyAreaKeys { get; set; } = Util.CreateJaggedArray(0x20, 3, 0x10); + public byte[] SaveMacKey { get; set; } = new byte[0x10]; public byte[][] SdCardKeys { get; set; } = Util.CreateJaggedArray(2, 0x20); public byte[] NcaHdrFixedKeyModulus { get; set; } = new byte[0x100]; public byte[] AcidFixedKeyModulus { get; set; } = new byte[0x100]; @@ -147,12 +150,21 @@ namespace LibHac private void DerivePerConsoleKeys() { + var kek = new byte[0x10]; + // Derive the device key if (!PerConsoleKeySource.IsEmpty() && !KeyblobKeys[0].IsEmpty()) { Crypto.DecryptEcb(KeyblobKeys[0], PerConsoleKeySource, DeviceKey, 0x10); } + // Derive save key + if (!SaveMacKekSource.IsEmpty() && !SaveMacKeySource.IsEmpty() && !DeviceKey.IsEmpty()) + { + Crypto.GenerateKek(DeviceKey, SaveMacKekSource, kek, AesKekGenerationSource, null); + Crypto.DecryptEcb(kek, SaveMacKeySource, SaveMacKey, 0x10); + } + // Derive BIS keys if (DeviceKey.IsEmpty() || BisKeySource[0].IsEmpty() @@ -166,8 +178,6 @@ namespace LibHac return; } - var kek = new byte[0x10]; - Crypto.DecryptEcb(DeviceKey, RetailSpecificAesKeySource, kek, 0x10); Crypto.DecryptEcb(kek, BisKeySource[0], BisKeys[0], 0x20); @@ -403,7 +413,10 @@ namespace LibHac new KeyValue("eticket_rsa_kek", 0x10, set => set.EticketRsaKek), new KeyValue("retail_specific_aes_key_source", 0x10, set => set.RetailSpecificAesKeySource), new KeyValue("per_console_key_source", 0x10, set => set.PerConsoleKeySource), - new KeyValue("bis_kek_source", 0x10, set => set.BisKekSource) + new KeyValue("bis_kek_source", 0x10, set => set.BisKekSource), + new KeyValue("save_mac_kek_source", 0x10, set => set.SaveMacKekSource), + new KeyValue("save_mac_key_source", 0x10, set => set.SaveMacKeySource), + new KeyValue("save_mac_key", 0x10, set => set.SaveMacKey) }; for (int slot = 0; slot < 0x20; slot++) diff --git a/LibHac/Savefile/Header.cs b/LibHac/Savefile/Header.cs index 77fcd75e..e01b9864 100644 --- a/LibHac/Savefile/Header.cs +++ b/LibHac/Savefile/Header.cs @@ -22,9 +22,12 @@ namespace LibHac.Savefile public byte[] DuplexMasterA { get; } public byte[] DuplexMasterB { get; } + public Validity SignatureValidity { get; } + public Validity HeaderHashValidity { get; } + public byte[] Data { get; } - public Header(BinaryReader reader, IProgressReport logger = null) + public Header(Keyset keyset, BinaryReader reader, IProgressReport logger = null) { reader.BaseStream.Position = 0; Data = reader.ReadBytes(0x4000); @@ -73,18 +76,29 @@ namespace LibHac.Savefile MetaMapEntries[i] = new MapEntry(reader); } - var hashStatus = ValidateHeaderHash() ? "valid" : "invalid"; - logger?.LogMessage($"Header hash is {hashStatus}"); + HeaderHashValidity = ValidateHeaderHash(); + SignatureValidity = ValidateSignature(keyset); + + logger?.LogMessage($"Header hash is {HeaderHashValidity}"); } - private bool ValidateHeaderHash() + private Validity ValidateHeaderHash() { using (SHA256 sha256 = SHA256.Create()) { var hash = sha256.ComputeHash(Data, 0x300, 0x3d00); - return Util.ArraysEqual(hash, Layout.Hash); + return Util.ArraysEqual(hash, Layout.Hash) ? Validity.Valid : Validity.Invalid; } } + + private Validity ValidateSignature(Keyset keyset) + { + var calculatedCmac = new byte[0x10]; + + Crypto.CalculateAesCmac(keyset.SaveMacKey, Data, 0x100, calculatedCmac, 0, 0x200); + + return Util.ArraysEqual(calculatedCmac, Cmac) ? Validity.Valid : Validity.Invalid; + } } public class FsLayout diff --git a/LibHac/Savefile/Savefile.cs b/LibHac/Savefile/Savefile.cs index 4fcc79e9..2d61b7db 100644 --- a/LibHac/Savefile/Savefile.cs +++ b/LibHac/Savefile/Savefile.cs @@ -37,11 +37,11 @@ namespace LibHac.Savefile public DirectoryEntry[] Directories { get; private set; } private Dictionary FileDict { get; } - public Savefile(Stream file, IProgressReport logger = null) + public Savefile(Keyset keyset, Stream file, IProgressReport logger = null) { using (var reader = new BinaryReader(file, Encoding.Default, true)) { - Header = new Header(reader, logger); + Header = new Header(keyset, reader, logger); var layout = Header.Layout; FileRemap = new RemapStream( new SubStream(file, layout.FileMapDataOffset, layout.FileMapDataSize), diff --git a/LibHac/SwitchFs.cs b/LibHac/SwitchFs.cs index 4b35d3cf..d429187a 100644 --- a/LibHac/SwitchFs.cs +++ b/LibHac/SwitchFs.cs @@ -114,7 +114,7 @@ namespace LibHac string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/'); var nax0 = new Nax0(Keyset, stream, sdPath, false); - save = new Savefile.Savefile(nax0.Stream); + save = new Savefile.Savefile(Keyset, nax0.Stream); } catch (Exception ex) { diff --git a/NandReader/Program.cs b/NandReader/Program.cs index 024e6132..1cb39fdc 100644 --- a/NandReader/Program.cs +++ b/NandReader/Program.cs @@ -31,7 +31,7 @@ namespace NandReader var calibration = new Calibration(prodinfo); keyset.EticketExtKeyRsa = Crypto.DecryptRsaKey(calibration.EticketExtKeyRsa, keyset.EticketRsaKek); - var tickets = GetTickets(nand, logger); + var tickets = GetTickets(keyset, nand, logger); foreach (var ticket in tickets) { @@ -72,7 +72,7 @@ namespace NandReader { var keyset = OpenKeyset(); var nand = new Nand(stream, keyset); - var tickets = GetTickets(nand, logger); + var tickets = GetTickets(keyset, nand, logger); Directory.CreateDirectory("tickets"); foreach (var ticket in tickets) @@ -83,26 +83,26 @@ namespace NandReader } } - private static Ticket[] GetTickets(Nand nand, IProgressReport logger = null) + private static Ticket[] GetTickets(Keyset keyset, Nand nand, IProgressReport logger = null) { var tickets = new List(); var system = nand.OpenSystemPartition(); var saveE1File = system.OpenFile("save\\80000000000000E1", FileMode.Open, FileAccess.Read); - tickets.AddRange(ReadTickets(saveE1File)); + tickets.AddRange(ReadTickets(keyset, saveE1File)); var saveE2 = system.OpenFile("save\\80000000000000E2", FileMode.Open, FileAccess.Read); - tickets.AddRange(ReadTickets(saveE2)); + tickets.AddRange(ReadTickets(keyset, saveE2)); logger?.LogMessage($"Found {tickets.Count} tickets"); return tickets.ToArray(); } - private static List ReadTickets(Stream savefile) + private static List ReadTickets(Keyset keyset, Stream savefile) { var tickets = new List(); - var save = new Savefile(savefile); + var save = new Savefile(keyset, savefile); var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin")); var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin")); diff --git a/NandReaderGui/ViewModel/NandViewModel.cs b/NandReaderGui/ViewModel/NandViewModel.cs index bdf3835f..bfcd9639 100644 --- a/NandReaderGui/ViewModel/NandViewModel.cs +++ b/NandReaderGui/ViewModel/NandViewModel.cs @@ -53,7 +53,7 @@ namespace NandReaderGui.ViewModel var calibration = new Calibration(prodinfo); keyset.EticketExtKeyRsa = Crypto.DecryptRsaKey(calibration.EticketExtKeyRsa, keyset.EticketRsaKek); - var tickets = GetTickets(nand); + var tickets = GetTickets(keyset, nand); using (var outStream = new StreamWriter("titlekeys.txt")) { @@ -65,26 +65,26 @@ namespace NandReaderGui.ViewModel } } - private static Ticket[] GetTickets(Nand nand, IProgressReport logger = null) + private static Ticket[] GetTickets(Keyset keyset, Nand nand, IProgressReport logger = null) { var tickets = new List(); var system = nand.OpenSystemPartition(); var saveE1File = system.OpenFile("save\\80000000000000E1", FileMode.Open, FileAccess.Read); - tickets.AddRange(ReadTickets(saveE1File)); + tickets.AddRange(ReadTickets(keyset, saveE1File)); var saveE2 = system.OpenFile("save\\80000000000000E2", FileMode.Open, FileAccess.Read); - tickets.AddRange(ReadTickets(saveE2)); + tickets.AddRange(ReadTickets(keyset, saveE2)); logger?.LogMessage($"Found {tickets.Count} tickets"); return tickets.ToArray(); } - private static List ReadTickets(Stream savefile) + private static List ReadTickets(Keyset keyset, Stream savefile) { var tickets = new List(); - var save = new Savefile(savefile); + var save = new Savefile(keyset, savefile); var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin")); var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin")); diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 742abb70..3cc0e948 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -95,7 +95,7 @@ namespace hactoolnet { using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read)) { - var save = new Savefile(file, ctx.Logger); + var save = new Savefile(ctx.Keyset, file, ctx.Logger); if (ctx.Options.OutDir != null) {