Skip to content

Commit 0a9aaff

Browse files
authored
Merge pull request #7 from maxraab/mr-add-support-for-hashed-signatures
Add support for verifying hashed signatures
2 parents 7df7d87 + eccc081 commit 0a9aaff

File tree

7 files changed

+120
-19
lines changed

7 files changed

+120
-19
lines changed

Minisign.Net.Tests/BaseTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ public void Sign2Test()
6767
File.Delete(signedFile);
6868
}
6969

70+
[Fact]
71+
public void ValidateLegacySignature()
72+
{
73+
var file = Path.Combine("Data", "testfile.jpg");
74+
var fileBinary = File.ReadAllBytes(file);
75+
76+
var minisignSignature = Core.LoadSignatureFromFile(Path.Combine("Data", "test.jpg.minisig"));
77+
var minisignPublicKey = Core.LoadPublicKeyFromFile(Path.Combine("Data", "test.pub"));
78+
79+
Assert.True(Core.ValidateSignature(file, minisignSignature, minisignPublicKey));
80+
}
81+
82+
[Fact]
83+
public void ValidateHashedSignature()
84+
{
85+
var file = Path.Combine("Data", "testfile.jpg");
86+
var fileBinary = File.ReadAllBytes(file);
87+
88+
var minisignSignature = Core.LoadSignatureFromFile(Path.Combine("Data", "test.jpg.minisig-hashed"));
89+
var minisignPublicKey = Core.LoadPublicKeyFromFile(Path.Combine("Data", "test2.pub"));
90+
91+
Assert.True(Core.ValidateSignature(file, minisignSignature, minisignPublicKey));
92+
}
7093

7194
[Fact]
7295
public void LoadSignatureFromStringTest()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
untrusted comment: signature from minisign secret key
2+
RURkEyeJ3BlJXNvdwel3JozOQ7kDi59j2qqyCo0lqBLL54h8ThExW6T07L2rpO+L4DRTOiiDU+MRd26EZMqcFBUoslWhgvEUpgQ=
3+
trusted comment: timestamp:1763131221 file:testfile.jpg hashed
4+
0SOzbBbowMWp1KAfhLw7P8TtrdTUAflySfHvGTlcoMUedxRW2J3FJ7fF6TSAU55yxsKNjHDUKQocr9YK2IPjAw==

Minisign.Net.Tests/Data/test2.key

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
untrusted comment: minisign encrypted secret key
2+
RWRTY0IyU6NgdxALuM7TmqLur9Vyow0JO/nq3K4EEgfMzAjsIxIAAAACAAAAAAAAAEAAAAAABSc/wG1tSf1x+XCTAwEImd1ajn3WFnsXo7vaQcV3XWVedffbKLRi5zGI0SdDcICtFgg2FS/gaVi6MAul0MhpD9JzEvJXblEKNQ1xJThMxoIl9loWfIOFFVTG41s5C3WRU4X0xA3y0vQ=

Minisign.Net.Tests/Data/test2.pub

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
untrusted comment: minisign public key 5C4919DC89271364
2+
RWRkEyeJ3BlJXHYzPYxOBLrCP2/b9hBN7tExRifl7KMX9EQCKuPYt9zR

Minisign.Net.Tests/Minisign.Net.Tests.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@
1919
<Content Include="Data\test.jpg.minisig">
2020
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2121
</Content>
22+
<Content Include="Data\test.jpg.minisig-hashed">
23+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
24+
</Content>
2225
<Content Include="Data\test.key">
2326
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2427
</Content>
2528
<Content Include="Data\test.pub">
2629
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2730
</Content>
31+
<Content Include="Data\test2.key">
32+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
33+
</Content>
34+
<Content Include="Data\test2.pub">
35+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
36+
</Content>
2837
<Content Include="Data\testfile.jpg">
2938
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3039
</Content>

Minisign.Net/Core.cs

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,7 @@ public static MinisignKeyPair GenerateKeyPair(string password, bool writeOutputF
278278
public static bool ValidateSignature(string filePath, MinisignSignature signature, MinisignPublicKey publicKey)
279279
{
280280
if (filePath != null && !File.Exists(filePath))
281-
{
282281
throw new FileNotFoundException("could not find filePath");
283-
}
284282

285283
if (signature == null)
286284
throw new ArgumentException("missing signature input", nameof(signature));
@@ -289,19 +287,30 @@ public static bool ValidateSignature(string filePath, MinisignSignature signatur
289287
throw new ArgumentException("missing publicKey input", nameof(publicKey));
290288

291289
if (!ArrayHelpers.ConstantTimeEquals(signature.KeyId, publicKey.KeyId)) return false;
292-
// load the file into memory
293-
var file = LoadMessageFile(filePath);
294-
// verify the signature
295-
if (PublicKeyAuth.VerifyDetached(signature.Signature, file, publicKey.PublicKey))
290+
291+
292+
if (!signature.IsHashed)
296293
{
297-
// verify the trusted comment
298-
return PublicKeyAuth.VerifyDetached(signature.GlobalSignature,
299-
ArrayHelpers.ConcatArrays(signature.Signature, signature.TrustedComment), publicKey.PublicKey);
294+
var file = LoadMessageFile(filePath);
295+
296+
// Legacy: Ed25519(message)
297+
if (!PublicKeyAuth.VerifyDetached(signature.Signature, file, publicKey.PublicKey)) return false;
298+
}
299+
else
300+
{
301+
// Hashed: Ed25519(Blake2b-512(message))
302+
var blake = ComputeBlake2bFileHash(filePath);
303+
if (!PublicKeyAuth.VerifyDetached(signature.Signature, blake, publicKey.PublicKey)) return false;
300304
}
301305

302-
return false;
306+
// Global signature is the same for both formats
307+
return PublicKeyAuth.VerifyDetached(
308+
signature.GlobalSignature,
309+
ArrayHelpers.ConcatArrays(signature.Signature, signature.TrustedComment),
310+
publicKey.PublicKey);
303311
}
304312

313+
305314
/// <summary>
306315
/// Validate a file with a MinisignSignature and a MinisignPublicKey object.
307316
/// </summary>
@@ -325,17 +334,27 @@ public static bool ValidateSignature(byte[] message, MinisignSignature signature
325334
throw new ArgumentException("missing publicKey input", nameof(publicKey));
326335

327336
if (!ArrayHelpers.ConstantTimeEquals(signature.KeyId, publicKey.KeyId)) return false;
328-
// verify the signature
329-
if (PublicKeyAuth.VerifyDetached(signature.Signature, message, publicKey.PublicKey))
337+
338+
if (!signature.IsHashed)
339+
{
340+
// Legacy: Ed25519(message)
341+
if (!PublicKeyAuth.VerifyDetached(signature.Signature, message, publicKey.PublicKey)) return false;
342+
}
343+
else
330344
{
331-
// verify the trusted comment
332-
return PublicKeyAuth.VerifyDetached(signature.GlobalSignature,
333-
ArrayHelpers.ConcatArrays(signature.Signature, signature.TrustedComment), publicKey.PublicKey);
345+
// Hashed: Ed25519(Blake2b-512(message))
346+
var blake = GenericHash.Hash(message, null, 64);
347+
if (!PublicKeyAuth.VerifyDetached(signature.Signature, blake, publicKey.PublicKey)) return false;
334348
}
335349

336-
return false;
350+
return PublicKeyAuth.VerifyDetached(
351+
signature.GlobalSignature,
352+
ArrayHelpers.ConcatArrays(signature.Signature, signature.TrustedComment),
353+
publicKey.PublicKey);
337354
}
338355

356+
357+
339358
#endregion
340359

341360
#region Signature Handling
@@ -430,15 +449,29 @@ public static MinisignSignature LoadSignature(byte[] signature, byte[] trustedCo
430449
if (globalSignature == null)
431450
throw new ArgumentException("missing globalSignature input", nameof(globalSignature));
432451

433-
var minisignSignature = new MinisignSignature
452+
var result = new MinisignSignature()
434453
{
435454
SignatureAlgorithm = ArrayHelpers.SubArray(signature, 0, 2),
436455
KeyId = ArrayHelpers.SubArray(signature, 2, 8),
437-
Signature = ArrayHelpers.SubArray(signature, 10),
438456
TrustedComment = trustedComment,
439457
GlobalSignature = globalSignature
440458
};
441-
return minisignSignature;
459+
460+
var alg = Encoding.UTF8.GetString(result.SignatureAlgorithm);
461+
result.IsHashed = alg == "ED";
462+
463+
if (!result.IsHashed)
464+
{
465+
// Legacy minisign: Ed + keyid(8) + raw signature
466+
result.Signature = ArrayHelpers.SubArray(signature, 10);
467+
}
468+
else
469+
{
470+
// Hashed minisign: ED + keyid(8) + signature(64)
471+
result.Signature = ArrayHelpers.SubArray(signature, 10, 64);
472+
}
473+
474+
return result;
442475
}
443476

444477
#endregion
@@ -710,6 +743,33 @@ private static byte[] LoadMessageFile(string messageFile)
710743
return File.ReadAllBytes(messageFile);
711744
}
712745

746+
/// <summary>
747+
/// Computes a BLAKE2b-512 hash of a file without loading it fully into memory.
748+
/// Used for hashed minisign signatures.
749+
/// </summary>
750+
/// <param name="messageFile">Path to the file.</param>
751+
/// <returns>64-byte BLAKE2b hash of the file contents.</returns>
752+
/// <exception cref="ArgumentException"></exception>
753+
/// <exception cref="FileNotFoundException"></exception>
754+
/// <exception cref="IOException"></exception>
755+
/// <exception cref="SecurityException"></exception>
756+
/// <exception cref="UnauthorizedAccessException"></exception>
757+
/// <exception cref="DirectoryNotFoundException"></exception>
758+
private static byte[] ComputeBlake2bFileHash(string messageFile)
759+
{
760+
if (messageFile == null)
761+
throw new ArgumentException("missing messageFile input", nameof(messageFile));
762+
763+
if (!File.Exists(messageFile))
764+
throw new FileNotFoundException("could not find messageFile");
765+
766+
using (var stream = File.OpenRead(messageFile))
767+
using (var hashStream = new GenericHash.GenericHashAlgorithm((byte[])null, 64))
768+
{
769+
return hashStream.ComputeHash(stream);
770+
}
771+
}
772+
713773
/// <summary>
714774
/// Get the current Unix Timestamp.
715775
/// </summary>

Minisign.Net/Models/MinisignSignature.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ public class MinisignSignature
77
public byte[] Signature { get; set; }
88
public byte[] GlobalSignature { get; set; }
99
public byte[] TrustedComment { get; set; }
10+
public bool IsHashed { get; set; }
1011
}
1112
}

0 commit comments

Comments
 (0)