mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Initial AesXtsFileSystem
This commit is contained in:
parent
5d4a3468c7
commit
01a3bef903
4 changed files with 273 additions and 0 deletions
57
src/LibHac/IO/AesXtsDirectory.cs
Normal file
57
src/LibHac/IO/AesXtsDirectory.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
public class AesXtsDirectory : IDirectory
|
||||
{
|
||||
public IFileSystem ParentFileSystem { get; }
|
||||
public string FullPath { get; }
|
||||
public OpenDirectoryMode Mode { get; }
|
||||
|
||||
private IFileSystem BaseFileSystem { get; }
|
||||
private IDirectory BaseDirectory { get; }
|
||||
|
||||
public AesXtsDirectory(IFileSystem parentFs, IDirectory baseDir, OpenDirectoryMode mode)
|
||||
{
|
||||
ParentFileSystem = parentFs;
|
||||
BaseDirectory = baseDir;
|
||||
Mode = mode;
|
||||
BaseFileSystem = BaseDirectory.ParentFileSystem;
|
||||
FullPath = BaseDirectory.FullPath;
|
||||
}
|
||||
|
||||
public IEnumerable<DirectoryEntry> Read()
|
||||
{
|
||||
foreach (DirectoryEntry entry in BaseDirectory.Read())
|
||||
{
|
||||
if (entry.Type == DirectoryEntryType.Directory)
|
||||
{
|
||||
yield return entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
long size = GetAesXtsFileSize(entry.FullPath);
|
||||
yield return new DirectoryEntry(entry.Name, entry.FullPath, entry.Type, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetEntryCount()
|
||||
{
|
||||
return BaseDirectory.GetEntryCount();
|
||||
}
|
||||
|
||||
private long GetAesXtsFileSize(string path)
|
||||
{
|
||||
IFile file = BaseFileSystem.OpenFile(path, OpenMode.Read);
|
||||
var buffer = new byte[8];
|
||||
|
||||
file.Read(buffer, 0);
|
||||
if (BitConverter.ToUInt32(buffer, 0) != 0x3058414E) return 0;
|
||||
|
||||
file.Read(buffer, 0x48);
|
||||
return BitConverter.ToInt32(buffer, 0);
|
||||
}
|
||||
}
|
||||
}
|
121
src/LibHac/IO/AesXtsFile.cs
Normal file
121
src/LibHac/IO/AesXtsFile.cs
Normal file
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
public class AesXtsFile : FileBase
|
||||
{
|
||||
private IFile BaseFile { get; }
|
||||
private string Path { get; }
|
||||
private byte[] KekSeed { get; }
|
||||
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 IStorage BaseStorage { get; }
|
||||
|
||||
private const int HeaderLength = 0x4000;
|
||||
|
||||
public AesXtsFile(OpenMode mode, IFile baseFile, string path, ReadOnlySpan<byte> kekSeed, ReadOnlySpan<byte> verificationKey, int blockSize)
|
||||
{
|
||||
Mode = mode;
|
||||
BaseFile = baseFile;
|
||||
Path = path;
|
||||
KekSeed = kekSeed.ToArray();
|
||||
VerificationKey = verificationKey.ToArray();
|
||||
BlockSize = blockSize;
|
||||
|
||||
ReadHeader(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))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ArgumentException("NAX0 key derivation failed.");
|
||||
}
|
||||
|
||||
public override int Read(Span<byte> destination, long offset)
|
||||
{
|
||||
int toRead = ValidateReadParamsAndGetSize(destination, offset);
|
||||
|
||||
BaseStorage.Read(destination.Slice(0, toRead), offset);
|
||||
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset)
|
||||
{
|
||||
ValidateWriteParams(source, offset);
|
||||
|
||||
BaseStorage.Write(source, offset);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
BaseStorage.Flush();
|
||||
}
|
||||
|
||||
public override long GetSize()
|
||||
{
|
||||
return Length;
|
||||
}
|
||||
|
||||
public override void SetSize(long size)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
93
src/LibHac/IO/AesXtsFileSystem.cs
Normal file
93
src/LibHac/IO/AesXtsFileSystem.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
|
||||
namespace LibHac.IO
|
||||
{
|
||||
public class AesXtsFileSystem : IFileSystem
|
||||
{
|
||||
public int BlockSize { get; }
|
||||
|
||||
private IFileSystem BaseFileSystem { get; }
|
||||
private byte[] KekSource { get; }
|
||||
private byte[] ValidationKey { get; }
|
||||
|
||||
public AesXtsFileSystem(IFileSystem fs, byte[] kekSource, byte[] validationKey, int blockSize)
|
||||
{
|
||||
BaseFileSystem = fs;
|
||||
KekSource = kekSource;
|
||||
ValidationKey = validationKey;
|
||||
BlockSize = blockSize;
|
||||
}
|
||||
|
||||
public AesXtsFileSystem(IFileSystem fs, byte[] keys, int blockSize)
|
||||
{
|
||||
BaseFileSystem = fs;
|
||||
KekSource = keys.AsSpan(0, 0x10).ToArray();
|
||||
ValidationKey = keys.AsSpan(0x10, 0x10).ToArray();
|
||||
BlockSize = blockSize;
|
||||
}
|
||||
|
||||
public void CreateDirectory(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void CreateFile(string path, long size)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void DeleteDirectory(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void DeleteFile(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IDirectory OpenDirectory(string path, OpenDirectoryMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
IDirectory baseDir = BaseFileSystem.OpenDirectory(path, mode);
|
||||
|
||||
var dir = new AesXtsDirectory(this, baseDir, mode);
|
||||
return dir;
|
||||
}
|
||||
|
||||
public IFile OpenFile(string path, OpenMode mode)
|
||||
{
|
||||
path = PathTools.Normalize(path);
|
||||
|
||||
IFile baseFile = BaseFileSystem.OpenFile(path, mode);
|
||||
var file = new AesXtsFile(mode, baseFile, path, KekSource, ValidationKey, BlockSize);
|
||||
return file;
|
||||
}
|
||||
|
||||
public void RenameDirectory(string srcPath, string dstPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void RenameFile(string srcPath, string dstPath)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool DirectoryExists(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ namespace LibHac.IO
|
|||
|
||||
public override void Write(ReadOnlySpan<byte> source, long offset)
|
||||
{
|
||||
ValidateWriteParams(source, offset);
|
||||
|
||||
BaseStorage.Write(source, offset);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue