From 9062754d9e0eb5a46b199ade160541c4ed0d6a95 Mon Sep 17 00:00:00 2001 From: Robert Hague Date: Sun, 18 Oct 2026 22:17:40 +0200 Subject: [PATCH] Pad RSA signatures which have leading zeroes removed The BCL (at least the Windows crypto provider) seems to expect signatures matching the modulus size and fails verification even if the difference is just leading zeroes. So add them if needed. This is basically the same as #1388 in the opposite direction. I believe this fixes one of the issues reported in #1799 that has roughly 1% or 2% failure rate (lines up with the upper byte of the signature being roughly uniform in [0, 255]). --- .../Cryptography/RsaDigitalSignature.cs | 4 +- .../Cryptography/RsaDigitalSignatureTest.cs | 48 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs index 384a8b562..88016dabd 100644 --- a/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs +++ b/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs @@ -2,6 +2,8 @@ using System; using System.Security.Cryptography; +using Renci.SshNet.Common; + namespace Renci.SshNet.Security.Cryptography { /// @@ -35,7 +37,7 @@ public RsaDigitalSignature(RsaKey rsaKey, HashAlgorithmName hashAlgorithmName) /// public override bool Verify(byte[] input, byte[] signature) { - return _key.RSA.VerifyData(input, signature, _hashAlgorithmName, RSASignaturePadding.Pkcs1); + return _key.RSA.VerifyData(input, signature.Pad((_key.RSA.KeySize + 7) / 8), _hashAlgorithmName, RSASignaturePadding.Pkcs1); } /// diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs index 17901523b..9f9aedc9c 100644 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs @@ -3,6 +3,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; + +using Renci.SshNet.Common; using Renci.SshNet.Security; using Renci.SshNet.Security.Cryptography; using Renci.SshNet.Tests.Common; @@ -126,11 +128,13 @@ public void Sha512_SignAndVerify() } [TestMethod] - public void SignatureDoesNotTruncateLeadingZeroes() + public void SignatureLeadingZeroes() { + // When signing, do not shorten signatures by truncating leading zeroes. + // Shortened/unpadded signatures are accepted by some servers but not all. // A regression test for https://github.com/sshnet/SSH.NET/issues/1388 - byte[] data = { 0x41, 0xdb, 0xf3, 0x09, 0x56 }; + byte[] data = { 0xe3, 0x1a, 0xb1, 0x33, 0x30 }; RsaKey rsaKey = GetRsaKey(); @@ -140,25 +144,33 @@ public void SignatureDoesNotTruncateLeadingZeroes() CollectionAssert.AreEqual(new byte[] { - 0x00, 0xa8, 0x35, 0x24, 0xc8, 0xc1, 0x94, 0x97, 0xc3, 0xdf, 0x94, 0x32, 0x62, 0xf2, 0x12, 0x57, - 0x49, 0x22, 0x7e, 0x52, 0xfe, 0x6e, 0x23, 0x1f, 0x28, 0x84, 0xf9, 0x3e, 0x16, 0xda, 0xc3, 0x6f, - 0xa0, 0xa4, 0x00, 0x9a, 0x9b, 0xf2, 0x3e, 0xf5, 0x47, 0x3a, 0x7a, 0x8a, 0xd1, 0x1e, 0xf1, 0xd0, - 0x0b, 0x4d, 0x04, 0x16, 0x6f, 0x29, 0xf1, 0xe7, 0x26, 0xfd, 0x5a, 0x6e, 0x9a, 0xc4, 0x53, 0x7f, - 0xbe, 0xa3, 0x3b, 0xa0, 0x95, 0x02, 0xe2, 0xcd, 0xd9, 0xa1, 0x4d, 0xae, 0x63, 0x4f, 0x95, 0x5e, - 0x4f, 0xd6, 0x34, 0x3a, 0x05, 0x93, 0xcb, 0xb4, 0x18, 0xd2, 0xd0, 0xd6, 0x5f, 0x8c, 0xe3, 0x77, - 0xca, 0x7f, 0x88, 0xfb, 0x72, 0x00, 0x00, 0x74, 0x31, 0xb7, 0xc5, 0xe8, 0xe2, 0x92, 0xf9, 0xab, - 0x63, 0x8f, 0x87, 0x07, 0xc5, 0x90, 0xf2, 0xdd, 0x6d, 0xc6, 0x38, 0xee, 0x19, 0x65, 0x05, 0xa8, - 0xda, 0xfc, 0x32, 0xe9, 0xbf, 0x2a, 0x0d, 0x6b, 0x2b, 0xac, 0x92, 0x48, 0xda, 0x3b, 0x8f, 0x4f, - 0x68, 0x58, 0xa6, 0xa0, 0xcb, 0xdc, 0x44, 0xe3, 0x12, 0x12, 0xb3, 0x0a, 0xf2, 0x4c, 0xa6, 0x17, - 0xab, 0xdb, 0x5b, 0x79, 0x63, 0x83, 0x8b, 0x10, 0x1c, 0x13, 0xc7, 0xf1, 0x15, 0x88, 0xc6, 0x92, - 0xcd, 0x6f, 0x81, 0xde, 0x02, 0x1d, 0x35, 0x91, 0xf4, 0x0c, 0x66, 0xe6, 0x7c, 0x3f, 0x02, 0x98, - 0xa7, 0x79, 0x6c, 0x90, 0x67, 0x14, 0x80, 0x18, 0xeb, 0xe7, 0x52, 0x44, 0x6b, 0x1b, 0x24, 0xac, - 0x71, 0xaa, 0xde, 0xeb, 0xf7, 0x3c, 0xfc, 0xc0, 0x46, 0x23, 0x40, 0x2f, 0xb0, 0xf2, 0x0b, 0x2e, - 0xfd, 0xd6, 0xd5, 0x2b, 0x3f, 0x00, 0xd8, 0xfe, 0x30, 0xa6, 0x67, 0xbc, 0x3d, 0xad, 0xb2, 0xf4, - 0xf3, 0x98, 0xb5, 0x0a, 0x91, 0xf3, 0x9d, 0xfb, 0xe8, 0xeb, 0xe0, 0x35, 0x5b, 0x11, 0xcf, 0xdb, + 0x00, 0x00, 0xc4, 0xa1, 0xa4, 0x63, 0xbd, 0x7c, 0xe7, 0x6e, 0xda, 0xb1, 0xee, 0x37, 0x61, 0x12, + 0x86, 0xa4, 0x4a, 0xed, 0x28, 0x42, 0x84, 0xc3, 0xc9, 0x73, 0xff, 0x99, 0x56, 0x40, 0xee, 0x8f, + 0x32, 0x4b, 0x3b, 0x6f, 0x2d, 0xeb, 0xcc, 0x6a, 0x2d, 0x54, 0x65, 0x97, 0x6f, 0xf0, 0xbc, 0x29, + 0xb9, 0xdc, 0x46, 0xea, 0x0a, 0xf8, 0xce, 0xae, 0xd2, 0x52, 0x47, 0xc0, 0x63, 0xdb, 0xb5, 0x6d, + 0xcd, 0xd8, 0xa3, 0xcb, 0x24, 0x11, 0xdd, 0x72, 0xdd, 0x1a, 0x63, 0x8a, 0xfb, 0xe3, 0xb5, 0x37, + 0x7f, 0x6a, 0x5d, 0xbc, 0x0d, 0x93, 0x8c, 0x82, 0xcc, 0xf5, 0x88, 0xe1, 0x0d, 0xde, 0xe8, 0x68, + 0x3b, 0x5c, 0x79, 0xd3, 0xd6, 0x34, 0x46, 0x2a, 0x14, 0x18, 0x14, 0xb6, 0x08, 0x8e, 0x1e, 0xf9, + 0x58, 0xfc, 0x11, 0xd2, 0x88, 0xdc, 0x6c, 0x9c, 0x90, 0xa0, 0xaf, 0x11, 0x98, 0x47, 0x3e, 0x56, + 0x6a, 0x10, 0x5d, 0x15, 0x08, 0x7e, 0xba, 0x82, 0x29, 0x3c, 0x44, 0xb9, 0x33, 0xe4, 0x51, 0x8a, + 0xaa, 0x5a, 0x45, 0xb1, 0xbd, 0x4f, 0xce, 0x98, 0x3b, 0x79, 0x9c, 0x33, 0xb5, 0x2c, 0xa0, 0xa1, + 0x15, 0x0b, 0x15, 0x47, 0xb6, 0x31, 0x94, 0xb0, 0xdb, 0xdb, 0x53, 0x22, 0x1f, 0xe6, 0x96, 0x88, + 0x41, 0xbc, 0x6f, 0x02, 0x1b, 0xa2, 0x7e, 0xc7, 0x66, 0x06, 0x4b, 0x14, 0xc5, 0xd7, 0x90, 0x68, + 0x87, 0x9e, 0xbe, 0x69, 0xaa, 0xd9, 0x5a, 0xda, 0x34, 0x11, 0x82, 0x24, 0x8c, 0x36, 0x3f, 0x44, + 0x43, 0x19, 0x74, 0xe7, 0x17, 0xaf, 0x35, 0xca, 0x9c, 0xdd, 0x4c, 0xd2, 0x1a, 0xdf, 0x02, 0x26, + 0xe7, 0x42, 0xbb, 0x3c, 0x21, 0xf4, 0xb2, 0xf8, 0x50, 0x7d, 0x98, 0x9c, 0x5d, 0xfa, 0x1e, 0x01, + 0x2a, 0xb3, 0x6a, 0x11, 0xf8, 0x3d, 0x0b, 0xf1, 0xd0, 0x75, 0x46, 0xd3, 0x21, 0xa9, 0x10, 0xb7, }, signedBytes); Assert.IsTrue(digitalSignature.Verify(data, signedBytes)); + + // We are lenient in the opposite direction, allowing unpadded signatures. + Assert.IsTrue(digitalSignature.Verify(data, signedBytes.TrimLeadingZeros())); + + signedBytes[2] = 0x00; + + Assert.IsFalse(digitalSignature.Verify(data, signedBytes)); + Assert.IsFalse(digitalSignature.Verify(data, signedBytes.TrimLeadingZeros())); } private static RsaKey GetRsaKey()