Skip to content

Commit 5864da7

Browse files
authored
Merge pull request #37 from SceneGate/feature/hash-verify-methods
✨ Add API to verify all the hashes and signature
2 parents b43ecce + 10cd8ca commit 5864da7

File tree

5 files changed

+287
-10
lines changed

5 files changed

+287
-10
lines changed

src/Directory.Packages.props

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
<PackageVersion Include="Texim" Version="0.1.0-preview.210" />
77
<PackageVersion Include="System.Data.HashFunction.CRC" Version="2.0.0" />
88
<PackageVersion Include="Portable.BouncyCastle" Version="1.9.0" />
9-
<PackageVersion Include="YamlDotNet" Version="15.1.1" />
9+
<PackageVersion Include="YamlDotNet" Version="15.1.4" />
1010
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
11-
<PackageVersion Include="nunit" Version="4.0.1" />
11+
<PackageVersion Include="nunit" Version="4.1.0" />
1212
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
1313
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
14-
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
14+
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
1515
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
16-
<PackageVersion Include="SonarAnalyzer.CSharp" Version="9.19.0.84025" />
16+
<PackageVersion Include="SonarAnalyzer.CSharp" Version="9.25.0.90414" />
1717
</ItemGroup>
1818
</Project>

src/Ekona.Tests/Containers/Rom/Binary2NitroRomTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ public void DeserializeRomWithKeysHasValidSignatures(string infoPath, string rom
133133

134134
programInfo.DsiInfo.DigestHashesStatus.Should().Be(HashStatus.Valid);
135135
}
136+
137+
programInfo.HasValidHashes().Should().BeTrue();
138+
programInfo.HasValidSignature().Should().BeTrue();
136139
}
137140

138141
[TestCaseSource(nameof(GetFiles))]
@@ -379,6 +382,9 @@ public void ReadWriteThreeWaysGenerateValidHashes(string infoPath, string romPat
379382
programInfo.DsiInfo.DigestHashesStatus.Should().Be(HashStatus.Valid);
380383
}
381384
}
385+
386+
programInfo.HasValidHashes().Should().BeTrue();
387+
programInfo.HasValidSignature().Should().Be(!isDsi);
382388
}
383389
}
384390
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
namespace SceneGate.Ekona.Tests.Containers.Rom;
2+
using System;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
using NUnit.Framework;
8+
using SceneGate.Ekona.Containers.Rom;
9+
using SceneGate.Ekona.Security;
10+
11+
[TestFixture]
12+
internal class ProgramInfoTests
13+
{
14+
[Test]
15+
public void HasValidHashesTrueIfNull()
16+
{
17+
var info = new ProgramInfo();
18+
19+
Assert.That(info.HasValidHashes(), Is.True);
20+
}
21+
22+
[Test]
23+
public void HasValidHashesTrueIfGenerated()
24+
{
25+
var info = new ProgramInfo();
26+
_ = CreateDsHashes(info, HashStatus.Generated);
27+
28+
Assert.That(info.HasValidHashes(), Is.True);
29+
}
30+
31+
[Test]
32+
public void HasValidHashesThrowsIfNotValidated()
33+
{
34+
var info = new ProgramInfo();
35+
_ = CreateDsHashes(info, HashStatus.NotValidated);
36+
37+
Assert.That(() => info.HasValidHashes(), Throws.InvalidOperationException);
38+
}
39+
40+
[Test]
41+
public void HasValidHashesFalseIfAnyInvalid()
42+
{
43+
var info = new ProgramInfo();
44+
HashInfo[] hashInfos = CreateDsHashes(info, HashStatus.Invalid);
45+
46+
foreach (HashInfo hashInfo in hashInfos) {
47+
Assert.That(info.HasValidHashes(), Is.False);
48+
hashInfo.Status = HashStatus.Valid;
49+
}
50+
51+
Assert.That(info.HasValidHashes(), Is.True);
52+
}
53+
54+
[Test]
55+
public void HasValidHashesTrueIfAllValid()
56+
{
57+
var info = new ProgramInfo();
58+
_ = CreateDsHashes(info, HashStatus.Valid);
59+
60+
Assert.That(info.HasValidHashes(), Is.True);
61+
}
62+
63+
[Test]
64+
public void HasValidHashesDsi()
65+
{
66+
var info = new ProgramInfo();
67+
HashInfo[] hashInfos = CreateDsiHashes(info, HashStatus.Invalid);
68+
69+
foreach (HashInfo hashInfo in hashInfos) {
70+
Assert.That(info.HasValidHashes(), Is.False);
71+
hashInfo.Status = HashStatus.Valid;
72+
}
73+
74+
Assert.That(info.HasValidHashes(), Is.False);
75+
76+
info.DsiInfo.DigestHashesStatus = HashStatus.Valid;
77+
Assert.That(info.HasValidHashes(), Is.True);
78+
}
79+
80+
[Test]
81+
public void HasValidHashesDsExtended()
82+
{
83+
var info = new ProgramInfo();
84+
HashInfo[] hashInfos = CreateDsExtendedHashes(info, HashStatus.Invalid);
85+
86+
foreach (HashInfo hashInfo in hashInfos) {
87+
Assert.That(info.HasValidHashes(), Is.False);
88+
hashInfo.Status = HashStatus.Valid;
89+
}
90+
91+
Assert.That(info.HasValidHashes(), Is.True);
92+
}
93+
94+
[Test]
95+
public void HasSignatureForDsIsTrue()
96+
{
97+
var info = new ProgramInfo();
98+
info.UnitCode = DeviceUnitKind.DS;
99+
100+
Assert.That(info.HasValidSignature(), Is.True);
101+
102+
info.Signature = CreateHashInfo(HashStatus.Invalid);
103+
Assert.That(info.HasValidSignature(), Is.True);
104+
}
105+
106+
[Test]
107+
public void HasSignatureForDsi()
108+
{
109+
var info = new ProgramInfo();
110+
info.UnitCode = DeviceUnitKind.DSiExclusive;
111+
112+
// null signature
113+
Assert.That(info.HasValidSignature(), Is.True);
114+
115+
info.Signature = CreateHashInfo(HashStatus.Generated);
116+
Assert.That(info.HasValidSignature(), Is.True);
117+
118+
info.Signature.Status = HashStatus.Invalid;
119+
Assert.That(info.HasValidSignature(), Is.False);
120+
121+
info.Signature.Status = HashStatus.Valid;
122+
Assert.That(info.HasValidSignature(), Is.True);
123+
124+
info.Signature.Status = HashStatus.NotValidated;
125+
Assert.That(() => info.HasValidSignature(), Throws.InvalidOperationException);
126+
}
127+
128+
private static HashInfo[] CreateDsHashes(ProgramInfo info, HashStatus status)
129+
{
130+
info.UnitCode = DeviceUnitKind.DS;
131+
132+
#pragma warning disable S1121 // cool syntax for tests
133+
var hashInfos = new List<HashInfo> {
134+
(info.ChecksumHeader = CreateHashInfo(status)),
135+
(info.ChecksumLogo = CreateHashInfo(status)),
136+
(info.ChecksumSecureArea = CreateHashInfo(status)),
137+
};
138+
#pragma warning restore S1121
139+
140+
return hashInfos.ToArray();
141+
}
142+
143+
private static HashInfo[] CreateDsExtendedHashes(ProgramInfo info, HashStatus status)
144+
{
145+
info.UnitCode = DeviceUnitKind.DS;
146+
147+
info.ProgramFeatures = DsiRomFeatures.NitroProgramSigned | DsiRomFeatures.NitroBannerSigned;
148+
149+
#pragma warning disable S1121 // cool syntax for tests
150+
var hashInfos = new List<HashInfo> {
151+
(info.ChecksumHeader = CreateHashInfo(status)),
152+
(info.ChecksumLogo = CreateHashInfo(status)),
153+
(info.ChecksumSecureArea = CreateHashInfo(status)),
154+
155+
(info.BannerMac = CreateHashInfo(status)),
156+
157+
(info.NitroProgramMac = CreateHashInfo(status)),
158+
(info.NitroOverlaysMac = CreateHashInfo(status)),
159+
};
160+
#pragma warning restore S1121
161+
162+
return hashInfos.ToArray();
163+
}
164+
165+
private static HashInfo[] CreateDsiHashes(ProgramInfo info, HashStatus status)
166+
{
167+
info.UnitCode = DeviceUnitKind.DSiExclusive;
168+
info.DsiInfo ??= new DsiProgramInfo();
169+
170+
info.DsiInfo.DigestHashesStatus = status;
171+
172+
#pragma warning disable S1121 // cool syntax for tests
173+
var hashInfos = new List<HashInfo> {
174+
(info.ChecksumHeader = CreateHashInfo(status)),
175+
(info.ChecksumLogo = CreateHashInfo(status)),
176+
(info.ChecksumSecureArea = CreateHashInfo(status)),
177+
178+
(info.BannerMac = CreateHashInfo(status)),
179+
180+
(info.DsiInfo.Arm9SecureMac = CreateHashInfo(status)),
181+
(info.DsiInfo.Arm7Mac = CreateHashInfo(status)),
182+
(info.DsiInfo.DigestMain = CreateHashInfo(status)),
183+
(info.DsiInfo.Arm9iMac = CreateHashInfo(status)),
184+
(info.DsiInfo.Arm7iMac = CreateHashInfo(status)),
185+
(info.DsiInfo.Arm9Mac = CreateHashInfo(status)),
186+
};
187+
#pragma warning restore S1121
188+
189+
return hashInfos.ToArray();
190+
}
191+
192+
private static HashInfo CreateHashInfo(HashStatus status)
193+
{
194+
return new HashInfo("TestAlgo", new byte[] { 0x42 }) {
195+
Status = status,
196+
};
197+
}
198+
}

src/Ekona.Tests/Ekona.Tests.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
<PackageReference Include="nunit" />
2020
<PackageReference Include="NUnit3TestAdapter" />
2121
<PackageReference Include="Microsoft.NET.Test.Sdk" />
22-
<PackageReference Include="coverlet.collector" />
22+
<PackageReference Include="coverlet.collector">
23+
<PrivateAssets>all</PrivateAssets>
24+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
25+
</PackageReference>
2326
</ItemGroup>
2427

2528
<ItemGroup>

src/Ekona/Containers/Rom/ProgramInfo.cs

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright(c) 2021 SceneGate
1+
// Copyright(c) 2021 SceneGate
22
//
33
// Permission is hereby granted, free of charge, to any person obtaining a copy
44
// of this software and associated documentation files (the "Software"), to deal
@@ -17,12 +17,13 @@
1717
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1818
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1919
// SOFTWARE.
20-
using System.Collections.ObjectModel;
21-
using SceneGate.Ekona.Security;
22-
using Yarhl.FileFormat;
23-
2420
namespace SceneGate.Ekona.Containers.Rom
2521
{
22+
using System;
23+
using System.Collections.ObjectModel;
24+
using SceneGate.Ekona.Security;
25+
using Yarhl.FileFormat;
26+
2627
/// <summary>
2728
/// Information about the content of the program.
2829
/// </summary>
@@ -240,5 +241,74 @@ public class ProgramInfo : IFormat
240241
/// Gets or sets the collection of information of overlays for ARM-7.
241242
/// </summary>
242243
public Collection<OverlayInfo> Overlays7Info { get; set; } = new Collection<OverlayInfo>();
244+
245+
/// <summary>
246+
/// Gets a value indicating if all the content hashes are valid.
247+
/// </summary>
248+
/// <returns>Value indicating whether the hashes are valid.</returns>
249+
/// <remarks>It takes into account hashes only available in DS or DSi games.</remarks>
250+
public bool HasValidHashes()
251+
{
252+
// Standard DS header checksums (CRC)
253+
bool isValid = IsValidHash(ChecksumHeader)
254+
&& IsValidHash(ChecksumLogo)
255+
&& IsValidHash(ChecksumSecureArea);
256+
257+
bool isDsi = UnitCode != DeviceUnitKind.DS;
258+
259+
// HMACs from DS games after DSi release
260+
if (isDsi || ProgramFeatures.HasFlag(DsiRomFeatures.NitroBannerSigned)) {
261+
isValid = isValid && IsValidHash(BannerMac);
262+
}
263+
264+
if (ProgramFeatures.HasFlag(DsiRomFeatures.NitroProgramSigned)) {
265+
isValid = isValid && IsValidHash(NitroProgramMac);
266+
isValid = isValid && IsValidHash(NitroOverlaysMac);
267+
}
268+
269+
// HMACs from DSi and DSi enhanced games
270+
if (isDsi) {
271+
isValid = isValid && IsValidHash(DsiInfo?.Arm9SecureMac);
272+
isValid = isValid && IsValidHash(DsiInfo?.Arm7Mac);
273+
isValid = isValid && IsValidHash(DsiInfo?.DigestMain);
274+
isValid = isValid && IsValidHash(DsiInfo?.Arm9iMac);
275+
isValid = isValid && IsValidHash(DsiInfo?.Arm7iMac);
276+
isValid = isValid && IsValidHash(DsiInfo?.Arm9Mac);
277+
278+
isValid = isValid && IsValidHash(DsiInfo?.DigestHashesStatus ?? HashStatus.Valid);
279+
}
280+
281+
return isValid;
282+
}
283+
284+
/// <summary>
285+
/// Gets a value indicating if the content signature is valid.
286+
/// </summary>
287+
/// <returns>Value indicating whether the signature is valid.</returns>
288+
/// <remarks>In the case of DS games it always returns true.</remarks>
289+
public bool HasValidSignature()
290+
{
291+
return (UnitCode is DeviceUnitKind.DS) || IsValidHash(Signature);
292+
}
293+
294+
private static bool IsValidHash(HashInfo hashInfo)
295+
{
296+
if (hashInfo is null) {
297+
return true;
298+
}
299+
300+
return IsValidHash(hashInfo.Status);
301+
}
302+
303+
private static bool IsValidHash(HashStatus status)
304+
{
305+
return status switch {
306+
HashStatus.Valid => true,
307+
HashStatus.Generated => true,
308+
HashStatus.Invalid => false,
309+
HashStatus.NotValidated => throw new InvalidOperationException("Hash not validated yet"),
310+
_ => throw new NotSupportedException("Unsupported hash status"),
311+
};
312+
}
243313
}
244314
}

0 commit comments

Comments
 (0)