Allow user to supply a titlekey on the command-line when processing NCAs (#290)

* feat: Enable user to supply a single titlekey when processing NCAs

* chore: update README

* feat: add `basetitlekey` support

* chore: README

* fix: check hasnt already been added via title.keys

* fix: Update logic so commandline supplied keys override existing keys in file

* hactoolnet: Parse title keys when reading CLI arguments
This commit is contained in:
[mRg] 2023-11-16 02:22:17 +00:00 committed by GitHub
parent dd478db70a
commit 300fa877e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 1 deletions

View file

@ -55,6 +55,8 @@ NCA options:
--romfsdir <dir> Specify RomFS directory path.
--listromfs List files in RomFS.
--basenca Set Base NCA to use with update partitions.
--basetitlekey Specify single (encrypted) titlekey for the base NCA.
--titlekey Specify single (encrypted) titlekey.
KIP1 options:
--uncompressed <f> Specify file path for saving uncompressed KIP1.
RomFS options:

View file

@ -2,6 +2,7 @@
using System.Globalization;
using System.Linq;
using System.Text;
using LibHac.Util;
namespace hactoolnet;
@ -49,6 +50,8 @@ internal static class CliParser
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]),
new CliOption("basenca", 1, (o, a) => o.BaseNca = a[0]),
new CliOption("basetitlekey", 1, (o, a) => o.BaseTitleKey = ParseTitleKey(o, a[0])),
new CliOption("titlekey", 1, (o, a) => o.TitleKey = ParseTitleKey(o, a[0])),
new CliOption("basefile", 1, (o, a) => o.BaseFile = a[0]),
new CliOption("rootdir", 1, (o, a) => o.RootDir = a[0]),
new CliOption("updatedir", 1, (o, a) => o.UpdateDir = a[0]),
@ -214,6 +217,19 @@ internal static class CliParser
return id;
}
private static byte[] ParseTitleKey(Options options, string input)
{
byte[] key = new byte[32];
if (input.Length != 32 || !StringUtils.TryFromHexString(input, key))
{
options.ParseErrorMessage ??= "TitleKey must be 32 hex characters long";
return default;
}
return key;
}
private static double ParseDouble(Options options, string input)
{
if (!double.TryParse(input, out double value))
@ -276,6 +292,8 @@ internal static class CliParser
sb.AppendLine(" --romfsdir <dir> Specify RomFS directory path.");
sb.AppendLine(" --listromfs List files in RomFS.");
sb.AppendLine(" --basenca Set Base NCA to use with update partitions.");
sb.AppendLine(" --basetitlekey Specify single (encrypted) titlekey for the base NCA.");
sb.AppendLine(" --titlekey Specify single (encrypted) titlekey for the NCA.");
sb.AppendLine("KIP1 options:");
sb.AppendLine(" --uncompressed <f> Specify file path for saving uncompressed KIP1.");
sb.AppendLine("RomFS options:");

View file

@ -62,6 +62,8 @@ internal class Options
public bool BuildHfs;
public bool ExtractIni1;
public ulong TitleId;
public byte[] TitleKey;
public byte[] BaseTitleKey;
public string BenchType;
public double CpuFrequencyGhz;

View file

@ -1,15 +1,19 @@
using System.IO;
using System;
using System.IO;
using System.Text;
using LibHac;
using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Impl;
using LibHac.FsSystem;
using LibHac.Spl;
using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.Npdm;
using LibHac.Util;
using static hactoolnet.Print;
using NcaFsHeader = LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader;
@ -24,6 +28,15 @@ internal static class ProcessNca
var nca = new Nca(ctx.KeySet, file);
Nca baseNca = null;
if (ctx.Options.TitleKey != null && nca.Header.HasRightsId)
{
if (!TryAddTitleKey(ctx.KeySet, ctx.Options.TitleKey, nca.Header.RightsId))
{
ctx.Logger.LogMessage($"Invalid title key \"{ctx.Options.TitleKey}\"");
return;
}
}
var ncaHolder = new NcaHolder { Nca = nca };
if (ctx.Options.HeaderOut != null)
@ -38,6 +51,15 @@ internal static class ProcessNca
{
IStorage baseFile = new LocalStorage(ctx.Options.BaseNca, FileAccess.Read);
baseNca = new Nca(ctx.KeySet, baseFile);
if (ctx.Options.BaseTitleKey != null && baseNca.Header.HasRightsId)
{
if (!TryAddTitleKey(ctx.KeySet, ctx.Options.BaseTitleKey, baseNca.Header.RightsId))
{
ctx.Logger.LogMessage($"Invalid base title key \"{ctx.Options.BaseTitleKey}\"");
return;
}
}
}
for (int i = 0; i < 4; i++)
@ -252,6 +274,20 @@ internal static class ProcessNca
}
}
private static bool TryAddTitleKey(KeySet keySet, ReadOnlySpan<byte> key, ReadOnlySpan<byte> rightsId)
{
if (key.Length != 32)
return false;
var titleKey = new AccessKey(key);
var rId = new RightsId(rightsId);
keySet.ExternalKeySet.Remove(rId);
keySet.ExternalKeySet.Add(rId, titleKey);
return true;
}
private static Validity VerifySignature2(this Nca nca)
{
if (nca.Header.ContentType != NcaContentType.Program) return Validity.Unchecked;