Add AesXtsFile creation and FileReader

This commit is contained in:
Alex Barney 2019-01-17 20:06:47 -06:00
parent fe1400476a
commit fba89fbb95
8 changed files with 303 additions and 72 deletions

View file

@ -44,6 +44,21 @@ namespace LibHac
public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) => public static void DecryptEcb(byte[] key, byte[] src, byte[] dest, int length) =>
DecryptEcb(key, src, 0, dest, 0, 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) public static void DecryptCbc(byte[] key, byte[] iv, byte[] src, int srcIndex, byte[] dest, int destIndex, int length)
{ {
using (Aes aes = Aes.Create()) using (Aes aes = Aes.Create())

View file

@ -48,7 +48,7 @@ namespace LibHac.IO
{ {
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; if (BitConverter.ToUInt32(buffer, 0) != 0x3058414E) return 0;
file.Read(buffer, 0x48); file.Read(buffer, 0x48);

View file

@ -1,7 +1,4 @@
using System; using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace LibHac.IO namespace LibHac.IO
{ {
@ -13,14 +10,10 @@ namespace LibHac.IO
private byte[] VerificationKey { get; } private byte[] VerificationKey { get; }
private int BlockSize { get; } private int BlockSize { get; }
public byte[] Hmac { get; private set; } private AesXtsFileHeader Header { get; }
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 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) 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(); VerificationKey = verificationKey.ToArray();
BlockSize = blockSize; BlockSize = blockSize;
ReadHeader(BaseFile); Header = new AesXtsFileHeader(BaseFile);
DeriveKeys(); if (!Header.TryDecryptHeader(Path, KekSeed, VerificationKey))
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.");
} }
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) public override int Read(Span<byte> destination, long offset)
@ -110,7 +58,7 @@ namespace LibHac.IO
public override long GetSize() public override long GetSize()
{ {
return Length; return Header.Size;
} }
public override void SetSize(long size) public override void SetSize(long size)

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

View file

@ -33,7 +33,15 @@ namespace LibHac.IO
public void CreateFile(string path, long size, CreateFileOptions options) 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) public void DeleteDirectory(string path)
@ -60,8 +68,10 @@ namespace LibHac.IO
{ {
path = PathTools.Normalize(path); 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); var file = new AesXtsFile(mode, baseFile, path, KekSource, ValidationKey, BlockSize);
file.ToDispose.Add(baseFile);
return file; return file;
} }

View file

@ -6,7 +6,7 @@ namespace LibHac.IO
public abstract class FileBase : IFile public abstract class FileBase : IFile
{ {
private bool _isDisposed; 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 int Read(Span<byte> destination, long offset);
public abstract void Write(ReadOnlySpan<byte> source, long offset); public abstract void Write(ReadOnlySpan<byte> source, long offset);

149
src/LibHac/IO/FileReader.cs Normal file
View 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);
}
}

View file

@ -1,7 +0,0 @@
namespace LibHac.IO
{
public class HierarchicalRomFileTable
{
}
}