mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add AesXtsFile creation and FileReader
This commit is contained in:
parent
fe1400476a
commit
fba89fbb95
8 changed files with 303 additions and 72 deletions
|
@ -44,6 +44,21 @@ namespace LibHac
|
|||
public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) =>
|
||||
DecryptEcb(key, src, 0, dest, 0, length);
|
||||
|
||||
public static void EncryptEcb(byte[] key, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
|
||||
{
|
||||
using (Aes aes = Aes.Create())
|
||||
{
|
||||
if (aes == null) throw new CryptographicException("Unable to create AES object");
|
||||
aes.Key = key;
|
||||
aes.Mode = CipherMode.ECB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
Array.Copy(aes.CreateEncryptor().TransformFinalBlock(src, srcIndex, length), 0, dest, destIndex, length);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EncryptEcb(byte[] key, byte[] src, byte[] dest, int length) =>
|
||||
EncryptEcb(key, src, 0, dest, 0, length);
|
||||
|
||||
public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
|
||||
{
|
||||
using (Aes aes = Aes.Create())
|
||||
|
|
|
@ -46,9 +46,9 @@ namespace LibHac.IO
|
|||
{
|
||||
using (IFile file = BaseFileSystem.OpenFile(path, OpenMode.Read))
|
||||
{
|
||||
var buffer = new byte[8];
|
||||
var buffer = new byte[8];
|
||||
|
||||
file.Read(buffer, 0);
|
||||
file.Read(buffer, 0x20);
|
||||
if (BitConverter.ToUInt32(buffer, 0) != 0x3058414E) return 0;
|
||||
|
||||
file.Read(buffer, 0x48);
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
|
@ -13,14 +10,10 @@ namespace LibHac.IO
|
|||
private byte[] VerificationKey { get; }
|
||||
private int BlockSize { get; }
|
||||
|
||||
public byte[] Hmac { get; private set; }
|
||||
public byte[][] EncKeys { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x10);
|
||||
public byte[][] Keys { get; } = Util.CreateJaggedArray<byte[][]>(2, 0x10);
|
||||
public byte[] Key { get; } = new byte[0x20];
|
||||
public long Length { get; private set; }
|
||||
private AesXtsFileHeader Header { get; }
|
||||
private IStorage BaseStorage { get; }
|
||||
|
||||
private const int HeaderLength = 0x4000;
|
||||
internal const int HeaderLength = 0x4000;
|
||||
|
||||
public AesXtsFile(OpenMode mode, IFile baseFile, string path, ReadOnlySpan<byte> kekSeed, ReadOnlySpan<byte> verificationKey, int blockSize)
|
||||
{
|
||||
|
@ -31,60 +24,15 @@ namespace LibHac.IO
|
|||
VerificationKey = verificationKey.ToArray();
|
||||
BlockSize = blockSize;
|
||||
|
||||
ReadHeader(BaseFile);
|
||||
Header = new AesXtsFileHeader(BaseFile);
|
||||
|
||||
DeriveKeys();
|
||||
Storage encStorage = new FileStorage(BaseFile).Slice(HeaderLength, Length);
|
||||
BaseStorage = new CachedStorage(new Aes128XtsStorage(encStorage, Key, BlockSize, true), 4, true);
|
||||
}
|
||||
|
||||
private void ReadHeader(IFile file)
|
||||
{
|
||||
var reader = new BinaryReader(file.AsStream());
|
||||
|
||||
Hmac = reader.ReadBytes(0x20);
|
||||
string magic = reader.ReadAscii(4);
|
||||
reader.BaseStream.Position += 4;
|
||||
if (magic != "NAX0") throw new InvalidDataException("Not an NAX0 file");
|
||||
EncKeys[0] = reader.ReadBytes(0x10);
|
||||
EncKeys[1] = reader.ReadBytes(0x10);
|
||||
Length = reader.ReadInt64();
|
||||
}
|
||||
|
||||
private void DeriveKeys()
|
||||
{
|
||||
var validationHashKey = new byte[0x60];
|
||||
BaseFile.Read(validationHashKey, 0x20);
|
||||
|
||||
var naxSpecificKeys = Util.CreateJaggedArray<byte[][]>(2, 0x10);
|
||||
|
||||
// Use the sd path to generate the kek for this NAX0
|
||||
var hash = new HMACSHA256(KekSeed);
|
||||
byte[] sdPathBytes = Encoding.ASCII.GetBytes(Path);
|
||||
byte[] checksum = hash.ComputeHash(sdPathBytes, 0, sdPathBytes.Length);
|
||||
Array.Copy(checksum, 0, naxSpecificKeys[0], 0, 0x10);
|
||||
Array.Copy(checksum, 0x10, naxSpecificKeys[1], 0, 0x10);
|
||||
|
||||
// Decrypt this NAX0's keys
|
||||
Crypto.DecryptEcb(naxSpecificKeys[0], EncKeys[0], Keys[0], 0x10);
|
||||
Crypto.DecryptEcb(naxSpecificKeys[1], EncKeys[1], Keys[1], 0x10);
|
||||
Array.Copy(Keys[0], 0, Key, 0, 0x10);
|
||||
Array.Copy(Keys[1], 0, Key, 0x10, 0x10);
|
||||
|
||||
// Copy the decrypted keys into the NAX0 header and use that for the HMAC key
|
||||
// for validating that the keys are correct
|
||||
Array.Copy(Keys[0], 0, validationHashKey, 8, 0x10);
|
||||
Array.Copy(Keys[1], 0, validationHashKey, 0x18, 0x10);
|
||||
|
||||
var validationHash = new HMACSHA256(validationHashKey);
|
||||
byte[] validationMac = validationHash.ComputeHash(VerificationKey);
|
||||
|
||||
if (Util.ArraysEqual(Hmac, validationMac))
|
||||
if (!Header.TryDecryptHeader(Path, KekSeed, VerificationKey))
|
||||
{
|
||||
return;
|
||||
throw new ArgumentException("NAX0 key derivation failed.");
|
||||
}
|
||||
|
||||
throw new ArgumentException("NAX0 key derivation failed.");
|
||||
Storage encStorage = new FileStorage(BaseFile).Slice(HeaderLength, Header.Size);
|
||||
BaseStorage = new CachedStorage(new Aes128XtsStorage(encStorage, Header.DecryptedKey1, Header.DecryptedKey2, BlockSize, true), 4, true);
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset)
|
||||
|
@ -110,7 +58,7 @@ namespace LibHac.IO
|
|||
|
||||
public override long GetSize()
|
||||
{
|
||||
return Length;
|
||||
return Header.Size;
|
||||
}
|
||||
|
||||
public override void SetSize(long size)
|
||||
|
|
116
src/LibHac/IO/AesXtsFileHeader.cs
Normal file
116
src/LibHac/IO/AesXtsFileHeader.cs
Normal file
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
public class AesXtsFileHeader
|
||||
{
|
||||
private const uint AesXtsFileMagic = 0x3058414E;
|
||||
public byte[] Signature { get; set; } = new byte[0x20];
|
||||
public uint Magic { get; }
|
||||
public byte[] EncryptedKey1 { get; } = new byte[0x10];
|
||||
public byte[] EncryptedKey2 { get; } = new byte[0x10];
|
||||
public long Size { get; }
|
||||
|
||||
public byte[] DecryptedKey1 { get; } = new byte[0x10];
|
||||
public byte[] DecryptedKey2 { get; } = new byte[0x10];
|
||||
public byte[] Kek1 { get; } = new byte[0x10];
|
||||
public byte[] Kek2 { get; } = new byte[0x10];
|
||||
|
||||
public AesXtsFileHeader(IFile aesXtsFile)
|
||||
{
|
||||
var reader = new FileReader(aesXtsFile);
|
||||
|
||||
reader.ReadBytes(Signature);
|
||||
Magic = reader.ReadUInt32();
|
||||
reader.Position += 4;
|
||||
reader.ReadBytes(EncryptedKey1);
|
||||
reader.ReadBytes(EncryptedKey2);
|
||||
Size = reader.ReadInt64();
|
||||
|
||||
if (Magic != AesXtsFileMagic)
|
||||
{
|
||||
throw new InvalidDataException("Invalid NAX0 magic value");
|
||||
}
|
||||
}
|
||||
|
||||
public AesXtsFileHeader(byte[] key1, byte[] key2, long fileSize, string path, byte[] kekSeed, byte[] verificationKey)
|
||||
{
|
||||
Array.Copy(key1, DecryptedKey1, 0x10);
|
||||
Array.Copy(key2, DecryptedKey2, 0x10);
|
||||
Magic = AesXtsFileMagic;
|
||||
Size = fileSize;
|
||||
|
||||
EncryptHeader(path, kekSeed, verificationKey);
|
||||
}
|
||||
|
||||
private void EncryptHeader(string path, byte[] kekSeed, byte[] verificationKey)
|
||||
{
|
||||
GenerateKek(kekSeed, path);
|
||||
EncryptKeys();
|
||||
Signature = CalculateHmac(verificationKey);
|
||||
}
|
||||
|
||||
public bool TryDecryptHeader(string path, byte[] kekSeed, byte[] verificationKey)
|
||||
{
|
||||
GenerateKek(kekSeed, path);
|
||||
DecryptKeys();
|
||||
|
||||
byte[] hmac = CalculateHmac(verificationKey);
|
||||
return Util.ArraysEqual(hmac, Signature);
|
||||
}
|
||||
|
||||
private void DecryptKeys()
|
||||
{
|
||||
Crypto.DecryptEcb(Kek1, EncryptedKey1, DecryptedKey1, 0x10);
|
||||
Crypto.DecryptEcb(Kek2, EncryptedKey2, DecryptedKey2, 0x10);
|
||||
}
|
||||
|
||||
private void EncryptKeys()
|
||||
{
|
||||
Crypto.EncryptEcb(Kek1, DecryptedKey1, EncryptedKey1, 0x10);
|
||||
Crypto.EncryptEcb(Kek2, DecryptedKey2, EncryptedKey2, 0x10);
|
||||
}
|
||||
|
||||
private void GenerateKek(byte[] kekSeed, string path)
|
||||
{
|
||||
var hash = new HMACSHA256(kekSeed);
|
||||
byte[] pathBytes = Encoding.ASCII.GetBytes(path);
|
||||
|
||||
byte[] checksum = hash.ComputeHash(pathBytes, 0, pathBytes.Length);
|
||||
Array.Copy(checksum, 0, Kek1, 0, 0x10);
|
||||
Array.Copy(checksum, 0x10, Kek2, 0, 0x10);
|
||||
}
|
||||
|
||||
private byte[] CalculateHmac(byte[] key)
|
||||
{
|
||||
byte[] message = ToBytes(true).AsSpan(0x20).ToArray();
|
||||
var hash = new HMACSHA256(message);
|
||||
|
||||
return hash.ComputeHash(key);
|
||||
}
|
||||
|
||||
public byte[] ToBytes(bool writeDecryptedKey)
|
||||
{
|
||||
uint magic = Magic;
|
||||
long size = Size;
|
||||
byte[] key1 = writeDecryptedKey ? DecryptedKey1 : EncryptedKey1;
|
||||
byte[] key2 = writeDecryptedKey ? DecryptedKey2 : EncryptedKey2;
|
||||
|
||||
var data = new byte[0x80];
|
||||
|
||||
Array.Copy(Signature, data, 0x20);
|
||||
MemoryMarshal.Write(data.AsSpan(0x20), ref magic);
|
||||
|
||||
Array.Copy(key1, 0, data, 0x28, 0x10);
|
||||
Array.Copy(key2, 0, data, 0x38, 0x10);
|
||||
|
||||
MemoryMarshal.Write(data.AsSpan(0x48), ref size);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,15 @@ namespace LibHac.IO
|
|||
|
||||
public void CreateFile(string path, long size, CreateFileOptions options)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
long containerSize = AesXtsFile.HeaderLength + Util.AlignUp(size, 0x16);
|
||||
BaseFileSystem.CreateFile(path, containerSize, options);
|
||||
|
||||
var header = new AesXtsFileHeader(new byte[0x10], new byte[0x10], size, path, KekSource, ValidationKey);
|
||||
|
||||
using (IFile baseFile = BaseFileSystem.OpenFile(path, OpenMode.Write))
|
||||
{
|
||||
baseFile.Write(header.ToBytes(false), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
|
@ -60,8 +68,10 @@ namespace LibHac.IO
|
|||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
IFile baseFile = BaseFileSystem.OpenFile(path, mode);
|
||||
IFile baseFile = BaseFileSystem.OpenFile(path, mode | OpenMode.Read);
|
||||
var file = new AesXtsFile(mode, baseFile, path, KekSource, ValidationKey, BlockSize);
|
||||
|
||||
file.ToDispose.Add(baseFile);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace LibHac.IO
|
|||
public abstract class FileBase : IFile
|
||||
{
|
||||
private bool _isDisposed;
|
||||
protected List<IDisposable> ToDispose { get; } = new List<IDisposable>();
|
||||
internal List<IDisposable> ToDispose { get; } = new List<IDisposable>();
|
||||
|
||||
public abstract int Read(Span<byte> destination, long offset);
|
||||
public abstract void Write(ReadOnlySpan<byte> source, long offset);
|
||||
|
|
149
src/LibHac/IO/FileReader.cs
Normal file
149
src/LibHac/IO/FileReader.cs
Normal file
|
@ -0,0 +1,149 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
public class FileReader
|
||||
{
|
||||
private const int BufferSize = 0x10;
|
||||
private IFile _file;
|
||||
private byte[] _buffer;
|
||||
private long _start;
|
||||
|
||||
public long Position { get; set; }
|
||||
|
||||
public FileReader(IFile file)
|
||||
{
|
||||
_file = file;
|
||||
_buffer = new byte[BufferSize];
|
||||
}
|
||||
|
||||
public FileReader(IFile file, long start)
|
||||
{
|
||||
_file = file;
|
||||
_start = start;
|
||||
_buffer = new byte[BufferSize];
|
||||
}
|
||||
|
||||
private void FillBuffer(long offset, int count, bool updatePosition)
|
||||
{
|
||||
Debug.Assert(count <= BufferSize);
|
||||
|
||||
_file.Read(_buffer.AsSpan(0, count), _start + offset);
|
||||
if (updatePosition) Position = offset + count;
|
||||
}
|
||||
|
||||
public byte ReadUInt8(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(byte), updatePosition);
|
||||
|
||||
return _buffer[0];
|
||||
}
|
||||
|
||||
public sbyte ReadInt8(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(sbyte), updatePosition);
|
||||
|
||||
return (sbyte)_buffer[0];
|
||||
}
|
||||
|
||||
public ushort ReadUInt16(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(ushort), updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<ushort>(_buffer);
|
||||
}
|
||||
|
||||
public short ReadInt16(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(short), updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<short>(_buffer);
|
||||
}
|
||||
|
||||
public uint ReadUInt32(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(uint), updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<uint>(_buffer);
|
||||
}
|
||||
|
||||
public int ReadInt32(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(int), updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<int>(_buffer);
|
||||
}
|
||||
|
||||
public ulong ReadUInt64(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(ulong), updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<ulong>(_buffer);
|
||||
}
|
||||
|
||||
public long ReadInt64(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(long), updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<long>(_buffer);
|
||||
}
|
||||
|
||||
public float ReadSingle(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(float), updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<float>(_buffer);
|
||||
}
|
||||
|
||||
public double ReadDouble(long offset, bool updatePosition)
|
||||
{
|
||||
FillBuffer(offset, sizeof(double), updatePosition);
|
||||
|
||||
return MemoryMarshal.Read<double>(_buffer);
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(long offset, int length, bool updatePosition)
|
||||
{
|
||||
var result = new byte[length];
|
||||
_file.Read(result, offset);
|
||||
|
||||
if (updatePosition) Position = offset + length;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void ReadBytes(Span<byte> destination, long offset, bool updatePosition)
|
||||
{
|
||||
_file.Read(destination, offset);
|
||||
|
||||
if (updatePosition) Position = offset + destination.Length;
|
||||
}
|
||||
|
||||
public byte ReadUInt8(long offset) => ReadUInt8(offset, true);
|
||||
public sbyte ReadInt8(long offset) => ReadInt8(offset, true);
|
||||
public ushort ReadUInt16(long offset) => ReadUInt16(offset, true);
|
||||
public short ReadInt16(long offset) => ReadInt16(offset, true);
|
||||
public uint ReadUInt32(long offset) => ReadUInt32(offset, true);
|
||||
public int ReadInt32(long offset) => ReadInt32(offset, true);
|
||||
public ulong ReadUInt64(long offset) => ReadUInt64(offset, true);
|
||||
public long ReadInt64(long offset) => ReadInt64(offset, true);
|
||||
public float ReadSingle(long offset) => ReadSingle(offset, true);
|
||||
public double ReadDouble(long offset) => ReadDouble(offset, true);
|
||||
public byte[] ReadBytes(long offset, int length) => ReadBytes(offset, length, true);
|
||||
public void ReadBytes(Span<byte> destination, long offset) => ReadBytes(destination, offset, true);
|
||||
|
||||
public byte ReadUInt8() => ReadUInt8(Position, true);
|
||||
public sbyte ReadInt8() => ReadInt8(Position, true);
|
||||
public ushort ReadUInt16() => ReadUInt16(Position, true);
|
||||
public short ReadInt16() => ReadInt16(Position, true);
|
||||
public uint ReadUInt32() => ReadUInt32(Position, true);
|
||||
public int ReadInt32() => ReadInt32(Position, true);
|
||||
public ulong ReadUInt64() => ReadUInt64(Position, true);
|
||||
public long ReadInt64() => ReadInt64(Position, true);
|
||||
public float ReadSingle() => ReadSingle(Position, true);
|
||||
public double ReadDouble() => ReadDouble(Position, true);
|
||||
public byte[] ReadBytes(int length) => ReadBytes(Position, length, true);
|
||||
public void ReadBytes(Span<byte> destination) => ReadBytes(destination, Position, true);
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
namespace LibHac.IO
|
||||
{
|
||||
public class HierarchicalRomFileTable
|
||||
{
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue