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. --romfsdir <dir> Specify RomFS directory path.
--listromfs List files in RomFS. --listromfs List files in RomFS.
--basenca Set Base NCA to use with update partitions. --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: KIP1 options:
--uncompressed <f> Specify file path for saving uncompressed KIP1. --uncompressed <f> Specify file path for saving uncompressed KIP1.
RomFS options: RomFS options:

View file

@ -2,6 +2,7 @@
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using LibHac.Util;
namespace hactoolnet; namespace hactoolnet;
@ -49,6 +50,8 @@ internal static class CliParser
new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]), new CliOption("sdseed", 1, (o, a) => o.SdSeed = a[0]),
new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]), new CliOption("sdpath", 1, (o, a) => o.SdPath = a[0]),
new CliOption("basenca", 1, (o, a) => o.BaseNca = 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("basefile", 1, (o, a) => o.BaseFile = a[0]),
new CliOption("rootdir", 1, (o, a) => o.RootDir = a[0]), new CliOption("rootdir", 1, (o, a) => o.RootDir = a[0]),
new CliOption("updatedir", 1, (o, a) => o.UpdateDir = a[0]), new CliOption("updatedir", 1, (o, a) => o.UpdateDir = a[0]),
@ -214,6 +217,19 @@ internal static class CliParser
return id; 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) private static double ParseDouble(Options options, string input)
{ {
if (!double.TryParse(input, out double value)) 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(" --romfsdir <dir> Specify RomFS directory path.");
sb.AppendLine(" --listromfs List files in RomFS."); sb.AppendLine(" --listromfs List files in RomFS.");
sb.AppendLine(" --basenca Set Base NCA to use with update partitions."); 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("KIP1 options:");
sb.AppendLine(" --uncompressed <f> Specify file path for saving uncompressed KIP1."); sb.AppendLine(" --uncompressed <f> Specify file path for saving uncompressed KIP1.");
sb.AppendLine("RomFS options:"); sb.AppendLine("RomFS options:");

View file

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

View file

@ -1,15 +1,19 @@
using System.IO; using System;
using System.IO;
using System.Text; using System.Text;
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Common.Keys;
using LibHac.Fs; using LibHac.Fs;
using LibHac.Fs.Fsa; using LibHac.Fs.Fsa;
using LibHac.Fs.Impl; using LibHac.Fs.Impl;
using LibHac.FsSystem; using LibHac.FsSystem;
using LibHac.Spl;
using LibHac.Tools.Fs; using LibHac.Tools.Fs;
using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem;
using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.FsSystem.NcaUtils;
using LibHac.Tools.Npdm; using LibHac.Tools.Npdm;
using LibHac.Util;
using static hactoolnet.Print; using static hactoolnet.Print;
using NcaFsHeader = LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader; using NcaFsHeader = LibHac.Tools.FsSystem.NcaUtils.NcaFsHeader;
@ -24,6 +28,15 @@ internal static class ProcessNca
var nca = new Nca(ctx.KeySet, file); var nca = new Nca(ctx.KeySet, file);
Nca baseNca = null; 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 }; var ncaHolder = new NcaHolder { Nca = nca };
if (ctx.Options.HeaderOut != null) if (ctx.Options.HeaderOut != null)
@ -38,6 +51,15 @@ internal static class ProcessNca
{ {
IStorage baseFile = new LocalStorage(ctx.Options.BaseNca, FileAccess.Read); IStorage baseFile = new LocalStorage(ctx.Options.BaseNca, FileAccess.Read);
baseNca = new Nca(ctx.KeySet, baseFile); 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++) 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) private static Validity VerifySignature2(this Nca nca)
{ {
if (nca.Header.ContentType != NcaContentType.Program) return Validity.Unchecked; if (nca.Header.ContentType != NcaContentType.Program) return Validity.Unchecked;