Add ExternalKeySet. Use C# 8.0

This commit is contained in:
Alex Barney 2019-10-02 13:45:58 -05:00
parent d7f3e94577
commit d291500b28
16 changed files with 266 additions and 59 deletions

View file

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace LibHac.Common
@ -6,13 +7,14 @@ namespace LibHac.Common
/// <summary>
/// A generic 128-bit ID value.
/// </summary>
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct Id128 : IEquatable<Id128>, IComparable<Id128>, IComparable
{
public readonly ulong High;
public readonly ulong Low;
public static readonly Id128 InvalidId = new Id128(0, 0);
public static Id128 Zero => default;
public Id128(ulong high, ulong low)
{
@ -28,6 +30,8 @@ namespace LibHac.Common
Low = longs[1];
}
public override string ToString() => AsBytes().ToHexString();
public bool Equals(Id128 other)
{
return High == other.High && Low == other.Low;
@ -48,11 +52,9 @@ namespace LibHac.Common
public int CompareTo(Id128 other)
{
// ReSharper disable ImpureMethodCallOnReadonlyValueField
int highComparison = High.CompareTo(other.High);
if (highComparison != 0) return highComparison;
return Low.CompareTo(other.Low);
// ReSharper restore ImpureMethodCallOnReadonlyValueField
}
public int CompareTo(object obj)
@ -61,7 +63,7 @@ namespace LibHac.Common
return obj is Id128 other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(Id128)}");
}
public void ToBytes(Span<byte> output)
public readonly void ToBytes(Span<byte> output)
{
Span<ulong> longs = MemoryMarshal.Cast<byte, ulong>(output);
@ -69,33 +71,17 @@ namespace LibHac.Common
longs[1] = Low;
}
public static bool operator ==(Id128 left, Id128 right)
public ReadOnlySpan<byte> AsBytes()
{
return left.Equals(right);
return SpanHelpers.AsByteSpan(ref this);
}
public static bool operator !=(Id128 left, Id128 right)
{
return !left.Equals(right);
}
public static bool operator <(Id128 left, Id128 right)
{
return left.CompareTo(right) < 0;
}
public static bool operator ==(Id128 left, Id128 right) => left.Equals(right);
public static bool operator !=(Id128 left, Id128 right) => !left.Equals(right);
public static bool operator >(Id128 left, Id128 right)
{
return left.CompareTo(right) > 0;
}
public static bool operator <=(Id128 left, Id128 right)
{
return left.CompareTo(right) <= 0;
}
public static bool operator >=(Id128 left, Id128 right)
{
return left.CompareTo(right) >= 0;
}
public static bool operator <(Id128 left, Id128 right) => left.CompareTo(right) < 0;
public static bool operator >(Id128 left, Id128 right) => left.CompareTo(right) > 0;
public static bool operator <=(Id128 left, Id128 right) => left.CompareTo(right) <= 0;
public static bool operator >=(Id128 left, Id128 right) => left.CompareTo(right) >= 0;
}
}

View file

@ -0,0 +1,48 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace LibHac.Common
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct Key128 : IEquatable<Key128>
{
private readonly ulong _dummy1;
private readonly ulong _dummy2;
public Span<byte> Value => SpanHelpers.AsByteSpan(ref this);
public Key128(ReadOnlySpan<byte> bytes)
{
ReadOnlySpan<ulong> longs = MemoryMarshal.Cast<byte, ulong>(bytes);
_dummy1 = longs[0];
_dummy2 = longs[1];
}
public override string ToString() => Value.ToHexString();
public override bool Equals(object obj)
{
return obj is Key128 key && Equals(key);
}
public bool Equals(Key128 other)
{
return _dummy1 == other._dummy1 &&
_dummy2 == other._dummy2;
}
public override int GetHashCode()
{
int hashCode = -1653217991;
hashCode = hashCode * -1521134295 + _dummy1.GetHashCode();
hashCode = hashCode * -1521134295 + _dummy2.GetHashCode();
return hashCode;
}
public static bool operator ==(Key128 left, Key128 right) => left.Equals(right);
public static bool operator !=(Key128 left, Key128 right) => !(left == right);
}
}

View file

@ -121,7 +121,7 @@ namespace LibHac.Fs
if (!openMode.HasFlag(OpenMode.AllowAppend))
{
return ResultFs.AllowAppendRequiredForImplicitExtension.Log();
return ResultFs.FileExtensionWithoutOpenModeAllowAppend.Log();
}
}

View file

@ -11,7 +11,9 @@
public static Result InsufficientFreeSpace => new Result(ModuleFs, 30);
public static Result MountNameAlreadyExists => new Result(ModuleFs, 60);
public static Result PartitionNotFound => new Result(ModuleFs, 1001);
public static Result TargetNotFound => new Result(ModuleFs, 1002);
public static Result ExternalKeyNotFound => new Result(ModuleFs, 1004);
public static Result NotImplemented => new Result(ModuleFs, 3001);
public static Result Result3002 => new Result(ModuleFs, 3002);
@ -80,9 +82,11 @@
public static Result InvalidSize => new Result(ModuleFs, 6062);
public static Result NullArgument => new Result(ModuleFs, 6063);
public static Result InvalidMountName => new Result(ModuleFs, 6065);
public static Result ExtensionSizeTooLarge => new Result(ModuleFs, 6066);
public static Result ExtensionSizeInvalid => new Result(ModuleFs, 6067);
public static Result InvalidOpenModeOperation => new Result(ModuleFs, 6200);
public static Result AllowAppendRequiredForImplicitExtension => new Result(ModuleFs, 6201);
public static Result FileExtensionWithoutOpenModeAllowAppend => new Result(ModuleFs, 6201);
public static Result InvalidOpenModeForRead => new Result(ModuleFs, 6202);
public static Result InvalidOpenModeForWrite => new Result(ModuleFs, 6203);
@ -105,11 +109,16 @@
public static Result UnsupportedOperationInPartitionFileSetSize => new Result(ModuleFs, 6376);
public static Result PermissionDenied => new Result(ModuleFs, 6400);
public static Result ExternalKeyAlreadyRegistered => new Result(ModuleFs, 6452);
public static Result WriteStateUnflushed => new Result(ModuleFs, 6454);
public static Result WritableFileOpen => new Result(ModuleFs, 6457);
public static Result MappingTableFull => new Result(ModuleFs, 6706);
public static Result AllocationTableInsufficientFreeBlocks => new Result(ModuleFs, 6707);
public static Result OpenCountLimit => new Result(ModuleFs, 6709);
public static Result RemapStorageMapFull => new Result(ModuleFs, 6811);
public static Result Result6902 => new Result(ModuleFs, 6902);
public static Result MountNameNotFound => new Result(ModuleFs, 6905);

View file

@ -1,9 +1,11 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Fs
{
[DebuggerDisplay("{DebugDisplay(),nq}")]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct RightsId : IEquatable<RightsId>, IComparable<RightsId>, IComparable
{
@ -19,12 +21,21 @@ namespace LibHac.Fs
Id = new Id128(uid);
}
public override string ToString() => Id.ToString();
public string DebugDisplay()
{
ReadOnlySpan<byte> highBytes = AsBytes().Slice(0, 8);
ReadOnlySpan<byte> lowBytes = AsBytes().Slice(8, 8);
return $"{highBytes.ToHexString()} {lowBytes.ToHexString()}";
}
public bool Equals(RightsId other) => Id == other.Id;
public override bool Equals(object obj) => obj is RightsId other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public int CompareTo(RightsId other) => Id.CompareTo(other.Id);
public int CompareTo(object obj)
@ -33,9 +44,13 @@ namespace LibHac.Fs
return obj is RightsId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(RightsId)}");
}
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public void ToBytes(Span<byte> output) => Id.ToBytes(output);
public ReadOnlySpan<byte> AsBytes()
{
return SpanHelpers.AsByteSpan(ref this);
}
public static bool operator ==(RightsId left, RightsId right) => left.Equals(right);
public static bool operator !=(RightsId left, RightsId right) => !left.Equals(right);

View file

@ -7,7 +7,7 @@ namespace LibHac.Fs
{
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId)
{
return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.EmptyId);
return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.Zero);
}
public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName,
@ -72,19 +72,19 @@ namespace LibHac.Fs
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size,
long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.EmptyId, ownerId, size, journalSize, flags);
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size,
long journalSize, uint flags)
{
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.EmptyId, 0, size, journalSize, flags);
return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, 0, size, journalSize, flags);
}
public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId,
ulong ownerId, long size, long journalSize, uint flags)
{
return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.EmptyId, ownerId, size, journalSize, flags);
return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.Zero, ownerId, size, journalSize, flags);
}
public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId)

View file

@ -1,13 +1,15 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Fs
{
[DebuggerDisplay("{ToString(),nq}")]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct UserId : IEquatable<UserId>, IComparable<UserId>, IComparable
{
public static readonly UserId EmptyId = new UserId(0, 0);
public static UserId Zero => default;
public readonly Id128 Id;
@ -21,12 +23,16 @@ namespace LibHac.Fs
Id = new Id128(uid);
}
public override string ToString()
{
return $"0x{Id.High:x8}{Id.Low:x8}";
}
public bool Equals(UserId other) => Id == other.Id;
public override bool Equals(object obj) => obj is UserId other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public int CompareTo(UserId other) => Id.CompareTo(other.Id);
public int CompareTo(object obj)
@ -35,9 +41,13 @@ namespace LibHac.Fs
return obj is UserId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(UserId)}");
}
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public void ToBytes(Span<byte> output) => Id.ToBytes(output);
public ReadOnlySpan<byte> AsBytes()
{
return SpanHelpers.AsByteSpan(ref this);
}
public static bool operator ==(UserId left, UserId right) => left.Equals(right);
public static bool operator !=(UserId left, UserId right) => !left.Equals(right);

View file

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using LibHac.Fs;
using LibHac.Spl;
namespace LibHac.FsService
{
public class ExternalKeySet
{
private readonly object _locker = new object();
private Dictionary<RightsId, AccessKey> ExternalKeys { get; set; } = new Dictionary<RightsId, AccessKey>();
public Result Add(RightsId rightsId, AccessKey key)
{
lock (_locker)
{
if (ExternalKeys.TryGetValue(rightsId, out AccessKey existingKey))
{
if (key == existingKey)
{
return Result.Success;
}
return ResultFs.ExternalKeyAlreadyRegistered.Log();
}
ExternalKeys.Add(rightsId, key);
}
return Result.Success;
}
public Result Get(RightsId rightsId, out AccessKey key)
{
lock (_locker)
{
if (ExternalKeys.TryGetValue(rightsId, out key))
{
return Result.Success;
}
return ResultFs.ExternalKeyNotFound.Log();
}
}
public bool Contains(RightsId rightsId)
{
lock (_locker)
{
return ExternalKeys.ContainsKey(rightsId);
}
}
public bool Remove(RightsId rightsId)
{
lock (_locker)
{
return ExternalKeys.Remove(rightsId);
}
}
public void Clear()
{
lock (_locker)
{
ExternalKeys.Clear();
}
}
public List<(RightsId rightsId, AccessKey key)> ToList()
{
lock (_locker)
{
var list = new List<(RightsId rightsId, AccessKey key)>(ExternalKeys.Count);
foreach (KeyValuePair<RightsId, AccessKey> kvp in ExternalKeys)
{
list.Add((kvp.Key, kvp.Value));
}
return list;
}
}
public void TrimExcess() => TrimExcess(0);
public void TrimExcess(int capacity)
{
lock (_locker)
{
int newCapacity = Math.Max(capacity, ExternalKeys.Count);
#if NETCOREAPP
ExternalKeys.TrimExcess(newCapacity);
#else
var trimmedDict = new Dictionary<RightsId, AccessKey>(newCapacity);
foreach (KeyValuePair<RightsId, AccessKey> kvp in ExternalKeys)
{
trimmedDict.Add(kvp.Key, kvp.Value);
}
ExternalKeys = trimmedDict;
#endif
}
}
}
}

View file

@ -147,7 +147,7 @@ namespace LibHac.FsService
private Result OpenSaveDataFileSystemImpl(out IFileSystem fileSystem, out ulong saveDataId,
SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData)
{
bool hasFixedId = attribute.SaveDataId != 0 && attribute.UserId.Id == Id128.InvalidId;
bool hasFixedId = attribute.SaveDataId != 0 && attribute.UserId == UserId.Zero;
if (hasFixedId)
{

View file

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using LibHac.Fs;
using LibHac.FsSystem.RomFs;
using LibHac.Spl;
namespace LibHac.FsSystem.NcaUtils
{
@ -47,9 +48,9 @@ namespace LibHac.FsSystem.NcaUtils
int keyRevision = Util.GetMasterKeyRevision(Header.KeyGeneration);
byte[] titleKek = Keyset.TitleKeks[keyRevision];
if (!Keyset.TitleKeys.TryGetValue(Header.RightsId.ToArray(), out byte[] encryptedKey))
if (Keyset.ExternalKeySet.Get(new RightsId(Header.RightsId), out AccessKey accessKey).IsFailure())
{
throw new MissingKeyException("Missing NCA title key.", Header.RightsId.ToHexString(), KeyType.Title);
throw new MissingKeyException("Missing NCA title key.", Header.RightsId.ToString(), KeyType.Title);
}
if (titleKek.IsEmpty())
@ -58,6 +59,7 @@ namespace LibHac.FsSystem.NcaUtils
throw new MissingKeyException("Unable to decrypt title key.", keyName, KeyType.Common);
}
byte[] encryptedKey = accessKey.Value.ToArray();
var decryptedKey = new byte[Crypto.Aes128Size];
Crypto.DecryptEcb(titleKek, encryptedKey, decryptedKey, Crypto.Aes128Size);
@ -89,7 +91,7 @@ namespace LibHac.FsSystem.NcaUtils
if (Header.HasRightsId)
{
return Keyset.TitleKeys.ContainsKey(Header.RightsId.ToArray()) &&
return Keyset.ExternalKeySet.Contains(new RightsId(Header.RightsId)) &&
!Keyset.TitleKeks[keyRevision].IsEmpty();
}

View file

@ -4,7 +4,10 @@ using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using LibHac.Fs;
using LibHac.FsService;
using LibHac.FsSystem;
using LibHac.Spl;
namespace LibHac
{
@ -129,7 +132,7 @@ namespace LibHac
0x49, 0x50, 0x95, 0x8C, 0x55, 0x80, 0x7E, 0x39, 0xB1, 0x48, 0x05, 0x1E, 0x21, 0xC7, 0x24, 0x4F
};
public Dictionary<byte[], byte[]> TitleKeys { get; } = new Dictionary<byte[], byte[]>(new ByteArray128BitComparer());
public ExternalKeySet ExternalKeySet { get; } = new ExternalKeySet();
public void SetSdSeed(byte[] sdseed)
{
@ -402,6 +405,7 @@ namespace LibHac
if (filename != null) ReadMainKeys(keyset, filename, AllKeyDict, logger);
if (consoleKeysFilename != null) ReadMainKeys(keyset, consoleKeysFilename, AllKeyDict, logger);
if (titleKeysFilename != null) ReadTitleKeys(keyset, titleKeysFilename, logger);
keyset.ExternalKeySet.TrimExcess();
keyset.DeriveKeys(logger);
}
@ -507,7 +511,7 @@ namespace LibHac
continue;
}
keyset.TitleKeys[rightsId] = titleKey;
keyset.ExternalKeySet.Add(new RightsId(rightsId), new AccessKey(titleKey)).ThrowIfFailure();
}
}
}
@ -557,9 +561,9 @@ namespace LibHac
{
var sb = new StringBuilder();
foreach (KeyValuePair<byte[], byte[]> kv in keyset.TitleKeys.OrderBy(x => x.Key.ToHexString()))
foreach ((RightsId rightsId, AccessKey key) kv in keyset.ExternalKeySet.ToList().OrderBy(x => x.rightsId.ToString()))
{
string line = $"{kv.Key.ToHexString()} = {kv.Value.ToHexString()}";
string line = $"{kv.rightsId} = {kv.key}";
sb.AppendLine(line);
}

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>netcoreapp2.1;netstandard2.0;net46</TargetFrameworks>
<LangVersion>7.3</LangVersion>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup>

View file

@ -1,9 +1,11 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Ncm
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct ContentId : IEquatable<ContentId>, IComparable<ContentId>, IComparable
{
@ -19,12 +21,13 @@ namespace LibHac.Ncm
Id = new Id128(uid);
}
public override string ToString() => Id.ToString();
public bool Equals(ContentId other) => Id == other.Id;
public override bool Equals(object obj) => obj is ContentId other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public int CompareTo(ContentId other) => Id.CompareTo(other.Id);
public int CompareTo(object obj)
@ -33,9 +36,13 @@ namespace LibHac.Ncm
return obj is ContentId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(ContentId)}");
}
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public void ToBytes(Span<byte> output) => Id.ToBytes(output);
public ReadOnlySpan<byte> AsBytes()
{
return SpanHelpers.AsByteSpan(ref this);
}
public static bool operator ==(ContentId left, ContentId right) => left.Equals(right);
public static bool operator !=(ContentId left, ContentId right) => !left.Equals(right);

View file

@ -1,9 +1,11 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Ncm
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct PlaceHolderId : IEquatable<PlaceHolderId>, IComparable<PlaceHolderId>, IComparable
{
@ -19,12 +21,13 @@ namespace LibHac.Ncm
Id = new Id128(uid);
}
public override string ToString() => Id.ToString();
public bool Equals(PlaceHolderId other) => Id == other.Id;
public override bool Equals(object obj) => obj is PlaceHolderId other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public int CompareTo(PlaceHolderId other) => Id.CompareTo(other.Id);
public int CompareTo(object obj)
@ -33,9 +36,13 @@ namespace LibHac.Ncm
return obj is PlaceHolderId other ? CompareTo(other) : throw new ArgumentException($"Object must be of type {nameof(PlaceHolderId)}");
}
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public void ToBytes(Span<byte> output) => Id.ToBytes(output);
public ReadOnlySpan<byte> AsBytes()
{
return SpanHelpers.AsByteSpan(ref this);
}
public static bool operator ==(PlaceHolderId left, PlaceHolderId right) => left.Equals(right);
public static bool operator !=(PlaceHolderId left, PlaceHolderId right) => !left.Equals(right);

View file

@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
@ -8,13 +7,23 @@ namespace LibHac.Spl
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
public struct AccessKey
public struct AccessKey : IEquatable<AccessKey>
{
private long _dummy1;
private long _dummy2;
private readonly Key128 Key;
public Span<byte> Key => SpanHelpers.CreateSpan(ref Unsafe.As<long, byte>(ref _dummy1), 0x10);
public ReadOnlySpan<byte> Value => SpanHelpers.AsByteSpan(ref this);
public override string ToString() => Key.ToHexString();
public AccessKey(ReadOnlySpan<byte> bytes)
{
Key = new Key128(bytes);
}
public override string ToString() => Key.ToString();
public override bool Equals(object obj) => obj is AccessKey key && Equals(key);
public bool Equals(AccessKey other) => Key.Equals(other.Key);
public override int GetHashCode() => Key.GetHashCode();
public static bool operator ==(AccessKey left, AccessKey right) => left.Equals(right);
public static bool operator !=(AccessKey left, AccessKey right) => !(left == right);
}
}

View file

@ -354,7 +354,9 @@ namespace LibHac
public static string ToHexString(this byte[] bytes) => ToHexString(bytes.AsSpan());
public static string ToHexString(this Span<byte> bytes)
public static string ToHexString(this Span<byte> bytes) => ToHexString((ReadOnlySpan<byte>)bytes);
public static string ToHexString(this ReadOnlySpan<byte> bytes)
{
uint[] lookup32 = Lookup32;
var result = new char[bytes.Length * 2];