mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Update some Nca classes for 14.0.0
This commit is contained in:
parent
fc3fb188c7
commit
398a142b27
8 changed files with 634 additions and 82 deletions
|
@ -6,8 +6,8 @@ namespace LibHac.Crypto;
|
|||
|
||||
public static class Rsa
|
||||
{
|
||||
public static readonly int ModulusSize2048Pss = 256;
|
||||
public static readonly int MaximumExponentSize2048Pss = 3;
|
||||
public const int ModulusSize2048Pss = 256;
|
||||
public const int MaximumExponentSize2048Pss = 3;
|
||||
|
||||
public static bool VerifyRsa2048PssSha256(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> modulus,
|
||||
ReadOnlySpan<byte> exponent, ReadOnlySpan<byte> message) =>
|
||||
|
|
304
src/LibHac/FsSystem/AesXtsStorageExternal.cs
Normal file
304
src/LibHac/FsSystem/AesXtsStorageExternal.cs
Normal file
|
@ -0,0 +1,304 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Crypto;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Reads and writes to an <see cref="IStorage"/> that's encrypted with AES-XTS-128.
|
||||
/// All encryption or decryption will be done externally via a provided function.
|
||||
/// This allows for using hardware decryption where the FS process doesn't has access to the actual keys.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public class AesXtsStorageExternal : IStorage
|
||||
{
|
||||
public const int AesBlockSize = Aes.BlockSize;
|
||||
public const int KeySize = Aes.KeySize128;
|
||||
public const int IvSize = Aes.KeySize128;
|
||||
|
||||
private IStorage _baseStorage;
|
||||
private Array2<Array16<byte>> _key;
|
||||
private Array16<byte> _iv;
|
||||
private uint _blockSize;
|
||||
private CryptAesXtsFunction _encryptFunction;
|
||||
private CryptAesXtsFunction _decryptFunction;
|
||||
|
||||
// The original class uses a template for both the shared and non-shared IStorage which avoids needing this field.
|
||||
private SharedRef<IStorage> _baseStorageShared;
|
||||
|
||||
public AesXtsStorageExternal(IStorage baseStorage, ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
|
||||
ReadOnlySpan<byte> iv, uint blockSize, CryptAesXtsFunction encryptFunction, CryptAesXtsFunction decryptFunction)
|
||||
{
|
||||
_baseStorage = baseStorage;
|
||||
_blockSize = blockSize;
|
||||
_encryptFunction = encryptFunction;
|
||||
_decryptFunction = decryptFunction;
|
||||
|
||||
Assert.SdkRequires(key1.Length is 0 or KeySize);
|
||||
Assert.SdkRequires(key2.Length is 0 or KeySize);
|
||||
Assert.SdkRequiresEqual(IvSize, iv.Length);
|
||||
Assert.SdkRequiresAligned(blockSize, AesBlockSize);
|
||||
|
||||
if (key1.Length != 0)
|
||||
key1.CopyTo(_key[0].Items);
|
||||
|
||||
if (key2.Length != 0)
|
||||
key2.CopyTo(_key[1].Items);
|
||||
|
||||
iv.CopyTo(_iv.Items);
|
||||
}
|
||||
|
||||
public AesXtsStorageExternal(in SharedRef<IStorage> baseStorage, ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2,
|
||||
ReadOnlySpan<byte> iv, uint blockSize, CryptAesXtsFunction encryptFunction, CryptAesXtsFunction decryptFunction)
|
||||
: this(baseStorage.Get, key1, key2, iv, blockSize, encryptFunction, decryptFunction)
|
||||
{
|
||||
_baseStorageShared = SharedRef<IStorage>.CreateCopy(in baseStorage);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_baseStorageShared.Destroy();
|
||||
|
||||
base.Dispose();
|
||||
}
|
||||
|
||||
public override Result Read(long offset, Span<byte> destination)
|
||||
{
|
||||
// Allow zero size.
|
||||
if (destination.Length == 0)
|
||||
return Result.Success;
|
||||
|
||||
// Ensure we can decrypt.
|
||||
if (_decryptFunction is null)
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
// We can only read at block aligned offsets.
|
||||
if (!Alignment.IsAlignedPow2(offset, AesBlockSize))
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
if (!Alignment.IsAlignedPow2(destination.Length, AesBlockSize))
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
// Read the encrypted data.
|
||||
Result rc = _baseStorage.Read(offset, destination);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
// Temporarily increase our thread priority while decrypting.
|
||||
using var changePriority = new ScopedThreadPriorityChanger(1, ScopedThreadPriorityChanger.Mode.Relative);
|
||||
|
||||
// Setup the counter.
|
||||
Span<byte> counter = stackalloc byte[IvSize];
|
||||
_iv.ItemsRo.CopyTo(counter);
|
||||
Utility.AddCounter(counter, (ulong)offset / _blockSize);
|
||||
|
||||
// Handle any unaligned data before the start.
|
||||
int processedSize = 0;
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (offset % _blockSize != 0)
|
||||
{
|
||||
// Determine the size of the pre-data read.
|
||||
int skipSize = (int)(offset - Alignment.AlignDownPow2(offset, _blockSize));
|
||||
int dataSize = (int)Math.Min(destination.Length, _blockSize - skipSize);
|
||||
|
||||
// Decrypt into a pooled buffer.
|
||||
using (var tmpBuffer = new PooledBuffer((int)_blockSize, (int)_blockSize))
|
||||
{
|
||||
Assert.SdkAssert(tmpBuffer.GetSize() >= _blockSize);
|
||||
|
||||
tmpBuffer.GetBuffer().Slice(0, skipSize).Clear();
|
||||
destination.Slice(0, dataSize).CopyTo(tmpBuffer.GetBuffer().Slice(skipSize, dataSize));
|
||||
Span<byte> decryptionBuffer = tmpBuffer.GetBuffer().Slice(0, (int)_blockSize);
|
||||
|
||||
// Decrypt and copy the partial block to the output buffer.
|
||||
rc = _decryptFunction(decryptionBuffer, _key[0], _key[1], counter, decryptionBuffer);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
tmpBuffer.GetBuffer().Slice(skipSize, dataSize).CopyTo(destination);
|
||||
}
|
||||
|
||||
Utility.AddCounter(counter, 1);
|
||||
processedSize += dataSize;
|
||||
Assert.SdkAssert(processedSize == Math.Min(destination.Length, _blockSize - skipSize));
|
||||
}
|
||||
|
||||
// Decrypt aligned chunks.
|
||||
Span<byte> currentOutput = destination.Slice(processedSize);
|
||||
int remainingSize = destination.Length - processedSize;
|
||||
|
||||
while (remainingSize > 0)
|
||||
{
|
||||
Span<byte> currentBlock = currentOutput.Slice(0, Math.Min((int)_blockSize, remainingSize));
|
||||
|
||||
rc = _decryptFunction(currentBlock, _key[0], _key[1], counter, currentBlock);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
remainingSize -= currentBlock.Length;
|
||||
currentOutput = currentBlock.Slice(currentBlock.Length);
|
||||
|
||||
Utility.AddCounter(counter, 1);
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Write(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
Result rc;
|
||||
|
||||
// Allow zero-size writes.
|
||||
if (source.Length == 0)
|
||||
return Result.Success;
|
||||
|
||||
// Ensure we can encrypt.
|
||||
if (_encryptFunction is null)
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
// We can only write at block aligned offsets.
|
||||
if (!Alignment.IsAlignedPow2(offset, AesBlockSize))
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
if (!Alignment.IsAlignedPow2(source.Length, AesBlockSize))
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
// Get a pooled buffer.
|
||||
using var pooledBuffer = new PooledBuffer();
|
||||
bool useWorkBuffer = !PooledBufferGlobalMethods.IsDeviceAddress(source);
|
||||
if (useWorkBuffer)
|
||||
{
|
||||
pooledBuffer.Allocate(source.Length, (int)_blockSize);
|
||||
}
|
||||
|
||||
// Setup the counter.
|
||||
Span<byte> counter = stackalloc byte[IvSize];
|
||||
_iv.ItemsRo.CopyTo(counter);
|
||||
Utility.AddCounter(counter, (ulong)offset / _blockSize);
|
||||
|
||||
// Handle any unaligned data before the start.
|
||||
int processedSize = 0;
|
||||
|
||||
// Todo: remove when fixed in Resharper
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (offset % _blockSize != 0)
|
||||
{
|
||||
// Determine the size of the pre-data write.
|
||||
int skipSize = (int)(offset - Alignment.AlignDownPow2(offset, _blockSize));
|
||||
int dataSize = (int)Math.Min(source.Length, _blockSize - skipSize);
|
||||
|
||||
// Encrypt into a pooled buffer.
|
||||
// Note: Nintendo allocates a second pooled buffer here despite having one already allocated above.
|
||||
using (var tmpBuffer = new PooledBuffer((int)_blockSize, (int)_blockSize))
|
||||
{
|
||||
Assert.SdkAssert(tmpBuffer.GetSize() >= _blockSize);
|
||||
|
||||
tmpBuffer.GetBuffer().Slice(0, skipSize).Clear();
|
||||
source.Slice(0, dataSize).CopyTo(tmpBuffer.GetBuffer().Slice(skipSize, dataSize));
|
||||
Span<byte> encryptionBuffer = tmpBuffer.GetBuffer().Slice(0, (int)_blockSize);
|
||||
|
||||
rc = _encryptFunction(encryptionBuffer, _key[0], _key[1], counter, encryptionBuffer);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
rc = _baseStorage.Write(offset, tmpBuffer.GetBuffer().Slice(skipSize, dataSize));
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
}
|
||||
|
||||
Utility.AddCounter(counter, 1);
|
||||
processedSize += dataSize;
|
||||
Assert.SdkAssert(processedSize == Math.Min(source.Length, _blockSize - skipSize));
|
||||
}
|
||||
|
||||
// Encrypt aligned chunks.
|
||||
int remainingSize = source.Length - processedSize;
|
||||
long currentOffset = offset + processedSize;
|
||||
|
||||
while (remainingSize > 0)
|
||||
{
|
||||
// Determine data we're writing and where.
|
||||
int writeSize = useWorkBuffer ? Math.Min(pooledBuffer.GetSize(), remainingSize) : remainingSize;
|
||||
|
||||
// Encrypt the data with temporarily increased priority.
|
||||
using (new ScopedThreadPriorityChanger(1, ScopedThreadPriorityChanger.Mode.Relative))
|
||||
{
|
||||
int remainingEncryptSize = writeSize;
|
||||
int encryptOffset = 0;
|
||||
|
||||
// Encrypt one block at a time.
|
||||
while (remainingEncryptSize > 0)
|
||||
{
|
||||
int currentSize = Math.Min(remainingEncryptSize, (int)_blockSize);
|
||||
ReadOnlySpan<byte> encryptSource = source.Slice(processedSize + encryptOffset, currentSize);
|
||||
|
||||
// const_cast the input buffer and encrypt in-place if it's a "device buffer".
|
||||
Span<byte> encryptDest = useWorkBuffer
|
||||
? pooledBuffer.GetBuffer().Slice(encryptOffset, currentSize)
|
||||
: MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(encryptSource), encryptSource.Length);
|
||||
|
||||
rc = _encryptFunction(encryptDest, _key[0], _key[1], counter, encryptSource);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
Utility.AddCounter(counter, 1);
|
||||
|
||||
encryptOffset += currentSize;
|
||||
remainingEncryptSize -= currentSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the encrypted data.
|
||||
ReadOnlySpan<byte> writeBuffer = useWorkBuffer
|
||||
? pooledBuffer.GetBuffer().Slice(0, writeSize)
|
||||
: source.Slice(processedSize, writeSize);
|
||||
|
||||
rc = _baseStorage.Write(currentOffset, writeBuffer);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
// Advance.
|
||||
currentOffset += writeSize;
|
||||
processedSize += writeSize;
|
||||
remainingSize -= writeSize;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public override Result Flush()
|
||||
{
|
||||
return _baseStorage.Flush();
|
||||
}
|
||||
|
||||
public override Result SetSize(long size)
|
||||
{
|
||||
return _baseStorage.SetSize(size);
|
||||
}
|
||||
|
||||
public override Result GetSize(out long size)
|
||||
{
|
||||
return _baseStorage.GetSize(out size);
|
||||
}
|
||||
|
||||
public override Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||
ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
if (operationId != OperationId.InvalidateCache)
|
||||
{
|
||||
// Handle the zero size case.
|
||||
if (size == 0)
|
||||
return Result.Success;
|
||||
|
||||
// Ensure alignment.
|
||||
if (!Alignment.IsAlignedPow2(offset, AesBlockSize))
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
if (!Alignment.IsAlignedPow2(size, AesBlockSize))
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
|
||||
Result rc = _baseStorage.OperateRange(outBuffer, operationId, offset, size, inBuffer);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
|
@ -5,11 +5,17 @@ using LibHac.Diag;
|
|||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public enum HashAlgorithmType : byte
|
||||
{
|
||||
Sha2 = 0,
|
||||
Sha3 = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a hash for a stream of data. The data can be given to the <see cref="IHash256Generator"/>
|
||||
/// as multiple, smaller sequential blocks of data.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public abstract class IHash256Generator : IDisposable
|
||||
{
|
||||
public static readonly long HashSize = 256 / 8;
|
||||
|
@ -41,14 +47,14 @@ public abstract class IHash256Generator : IDisposable
|
|||
/// <summary>
|
||||
/// Creates <see cref="IHash256Generator"/> objects and can generate a hash for a single, in-memory block of data.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public abstract class IHash256GeneratorFactory : IDisposable
|
||||
{
|
||||
public virtual void Dispose() { }
|
||||
|
||||
public UniqueRef<IHash256Generator> Create()
|
||||
public Result Create(ref UniqueRef<IHash256Generator> outGenerator)
|
||||
{
|
||||
return DoCreate();
|
||||
return DoCreate(ref outGenerator);
|
||||
}
|
||||
|
||||
public void GenerateHash(Span<byte> hashBuffer, ReadOnlySpan<byte> data)
|
||||
|
@ -58,22 +64,22 @@ public abstract class IHash256GeneratorFactory : IDisposable
|
|||
DoGenerateHash(hashBuffer, data);
|
||||
}
|
||||
|
||||
protected abstract UniqueRef<IHash256Generator> DoCreate();
|
||||
protected abstract Result DoCreate(ref UniqueRef<IHash256Generator> outGenerator);
|
||||
protected abstract void DoGenerateHash(Span<byte> hashBuffer, ReadOnlySpan<byte> data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates <see cref="IHash256GeneratorFactory"/> objects.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public abstract class IHash256GeneratorFactorySelector : IDisposable
|
||||
{
|
||||
public virtual void Dispose() { }
|
||||
|
||||
public IHash256GeneratorFactory GetFactory()
|
||||
public IHash256GeneratorFactory GetFactory(HashAlgorithmType type)
|
||||
{
|
||||
return DoGetFactory();
|
||||
return DoGetFactory(type);
|
||||
}
|
||||
|
||||
protected abstract IHash256GeneratorFactory DoGetFactory();
|
||||
protected abstract IHash256GeneratorFactory DoGetFactory(HashAlgorithmType type);
|
||||
}
|
|
@ -8,21 +8,27 @@ using LibHac.FsSrv;
|
|||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the configuration used for decrypting NCAs.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public struct NcaCryptoConfiguration
|
||||
{
|
||||
public static readonly int Rsa2048KeyModulusSize = Rsa.ModulusSize2048Pss;
|
||||
public static readonly int Rsa2048KeyPublicExponentSize = Rsa.MaximumExponentSize2048Pss;
|
||||
public static readonly int Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize;
|
||||
public const int Rsa2048KeyModulusSize = Rsa.ModulusSize2048Pss;
|
||||
public const int Rsa2048KeyPublicExponentSize = Rsa.MaximumExponentSize2048Pss;
|
||||
public const int Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize;
|
||||
|
||||
public static readonly int Aes128KeySize = Aes.KeySize128;
|
||||
public const int Aes128KeySize = Aes.KeySize128;
|
||||
|
||||
public static readonly int Header1SignatureKeyGenerationMax = 1;
|
||||
public const int Header1SignatureKeyGenerationMax = 1;
|
||||
|
||||
public static readonly int KeyAreaEncryptionKeyIndexCount = 3;
|
||||
public static readonly int HeaderEncryptionKeyCount = 2;
|
||||
public const int KeyAreaEncryptionKeyIndexCount = 3;
|
||||
public const int HeaderEncryptionKeyCount = 2;
|
||||
|
||||
public static readonly int KeyGenerationMax = 32;
|
||||
public static readonly int KeyAreaEncryptionKeyCount = KeyAreaEncryptionKeyIndexCount * KeyGenerationMax;
|
||||
public const byte KeyAreaEncryptionKeyIndexZeroKey = 0xFF;
|
||||
|
||||
public const int KeyGenerationMax = 32;
|
||||
public const int KeyAreaEncryptionKeyCount = KeyAreaEncryptionKeyIndexCount * KeyGenerationMax;
|
||||
|
||||
public Array2<Array256<byte>> Header1SignKeyModuli;
|
||||
public Array3<byte> Header1SignKeyPublicExponent;
|
||||
|
@ -30,9 +36,13 @@ public struct NcaCryptoConfiguration
|
|||
public Array16<byte> HeaderEncryptionKeySource;
|
||||
public Array2<Array16<byte>> HeaderEncryptedEncryptionKeys;
|
||||
public GenerateKeyFunction GenerateKey;
|
||||
public CryptAesXtsFunction EncryptAesXtsForExternalKey;
|
||||
public CryptAesXtsFunction DecryptAesXtsForExternalKey;
|
||||
public DecryptAesCtrFunction DecryptAesCtr;
|
||||
public DecryptAesCtrFunction DecryptAesCtrForExternalKey;
|
||||
public VerifySign1Function VerifySign1;
|
||||
public bool IsDev;
|
||||
public bool IsAvailableSwKey;
|
||||
}
|
||||
|
||||
public struct NcaCompressionConfiguration
|
||||
|
@ -49,22 +59,30 @@ public static class NcaKeyFunctions
|
|||
|
||||
public static int GetKeyTypeValue(byte keyIndex, byte keyGeneration)
|
||||
{
|
||||
const int invalidKeyTypeValue = -1;
|
||||
if (keyIndex == NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexZeroKey)
|
||||
{
|
||||
return (int)KeyType.ZeroKey;
|
||||
}
|
||||
|
||||
if (keyIndex >= NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount)
|
||||
return invalidKeyTypeValue;
|
||||
if (keyIndex < NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount)
|
||||
{
|
||||
return NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount * keyGeneration + keyIndex;
|
||||
}
|
||||
|
||||
return NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount * keyGeneration + keyIndex;
|
||||
return (int)KeyType.InvalidKey;
|
||||
}
|
||||
}
|
||||
|
||||
public enum KeyType
|
||||
{
|
||||
NcaHeaderKey = 0x60,
|
||||
NcaExternalKey = 0x61,
|
||||
SaveDataDeviceUniqueMac = 0x62,
|
||||
SaveDataSeedUniqueMac = 0x63,
|
||||
SaveDataTransferMac = 0x64
|
||||
ZeroKey = -2,
|
||||
InvalidKey = -1,
|
||||
NcaHeaderKey1 = NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 0,
|
||||
NcaHeaderKey2 = NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 1,
|
||||
NcaExternalKey = NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 2,
|
||||
SaveDataDeviceUniqueMac = NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 3,
|
||||
SaveDataSeedUniqueMac = NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 4,
|
||||
SaveDataTransferMac = NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 5
|
||||
}
|
||||
|
||||
public class NcaFileSystemDriver : IDisposable
|
||||
|
@ -85,6 +103,8 @@ public class NcaFileSystemDriver : IDisposable
|
|||
public SharedRef<IStorage> FsDataStorage;
|
||||
public SharedRef<IStorage> CompressedStorageMetaStorage;
|
||||
public SharedRef<CompressedStorage> CompressedStorage;
|
||||
public SharedRef<IStorage> PatchLayerInfoStorage;
|
||||
public SharedRef<IStorage> SparseLayerInfoStorage;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -100,6 +120,8 @@ public class NcaFileSystemDriver : IDisposable
|
|||
FsDataStorage.Destroy();
|
||||
CompressedStorageMetaStorage.Destroy();
|
||||
CompressedStorage.Destroy();
|
||||
PatchLayerInfoStorage.Destroy();
|
||||
SparseLayerInfoStorage.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,6 +206,14 @@ public class NcaFileSystemDriver : IDisposable
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result CreateSparseStorageMetaStorageWithVerification(ref SharedRef<IStorage> outStorage,
|
||||
ref SharedRef<IStorage> outLayerInfoStorage, ref SharedRef<IStorage> baseStorage, long offset,
|
||||
in NcaAesCtrUpperIv upperIv, in NcaSparseInfo sparseInfo, in NcaMetaDataHashDataInfo metaDataHashDataInfo,
|
||||
IHash256GeneratorFactory hashGeneratorFactory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result CreateSparseStorageCore(ref SharedRef<SparseStorage> outStorage, ref SharedRef<IStorage> baseStorage,
|
||||
long baseStorageSize, ref SharedRef<IStorage> sparseStorageMetaStorage, in NcaSparseInfo sparseInfo,
|
||||
bool hasExternalInfo)
|
||||
|
@ -198,8 +228,26 @@ public class NcaFileSystemDriver : IDisposable
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result CreateSparseStorageWithVerification(ref SharedRef<IStorage> outStorage, out long outFsDataOffset,
|
||||
out SharedRef<SparseStorage> outSparseStorage, ref SharedRef<IStorage> outSparseStorageMetaStorage,
|
||||
ref SharedRef<IStorage> outLayerInfoStorage, int index, in NcaAesCtrUpperIv upperIv,
|
||||
in NcaSparseInfo sparseInfo, in NcaMetaDataHashDataInfo metaDataHashDataInfo,
|
||||
NcaFsHeader.MetaDataHashType metaDataHashType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result CreatePatchMetaStorage(ref SharedRef<IStorage> outAesCtrExMetaStorage,
|
||||
ref SharedRef<IStorage> outIndirectMetaStorage, ref SharedRef<IStorage> outLayerInfoStorage,
|
||||
ref SharedRef<IStorage> baseStorage, long offset, in NcaAesCtrUpperIv upperIv, in NcaPatchInfo patchInfo,
|
||||
in NcaMetaDataHashDataInfo metaDataHashDataInfo, IHash256GeneratorFactory hashGeneratorFactory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result CreateAesCtrExStorageMetaStorage(ref SharedRef<IStorage> outStorage,
|
||||
ref SharedRef<IStorage> baseStorage, long offset, in NcaAesCtrUpperIv upperIv, in NcaPatchInfo patchInfo)
|
||||
ref SharedRef<IStorage> baseStorage, long offset, NcaFsHeader.EncryptionType encryptionType,
|
||||
in NcaAesCtrUpperIv upperIv, in NcaPatchInfo patchInfo)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@ -227,13 +275,29 @@ public class NcaFileSystemDriver : IDisposable
|
|||
}
|
||||
|
||||
private Result CreateSha256Storage(ref SharedRef<IStorage> outStorage, ref SharedRef<IStorage> baseStorage,
|
||||
in NcaFsHeader.HashData.HierarchicalSha256Data sha256Data)
|
||||
in NcaFsHeader.HashData.HierarchicalSha256Data sha256Data, IHash256GeneratorFactory hashGeneratorFactory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result HierarchicalSha256Data(ref SharedRef<IStorage> outStorage, ref SharedRef<IStorage> baseStorage,
|
||||
in NcaFsHeader.HashData.IntegrityMetaInfo metaInfo)
|
||||
private Result CreateIntegrityVerificationStorage(ref SharedRef<IStorage> outStorage,
|
||||
ref SharedRef<IStorage> baseStorage, in NcaFsHeader.HashData.IntegrityMetaInfo metaInfo,
|
||||
IHash256GeneratorFactory hashGeneratorFactory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result CreateIntegrityVerificationStorageImpl(ref SharedRef<IStorage> outStorage,
|
||||
ref SharedRef<IStorage> baseStorage, in NcaFsHeader.HashData.IntegrityMetaInfo metaInfo, long layerInfoOffset,
|
||||
int maxDataCacheEntries, int maxHashCacheEntries, sbyte bufferLevel,
|
||||
IHash256GeneratorFactory hashGeneratorFactory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Result CreateIntegrityVerificationStorageForMeta(ref SharedRef<IStorage> outStorage,
|
||||
ref SharedRef<IStorage> outLayerInfoStorage, ref SharedRef<IStorage> baseStorage, long offset,
|
||||
in NcaMetaDataHashDataInfo metaDataHashDataInfo, IHash256GeneratorFactory hashGeneratorFactory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@ -252,4 +316,10 @@ public class NcaFileSystemDriver : IDisposable
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result CreateRegionSwitchStorage(ref SharedRef<IStorage> outStorage, NcaFsHeaderReader headerReader,
|
||||
ref SharedRef<IStorage> insideRegionStorage, ref SharedRef<IStorage> outsideRegionStorage)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
|
@ -1,10 +1,16 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Common.FixedArrays;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The structure used as the header for an NCA file.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public struct NcaHeader
|
||||
{
|
||||
public enum ContentType : byte
|
||||
|
@ -133,6 +139,19 @@ public struct NcaCompressionInfo
|
|||
public ulong Reserved;
|
||||
}
|
||||
|
||||
public struct NcaMetaDataHashDataInfo
|
||||
{
|
||||
public long Offset;
|
||||
public long Size;
|
||||
public Hash Hash;
|
||||
}
|
||||
|
||||
public struct NcaMetaDataHashData
|
||||
{
|
||||
public long LayerInfoOffset;
|
||||
public NcaFsHeader.HashData.IntegrityMetaInfo IntegrityMetaInfo;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct NcaAesCtrUpperIv
|
||||
{
|
||||
|
@ -149,19 +168,25 @@ public struct NcaAesCtrUpperIv
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The structure used as the header for an NCA file system.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public struct NcaFsHeader
|
||||
{
|
||||
public ushort Version;
|
||||
public FsType FsTypeValue;
|
||||
public HashType HashTypeValue;
|
||||
public EncryptionType EncryptionTypeValue;
|
||||
public Array3<byte> Reserved;
|
||||
public MetaDataHashType MetaDataHashTypeValue;
|
||||
public Array2<byte> Reserved;
|
||||
public HashData HashDataValue;
|
||||
public NcaPatchInfo PatchInfo;
|
||||
public NcaAesCtrUpperIv AesCtrUpperIv;
|
||||
public NcaSparseInfo SparseInfo;
|
||||
public NcaCompressionInfo CompressionInfo;
|
||||
public Array96<byte> Padding;
|
||||
public NcaMetaDataHashDataInfo MetaDataHashDataInfo;
|
||||
public Array48<byte> Padding;
|
||||
|
||||
public enum FsType : byte
|
||||
{
|
||||
|
@ -175,7 +200,9 @@ public struct NcaFsHeader
|
|||
None = 1,
|
||||
AesXts = 2,
|
||||
AesCtr = 3,
|
||||
AesCtrEx = 4
|
||||
AesCtrEx = 4,
|
||||
AesCtrSkipLayerHash = 5,
|
||||
AesCtrExSkipLayerHash = 6
|
||||
}
|
||||
|
||||
public enum HashType : byte
|
||||
|
@ -183,7 +210,16 @@ public struct NcaFsHeader
|
|||
Auto = 0,
|
||||
None = 1,
|
||||
HierarchicalSha256Hash = 2,
|
||||
HierarchicalIntegrityHash = 3
|
||||
HierarchicalIntegrityHash = 3,
|
||||
AutoSha3 = 4,
|
||||
HierarchicalSha3256Hash = 5,
|
||||
HierarchicalIntegritySha3Hash = 6
|
||||
}
|
||||
|
||||
public enum MetaDataHashType : byte
|
||||
{
|
||||
None = 0,
|
||||
HierarchicalIntegrity = 1
|
||||
}
|
||||
|
||||
public struct Region
|
||||
|
@ -235,4 +271,31 @@ public struct NcaFsHeader
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Result GetHashTargetOffset(out long outOffset)
|
||||
{
|
||||
UnsafeHelpers.SkipParamInit(out outOffset);
|
||||
|
||||
if (HashTypeValue is HashType.HierarchicalIntegrityHash or HashType.HierarchicalIntegritySha3Hash)
|
||||
{
|
||||
ref readonly HashData.IntegrityMetaInfo.InfoLevelHash hashInfo = ref HashDataValue.IntegrityMeta.LevelHashInfo;
|
||||
outOffset = hashInfo.Layers[hashInfo.MaxLayers - 2].Offset;
|
||||
}
|
||||
else if (HashTypeValue is HashType.HierarchicalSha256Hash or HashType.HierarchicalSha3256Hash)
|
||||
{
|
||||
ref readonly HashData.HierarchicalSha256Data hashInfo = ref HashDataValue.HierarchicalSha256;
|
||||
outOffset = hashInfo.LayerRegions[hashInfo.LayerCount - 1].Offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultFs.InvalidNcaFsHeader.Log();
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public readonly bool IsSkipLayerHashEncryption()
|
||||
{
|
||||
return EncryptionTypeValue is EncryptionType.AesCtrSkipLayerHash or EncryptionType.AesCtrExSkipLayerHash;
|
||||
}
|
||||
}
|
|
@ -8,13 +8,15 @@ using LibHac.Fs;
|
|||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public delegate Result GenerateKeyFunction(Span<byte> destKey, ReadOnlySpan<byte> sourceKey, int keyType, in NcaCryptoConfiguration config);
|
||||
public delegate Result DecryptAesCtrFunction(Span<byte> dest, int keyType, ReadOnlySpan<byte> encryptedKey, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> source);
|
||||
public delegate void GenerateKeyFunction(Span<byte> destKey, ReadOnlySpan<byte> sourceKey, int keyType);
|
||||
public delegate Result DecryptAesCtrFunction(Span<byte> dest, int keyIndex, int keyGeneration, ReadOnlySpan<byte> encryptedKey, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> source);
|
||||
public delegate Result CryptAesXtsFunction(Span<byte> dest, ReadOnlySpan<byte> key1, ReadOnlySpan<byte> key2, ReadOnlySpan<byte> iv, ReadOnlySpan<byte> source);
|
||||
public delegate bool VerifySign1Function(ReadOnlySpan<byte> signature, ReadOnlySpan<byte> data, bool isProd, byte generation);
|
||||
|
||||
/// <summary>
|
||||
/// Handles reading information from an NCA file's header.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public class NcaReader : IDisposable
|
||||
{
|
||||
private const uint SdkAddonVersionMin = 0xB0000;
|
||||
|
@ -27,9 +29,10 @@ public class NcaReader : IDisposable
|
|||
private DecryptAesCtrFunction _decryptAesCtr;
|
||||
private DecryptAesCtrFunction _decryptAesCtrForExternalKey;
|
||||
private bool _isSoftwareAesPrioritized;
|
||||
private bool _isAvailableSwKey;
|
||||
private NcaHeader.EncryptionType _headerEncryptionType;
|
||||
private GetDecompressorFunction _getDecompressorFunc;
|
||||
private IHash256GeneratorFactory _hashGeneratorFactory;
|
||||
private IHash256GeneratorFactorySelector _hashGeneratorFactorySelector;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
@ -44,23 +47,44 @@ public class NcaReader : IDisposable
|
|||
Assert.SdkRequiresNotNull(hashGeneratorFactorySelector);
|
||||
Assert.SdkRequiresNull(in _bodyStorage);
|
||||
|
||||
if (cryptoConfig.GenerateKey is null)
|
||||
if (cryptoConfig.VerifySign1 is null)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
using var headerStorage = new UniqueRef<IStorage>();
|
||||
|
||||
// Generate the keys for decrypting the NCA header.
|
||||
Unsafe.SkipInit(out Array2<Array16<byte>> commonDecryptionKeys);
|
||||
for (int i = 0; i < NcaCryptoConfiguration.HeaderEncryptionKeyCount; i++)
|
||||
if (cryptoConfig.IsAvailableSwKey)
|
||||
{
|
||||
cryptoConfig.GenerateKey(commonDecryptionKeys[i].Items, cryptoConfig.HeaderEncryptedEncryptionKeys[i], 0x60,
|
||||
in cryptoConfig);
|
||||
}
|
||||
if (cryptoConfig.GenerateKey is null)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
// Create an XTS storage to read the encrypted header.
|
||||
Array16<byte> headerIv = default;
|
||||
headerStorage.Reset(new AesXtsStorage(baseStorage.Get, commonDecryptionKeys[0], commonDecryptionKeys[1],
|
||||
headerIv, NcaHeader.XtsBlockSize));
|
||||
ReadOnlySpan<int> headerKeyTypes = stackalloc int[NcaCryptoConfiguration.HeaderEncryptionKeyCount]
|
||||
{ (int)KeyType.NcaHeaderKey1, (int)KeyType.NcaHeaderKey2 };
|
||||
|
||||
// Generate the keys for decrypting the NCA header.
|
||||
Unsafe.SkipInit(out Array2<Array16<byte>> commonDecryptionKeys);
|
||||
for (int i = 0; i < NcaCryptoConfiguration.HeaderEncryptionKeyCount; i++)
|
||||
{
|
||||
cryptoConfig.GenerateKey(commonDecryptionKeys[i].Items, cryptoConfig.HeaderEncryptedEncryptionKeys[i],
|
||||
headerKeyTypes[i]);
|
||||
}
|
||||
|
||||
// Create an XTS storage to read the encrypted header.
|
||||
Array16<byte> headerIv = default;
|
||||
headerStorage.Reset(new AesXtsStorage(baseStorage.Get, commonDecryptionKeys[0], commonDecryptionKeys[1],
|
||||
headerIv, NcaHeader.XtsBlockSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Software key isn't available, so we need to be able to decrypt externally.
|
||||
if (cryptoConfig.DecryptAesXtsForExternalKey is null)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
// Create the header storage.
|
||||
Array16<byte> headerIv = default;
|
||||
headerStorage.Reset(new AesXtsStorageExternal(baseStorage.Get, ReadOnlySpan<byte>.Empty,
|
||||
ReadOnlySpan<byte>.Empty, headerIv, (uint)NcaHeader.XtsBlockSize, cryptoConfig.EncryptAesXtsForExternalKey,
|
||||
cryptoConfig.DecryptAesXtsForExternalKey));
|
||||
}
|
||||
|
||||
if (!headerStorage.HasValue)
|
||||
return ResultFs.AllocationMemoryFailedInNcaReaderA.Log();
|
||||
|
@ -108,11 +132,9 @@ public class NcaReader : IDisposable
|
|||
int signMessageOffset = NcaHeader.HeaderSignSize * NcaHeader.HeaderSignCount;
|
||||
int signMessageSize = NcaHeader.Size - signMessageOffset;
|
||||
ReadOnlySpan<byte> signature = _header.Signature1;
|
||||
ReadOnlySpan<byte> modulus = cryptoConfig.Header1SignKeyModuli[_header.Header1SignatureKeyGeneration];
|
||||
ReadOnlySpan<byte> exponent = cryptoConfig.Header1SignKeyPublicExponent;
|
||||
ReadOnlySpan<byte> message = SpanHelpers.AsReadOnlyByteSpan(in _header).Slice(signMessageOffset, signMessageSize);
|
||||
|
||||
if (!Rsa.VerifyRsa2048PssSha256(signature, modulus, exponent, message))
|
||||
if (!cryptoConfig.VerifySign1(signature, message, !cryptoConfig.IsDev, _header.Header1SignatureKeyGeneration))
|
||||
return ResultFs.NcaHeaderSignature1VerificationFailed.Log();
|
||||
|
||||
// Validate the sdk version.
|
||||
|
@ -120,32 +142,40 @@ public class NcaReader : IDisposable
|
|||
return ResultFs.UnsupportedSdkVersion.Log();
|
||||
|
||||
// Validate the key index.
|
||||
if (_header.KeyAreaEncryptionKeyIndex >= NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount)
|
||||
if (_header.KeyAreaEncryptionKeyIndex >= NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexCount &&
|
||||
_header.KeyAreaEncryptionKeyIndex != NcaCryptoConfiguration.KeyAreaEncryptionKeyIndexZeroKey)
|
||||
{
|
||||
return ResultFs.InvalidNcaKeyIndex.Log();
|
||||
}
|
||||
|
||||
_hashGeneratorFactorySelector = hashGeneratorFactorySelector;
|
||||
|
||||
// Get keys from the key area if the NCA doesn't have a rights ID.
|
||||
Array16<byte> zeroRightsId = default;
|
||||
if (CryptoUtil.IsSameBytes(zeroRightsId, _header.RightsId, NcaHeader.RightsIdSize))
|
||||
{
|
||||
// If we don't have a rights ID we need to generate decryption keys.
|
||||
int keyType = NcaKeyFunctions.GetKeyTypeValue(_header.KeyAreaEncryptionKeyIndex, _header.GetProperKeyGeneration());
|
||||
ReadOnlySpan<byte> encryptedKeyCtr = _header.EncryptedKeys.ItemsRo.Slice((int)NcaHeader.DecryptionKey.AesCtr * Aes.KeySize128, Aes.KeySize128);
|
||||
ReadOnlySpan<byte> keyCtrHw = _header.EncryptedKeys.ItemsRo.Slice((int)NcaHeader.DecryptionKey.AesCtrHw * Aes.KeySize128, Aes.KeySize128);
|
||||
// If we don't have a rights ID we need to generate decryption keys if software keys are available.
|
||||
if (cryptoConfig.IsAvailableSwKey)
|
||||
{
|
||||
int keyTypeValue = NcaKeyFunctions.GetKeyTypeValue(_header.KeyAreaEncryptionKeyIndex, _header.GetProperKeyGeneration());
|
||||
ReadOnlySpan<byte> encryptedKeyCtr = _header.EncryptedKeys.ItemsRo.Slice((int)NcaHeader.DecryptionKey.AesCtr * Aes.KeySize128, Aes.KeySize128);
|
||||
|
||||
cryptoConfig.GenerateKey(_decryptionKeys[(int)NcaHeader.DecryptionKey.AesCtr].Items, encryptedKeyCtr, keyType, in cryptoConfig);
|
||||
cryptoConfig.GenerateKey(_decryptionKeys[(int)NcaHeader.DecryptionKey.AesCtr].Items, encryptedKeyCtr, keyTypeValue);
|
||||
}
|
||||
|
||||
// Copy the plaintext hardware key.
|
||||
ReadOnlySpan<byte> keyCtrHw = _header.EncryptedKeys.ItemsRo.Slice((int)NcaHeader.DecryptionKey.AesCtrHw * Aes.KeySize128, Aes.KeySize128);
|
||||
keyCtrHw.CopyTo(_decryptionKeys[(int)NcaHeader.DecryptionKey.AesCtrHw].Items);
|
||||
}
|
||||
|
||||
// Clear the external decryption key.
|
||||
_externalDataDecryptionKey.Items.Clear();
|
||||
|
||||
// Copy the configuration to the NcaReader.
|
||||
_isAvailableSwKey = cryptoConfig.IsAvailableSwKey;
|
||||
_decryptAesCtr = cryptoConfig.DecryptAesCtr;
|
||||
_decryptAesCtrForExternalKey = cryptoConfig.DecryptAesCtrForExternalKey;
|
||||
_getDecompressorFunc = compressionConfig.GetDecompressorFunc;
|
||||
_hashGeneratorFactory = hashGeneratorFactorySelector.GetFactory();
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactory);
|
||||
|
||||
_bodyStorage.SetByMove(ref baseStorage);
|
||||
_headerStorage.Set(ref headerStorage.Ref());
|
||||
|
@ -187,7 +217,7 @@ public class NcaReader : IDisposable
|
|||
|
||||
public void GetHeaderSign2TargetHash(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactory);
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactorySelector);
|
||||
Assert.SdkRequiresEqual(IHash256Generator.HashSize, outBuffer.Length);
|
||||
|
||||
int signTargetOffset = NcaHeader.HeaderSignSize * NcaHeader.HeaderSignCount;
|
||||
|
@ -195,7 +225,8 @@ public class NcaReader : IDisposable
|
|||
ReadOnlySpan<byte> signTarget =
|
||||
SpanHelpers.AsReadOnlyByteSpan(in _header).Slice(signTargetOffset, signTargetSize);
|
||||
|
||||
_hashGeneratorFactory.GenerateHash(outBuffer, signTarget);
|
||||
IHash256GeneratorFactory factory = _hashGeneratorFactorySelector.GetFactory(HashAlgorithmType.Sha2);
|
||||
factory.GenerateHash(outBuffer, signTarget);
|
||||
}
|
||||
|
||||
public SharedRef<IStorage> GetSharedBodyStorage()
|
||||
|
@ -390,6 +421,11 @@ public class NcaReader : IDisposable
|
|||
_isSoftwareAesPrioritized = true;
|
||||
}
|
||||
|
||||
public bool IsAvailableSwKey()
|
||||
{
|
||||
return _isAvailableSwKey;
|
||||
}
|
||||
|
||||
public void SetExternalDecryptionKey(ReadOnlySpan<byte> key)
|
||||
{
|
||||
Assert.SdkRequiresEqual(_externalDataDecryptionKey.ItemsRo.Length, key.Length);
|
||||
|
@ -434,17 +470,17 @@ public class NcaReader : IDisposable
|
|||
return _getDecompressorFunc;
|
||||
}
|
||||
|
||||
public IHash256GeneratorFactory GetHashGeneratorFactory()
|
||||
public IHash256GeneratorFactorySelector GetHashGeneratorFactorySelector()
|
||||
{
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactory);
|
||||
return _hashGeneratorFactory;
|
||||
Assert.SdkRequiresNotNull(_hashGeneratorFactorySelector);
|
||||
return _hashGeneratorFactorySelector;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles reading information from the <see cref="NcaFsHeader"/> of a file system inside an NCA file.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
public class NcaFsHeaderReader
|
||||
{
|
||||
private NcaFsHeader _header;
|
||||
|
@ -468,7 +504,8 @@ public class NcaFsHeaderReader
|
|||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
Unsafe.SkipInit(out Hash hash);
|
||||
reader.GetHashGeneratorFactory().GenerateHash(hash.Value.Items, SpanHelpers.AsReadOnlyByteSpan(in _header));
|
||||
IHash256GeneratorFactory generator = reader.GetHashGeneratorFactorySelector().GetFactory(HashAlgorithmType.Sha2);
|
||||
generator.GenerateHash(hash.Value.Items, SpanHelpers.AsReadOnlyByteSpan(in _header));
|
||||
|
||||
if (!CryptoUtil.IsSameBytes(reader.GetFsHeaderHash(index).Value, hash.Value, Unsafe.SizeOf<Hash>()))
|
||||
{
|
||||
|
@ -515,6 +552,34 @@ public class NcaFsHeaderReader
|
|||
return _header.EncryptionTypeValue;
|
||||
}
|
||||
|
||||
public NcaFsHeader.MetaDataHashType GetPatchMetaHashType()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.MetaDataHashTypeValue;
|
||||
}
|
||||
|
||||
public NcaFsHeader.MetaDataHashType GetSparseMetaHashType()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.MetaDataHashTypeValue;
|
||||
}
|
||||
|
||||
public Result GetHashTargetOffset(out long outOffset)
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
|
||||
Result rc = _header.GetHashTargetOffset(out outOffset);
|
||||
if (rc.IsFailure()) return rc.Miss();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public bool IsSkipLayerHashEncryption()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.IsSkipLayerHashEncryption();
|
||||
}
|
||||
|
||||
public ref readonly NcaPatchInfo GetPatchInfo()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
|
@ -551,6 +616,30 @@ public class NcaFsHeaderReader
|
|||
return ref _header.CompressionInfo;
|
||||
}
|
||||
|
||||
public bool ExistsPatchMetaHashLayer()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.MetaDataHashDataInfo.Size != 0 && GetPatchInfo().HasIndirectTable();
|
||||
}
|
||||
|
||||
public bool ExistsSparseMetaHashLayer()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return _header.MetaDataHashDataInfo.Size != 0 && ExistsSparseLayer();
|
||||
}
|
||||
|
||||
public ref readonly NcaMetaDataHashDataInfo GetPatchMetaDataHashDataInfo()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return ref _header.MetaDataHashDataInfo;
|
||||
}
|
||||
|
||||
public ref readonly NcaMetaDataHashDataInfo GetSparseMetaDataHashDataInfo()
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
return ref _header.MetaDataHashDataInfo;
|
||||
}
|
||||
|
||||
public void GetRawData(Span<byte> outBuffer)
|
||||
{
|
||||
Assert.SdkRequires(IsInitialized());
|
||||
|
|
|
@ -476,7 +476,7 @@ public class TypeLayoutTests
|
|||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct Int64AlignmentTest
|
||||
{
|
||||
public int A;
|
||||
public byte A;
|
||||
public Int64 B;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,13 +28,15 @@ public class TypeLayoutTests
|
|||
Assert.Equal(0x002, GetOffset(in s, in s.FsTypeValue));
|
||||
Assert.Equal(0x003, GetOffset(in s, in s.HashTypeValue));
|
||||
Assert.Equal(0x004, GetOffset(in s, in s.EncryptionTypeValue));
|
||||
Assert.Equal(0x005, GetOffset(in s, in s.Reserved));
|
||||
Assert.Equal(0x005, GetOffset(in s, in s.MetaDataHashTypeValue));
|
||||
Assert.Equal(0x006, GetOffset(in s, in s.Reserved));
|
||||
Assert.Equal(0x008, GetOffset(in s, in s.HashDataValue));
|
||||
Assert.Equal(0x100, GetOffset(in s, in s.PatchInfo));
|
||||
Assert.Equal(0x140, GetOffset(in s, in s.AesCtrUpperIv));
|
||||
Assert.Equal(0x148, GetOffset(in s, in s.SparseInfo));
|
||||
Assert.Equal(0x178, GetOffset(in s, in s.CompressionInfo));
|
||||
Assert.Equal(0x1A0, GetOffset(in s, in s.Padding));
|
||||
Assert.Equal(0x1A0, GetOffset(in s, in s.MetaDataHashDataInfo));
|
||||
Assert.Equal(0x1D0, GetOffset(in s, in s.Padding));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -99,7 +101,7 @@ public class TypeLayoutTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public static void HierarchicalIntegrityVerificationLevelInformation_Layout()
|
||||
public static void InfoLevelHash_HierarchicalIntegrityVerificationLevelInformation_Layout()
|
||||
{
|
||||
NcaFsHeader.HashData.IntegrityMetaInfo.InfoLevelHash.HierarchicalIntegrityVerificationLevelInformation s = default;
|
||||
|
||||
|
@ -112,7 +114,7 @@ public class TypeLayoutTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public static void SignatureSalt_Layout()
|
||||
public static void InfoLevelHash_SignatureSalt_Layout()
|
||||
{
|
||||
NcaFsHeader.HashData.IntegrityMetaInfo.InfoLevelHash.SignatureSalt s = default;
|
||||
|
||||
|
@ -164,6 +166,29 @@ public class TypeLayoutTests
|
|||
Assert.Equal(0x20, GetOffset(in s, in s.Reserved));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaMetaDataHashDataInfo_Layout()
|
||||
{
|
||||
NcaMetaDataHashDataInfo s = default;
|
||||
|
||||
Assert.Equal(0x30, Unsafe.SizeOf<NcaMetaDataHashDataInfo>());
|
||||
|
||||
Assert.Equal(0x00, GetOffset(in s, in s.Offset));
|
||||
Assert.Equal(0x08, GetOffset(in s, in s.Size));
|
||||
Assert.Equal(0x10, GetOffset(in s, in s.Hash));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaMetaDataHashData_Layout()
|
||||
{
|
||||
NcaMetaDataHashData s = default;
|
||||
|
||||
Assert.Equal(0xE8, Unsafe.SizeOf<NcaMetaDataHashData>());
|
||||
|
||||
Assert.Equal(0, GetOffset(in s, in s.LayerInfoOffset));
|
||||
Assert.Equal(8, GetOffset(in s, in s.IntegrityMetaInfo));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NcaAesCtrUpperIv_Layout()
|
||||
{
|
||||
|
@ -240,12 +265,6 @@ public class TypeLayoutTests
|
|||
Assert.Equal(NcaCryptoConfiguration.Aes128KeySize, s.HeaderEncryptionKeySource.ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.HeaderEncryptionKeyCount, s.HeaderEncryptedEncryptionKeys.ItemsRo.Length);
|
||||
Assert.Equal(NcaCryptoConfiguration.Aes128KeySize, s.HeaderEncryptedEncryptionKeys.ItemsRo[0].ItemsRo.Length);
|
||||
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 0, (int)KeyType.NcaHeaderKey);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 1, (int)KeyType.NcaExternalKey);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 2, (int)KeyType.SaveDataDeviceUniqueMac);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 3, (int)KeyType.SaveDataSeedUniqueMac);
|
||||
Assert.Equal(NcaCryptoConfiguration.KeyAreaEncryptionKeyCount + 4, (int)KeyType.SaveDataTransferMac);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -256,7 +275,8 @@ public class TypeLayoutTests
|
|||
Assert.Equal(0x10, Unsafe.SizeOf<AesCtrCounterExtendedStorage.Entry>());
|
||||
|
||||
Assert.Equal(0x0, GetOffset(in s, in s.Offset));
|
||||
Assert.Equal(0x8, GetOffset(in s, in s.Reserved));
|
||||
Assert.Equal(0x8, GetOffset(in s, in s.EncryptionValue));
|
||||
Assert.Equal(0x9, GetOffset(in s, in s.Reserved));
|
||||
Assert.Equal(0xC, GetOffset(in s, in s.Generation));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue