mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Validate save signature
This commit is contained in:
parent
77dc8fb9a1
commit
e651eb731c
8 changed files with 56 additions and 25 deletions
|
@ -88,6 +88,10 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
DecryptEcb(srcKek, keySeed, dest, Aes128Size);
|
DecryptEcb(srcKek, keySeed, dest, Aes128Size);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Array.Copy(srcKek, dest, Aes128Size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BigInteger GetBigInteger(byte[] bytes)
|
private static BigInteger GetBigInteger(byte[] bytes)
|
||||||
|
|
|
@ -27,6 +27,8 @@ namespace LibHac
|
||||||
public byte[] KeyAreaKeyApplicationSource { get; set; } = new byte[0x10];
|
public byte[] KeyAreaKeyApplicationSource { get; set; } = new byte[0x10];
|
||||||
public byte[] KeyAreaKeyOceanSource { get; set; } = new byte[0x10];
|
public byte[] KeyAreaKeyOceanSource { get; set; } = new byte[0x10];
|
||||||
public byte[] KeyAreaKeySystemSource { 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[] TitlekekSource { get; set; } = new byte[0x10];
|
||||||
public byte[] HeaderKekSource { get; set; } = new byte[0x10];
|
public byte[] HeaderKekSource { get; set; } = new byte[0x10];
|
||||||
public byte[] SdCardKekSource { 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[] XciHeaderKey { get; set; } = new byte[0x10];
|
||||||
public byte[][] Titlekeks { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
public byte[][] Titlekeks { get; set; } = Util.CreateJaggedArray<byte[][]>(0x20, 0x10);
|
||||||
public byte[][][] KeyAreaKeys { get; set; } = Util.CreateJaggedArray<byte[][][]>(0x20, 3, 0x10);
|
public byte[][][] KeyAreaKeys { get; set; } = Util.CreateJaggedArray<byte[][][]>(0x20, 3, 0x10);
|
||||||
|
public byte[] SaveMacKey { get; set; } = new byte[0x10];
|
||||||
public byte[][] SdCardKeys { get; set; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
|
public byte[][] SdCardKeys { get; set; } = Util.CreateJaggedArray<byte[][]>(2, 0x20);
|
||||||
public byte[] NcaHdrFixedKeyModulus { get; set; } = new byte[0x100];
|
public byte[] NcaHdrFixedKeyModulus { get; set; } = new byte[0x100];
|
||||||
public byte[] AcidFixedKeyModulus { get; set; } = new byte[0x100];
|
public byte[] AcidFixedKeyModulus { get; set; } = new byte[0x100];
|
||||||
|
@ -147,12 +150,21 @@ namespace LibHac
|
||||||
|
|
||||||
private void DerivePerConsoleKeys()
|
private void DerivePerConsoleKeys()
|
||||||
{
|
{
|
||||||
|
var kek = new byte[0x10];
|
||||||
|
|
||||||
// Derive the device key
|
// Derive the device key
|
||||||
if (!PerConsoleKeySource.IsEmpty() && !KeyblobKeys[0].IsEmpty())
|
if (!PerConsoleKeySource.IsEmpty() && !KeyblobKeys[0].IsEmpty())
|
||||||
{
|
{
|
||||||
Crypto.DecryptEcb(KeyblobKeys[0], PerConsoleKeySource, DeviceKey, 0x10);
|
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
|
// Derive BIS keys
|
||||||
if (DeviceKey.IsEmpty()
|
if (DeviceKey.IsEmpty()
|
||||||
|| BisKeySource[0].IsEmpty()
|
|| BisKeySource[0].IsEmpty()
|
||||||
|
@ -166,8 +178,6 @@ namespace LibHac
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var kek = new byte[0x10];
|
|
||||||
|
|
||||||
Crypto.DecryptEcb(DeviceKey, RetailSpecificAesKeySource, kek, 0x10);
|
Crypto.DecryptEcb(DeviceKey, RetailSpecificAesKeySource, kek, 0x10);
|
||||||
Crypto.DecryptEcb(kek, BisKeySource[0], BisKeys[0], 0x20);
|
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("eticket_rsa_kek", 0x10, set => set.EticketRsaKek),
|
||||||
new KeyValue("retail_specific_aes_key_source", 0x10, set => set.RetailSpecificAesKeySource),
|
new KeyValue("retail_specific_aes_key_source", 0x10, set => set.RetailSpecificAesKeySource),
|
||||||
new KeyValue("per_console_key_source", 0x10, set => set.PerConsoleKeySource),
|
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++)
|
for (int slot = 0; slot < 0x20; slot++)
|
||||||
|
|
|
@ -22,9 +22,12 @@ namespace LibHac.Savefile
|
||||||
public byte[] DuplexMasterA { get; }
|
public byte[] DuplexMasterA { get; }
|
||||||
public byte[] DuplexMasterB { get; }
|
public byte[] DuplexMasterB { get; }
|
||||||
|
|
||||||
|
public Validity SignatureValidity { get; }
|
||||||
|
public Validity HeaderHashValidity { get; }
|
||||||
|
|
||||||
public byte[] Data { 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;
|
reader.BaseStream.Position = 0;
|
||||||
Data = reader.ReadBytes(0x4000);
|
Data = reader.ReadBytes(0x4000);
|
||||||
|
@ -73,18 +76,29 @@ namespace LibHac.Savefile
|
||||||
MetaMapEntries[i] = new MapEntry(reader);
|
MetaMapEntries[i] = new MapEntry(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
var hashStatus = ValidateHeaderHash() ? "valid" : "invalid";
|
HeaderHashValidity = ValidateHeaderHash();
|
||||||
logger?.LogMessage($"Header hash is {hashStatus}");
|
SignatureValidity = ValidateSignature(keyset);
|
||||||
|
|
||||||
|
logger?.LogMessage($"Header hash is {HeaderHashValidity}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ValidateHeaderHash()
|
private Validity ValidateHeaderHash()
|
||||||
{
|
{
|
||||||
using (SHA256 sha256 = SHA256.Create())
|
using (SHA256 sha256 = SHA256.Create())
|
||||||
{
|
{
|
||||||
var hash = sha256.ComputeHash(Data, 0x300, 0x3d00);
|
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
|
public class FsLayout
|
||||||
|
|
|
@ -37,11 +37,11 @@ namespace LibHac.Savefile
|
||||||
public DirectoryEntry[] Directories { get; private set; }
|
public DirectoryEntry[] Directories { get; private set; }
|
||||||
private Dictionary<string, FileEntry> FileDict { get; }
|
private Dictionary<string, FileEntry> 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))
|
using (var reader = new BinaryReader(file, Encoding.Default, true))
|
||||||
{
|
{
|
||||||
Header = new Header(reader, logger);
|
Header = new Header(keyset, reader, logger);
|
||||||
var layout = Header.Layout;
|
var layout = Header.Layout;
|
||||||
FileRemap = new RemapStream(
|
FileRemap = new RemapStream(
|
||||||
new SubStream(file, layout.FileMapDataOffset, layout.FileMapDataSize),
|
new SubStream(file, layout.FileMapDataOffset, layout.FileMapDataSize),
|
||||||
|
|
|
@ -114,7 +114,7 @@ namespace LibHac
|
||||||
|
|
||||||
string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/');
|
string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/');
|
||||||
var nax0 = new Nax0(Keyset, stream, sdPath, false);
|
var nax0 = new Nax0(Keyset, stream, sdPath, false);
|
||||||
save = new Savefile.Savefile(nax0.Stream);
|
save = new Savefile.Savefile(Keyset, nax0.Stream);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace NandReader
|
||||||
var calibration = new Calibration(prodinfo);
|
var calibration = new Calibration(prodinfo);
|
||||||
|
|
||||||
keyset.EticketExtKeyRsa = Crypto.DecryptRsaKey(calibration.EticketExtKeyRsa, keyset.EticketRsaKek);
|
keyset.EticketExtKeyRsa = Crypto.DecryptRsaKey(calibration.EticketExtKeyRsa, keyset.EticketRsaKek);
|
||||||
var tickets = GetTickets(nand, logger);
|
var tickets = GetTickets(keyset, nand, logger);
|
||||||
|
|
||||||
foreach (var ticket in tickets)
|
foreach (var ticket in tickets)
|
||||||
{
|
{
|
||||||
|
@ -72,7 +72,7 @@ namespace NandReader
|
||||||
{
|
{
|
||||||
var keyset = OpenKeyset();
|
var keyset = OpenKeyset();
|
||||||
var nand = new Nand(stream, keyset);
|
var nand = new Nand(stream, keyset);
|
||||||
var tickets = GetTickets(nand, logger);
|
var tickets = GetTickets(keyset, nand, logger);
|
||||||
|
|
||||||
Directory.CreateDirectory("tickets");
|
Directory.CreateDirectory("tickets");
|
||||||
foreach (var ticket in 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<Ticket>();
|
var tickets = new List<Ticket>();
|
||||||
var system = nand.OpenSystemPartition();
|
var system = nand.OpenSystemPartition();
|
||||||
|
|
||||||
var saveE1File = system.OpenFile("save\\80000000000000E1", FileMode.Open, FileAccess.Read);
|
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);
|
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");
|
logger?.LogMessage($"Found {tickets.Count} tickets");
|
||||||
|
|
||||||
return tickets.ToArray();
|
return tickets.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Ticket> ReadTickets(Stream savefile)
|
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
|
||||||
{
|
{
|
||||||
var tickets = new List<Ticket>();
|
var tickets = new List<Ticket>();
|
||||||
var save = new Savefile(savefile);
|
var save = new Savefile(keyset, savefile);
|
||||||
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin"));
|
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin"));
|
||||||
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));
|
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ namespace NandReaderGui.ViewModel
|
||||||
var calibration = new Calibration(prodinfo);
|
var calibration = new Calibration(prodinfo);
|
||||||
|
|
||||||
keyset.EticketExtKeyRsa = Crypto.DecryptRsaKey(calibration.EticketExtKeyRsa, keyset.EticketRsaKek);
|
keyset.EticketExtKeyRsa = Crypto.DecryptRsaKey(calibration.EticketExtKeyRsa, keyset.EticketRsaKek);
|
||||||
var tickets = GetTickets(nand);
|
var tickets = GetTickets(keyset, nand);
|
||||||
|
|
||||||
using (var outStream = new StreamWriter("titlekeys.txt"))
|
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<Ticket>();
|
var tickets = new List<Ticket>();
|
||||||
var system = nand.OpenSystemPartition();
|
var system = nand.OpenSystemPartition();
|
||||||
|
|
||||||
var saveE1File = system.OpenFile("save\\80000000000000E1", FileMode.Open, FileAccess.Read);
|
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);
|
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");
|
logger?.LogMessage($"Found {tickets.Count} tickets");
|
||||||
|
|
||||||
return tickets.ToArray();
|
return tickets.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Ticket> ReadTickets(Stream savefile)
|
private static List<Ticket> ReadTickets(Keyset keyset, Stream savefile)
|
||||||
{
|
{
|
||||||
var tickets = new List<Ticket>();
|
var tickets = new List<Ticket>();
|
||||||
var save = new Savefile(savefile);
|
var save = new Savefile(keyset, savefile);
|
||||||
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin"));
|
var ticketList = new BinaryReader(save.OpenFile("/ticket_list.bin"));
|
||||||
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));
|
var ticketFile = new BinaryReader(save.OpenFile("/ticket.bin"));
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
|
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)
|
if (ctx.Options.OutDir != null)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue