WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 58e4b42

Browse files
committed
Add signature verification enhancements in KeeShare tests and core logic
1 parent 48f0bfa commit 58e4b42

File tree

2 files changed

+139
-19
lines changed

2 files changed

+139
-19
lines changed

src/KeeShare.Tests/SignatureVerificationTests.cs

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ namespace KeeShare.Tests
66
{
77
public class SignatureVerificationTests
88
{
9+
/// <summary>
10+
/// Helper to convert bytes to hex string (KeeShare format)
11+
/// </summary>
12+
private static string BytesToHex(byte[] bytes)
13+
{
14+
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
15+
}
16+
17+
/// <summary>
18+
/// Helper to format signature in KeeShare format: "rsa|<hex>"
19+
/// </summary>
20+
private static byte[] FormatKeeShareSignature(byte[] signature)
21+
{
22+
string hex = BytesToHex(signature);
23+
return Encoding.UTF8.GetBytes($"rsa|{hex}");
24+
}
25+
926
[Fact]
1027
public void VerifySignature_WithValidSignature_ReturnsTrue()
1128
{
@@ -15,12 +32,27 @@ public void VerifySignature_WithValidSignature_ReturnsTrue()
1532
byte[] testData = Encoding.UTF8.GetBytes("Test KDBX data content");
1633
byte[] hash = SHA256.HashData(testData);
1734
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
18-
string signatureBase64 = Convert.ToBase64String(signature);
19-
byte[] signatureData = Encoding.UTF8.GetBytes(signatureBase64);
35+
byte[] signatureData = FormatKeeShareSignature(signature);
2036
bool result = KeeShare.VerifySignatureCore(publicKeyCert, testData, signatureData);
2137
Assert.True(result, "Signature verification should succeed with valid signature");
2238
}
2339

40+
[Fact]
41+
public void VerifySignature_WithValidSignatureWithoutPrefix_ReturnsTrue()
42+
{
43+
using var rsa = RSA.Create(2048);
44+
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
45+
var publicKeyCert = Convert.ToBase64String(publicKeyBytes);
46+
byte[] testData = Encoding.UTF8.GetBytes("Test KDBX data content");
47+
byte[] hash = SHA256.HashData(testData);
48+
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
49+
// Hex without "rsa|" prefix should also work
50+
string signatureHex = BytesToHex(signature);
51+
byte[] signatureData = Encoding.UTF8.GetBytes(signatureHex);
52+
bool result = KeeShare.VerifySignatureCore(publicKeyCert, testData, signatureData);
53+
Assert.True(result, "Signature verification should succeed with hex signature without prefix");
54+
}
55+
2456
[Fact]
2557
public void VerifySignature_WithInvalidSignature_ReturnsFalse()
2658
{
@@ -30,8 +62,7 @@ public void VerifySignature_WithInvalidSignature_ReturnsFalse()
3062
byte[] testData = Encoding.UTF8.GetBytes("Test KDBX data content");
3163
byte[] invalidSignature = new byte[256];
3264
new Random().NextBytes(invalidSignature);
33-
string signatureBase64 = Convert.ToBase64String(invalidSignature);
34-
byte[] signatureData = Encoding.UTF8.GetBytes(signatureBase64);
65+
byte[] signatureData = FormatKeeShareSignature(invalidSignature);
3566
bool result = KeeShare.VerifySignatureCore(publicKeyCert, testData, signatureData);
3667
Assert.False(result, "Signature verification should fail with invalid signature");
3768
}
@@ -45,8 +76,7 @@ public void VerifySignature_WithTamperedData_ReturnsFalse()
4576
byte[] originalData = Encoding.UTF8.GetBytes("Original KDBX data");
4677
byte[] hash = SHA256.HashData(originalData);
4778
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
48-
string signatureBase64 = Convert.ToBase64String(signature);
49-
byte[] signatureData = Encoding.UTF8.GetBytes(signatureBase64);
79+
byte[] signatureData = FormatKeeShareSignature(signature);
5080
byte[] tamperedData = Encoding.UTF8.GetBytes("Tampered KDBX data");
5181
bool result = KeeShare.VerifySignatureCore(publicKeyCert, tamperedData, signatureData);
5282
Assert.False(result, "Signature verification should fail when data is tampered");
@@ -62,8 +92,7 @@ public void VerifySignature_WithPemFormattedCertificate_ReturnsTrue()
6292
byte[] testData = Encoding.UTF8.GetBytes("Test KDBX data content");
6393
byte[] hash = SHA256.HashData(testData);
6494
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
65-
string signatureBase64 = Convert.ToBase64String(signature);
66-
byte[] signatureData = Encoding.UTF8.GetBytes(signatureBase64);
95+
byte[] signatureData = FormatKeeShareSignature(signature);
6796
bool result = KeeShare.VerifySignatureCore(publicKeyCertPem, testData, signatureData);
6897
Assert.True(result, "Signature verification should work with PEM formatted certificate");
6998
}
@@ -72,7 +101,7 @@ public void VerifySignature_WithPemFormattedCertificate_ReturnsTrue()
72101
public void VerifySignature_WithEmptyCertificate_ReturnsFalse()
73102
{
74103
byte[] testData = Encoding.UTF8.GetBytes("Test data");
75-
byte[] signatureData = Encoding.UTF8.GetBytes("fake signature");
104+
byte[] signatureData = Encoding.UTF8.GetBytes("rsa|abcd1234");
76105
bool result = KeeShare.VerifySignatureCore("", testData, signatureData);
77106
Assert.False(result, "Signature verification should fail with empty certificate");
78107
}
@@ -83,7 +112,7 @@ public void VerifySignature_WithNullData_ReturnsFalse()
83112
using var rsa = RSA.Create(2048);
84113
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
85114
var publicKeyCert = Convert.ToBase64String(publicKeyBytes);
86-
byte[] signatureData = Encoding.UTF8.GetBytes("signature");
115+
byte[] signatureData = Encoding.UTF8.GetBytes("rsa|abcd1234");
87116
bool result = KeeShare.VerifySignatureCore(publicKeyCert, null, signatureData);
88117
Assert.False(result, "Signature verification should fail with null data");
89118
}
@@ -94,21 +123,35 @@ public void VerifySignature_WithEmptyData_ReturnsFalse()
94123
using var rsa = RSA.Create(2048);
95124
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
96125
var publicKeyCert = Convert.ToBase64String(publicKeyBytes);
97-
byte[] signatureData = Encoding.UTF8.GetBytes("signature");
126+
byte[] signatureData = Encoding.UTF8.GetBytes("rsa|abcd1234");
98127
bool result = KeeShare.VerifySignatureCore(publicKeyCert, new byte[0], signatureData);
99128
Assert.False(result, "Signature verification should fail with empty data");
100129
}
101130

102131
[Fact]
103-
public void VerifySignature_WithMalformedBase64Signature_ReturnsFalse()
132+
public void VerifySignature_WithMalformedHexSignature_ReturnsFalse()
133+
{
134+
using var rsa = RSA.Create(2048);
135+
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
136+
var publicKeyCert = Convert.ToBase64String(publicKeyBytes);
137+
byte[] testData = Encoding.UTF8.GetBytes("Test data");
138+
// Invalid hex characters
139+
byte[] signatureData = Encoding.UTF8.GetBytes("rsa|not-valid-hex!@#$GHIJ");
140+
bool result = KeeShare.VerifySignatureCore(publicKeyCert, testData, signatureData);
141+
Assert.False(result, "Signature verification should fail with malformed hex");
142+
}
143+
144+
[Fact]
145+
public void VerifySignature_WithOddLengthHex_ReturnsFalse()
104146
{
105147
using var rsa = RSA.Create(2048);
106148
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
107149
var publicKeyCert = Convert.ToBase64String(publicKeyBytes);
108150
byte[] testData = Encoding.UTF8.GetBytes("Test data");
109-
byte[] signatureData = Encoding.UTF8.GetBytes("not-valid-base64!@#$");
151+
// Odd-length hex string (invalid)
152+
byte[] signatureData = Encoding.UTF8.GetBytes("rsa|abc");
110153
bool result = KeeShare.VerifySignatureCore(publicKeyCert, testData, signatureData);
111-
Assert.False(result, "Signature verification should fail with malformed base64");
154+
Assert.False(result, "Signature verification should fail with odd-length hex");
112155
}
113156

114157
[Fact]
@@ -120,11 +163,44 @@ public void VerifySignature_WithSignatureContainingWhitespace_ReturnsTrue()
120163
byte[] testData = Encoding.UTF8.GetBytes("Test KDBX data content");
121164
byte[] hash = SHA256.HashData(testData);
122165
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
123-
string signatureBase64 = Convert.ToBase64String(signature);
124-
string signatureWithWhitespace = $"\r\n {signatureBase64} \r\n";
166+
string signatureHex = BytesToHex(signature);
167+
// Add whitespace around the signature
168+
string signatureWithWhitespace = $"\r\n rsa|{signatureHex} \r\n";
125169
byte[] signatureData = Encoding.UTF8.GetBytes(signatureWithWhitespace);
126170
bool result = KeeShare.VerifySignatureCore(publicKeyCert, testData, signatureData);
127171
Assert.True(result, "Signature verification should handle whitespace in signature");
128172
}
173+
174+
[Fact]
175+
public void VerifySignature_WithUppercaseHex_ReturnsTrue()
176+
{
177+
using var rsa = RSA.Create(2048);
178+
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
179+
var publicKeyCert = Convert.ToBase64String(publicKeyBytes);
180+
byte[] testData = Encoding.UTF8.GetBytes("Test KDBX data content");
181+
byte[] hash = SHA256.HashData(testData);
182+
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
183+
// Use uppercase hex
184+
string signatureHex = BitConverter.ToString(signature).Replace("-", "").ToUpperInvariant();
185+
byte[] signatureData = Encoding.UTF8.GetBytes($"rsa|{signatureHex}");
186+
bool result = KeeShare.VerifySignatureCore(publicKeyCert, testData, signatureData);
187+
Assert.True(result, "Signature verification should handle uppercase hex");
188+
}
189+
190+
[Fact]
191+
public void VerifySignature_WithUppercaseRsaPrefix_ReturnsTrue()
192+
{
193+
using var rsa = RSA.Create(2048);
194+
var publicKeyBytes = rsa.ExportSubjectPublicKeyInfo();
195+
var publicKeyCert = Convert.ToBase64String(publicKeyBytes);
196+
byte[] testData = Encoding.UTF8.GetBytes("Test KDBX data content");
197+
byte[] hash = SHA256.HashData(testData);
198+
byte[] signature = rsa.SignHash(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
199+
string signatureHex = BytesToHex(signature);
200+
// Use uppercase "RSA|" prefix
201+
byte[] signatureData = Encoding.UTF8.GetBytes($"RSA|{signatureHex}");
202+
bool result = KeeShare.VerifySignatureCore(publicKeyCert, testData, signatureData);
203+
Assert.True(result, "Signature verification should handle uppercase RSA prefix");
204+
}
129205
}
130206
}

src/keepass2android-app/KeeShare.cs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ private bool VerifySignature(PwGroup group, byte[] kdbxData, byte[] signatureDat
422422
/// </summary>
423423
/// <param name="trustedCertificate">The trusted certificate (public key) as base64-encoded DER or PEM format</param>
424424
/// <param name="kdbxData">The KDBX file data that was signed</param>
425-
/// <param name="signatureData">The signature data (base64-encoded)</param>
425+
/// <param name="signatureData">The signature data in KeeShare format ("rsa|&lt;hex&gt;")</param>
426426
/// <returns>True if signature is valid, false otherwise</returns>
427427
internal static bool VerifySignatureCore(string trustedCertificate, byte[]? kdbxData, byte[]? signatureData)
428428
{
@@ -443,22 +443,35 @@ internal static bool VerifySignatureCore(string trustedCertificate, byte[]? kdbx
443443
return false;
444444
}
445445

446-
// KeeShare signature format: base64-encoded RSA signature
446+
// KeeShare signature format: "rsa|<hex>" where hex is the RSA signature
447447
// The signature is computed over the kdbx file data using SHA-256
448448
string signatureText = Encoding.UTF8.GetString(signatureData).Trim();
449449

450450
// Remove any whitespace/newlines
451451
signatureText = signatureText.Replace("\r", "").Replace("\n", "").Replace(" ", "");
452452

453+
// Strip "rsa|" prefix if present
454+
const string rsaPrefix = "rsa|";
455+
if (signatureText.StartsWith(rsaPrefix, StringComparison.OrdinalIgnoreCase))
456+
{
457+
signatureText = signatureText.Substring(rsaPrefix.Length);
458+
}
459+
460+
// Hex-decode the signature
453461
byte[] signatureBytes;
454462
try
455463
{
456-
signatureBytes = Convert.FromBase64String(signatureText);
464+
signatureBytes = HexStringToBytes(signatureText);
457465
}
458466
catch (Exception)
459467
{
460468
return false;
461469
}
470+
471+
if (signatureBytes == null || signatureBytes.Length == 0)
472+
{
473+
return false;
474+
}
462475

463476
// Parse the trusted certificate (public key)
464477
// Format: PEM-encoded public key or base64-encoded DER
@@ -542,5 +555,36 @@ internal static bool VerifySignatureCore(string trustedCertificate, byte[]? kdbx
542555
return false;
543556
}
544557
}
558+
559+
/// <summary>
560+
/// Converts a hexadecimal string to a byte array.
561+
/// </summary>
562+
/// <param name="hex">The hexadecimal string (case-insensitive, no separators)</param>
563+
/// <returns>The decoded byte array, or null if the input is invalid</returns>
564+
private static byte[]? HexStringToBytes(string hex)
565+
{
566+
if (string.IsNullOrEmpty(hex))
567+
{
568+
return null;
569+
}
570+
571+
// Hex string must have even length
572+
if (hex.Length % 2 != 0)
573+
{
574+
return null;
575+
}
576+
577+
byte[] bytes = new byte[hex.Length / 2];
578+
for (int i = 0; i < bytes.Length; i++)
579+
{
580+
string byteStr = hex.Substring(i * 2, 2);
581+
if (!byte.TryParse(byteStr, System.Globalization.NumberStyles.HexNumber, null, out bytes[i]))
582+
{
583+
return null;
584+
}
585+
}
586+
587+
return bytes;
588+
}
545589
}
546590
}

0 commit comments

Comments
 (0)