Use System.Security.Cryptography for DSA (#1458)

* Use System.Security.Cryptography for DSA

This is the analogue of the RSA change #1373 for DSA. This has a couple of caveats:

- The BCL supports only FIPS 186-compliant keys, that is, public (P, Q) lengths
  of (512 <= P <= 1024, 160) for FIPS 186-1/186-2; and (2048, 256), (3072, 256)
  for FIPS 186-3/186-4. The latter also specifies (2048, 224) but due to a quirk
  in the Windows API, the BCL does not support Q values of length 224[^1].
- OpenSSH, based on the SSH spec, only supports (supported) Q values of length 160,
  but appears to also work in non-FIPS-compliant cases such as in our integration
  tests with a (2048, 160) host key. That test now fails and I changed that host key
  to (1024, 160).

This basically means that (1024, 160) is the largest DSA key size supported by both
SSH.NET and OpenSSH. However, given that OpenSSH deprecated DSA in 2015[^2], and the
alternative that I have been considering is just to delete support for DSA in the
library, this change seems reasonable to me. I don't think we can justify keeping the
current handwritten code around.

I think we may still consider dropping DSA from the library, I just had this branch
laying around and figured I'd finish it off.

[^1]: https://github.com/dotnet/runtime/blob/fadd8313653f71abd0068c8bf914be88edb2c8d3/src/libraries/Common/src/System/Security/Cryptography/DSACng.ImportExport.cs#L259-L265
[^2]: https://www.openssh.com/txt/release-7.0

* Appease mono

* test experiment

* Revert "Appease mono"

This reverts commit 881eefe5e8.
This commit is contained in:
Rob Hague
2024-08-11 07:48:58 +02:00
committed by GitHub
parent c7a0ca93de
commit fe65570ebc
11 changed files with 372 additions and 196 deletions
@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Security.Cryptography;
using Renci.SshNet.Common;
@@ -11,8 +12,6 @@ namespace Renci.SshNet.Security.Cryptography
public class DsaDigitalSignature : DigitalSignature, IDisposable
{
private readonly DsaKey _key;
private HashAlgorithm _hash;
private bool _isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="DsaDigitalSignature" /> class.
@@ -27,71 +26,23 @@ namespace Renci.SshNet.Security.Cryptography
}
_key = key;
_hash = SHA1.Create();
}
/// <summary>
/// Verifies the signature.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="signature">The signature.</param>
/// <returns>
/// <see langword="true"/> if signature was successfully verified; otherwise <see langword="false"/>.
/// </returns>
/// <exception cref="InvalidOperationException">Invalid signature.</exception>
/// <inheritdoc/>
public override bool Verify(byte[] input, byte[] signature)
{
var hashInput = _hash.ComputeHash(input);
var hm = new BigInteger(hashInput.Reverse().Concat(new byte[] { 0 }));
if (signature.Length != 40)
#if NETSTANDARD2_1_OR_GREATER || NET
return _key.DSA.VerifyData(input, signature, HashAlgorithmName.SHA1);
#else
// VerifyData does not exist on netstandard2.0.
// It does exist on net462, but in order to keep the path tested,
// use it on netfx as well.
using (var sha1 = SHA1.Create())
{
throw new InvalidOperationException("Invalid signature.");
var hash = sha1.ComputeHash(input);
return _key.DSA.VerifySignature(hash, signature);
}
// Extract r and s numbers from the signature
var rBytes = new byte[21];
var sBytes = new byte[21];
for (int i = 0, j = 20; i < 20; i++, j--)
{
rBytes[i] = signature[j - 1];
sBytes[i] = signature[j + 20 - 1];
}
var r = new BigInteger(rBytes);
var s = new BigInteger(sBytes);
// Reject the signature if 0 < r < q or 0 < s < q is not satisfied.
if (r <= 0 || r >= _key.Q)
{
return false;
}
if (s <= 0 || s >= _key.Q)
{
return false;
}
// Calculate w = s1 mod q
var w = BigInteger.ModInverse(s, _key.Q);
// Calculate u1 = H(m)·w mod q
var u1 = (hm * w) % _key.Q;
// Calculate u2 = r * w mod q
var u2 = (r * w) % _key.Q;
u1 = BigInteger.ModPow(_key.G, u1, _key.P);
u2 = BigInteger.ModPow(_key.Y, u2, _key.P);
// Calculate v = ((g pow u1 * y pow u2) mod p) mod q
var v = ((u1 * u2) % _key.P) % _key.Q;
// The signature is valid if v = r
return v == r;
#endif
}
/// <summary>
@@ -104,60 +55,18 @@ namespace Renci.SshNet.Security.Cryptography
/// <exception cref="SshException">Invalid DSA key.</exception>
public override byte[] Sign(byte[] input)
{
var hashInput = _hash.ComputeHash(input);
var m = new BigInteger(hashInput.Reverse().Concat(new byte[] { 0 }));
BigInteger s;
BigInteger r;
do
#if NETSTANDARD2_1_OR_GREATER || NET
return _key.DSA.SignData(input, HashAlgorithmName.SHA1);
#else
// SignData does not exist on netstandard2.0.
// It does exist on net462, but in order to keep the path tested,
// use it on netfx as well.
using (var sha1 = SHA1.Create())
{
var k = BigInteger.Zero;
do
{
// Generate a random per-message value k where 0 < k < q
var bitLength = _key.Q.BitLength;
if (_key.Q < BigInteger.Zero)
{
throw new SshException("Invalid DSA key.");
}
while (k <= 0 || k >= _key.Q)
{
k = BigInteger.Random(bitLength);
}
// Calculate r = ((g pow k) mod p) mod q
r = BigInteger.ModPow(_key.G, k, _key.P) % _key.Q;
// In the unlikely case that r = 0, start again with a different random k
}
while (r.IsZero);
// Calculate s = ((k pow 1)(H(m) + x*r)) mod q
k = BigInteger.ModInverse(k, _key.Q) * (m + (_key.X * r));
s = k % _key.Q;
// In the unlikely case that s = 0, start again with a different random k
var hash = sha1.ComputeHash(input);
return _key.DSA.CreateSignature(hash);
}
while (s.IsZero);
// The signature is (r, s)
var signature = new byte[40];
// issue #1918: pad part with zero's on the left if length is less than 20
var rBytes = r.ToByteArray().Reverse().TrimLeadingZeros();
Array.Copy(rBytes, 0, signature, 20 - rBytes.Length, rBytes.Length);
// issue #1918: pad part with zero's on the left if length is less than 20
var sBytes = s.ToByteArray().Reverse().TrimLeadingZeros();
Array.Copy(sBytes, 0, signature, 40 - sBytes.Length, sBytes.Length);
return signature;
#endif
}
/// <summary>
@@ -175,30 +84,6 @@ namespace Renci.SshNet.Security.Cryptography
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
if (disposing)
{
var hash = _hash;
if (hash != null)
{
hash.Dispose();
_hash = null;
}
_isDisposed = true;
}
}
/// <summary>
/// Finalizes an instance of the <see cref="DsaDigitalSignature"/> class.
/// </summary>
~DsaDigitalSignature()
{
Dispose(disposing: false);
}
}
}
@@ -1,4 +1,6 @@
using System;
#nullable enable
using System;
using System.Security.Cryptography;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;
@@ -10,8 +12,9 @@ namespace Renci.SshNet.Security
/// </summary>
public class DsaKey : Key, IDisposable
{
private DsaDigitalSignature _digitalSignature;
private bool _isDisposed;
private DsaDigitalSignature? _digitalSignature;
internal DSA DSA { get; }
/// <summary>
/// Gets the P.
@@ -39,10 +42,10 @@ namespace Renci.SshNet.Security
public BigInteger X { get; }
/// <summary>
/// Gets the length of the key.
/// Gets the length of the key in bits.
/// </summary>
/// <value>
/// The length of the key.
/// The bit-length of the key.
/// </value>
public override int KeyLength
{
@@ -104,6 +107,8 @@ namespace Renci.SshNet.Security
Q = publicKeyData.Keys[1];
G = publicKeyData.Keys[2];
Y = publicKeyData.Keys[3];
DSA = LoadDSA();
}
/// <summary>
@@ -130,6 +135,8 @@ namespace Renci.SshNet.Security
{
throw new InvalidOperationException("Invalid private key (expected EOF).");
}
DSA = LoadDSA();
}
/// <summary>
@@ -147,6 +154,54 @@ namespace Renci.SshNet.Security
G = g;
Y = y;
X = x;
DSA = LoadDSA();
}
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
#pragma warning disable CA5384 // Do Not Use Digital Signature Algorithm (DSA)
private DSA LoadDSA()
{
#if NETFRAMEWORK
// On .NET Framework we use the concrete CNG type which is FIPS-186-3
// compatible. The CryptoServiceProvider type returned by DSA.Create()
// is limited to FIPS-186-1 (max 1024 bit key).
var dsa = new DSACng();
#else
var dsa = DSA.Create();
#endif
dsa.ImportParameters(GetDSAParameters());
return dsa;
}
#pragma warning restore CA5384 // Do Not Use Digital Signature Algorithm (DSA)
#pragma warning restore CA1859 // Use concrete types when possible for improved performance
internal DSAParameters GetDSAParameters()
{
// P, G, Y, Q are required.
// P, G, Y must have the same length.
// If X is present, it must have the same length as Q.
// See https://github.com/dotnet/runtime/blob/fadd8313653f71abd0068c8bf914be88edb2c8d3/src/libraries/Common/src/System/Security/Cryptography/DSACng.ImportExport.cs#L23
// and https://github.com/dotnet/runtime/blob/fadd8313653f71abd0068c8bf914be88edb2c8d3/src/libraries/Common/src/System/Security/Cryptography/DSAKeyFormatHelper.cs#L18
// (and similar code in RsaKey.cs)
var ret = new DSAParameters
{
P = P.ToByteArray(isUnsigned: true, isBigEndian: true),
Q = Q.ToByteArray(isUnsigned: true, isBigEndian: true),
};
ret.G = G.ExportKeyParameter(ret.P.Length);
ret.Y = Y.ExportKeyParameter(ret.P.Length);
if (!X.IsZero)
{
ret.X = X.ExportKeyParameter(ret.Q.Length);
}
return ret;
}
/// <summary>
@@ -164,30 +219,11 @@ namespace Renci.SshNet.Security
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_isDisposed)
{
return;
}
if (disposing)
{
var digitalSignature = _digitalSignature;
if (digitalSignature != null)
{
digitalSignature.Dispose();
_digitalSignature = null;
}
_isDisposed = true;
_digitalSignature?.Dispose();
DSA.Dispose();
}
}
/// <summary>
/// Finalizes an instance of the <see cref="DsaKey"/> class.
/// </summary>
~DsaKey()
{
Dispose(disposing: false);
}
}
}
@@ -92,10 +92,10 @@ namespace Renci.SshNet.Security
public BigInteger InverseQ { get; }
/// <summary>
/// Gets the length of the key.
/// Gets the length of the key in bits.
/// </summary>
/// <value>
/// The length of the key.
/// The bit-length of the key.
/// </value>
public override int KeyLength
{
+1 -1
View File
@@ -1 +1 @@
ssh-dss AAAAB3NzaC1kc3MAAACBAI8gyHFchkVhkPiwkhkjFDqN6w2nFWTqVy9sLjFs38oEWLMpAw9+c132erUptAhNQ6JZUAVZGllv/3V5hksSDyChe9WY5IfsOlh6X0dcZCwBKysEzQlPyMFqAtbc9uv7oUWNzBfvEbtV6WN/VmcmXf7dyo3EBVXbBFdPl1NKC7W9AAAAFQDY1+bTt7s2iNmYoBE4C9hdWRCyeQAAAIAEtj09ugx/Tdl6bo7X6mX17hcgVgIxcYj5VNONg2k6IHmRFriLviYaS68mIB4SG3jmvvxbXAGqR1bWBUrv90n0wpxxcuuNoCFylJQyuqUkzSsUHb0WMcncZ/tBQt+NJnRB1Zp9sw8n20ocpg3WVPdaXTtc4pk83NYB6ywG6UFPvgAAAIAX+De5dwo33LMl9W8IvA4dY8Q1wshdycAGJzhy+qYF9dCcwD1Pg+4EbPjYPmzJopsVrK97v9QhxyYcXMr/iHhngGwd9nYNzzSKx665vkSjzyeJWpeQ+fvNV3CLItP01ypbUreM+s+Vz1wor5joLKcDS4X0oQ0RIVZNEHnekuLuFg==
ssh-dss AAAAB3NzaC1kc3MAAACBAOCSGuOxxY/DQBowG7fkS30AJmwN4fDPXToyTaAaxwpOWnGJXFhgP4Il+GSKlpaxnUWkajKpMc1b1hhawPWN4sxoT8QRb6SAW30ErnT/pUtsxqoFlkFla4xvWSgNAuHAVkUBrgPsJ4sHehSbNFn3I6q8Rgle2jyHDHTOqPj2KEXhAAAAFQC740YkVJdVpTJRTxd9Myi0Nx3t4wAAAIArvP7AGh5jY+zxeQRb52zxcUamRBkVYL/ferdJNi9hoM8ZaO4++Xgs8wMbpmoEch9DsXdtufjqXWpk7ywlPjcdhhsb3MxJAeEeFtTRsu2/IUTKqKPHIOgoiPzs8q69AxWhV10aDDUdYWLkqPV/tMGl6S/jC7vTJLmhZum4BUv8MQAAAIEAt19oHPIPyv/8mbMaYpu8I6kvj1/97Ejw0neN7Cd9cGZLqIPBwTyUHEQvKSAm4BvNP0Le3JDn3RFayhRmf+8RrAmS4d1MjrCOs6fmeyLnk2kTpRPFZ2x0H1udIRAhzRehyfj6OsAHn7Jclr+mqDhoq7nIfC3tSgWxFH5g948+7ks= imported-openssh-key
+10 -9
View File
@@ -1,12 +1,13 @@
---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----
Comment: "imported-openssh-key"
P2/56wAAAgIAAAAmZGwtbW9kcHtzaWdue2RzYS1uaXN0LXNoYTF9LGRoe3BsYWlufX0AAA
AEbm9uZQAAAcQAAAHAAAAAAAAABACPIMhxXIZFYZD4sJIZIxQ6jesNpxVk6lcvbC4xbN/K
BFizKQMPfnNd9nq1KbQITUOiWVAFWRpZb/91eYZLEg8goXvVmOSH7DpYel9HXGQsASsrBM
0JT8jBagLW3Pbr+6FFjcwX7xG7Veljf1ZnJl3+3cqNxAVV2wRXT5dTSgu1vQAAA/sEtj09
ugx/Tdl6bo7X6mX17hcgVgIxcYj5VNONg2k6IHmRFriLviYaS68mIB4SG3jmvvxbXAGqR1
bWBUrv90n0wpxxcuuNoCFylJQyuqUkzSsUHb0WMcncZ/tBQt+NJnRB1Zp9sw8n20ocpg3W
VPdaXTtc4pk83NYB6ywG6UFPvgAAAKDY1+bTt7s2iNmYoBE4C9hdWRCyeQAAA/0X+De5dw
o33LMl9W8IvA4dY8Q1wshdycAGJzhy+qYF9dCcwD1Pg+4EbPjYPmzJopsVrK97v9QhxyYc
XMr/iHhngGwd9nYNzzSKx665vkSjzyeJWpeQ+fvNV3CLItP01ypbUreM+s+Vz1wor5joLK
cDS4X0oQ0RIVZNEHnekuLuFgAAAJ4j+lpXSvEZexhbiACKenUniZ/Qkg==
AEbm9uZQAAAcQAAAHAAAAAAAAABADgkhrjscWPw0AaMBu35Et9ACZsDeHwz106Mk2gGscK
TlpxiVxYYD+CJfhkipaWsZ1FpGoyqTHNW9YYWsD1jeLMaE/EEW+kgFt9BK50/6VLbMaqBZ
ZBZWuMb1koDQLhwFZFAa4D7CeLB3oUmzRZ9yOqvEYJXto8hwx0zqj49ihF4QAAA/4rvP7A
Gh5jY+zxeQRb52zxcUamRBkVYL/ferdJNi9hoM8ZaO4++Xgs8wMbpmoEch9DsXdtufjqXW
pk7ywlPjcdhhsb3MxJAeEeFtTRsu2/IUTKqKPHIOgoiPzs8q69AxWhV10aDDUdYWLkqPV/
tMGl6S/jC7vTJLmhZum4BUv8MQAAAKC740YkVJdVpTJRTxd9Myi0Nx3t4wAABAC3X2gc8g
/K//yZsxpim7wjqS+PX/3sSPDSd43sJ31wZkuog8HBPJQcRC8pICbgG80/Qt7ckOfdEVrK
FGZ/7xGsCZLh3UyOsI6zp+Z7IueTaROlE8VnbHQfW50hECHNF6HJ+Po6wAefslyWv6aoOG
iruch8Le1KBbEUfmD3jz7uSwAAAJ9mUEtdk3zSMZJ1umUnNSo5zC+UxA==
---- END SSH2 ENCRYPTED PRIVATE KEY ----
@@ -26,7 +26,7 @@ namespace Renci.SshNet.IntegrationTests
[TestMethod]
public void SshDss()
{
DoTest(HostKeyAlgorithm.SshDss, HostKeyFile.Dsa, 2048);
DoTest(HostKeyAlgorithm.SshDss, HostKeyFile.Dsa, 1024);
}
[TestMethod]
@@ -3,7 +3,7 @@
public sealed class HostKeyFile
{
public static readonly HostKeyFile Rsa = new HostKeyFile("ssh-rsa", "/etc/ssh/ssh_host_rsa_key", new byte[] { 0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13, 0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b });
public static readonly HostKeyFile Dsa = new HostKeyFile("ssh-dsa", "/etc/ssh/ssh_host_dsa_key", new byte[] { 0x50, 0xe0, 0xd5, 0x11, 0xf7, 0xed, 0x54, 0x75, 0x0d, 0x03, 0xc6, 0x52, 0x9b, 0x3b, 0x3c, 0x9f });
public static readonly HostKeyFile Dsa = new HostKeyFile("ssh-dsa", "/etc/ssh/ssh_host_dsa_key", new byte[] { 0xcc, 0xb4, 0x4c, 0xe1, 0xba, 0x6d, 0x15, 0x79, 0xec, 0xe1, 0x31, 0x9f, 0xc0, 0x4a, 0x07, 0x9d });
public static readonly HostKeyFile Ed25519 = new HostKeyFile("ssh-ed25519", "/etc/ssh/ssh_host_ed25519_key", new byte[] { 0xb3, 0xb9, 0xd0, 0x1b, 0x73, 0xc4, 0x60, 0xb4, 0xce, 0xed, 0x06, 0xf8, 0x58, 0x49, 0xa3, 0xda });
public const string Ecdsa = "/etc/ssh/ssh_host_ecdsa_key";
@@ -32,6 +32,10 @@ namespace Renci.SshNet.IntegrationTests.TestsFixtures
public SshUser User = new SshUser("sshnet", "ssh4ever");
// To get the sshd logs (also uncomment WithOutputConsumer below)
private readonly Stream _fsOut = Stream.Null; // File.Create("fsout.txt");
private readonly Stream _fsErr = Stream.Null; // File.Create("fserr.txt");
public async Task InitializeAsync()
{
_sshServerImage = new ImageFromDockerfileBuilder()
@@ -47,6 +51,7 @@ namespace Renci.SshNet.IntegrationTests.TestsFixtures
.WithHostname("renci-ssh-tests-server")
.WithImage(_sshServerImage)
.WithPortBinding(22, true)
//.WithOutputConsumer(Consume.RedirectStdoutAndStderrToStream(_fsOut, _fsErr))
.Build();
await _sshServer.StartAsync();
@@ -74,6 +79,9 @@ namespace Renci.SshNet.IntegrationTests.TestsFixtures
{
await _sshServerImage.DisposeAsync();
}
_fsOut.Dispose();
_fsErr.Dispose();
}
public void Dispose()
@@ -1,20 +1,12 @@
-----BEGIN DSA PRIVATE KEY-----
MIIDPgIBAAKCAQEAuXza5HoqeOTKgTBY0iTglJGVLmmGvp9mWbrx20Xj8V1ouy8u
0ceju7/4AR6m9BzYWm2sAMAwvQcDeUi6pD4C4oIRzQSOg/nuUJO6RkneLQjMYEzD
61FokmxcUzHXQiKtqRRGL97naxj5fFIOppQXfllRASuvHeiG+I6EiFJL4zL7Uwen
CshEkpZsLZ2Xj8nfaD8yPmviDT/QWRUsZgw8lte7MonYVdKd0yeRQwS3vgJwusZv
fFHP4X7aXSwDJTlTGagxFV7jCktwtSc6QFoLWv5LZ2OAJxmgBM1HJQKOnP8dvn56
EZub3DQrx3IpAgtsxa/8bxt/xFbbfp4sDHwLLQIVAKTEaiNtqneHljGoEGhUWJrs
+kdpAoIBAQCdBG7aHBIV83/icpkELAZ87I/0XDA9pVG+Sgs/OFgUd24tXi9S+dwp
LsVMVaBnN9TaEwZYR6z7Zg12r2j2q8BDTrRwwYHYJvwjHtsZVqaHi35fgBT2RO4T
SqRKYjrjb4mtPodUEo7CzK5+rLpvLM1SiiHfeqmUJqbkDwxQ9xXkCjRP50huJ+tA
ccgQIUyOYioz9omszJGANZlF5ZabzbAiTcXews2p97OeFWNTGTbXebV3FPSV+KBO
c0A5jxzQhEo3Kk58GXuog8t3OksNISdZPIJxHn+th644ZOj0L1v6PrUbXshPL1hp
VNlbn9fO4/HbQzL4NThmgzaZkT2FqxPxAoIBADuwcLTKtLX2cy9cqFiraeEaBXT3
lQiPTFSLQKVm/k+iumXuOy5Fh3Akzu35MpNLK2gsdoWN9ZRQ8eWODdcnFXSJrnqX
cMWV6ONQ+nZ9YHrRp47KHKWKe+2c0T++S8QZAimb3KCjSOyEwn+i4aAGIvoaYIoH
+tRKmeL+7z1Ff/zJEB1FYVDmcqxhUKd74En6O17EmUHPfiQvwwTYvP5NvlLB23Hz
9ZO4nwrUSUIyVsWYT01s0JThkjI06N0dqKS1we94Ht1mT7iNJ5x5DhVR6qSNOgQH
FMKxKdXHdSFopwwHUrzm3BpKzKW3NuQHazcdZEl1vHb6LpfTv6O6bZIANyACFGBZ
9othW6gmt8t4cI6IyoaLCtLp
MIIBugIBAAKBgQDK1HgEGb/kOVsvVlJpCOpnotUAwoYN1EIAl2Zqj+sxwtUZGR9j
pBV3XYo2IBqTk+2h8Wup8MD3MyoEEMcOem9Jam3VZmvKFTIMBqJglbb+srszsSqw
OiXueV/TPrqMSIEFfNVfPPgHIJ0PwhZ2f3uPidWDEc6t3GzzYRnBFM2zawIVANqN
Qhz4qsDa7V8UamatuXMlhdkHAoGAf28PeajaIq09LwxxN3Ed7FAzsJ9DkodSpjD3
2E/ezv3eyw1RaFR34g2G0R8xRql0ZgJpb48my4JT5JLmcDtZX0xTdy5Gh2Ke/qxR
smazFLds8CSqVK7e6CVaS6xv2dfI+KgsIVXhDL1GCy8KRJjMmYdx/+4KHgNzGhCZ
lA04mw4CgYBQtVdXgipcs8jLUI/9BkUvgiPJibi5SZibxpL+vVCEOKiMK3I82TuM
YR7S/SJWz1W3KQb9Lmrd/nVwKlk1Ey7Q4J487UpnOuN5Mu7P6PbZi1TXd442XG2r
WbFVGIJVr78ZURaa4mykZJ2DRaanmemRAPSvWTnFYQ6qC2CoYDPaLwIUYatoN3LK
IwehRooj2lc7/l2xxXQ=
-----END DSA PRIVATE KEY-----
@@ -0,0 +1,56 @@
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Security;
using Renci.SshNet.Security.Cryptography;
using Renci.SshNet.Tests.Common;
namespace Renci.SshNet.Tests.Classes.Security.Cryptography
{
[TestClass]
public class DsaDigitalSignatureTest : TestBase
{
[TestMethod]
public void Verify()
{
byte[] data = Encoding.UTF8.GetBytes("Hello, World!");
DsaKey dsaKey = GetDsaKey("Key.DSA.txt");
Assert.AreEqual(1024, dsaKey.P.BitLength);
Assert.AreEqual(160, dsaKey.Q.BitLength);
var digitalSignature = new DsaDigitalSignature(dsaKey);
byte[] signedBytes = digitalSignature.Sign(data);
// We can't compare signatures for value equality because they have a source of randomness
Assert.AreEqual(40, signedBytes.Length);
Assert.IsTrue(digitalSignature.Verify(data, signedBytes));
byte[] signatureToVerify = new byte[]
{
// Generated with a previous DsaDigitalSignature implementation in order to confirm consistent
// behaviour. We can't seem to validate against openssl because openssl outputs a DER signature,
// where as we want IEEE P1363 (fixed size) format.
0x07, 0x4c, 0x5e, 0x15, 0x53, 0x36, 0x21, 0xbe, 0x5a, 0x82, 0x35, 0xd5, 0xb6, 0xe6, 0x7d, 0x2f,
0x01, 0x2a, 0x78, 0x9b, 0x16, 0x4a, 0xe5, 0x8d, 0x85, 0xa6, 0x34, 0x56, 0x9d, 0x38, 0xd6, 0x1a,
0xa4, 0xa1, 0x5b, 0x98, 0x7d, 0xd5, 0x35, 0x40
};
Assert.IsTrue(digitalSignature.Verify(data, signatureToVerify));
Assert.IsFalse(digitalSignature.Verify(data, CryptoAbstraction.GenerateRandom(40)));
}
private static DsaKey GetDsaKey(string fileName, string passPhrase = null)
{
using (var stream = GetData(fileName))
{
return (DsaKey)new PrivateKeyFile(stream, passPhrase).Key;
}
}
}
}
@@ -0,0 +1,198 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Security;
using Renci.SshNet.Tests.Common;
namespace Renci.SshNet.Tests.Classes.Security.Cryptography
{
[TestClass]
public class DsaKeyTest : TestBase
{
private static DsaKey GetDsaKey(string fileName, string passPhrase = null)
{
using (var stream = GetData(fileName))
{
return (DsaKey)new PrivateKeyFile(stream, passPhrase).Key;
}
}
// This is just to line up any differences in the assertion message.
private static void AssertEqual(byte[] actualBytes, string expectedHex)
{
string actualHex = BitConverter.ToString(actualBytes).Replace("-", "");
Assert.AreEqual(expectedHex, actualHex,
$"{Environment.NewLine}Expected: {expectedHex}{Environment.NewLine} Actual: {actualHex}");
}
// These tests generated by converting the keys to PKCS8, importing them to BCL DSA,
// and printing out the expected DSAParameter values.
// Some useful commands:
// Generate a new params file with specific parameters:
// openssl genpkey -genparam -algorithm dsa -pkeyopt pbits:1024 -pkeyopt qbits:160 -out dsa.1024.params
// Generate PKCS8 key file from the params:
// openssl genpkey -paramfile dsa.1024.params -out dsa.1024.txt
// Convert to PKCS1:
// openssl pkcs8 -in dsa.1024.txt -nocrypt -traditional -out dsa.1024.pkcs1.txt
// Convert PKCS1 to ssh.com:
// puttygen dsa.1024.pkcs1.txt -O private-sshcom -o dsa.1024.ssh2.txt
// Convert to PKCS8:
// openssl pkcs8 -topk8 -nocrypt -in Key.DSA.txt -out Key.DSA.PKCS8.txt
/*
using IndentedTextWriter tw = new(Console.Out);
foreach (string filePath in Directory.EnumerateFiles(dir, "*.DSA.*txt"))
{
string pkFile = Path.GetFileNameWithoutExtension(filePath);
tw.WriteLine("[TestMethod]");
tw.WriteLine($"public void {pkFile.Replace('.', '_')}()");
tw.WriteLine("{");
tw.Indent++;
tw.WriteLine($"DsaKey dsaKey = GetDsaKey(\"{pkFile}.txt\");");
tw.WriteLine();
tw.WriteLine("DSAParameters p = dsaKey.GetDSAParameters();");
tw.WriteLine();
using DSA dsa = DSA.Create();
dsa.ImportFromPem(File.ReadAllText(filePath));
DSAParameters p = dsa.ExportParameters(true);
WriteParamAssert(p.P);
WriteParamAssert(p.G);
WriteParamAssert(p.Y);
WriteParamAssert(p.Q);
WriteParamAssert(p.X);
tw.Indent--;
tw.WriteLine("}");
tw.WriteLine();
}
void WriteParamAssert(byte[] bytes, [CallerArgumentExpression(nameof(bytes))] string name = null)
{
tw.WriteLine($"AssertEqual({name}, \"{Convert.ToHexString(bytes)}\");");
}
*/
[TestMethod]
public void Key_DSA()
{
DsaKey dsaKey = GetDsaKey("Key.DSA.txt");
Assert.AreEqual(1024, dsaKey.P.BitLength);
Assert.AreEqual(160, dsaKey.Q.BitLength);
DSAParameters p = dsaKey.GetDSAParameters();
AssertEqual(p.P, "B565DDF69ED8EE2AC2C00AF794944A15F428C50D3FECA5FEE4F79461FD4FF669B671D296B4F19D35970A5D20F752847826849C30E12F19B8682BF5020E01FF2BDC338BB7E2A92668D2F2D8B880C62A9DA1B65C346EB53EAC2A779203929DFC2C1B27F2D99BD76C6EF4D6A5A547CE892101D5CC82AF8CC564CAE6D30B5DC89415");
AssertEqual(p.G, "0E549E37E14011DC79FA940E6758D7C53AE5151F75BB9C968FD054098883F1EC651B7713BDAAD0CD4DB5A458BDDCF6AC79F81ECE95EE3133B72FC973EB3505180C7085952F947B6C7721E26B91D7D9907F5E3CFDB9CB9034278FDCFBC5D7BD06A3E330399DFC35DE8CB93EDC9DEDDDCAAB9B440CCF0A8957488709178D40373D");
AssertEqual(p.Y, "9BC2066506AD4BB33A01F3484CD586E1323B6766914232DBF7F316248203EDCFD5438CDD4D9746DE4A64D068FD9F8C6B7A8A4AE4D99801D8FCAED15F3C18265E5B6C2EAB7E6C1717929C56FFFED60B6F563975B7B7DD3249387E716B967EBF5F57B99FE1097FEAEFD9220A5036F9CD61ECDACEDDDF2BC1178C8D5D01712E4311");
AssertEqual(p.Q, "AEFA2364A9FFD838062362B1D20871665807C461");
AssertEqual(p.X, "18463B393E02299EA1BF7AE04F3E1EC40D97275E");
}
[TestMethod]
public void Key_SSH2_DSA_Encrypted_Des_CBC_12345()
{
DsaKey dsaKey = GetDsaKey("Key.SSH2.DSA.Encrypted.Des.CBC.12345.txt", "12345");
Assert.AreEqual(1024, dsaKey.P.BitLength);
Assert.AreEqual(160, dsaKey.Q.BitLength);
DSAParameters p = dsaKey.GetDSAParameters();
AssertEqual(p.P, "8F20C8715C86456190F8B0921923143A8DEB0DA71564EA572F6C2E316CDFCA0458B329030F7E735DF67AB529B4084D43A2595005591A596FFF7579864B120F20A17BD598E487EC3A587A5F475C642C012B2B04CD094FC8C16A02D6DCF6EBFBA1458DCC17EF11BB55E9637F5667265DFEDDCA8DC40555DB04574F97534A0BB5BD");
AssertEqual(p.G, "04B63D3DBA0C7F4DD97A6E8ED7EA65F5EE17205602317188F954D38D83693A20799116B88BBE261A4BAF26201E121B78E6BEFC5B5C01AA4756D6054AEFF749F4C29C7172EB8DA02172949432BAA524CD2B141DBD1631C9DC67FB4142DF8D267441D59A7DB30F27DB4A1CA60DD654F75A5D3B5CE2993CDCD601EB2C06E9414FBE");
AssertEqual(p.Y, "17F837B9770A37DCB325F56F08BC0E1D63C435C2C85DC9C006273872FAA605F5D09CC03D4F83EE046CF8D83E6CC9A29B15ACAF7BBFD421C7261C5CCAFF887867806C1DF6760DCF348AC7AEB9BE44A3CF27895A9790F9FBCD57708B22D3F4D72A5B52B78CFACF95CF5C28AF98E82CA7034B85F4A10D1121564D1079DE92E2EE16");
AssertEqual(p.Q, "D8D7E6D3B7BB3688D998A011380BD85D5910B279");
AssertEqual(p.X, "23FA5A574AF1197B185B88008A7A7527899FD092");
}
[TestMethod]
public void Key_SSH2_DSA()
{
DsaKey dsaKey = GetDsaKey("Key.SSH2.DSA.txt");
Assert.AreEqual(1024, dsaKey.P.BitLength);
Assert.AreEqual(160, dsaKey.Q.BitLength);
DSAParameters p = dsaKey.GetDSAParameters();
AssertEqual(p.P, "E0921AE3B1C58FC3401A301BB7E44B7D00266C0DE1F0CF5D3A324DA01AC70A4E5A71895C58603F8225F8648A9696B19D45A46A32A931CD5BD6185AC0F58DE2CC684FC4116FA4805B7D04AE74FFA54B6CC6AA059641656B8C6F59280D02E1C0564501AE03EC278B077A149B3459F723AABC46095EDA3C870C74CEA8F8F62845E1");
AssertEqual(p.G, "2BBCFEC01A1E6363ECF179045BE76CF17146A644191560BFDF7AB749362F61A0CF1968EE3EF9782CF3031BA66A04721F43B1776DB9F8EA5D6A64EF2C253E371D861B1BDCCC4901E11E16D4D1B2EDBF2144CAA8A3C720E82888FCECF2AEBD0315A1575D1A0C351D6162E4A8F57FB4C1A5E92FE30BBBD324B9A166E9B8054BFC31");
AssertEqual(p.Y, "B75F681CF20FCAFFFC99B31A629BBC23A92F8F5FFDEC48F0D2778DEC277D70664BA883C1C13C941C442F292026E01BCD3F42DEDC90E7DD115ACA14667FEF11AC0992E1DD4C8EB08EB3A7E67B22E7936913A513C5676C741F5B9D211021CD17A1C9F8FA3AC0079FB25C96BFA6A83868ABB9C87C2DED4A05B1147E60F78F3EEE4B");
AssertEqual(p.Q, "BBE34624549755A532514F177D3328B4371DEDE3");
AssertEqual(p.X, "66504B5D937CD2319275BA6527352A39CC2F94C4");
}
[TestMethod]
public void Key_DSA_3072_256()
{
// Not supported by OpenSSH but easy enough to test here.
var keyString = """
-----BEGIN DSA PRIVATE KEY-----
MIIE1gIBAAKCAYEA6Rb9Cogx64CZcrnP35Nr8W9sjcUqoCpSfZrJdvIhTxgyAkEl
d9U868azCTZ1QGvUuPOCGB5Ll8nRQ6QSK9bicSQ3q3C69BcjzTfZcFuZEi533wEl
9m9xZTbMAW8643jgARKszoNWIeTGsk2JcXQ2c6+VfPOml6F7yMv3KDIpqjUQlpK+
RoZn5Eg5R2+2VqcNwwqbP8dyQGnGmgX2hlzbWx9Cld8NTG2b0B6Taaea4zqiMLHC
MeOUh+3WkbGM+aVQ7kthQbkhTEMmZeB/zyJrlEQGmKJl/cuvx69iO8nPSIZJDSEp
sZwg6p+Fqlm0+IyaFaxJMF+SuQirYLR+ee5oor5lcWS3Szaaikz0u1ONO3ndWAmO
eEHq7BL4vuHc/SDxfO5RouhEirCUFtGwtYq/Kf7x53ccd0Jmlj1FalubKHaiyYSg
baHrvN8rFv12XDMI0vPDeOxLVaxaB3zSJTz/ZwDjUcFx7PbuWsvOoSZeagd0sCm9
aVNQoe7dy/5YCrM7AiEA1oznSuokVWnPt9GaBlmggzk9lMjZ/XVr9MFuDzWDycsC
ggGAYVSdm43WuonZQ40M9An5oBZuWsv22ZNXFnMXzcrKkvdslWGP+za/Ipa+tm+j
YE82vuqCdGjDuDJViJbrx2FProe4J/to2Gpd0jc0uV2c1PFO0B1hDk7tkglQyhmV
q9GAQatA4XUM5cIiInseF5JxHMmfgwEKmfIUDotWnpFQImMvehHxZfI29ngrB2ND
Ba4wBIj1Ua4UqsQi2beNfope7+XSsBQm98prswjvdR6n6wg9Xkvtn4ti3lLbEHzO
ZlwnxoRxFlNE/s2RaygGySRRYAe9TPriGWeMpWirYF3/SDAQyt5qDJsPQU0fWAuF
WsuxDlOsaLmJNJNnvySwx5Qmiw4e17U+6/IKptVbOHWowq05PxZKSVYmcP5+Jbt2
eVzozKPNGkEw6aBy18l4t+ehx7fiUha7uCPaZ+VTPodLRZE6sKT1uiKOBtPqhrsp
bxKJ3zzmF1KZpoOG++1JHzqOPL+Npokd+K3ce9vNfU7eWTYP2DuiooyF9EktJt7w
AVilAoIBgBCpFSwJxhdAIQRefnQmweW+eXRbx8KL+8t53YsPS/fQhdzIEVEi5LWz
cJ0iAU2l2Mj43AC/6yehA8KF6vJEy4LWSXNwoGVqEoSA2LxBaomLxLARqlFyfCZN
yBlUVfXDiqFw2ajRaLQKUIxWbfC6ard69brWZuS61YQd8Jrk782VAa+sQO6Ca6Ii
vtyyRjNKxbeYwKLkZydi9JFdYYR6kmVG1ge7spemMHlozza6VvNcDU3hE4T4PwbY
Ns555ihK79EWGO2zCNjhPEIN34IjN2WjbUidCBWPGgXLhw9BYyEEREhfP1QUTe2q
S3gup/j8//v46O+OZFcB6g/MZB2IFpRRSw16qM2+pNZFKVTvXs/dUq5tEXylbMCg
+7jS5eLVAkDYFwSUai4Ht9VOHGASz7VyfPfngL8nx+KLNyegB12OLwr6ho5tc9dE
Rib63kEJnsK6CzIjg1+iFblWy4pQsHnKEgvBWxk4+sLyEZFTtjCe7KjigdH6WRDH
U5ejA3bnFQIhAIkT3ff8AjkByyJg1CRkpwDCvFag1fbPXEdg1Ru1E+l/
-----END DSA PRIVATE KEY-----
""";
using MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(keyString));
DsaKey dsaKey = (DsaKey)new PrivateKeyFile(stream).Key;
Assert.AreEqual(3072, dsaKey.P.BitLength);
Assert.AreEqual(256, dsaKey.Q.BitLength);
DSAParameters p = dsaKey.GetDSAParameters();
AssertEqual(p.P, "E916FD0A8831EB809972B9CFDF936BF16F6C8DC52AA02A527D9AC976F2214F183202412577D53CEBC6B3093675406BD4B8F382181E4B97C9D143A4122BD6E2712437AB70BAF41723CD37D9705B99122E77DF0125F66F716536CC016F3AE378E00112ACCE835621E4C6B24D8971743673AF957CF3A697A17BC8CBF7283229AA35109692BE468667E44839476FB656A70DC30A9B3FC7724069C69A05F6865CDB5B1F4295DF0D4C6D9BD01E9369A79AE33AA230B1C231E39487EDD691B18CF9A550EE4B6141B9214C432665E07FCF226B94440698A265FDCBAFC7AF623BC9CF4886490D2129B19C20EA9F85AA59B4F88C9A15AC49305F92B908AB60B47E79EE68A2BE657164B74B369A8A4CF4BB538D3B79DD58098E7841EAEC12F8BEE1DCFD20F17CEE51A2E8448AB09416D1B0B58ABF29FEF1E7771C774266963D456A5B9B2876A2C984A06DA1EBBCDF2B16FD765C3308D2F3C378EC4B55AC5A077CD2253CFF6700E351C171ECF6EE5ACBCEA1265E6A0774B029BD695350A1EEDDCBFE580AB33B");
AssertEqual(p.G, "61549D9B8DD6BA89D9438D0CF409F9A0166E5ACBF6D99357167317CDCACA92F76C95618FFB36BF2296BEB66FA3604F36BEEA827468C3B832558896EBC7614FAE87B827FB68D86A5DD23734B95D9CD4F14ED01D610E4EED920950CA1995ABD18041AB40E1750CE5C222227B1E1792711CC99F83010A99F2140E8B569E915022632F7A11F165F236F6782B07634305AE300488F551AE14AAC422D9B78D7E8A5EEFE5D2B01426F7CA6BB308EF751EA7EB083D5E4BED9F8B62DE52DB107CCE665C27C68471165344FECD916B2806C924516007BD4CFAE219678CA568AB605DFF483010CADE6A0C9B0F414D1F580B855ACBB10E53AC68B989349367BF24B0C794268B0E1ED7B53EEBF20AA6D55B3875A8C2AD393F164A49562670FE7E25BB76795CE8CCA3CD1A4130E9A072D7C978B7E7A1C7B7E25216BBB823DA67E5533E874B45913AB0A4F5BA228E06D3EA86BB296F1289DF3CE6175299A68386FBED491F3A8E3CBF8DA6891DF8ADDC7BDBCD7D4EDE59360FD83BA2A28C85F4492D26DEF00158A5");
AssertEqual(p.Y, "10A9152C09C6174021045E7E7426C1E5BE79745BC7C28BFBCB79DD8B0F4BF7D085DCC8115122E4B5B3709D22014DA5D8C8F8DC00BFEB27A103C285EAF244CB82D6497370A0656A128480D8BC416A898BC4B011AA51727C264DC8195455F5C38AA170D9A8D168B40A508C566DF0BA6AB77AF5BAD666E4BAD5841DF09AE4EFCD9501AFAC40EE826BA222BEDCB246334AC5B798C0A2E4672762F4915D61847A926546D607BBB297A6307968CF36BA56F35C0D4DE11384F83F06D836CE79E6284AEFD11618EDB308D8E13C420DDF82233765A36D489D08158F1A05CB870F4163210444485F3F54144DEDAA4B782EA7F8FCFFFBF8E8EF8E645701EA0FCC641D881694514B0D7AA8CDBEA4D6452954EF5ECFDD52AE6D117CA56CC0A0FBB8D2E5E2D50240D81704946A2E07B7D54E1C6012CFB5727CF7E780BF27C7E28B3727A0075D8E2F0AFA868E6D73D7444626FADE41099EC2BA0B3223835FA215B956CB8A50B079CA120BC15B1938FAC2F2119153B6309EECA8E281D1FA5910C75397A30376E715");
AssertEqual(p.Q, "D68CE74AEA245569CFB7D19A0659A083393D94C8D9FD756BF4C16E0F3583C9CB");
AssertEqual(p.X, "8913DDF7FC023901CB2260D42464A700C2BC56A0D5F6CF5C4760D51BB513E97F");
}
}
}