Initial AesXtsFileSystem

This commit is contained in:
Alex Barney 2019-01-09 12:21:12 -06:00
parent 5d4a3468c7
commit 01a3bef903
4 changed files with 273 additions and 0 deletions

View 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
View 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();
}
}
}

View 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();
}
}
}

View file

@ -23,6 +23,8 @@ namespace LibHac.IO
public override void Write(ReadOnlySpan<byte> source, long offset)
{
ValidateWriteParams(source, offset);
BaseStorage.Write(source, offset);
}