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)
|
public override void Write(ReadOnlySpan<byte> source, long offset)
|
||||||
{
|
{
|
||||||
|
ValidateWriteParams(source, offset);
|
||||||
|
|
||||||
BaseStorage.Write(source, offset);
|
BaseStorage.Write(source, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue