mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Flesh out NCA encryption
Allows opening decrypted NCAs. Allows opening encrypted and decrypted storages of any NCA, whether encrypted or decrypted. hactoolnet: Allow saving an encrypted version of any NCA.
This commit is contained in:
parent
e0ddf17945
commit
3819cfdca7
4 changed files with 72 additions and 34 deletions
|
@ -13,6 +13,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
public class Nca
|
||||
{
|
||||
private Keyset Keyset { get; }
|
||||
private bool IsEncrypted { get; set; }
|
||||
public IStorage BaseStorage { get; }
|
||||
|
||||
public NcaHeader Header { get; }
|
||||
|
@ -21,11 +22,11 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
{
|
||||
Span<byte> magic = stackalloc byte[3];
|
||||
storage.Read(0x200, magic);
|
||||
bool encrypted = magic[0] != 'N' && magic[1] != 'C' && magic[2] != 'A';
|
||||
IsEncrypted = magic[0] != 'N' && magic[1] != 'C' && magic[2] != 'A';
|
||||
|
||||
Keyset = keyset;
|
||||
BaseStorage = storage;
|
||||
Header = encrypted ? new NcaHeader(keyset, storage) : new NcaHeader(storage);
|
||||
Header = IsEncrypted ? new NcaHeader(keyset, storage) : new NcaHeader(storage);
|
||||
}
|
||||
|
||||
public byte[] GetDecryptedKey(int index)
|
||||
|
@ -139,7 +140,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
return BaseStorage.Slice(offset, size);
|
||||
}
|
||||
|
||||
private IStorage OpenDecryptedStorage(IStorage baseStorage, int index)
|
||||
private IStorage OpenDecryptedStorage(IStorage baseStorage, int index, bool decrypting)
|
||||
{
|
||||
NcaFsHeader header = Header.GetFsHeader(index);
|
||||
|
||||
|
@ -148,18 +149,18 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
case NcaEncryptionType.None:
|
||||
return baseStorage;
|
||||
case NcaEncryptionType.XTS:
|
||||
return OpenAesXtsStorage(baseStorage, index);
|
||||
return OpenAesXtsStorage(baseStorage, index, decrypting);
|
||||
case NcaEncryptionType.AesCtr:
|
||||
return OpenAesCtrStorage(baseStorage, index);
|
||||
case NcaEncryptionType.AesCtrEx:
|
||||
return OpenAesCtrExStorage(baseStorage, index);
|
||||
return OpenAesCtrExStorage(baseStorage, index, decrypting);
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable UnusedParameter.Local
|
||||
private IStorage OpenAesXtsStorage(IStorage baseStorage, int index)
|
||||
private IStorage OpenAesXtsStorage(IStorage baseStorage, int index, bool decrypting)
|
||||
{
|
||||
const int sectorSize = 0x200;
|
||||
|
||||
|
@ -167,7 +168,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
byte[] key1 = GetContentKey(NcaKeyType.AesXts1);
|
||||
|
||||
// todo: Handle xts for nca version 3
|
||||
return new CachedStorage(new Aes128XtsStorage(baseStorage, key0, key1, sectorSize, true), 2, true);
|
||||
return new CachedStorage(new Aes128XtsStorage(baseStorage, key0, key1, sectorSize, true, decrypting), 2, true);
|
||||
}
|
||||
// ReSharper restore UnusedParameter.Local
|
||||
|
||||
|
@ -181,7 +182,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
return new CachedStorage(aesStorage, 0x4000, 4, true);
|
||||
}
|
||||
|
||||
private IStorage OpenAesCtrExStorage(IStorage baseStorage, int index)
|
||||
private IStorage OpenAesCtrExStorage(IStorage baseStorage, int index, bool decrypting)
|
||||
{
|
||||
NcaFsHeader fsHeader = Header.GetFsHeader(index);
|
||||
NcaFsPatchInfo info = fsHeader.GetPatchInfo();
|
||||
|
@ -197,7 +198,20 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
byte[] counter = Aes128CtrStorage.CreateCounter(fsHeader.Counter, bktrOffset + sectionOffset);
|
||||
byte[] counterEx = Aes128CtrStorage.CreateCounter(fsHeader.Counter, sectionOffset);
|
||||
|
||||
IStorage bucketTreeData = new CachedStorage(new Aes128CtrStorage(baseStorage.Slice(bktrOffset, bktrSize), key, counter, true), 4, true);
|
||||
IStorage bucketTreeData;
|
||||
IStorage outputBucketTreeData;
|
||||
|
||||
if (decrypting)
|
||||
{
|
||||
bucketTreeData = new CachedStorage(new Aes128CtrStorage(baseStorage.Slice(bktrOffset, bktrSize), key, counter, true), 4, true);
|
||||
outputBucketTreeData = bucketTreeData;
|
||||
}
|
||||
else
|
||||
{
|
||||
bucketTreeData = baseStorage.Slice(bktrOffset, bktrSize);
|
||||
outputBucketTreeData = new CachedStorage(new Aes128CtrStorage(baseStorage.Slice(bktrOffset, bktrSize), key, counter, true), 4, true);
|
||||
}
|
||||
|
||||
var encryptionBucketTreeData = new SubStorage(bucketTreeData,
|
||||
info.EncryptionTreeOffset - bktrOffset, sectionSize - info.EncryptionTreeOffset);
|
||||
|
||||
|
@ -214,17 +228,25 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
IStorage decStorage = new Aes128CtrExStorage(baseStorage.Slice(0, dataSize), tableNodeStorage,
|
||||
tableEntryStorage, treeHeader.EntryCount, key, counterEx, true);
|
||||
|
||||
return new ConcatenationStorage(new[] { decStorage, bucketTreeData }, true);
|
||||
return new ConcatenationStorage(new[] { decStorage, outputBucketTreeData }, true);
|
||||
}
|
||||
|
||||
public IStorage OpenRawStorage(int index)
|
||||
public IStorage OpenRawStorage(int index, bool openEncrypted)
|
||||
{
|
||||
IStorage encryptedStorage = OpenSectionStorage(index);
|
||||
IStorage decryptedStorage = OpenDecryptedStorage(encryptedStorage, index);
|
||||
IStorage storage = OpenSectionStorage(index);
|
||||
|
||||
if (IsEncrypted == openEncrypted)
|
||||
{
|
||||
return storage;
|
||||
}
|
||||
|
||||
IStorage decryptedStorage = OpenDecryptedStorage(storage, index, !openEncrypted);
|
||||
|
||||
return decryptedStorage;
|
||||
}
|
||||
|
||||
public IStorage OpenRawStorage(int index) => OpenRawStorage(index, false);
|
||||
|
||||
public IStorage OpenRawStorageWithPatch(Nca patchNca, int index)
|
||||
{
|
||||
IStorage patchStorage = patchNca.OpenRawStorage(index);
|
||||
|
@ -357,18 +379,24 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
return OpenStorageWithPatch(patchNca, GetSectionIndexFromType(type), integrityCheckLevel);
|
||||
}
|
||||
|
||||
public IStorage OpenDecryptedNca() => TransformNca(true);
|
||||
public IStorage OpenEncryptedNca() => OpenFullNca(true);
|
||||
public IStorage OpenDecryptedNca() => OpenFullNca(false);
|
||||
|
||||
public IStorage TransformNca(bool decrypting)
|
||||
public IStorage OpenFullNca(bool openEncrypted)
|
||||
{
|
||||
if (openEncrypted == IsEncrypted)
|
||||
{
|
||||
return BaseStorage;
|
||||
}
|
||||
|
||||
var builder = new ConcatenationStorageBuilder();
|
||||
builder.Add(TransformHeaderStorage(decrypting), 0);
|
||||
builder.Add(OpenHeaderStorage(openEncrypted), 0);
|
||||
|
||||
for (int i = 0; i < NcaHeader.SectionCount; i++)
|
||||
{
|
||||
if (Header.IsSectionEnabled(i))
|
||||
{
|
||||
builder.Add(OpenRawStorage(i), Header.GetSectionStartOffset(i));
|
||||
builder.Add(OpenRawStorage(i, openEncrypted), Header.GetSectionStartOffset(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -505,9 +533,9 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
return new HierarchicalIntegrityVerificationStorage(initInfo, integrityCheckLevel, leaveOpen);
|
||||
}
|
||||
|
||||
public IStorage OpenDecryptedHeaderStorage() => TransformHeaderStorage(true);
|
||||
public IStorage OpenDecryptedHeaderStorage() => OpenHeaderStorage(true);
|
||||
|
||||
public IStorage TransformHeaderStorage(bool decrypting)
|
||||
public IStorage OpenHeaderStorage(bool openEncrypted)
|
||||
{
|
||||
long firstSectionOffset = long.MaxValue;
|
||||
bool hasEnabledSection = false;
|
||||
|
@ -523,36 +551,38 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
}
|
||||
|
||||
long headerSize = hasEnabledSection ? firstSectionOffset : NcaHeader.HeaderSize;
|
||||
IStorage rawHeaderStorage = BaseStorage.Slice(0, headerSize);
|
||||
|
||||
IStorage header = new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, headerSize), Keyset.HeaderKey, NcaHeader.HeaderSectorSize, true, decrypting), 1, true);
|
||||
|
||||
int version = ReadHeaderVersion(header);
|
||||
if (openEncrypted == IsEncrypted)
|
||||
return rawHeaderStorage;
|
||||
|
||||
if (version == 2)
|
||||
IStorage header;
|
||||
|
||||
switch (Header.Version)
|
||||
{
|
||||
header = OpenNca2Header(headerSize);
|
||||
case 3:
|
||||
header = new CachedStorage(new Aes128XtsStorage(rawHeaderStorage, Keyset.HeaderKey, NcaHeader.HeaderSectorSize, true, !openEncrypted), 1, true);
|
||||
break;
|
||||
case 2:
|
||||
header = OpenNca2Header(headerSize, !openEncrypted);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported NCA version");
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private int ReadHeaderVersion(IStorage header)
|
||||
{
|
||||
Span<byte> buf = stackalloc byte[1];
|
||||
header.Read(0x203, buf).Log();
|
||||
return buf[0] - '0';
|
||||
}
|
||||
|
||||
private IStorage OpenNca2Header(long size)
|
||||
private IStorage OpenNca2Header(long size, bool decrypting)
|
||||
{
|
||||
const int sectorSize = NcaHeader.HeaderSectorSize;
|
||||
|
||||
var sources = new List<IStorage>();
|
||||
sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), Keyset.HeaderKey, sectorSize, true), 1, true));
|
||||
sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(0, 0x400), Keyset.HeaderKey, sectorSize, true, decrypting), 1, true));
|
||||
|
||||
for (int i = 0x400; i < size; i += sectorSize)
|
||||
{
|
||||
sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, sectorSize), Keyset.HeaderKey, sectorSize, true), 1, true));
|
||||
sources.Add(new CachedStorage(new Aes128XtsStorage(BaseStorage.Slice(i, sectorSize), Keyset.HeaderKey, sectorSize, true, decrypting), 1, true));
|
||||
}
|
||||
|
||||
return new ConcatenationStorage(sources, true);
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace hactoolnet
|
|||
new CliOption("outdir", 1, (o, a) => o.OutDir = a[0]),
|
||||
new CliOption("outfile", 1, (o, a) => o.OutFile = a[0]),
|
||||
new CliOption("plaintext", 1, (o, a) => o.PlaintextOut = a[0]),
|
||||
new CliOption("ciphertext", 1, (o, a) => o.CiphertextOut = a[0]),
|
||||
new CliOption("uncompressed", 1, (o, a) => o.UncompressedOut = a[0]),
|
||||
new CliOption("nspout", 1, (o, a) => o.NspOut = a[0]),
|
||||
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
|
||||
|
@ -207,6 +208,7 @@ namespace hactoolnet
|
|||
sb.AppendLine(" --accesslog <file> Specify the access log file path.");
|
||||
sb.AppendLine("NCA options:");
|
||||
sb.AppendLine(" --plaintext <file> Specify file path for saving a decrypted copy of the NCA.");
|
||||
sb.AppendLine(" --ciphertext <file> Specify file path for saving an encrypted copy of the NCA.");
|
||||
sb.AppendLine(" --header <file> Specify Header file path.");
|
||||
sb.AppendLine(" --section0 <file> Specify Section 0 file path.");
|
||||
sb.AppendLine(" --section1 <file> Specify Section 1 file path.");
|
||||
|
|
|
@ -30,6 +30,7 @@ namespace hactoolnet
|
|||
public string OutDir;
|
||||
public string OutFile;
|
||||
public string PlaintextOut;
|
||||
public string CiphertextOut;
|
||||
public string UncompressedOut;
|
||||
public string SdSeed;
|
||||
public string NspOut;
|
||||
|
|
|
@ -170,6 +170,11 @@ namespace hactoolnet
|
|||
nca.OpenDecryptedNca().WriteAllBytes(ctx.Options.PlaintextOut, ctx.Logger);
|
||||
}
|
||||
|
||||
if (ctx.Options.CiphertextOut != null)
|
||||
{
|
||||
nca.OpenEncryptedNca().WriteAllBytes(ctx.Options.CiphertextOut, ctx.Logger);
|
||||
}
|
||||
|
||||
if (!ctx.Options.ReadBench) ctx.Logger.LogMessage(ncaHolder.Print());
|
||||
|
||||
IStorage OpenStorage(int index)
|
||||
|
|
Loading…
Reference in a new issue