Skip to content

Commit fe65570

Browse files
authored
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 881eefe.
1 parent c7a0ca9 commit fe65570

File tree

11 files changed

+372
-196
lines changed

11 files changed

+372
-196
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System;
1+
#nullable enable
2+
using System;
23
using System.Security.Cryptography;
34

45
using Renci.SshNet.Common;
@@ -11,8 +12,6 @@ namespace Renci.SshNet.Security.Cryptography
1112
public class DsaDigitalSignature : DigitalSignature, IDisposable
1213
{
1314
private readonly DsaKey _key;
14-
private HashAlgorithm _hash;
15-
private bool _isDisposed;
1615

1716
/// <summary>
1817
/// Initializes a new instance of the <see cref="DsaDigitalSignature" /> class.
@@ -27,71 +26,23 @@ public DsaDigitalSignature(DsaKey key)
2726
}
2827

2928
_key = key;
30-
31-
_hash = SHA1.Create();
3229
}
3330

34-
/// <summary>
35-
/// Verifies the signature.
36-
/// </summary>
37-
/// <param name="input">The input.</param>
38-
/// <param name="signature">The signature.</param>
39-
/// <returns>
40-
/// <see langword="true"/> if signature was successfully verified; otherwise <see langword="false"/>.
41-
/// </returns>
42-
/// <exception cref="InvalidOperationException">Invalid signature.</exception>
31+
/// <inheritdoc/>
4332
public override bool Verify(byte[] input, byte[] signature)
4433
{
45-
var hashInput = _hash.ComputeHash(input);
46-
47-
var hm = new BigInteger(hashInput.Reverse().Concat(new byte[] { 0 }));
48-
49-
if (signature.Length != 40)
50-
{
51-
throw new InvalidOperationException("Invalid signature.");
52-
}
53-
54-
// Extract r and s numbers from the signature
55-
var rBytes = new byte[21];
56-
var sBytes = new byte[21];
57-
58-
for (int i = 0, j = 20; i < 20; i++, j--)
59-
{
60-
rBytes[i] = signature[j - 1];
61-
sBytes[i] = signature[j + 20 - 1];
62-
}
63-
64-
var r = new BigInteger(rBytes);
65-
var s = new BigInteger(sBytes);
66-
67-
// Reject the signature if 0 < r < q or 0 < s < q is not satisfied.
68-
if (r <= 0 || r >= _key.Q)
34+
#if NETSTANDARD2_1_OR_GREATER || NET
35+
return _key.DSA.VerifyData(input, signature, HashAlgorithmName.SHA1);
36+
#else
37+
// VerifyData does not exist on netstandard2.0.
38+
// It does exist on net462, but in order to keep the path tested,
39+
// use it on netfx as well.
40+
using (var sha1 = SHA1.Create())
6941
{
70-
return false;
42+
var hash = sha1.ComputeHash(input);
43+
return _key.DSA.VerifySignature(hash, signature);
7144
}
72-
73-
if (s <= 0 || s >= _key.Q)
74-
{
75-
return false;
76-
}
77-
78-
// Calculate w = s−1 mod q
79-
var w = BigInteger.ModInverse(s, _key.Q);
80-
81-
// Calculate u1 = H(m)·w mod q
82-
var u1 = (hm * w) % _key.Q;
83-
84-
// Calculate u2 = r * w mod q
85-
var u2 = (r * w) % _key.Q;
86-
87-
u1 = BigInteger.ModPow(_key.G, u1, _key.P);
88-
u2 = BigInteger.ModPow(_key.Y, u2, _key.P);
89-
90-
// Calculate v = ((g pow u1 * y pow u2) mod p) mod q
91-
var v = ((u1 * u2) % _key.P) % _key.Q;
92-
93-
// The signature is valid if v = r
94-
return v == r;
45+
#endif
9546
}
9647

9748
/// <summary>
@@ -104,60 +55,18 @@ public override bool Verify(byte[] input, byte[] signature)
10455
/// <exception cref="SshException">Invalid DSA key.</exception>
10556
public override byte[] Sign(byte[] input)
10657
{
107-
var hashInput = _hash.ComputeHash(input);
108-
109-
var m = new BigInteger(hashInput.Reverse().Concat(new byte[] { 0 }));
110-
111-
BigInteger s;
112-
BigInteger r;
113-
114-
do
58+
#if NETSTANDARD2_1_OR_GREATER || NET
59+
return _key.DSA.SignData(input, HashAlgorithmName.SHA1);
60+
#else
61+
// SignData does not exist on netstandard2.0.
62+
// It does exist on net462, but in order to keep the path tested,
63+
// use it on netfx as well.
64+
using (var sha1 = SHA1.Create())
11565
{
116-
var k = BigInteger.Zero;
117-
118-
do
119-
{
120-
// Generate a random per-message value k where 0 < k < q
121-
var bitLength = _key.Q.BitLength;
122-
123-
if (_key.Q < BigInteger.Zero)
124-
{
125-
throw new SshException("Invalid DSA key.");
126-
}
127-
128-
while (k <= 0 || k >= _key.Q)
129-
{
130-
k = BigInteger.Random(bitLength);
131-
}
132-
133-
// Calculate r = ((g pow k) mod p) mod q
134-
r = BigInteger.ModPow(_key.G, k, _key.P) % _key.Q;
135-
136-
// In the unlikely case that r = 0, start again with a different random k
137-
}
138-
while (r.IsZero);
139-
140-
// Calculate s = ((k pow −1)(H(m) + x*r)) mod q
141-
k = BigInteger.ModInverse(k, _key.Q) * (m + (_key.X * r));
142-
143-
s = k % _key.Q;
144-
145-
// In the unlikely case that s = 0, start again with a different random k
66+
var hash = sha1.ComputeHash(input);
67+
return _key.DSA.CreateSignature(hash);
14668
}
147-
while (s.IsZero);
148-
149-
// The signature is (r, s)
150-
var signature = new byte[40];
151-
152-
// issue #1918: pad part with zero's on the left if length is less than 20
153-
var rBytes = r.ToByteArray().Reverse().TrimLeadingZeros();
154-
Array.Copy(rBytes, 0, signature, 20 - rBytes.Length, rBytes.Length);
155-
156-
// issue #1918: pad part with zero's on the left if length is less than 20
157-
var sBytes = s.ToByteArray().Reverse().TrimLeadingZeros();
158-
Array.Copy(sBytes, 0, signature, 40 - sBytes.Length, sBytes.Length);
159-
160-
return signature;
69+
#endif
16170
}
16271

16372
/// <summary>
@@ -175,30 +84,6 @@ public void Dispose()
17584
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
17685
protected virtual void Dispose(bool disposing)
17786
{
178-
if (_isDisposed)
179-
{
180-
return;
181-
}
182-
183-
if (disposing)
184-
{
185-
var hash = _hash;
186-
if (hash != null)
187-
{
188-
hash.Dispose();
189-
_hash = null;
190-
}
191-
192-
_isDisposed = true;
193-
}
194-
}
195-
196-
/// <summary>
197-
/// Finalizes an instance of the <see cref="DsaDigitalSignature"/> class.
198-
/// </summary>
199-
~DsaDigitalSignature()
200-
{
201-
Dispose(disposing: false);
20287
}
20388
}
20489
}

src/Renci.SshNet/Security/Cryptography/DsaKey.cs

+62-26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using System;
1+
#nullable enable
2+
using System;
3+
using System.Security.Cryptography;
24

35
using Renci.SshNet.Common;
46
using Renci.SshNet.Security.Cryptography;
@@ -10,8 +12,9 @@ namespace Renci.SshNet.Security
1012
/// </summary>
1113
public class DsaKey : Key, IDisposable
1214
{
13-
private DsaDigitalSignature _digitalSignature;
14-
private bool _isDisposed;
15+
private DsaDigitalSignature? _digitalSignature;
16+
17+
internal DSA DSA { get; }
1518

1619
/// <summary>
1720
/// Gets the P.
@@ -39,10 +42,10 @@ public class DsaKey : Key, IDisposable
3942
public BigInteger X { get; }
4043

4144
/// <summary>
42-
/// Gets the length of the key.
45+
/// Gets the length of the key in bits.
4346
/// </summary>
4447
/// <value>
45-
/// The length of the key.
48+
/// The bit-length of the key.
4649
/// </value>
4750
public override int KeyLength
4851
{
@@ -104,6 +107,8 @@ public DsaKey(SshKeyData publicKeyData)
104107
Q = publicKeyData.Keys[1];
105108
G = publicKeyData.Keys[2];
106109
Y = publicKeyData.Keys[3];
110+
111+
DSA = LoadDSA();
107112
}
108113

109114
/// <summary>
@@ -130,6 +135,8 @@ public DsaKey(byte[] privateKeyData)
130135
{
131136
throw new InvalidOperationException("Invalid private key (expected EOF).");
132137
}
138+
139+
DSA = LoadDSA();
133140
}
134141

135142
/// <summary>
@@ -147,6 +154,54 @@ public DsaKey(BigInteger p, BigInteger q, BigInteger g, BigInteger y, BigInteger
147154
G = g;
148155
Y = y;
149156
X = x;
157+
158+
DSA = LoadDSA();
159+
}
160+
161+
#pragma warning disable CA1859 // Use concrete types when possible for improved performance
162+
#pragma warning disable CA5384 // Do Not Use Digital Signature Algorithm (DSA)
163+
private DSA LoadDSA()
164+
{
165+
#if NETFRAMEWORK
166+
// On .NET Framework we use the concrete CNG type which is FIPS-186-3
167+
// compatible. The CryptoServiceProvider type returned by DSA.Create()
168+
// is limited to FIPS-186-1 (max 1024 bit key).
169+
var dsa = new DSACng();
170+
#else
171+
var dsa = DSA.Create();
172+
#endif
173+
dsa.ImportParameters(GetDSAParameters());
174+
175+
return dsa;
176+
}
177+
#pragma warning restore CA5384 // Do Not Use Digital Signature Algorithm (DSA)
178+
#pragma warning restore CA1859 // Use concrete types when possible for improved performance
179+
180+
internal DSAParameters GetDSAParameters()
181+
{
182+
// P, G, Y, Q are required.
183+
// P, G, Y must have the same length.
184+
// If X is present, it must have the same length as Q.
185+
186+
// See https://github.com/dotnet/runtime/blob/fadd8313653f71abd0068c8bf914be88edb2c8d3/src/libraries/Common/src/System/Security/Cryptography/DSACng.ImportExport.cs#L23
187+
// and https://github.com/dotnet/runtime/blob/fadd8313653f71abd0068c8bf914be88edb2c8d3/src/libraries/Common/src/System/Security/Cryptography/DSAKeyFormatHelper.cs#L18
188+
// (and similar code in RsaKey.cs)
189+
190+
var ret = new DSAParameters
191+
{
192+
P = P.ToByteArray(isUnsigned: true, isBigEndian: true),
193+
Q = Q.ToByteArray(isUnsigned: true, isBigEndian: true),
194+
};
195+
196+
ret.G = G.ExportKeyParameter(ret.P.Length);
197+
ret.Y = Y.ExportKeyParameter(ret.P.Length);
198+
199+
if (!X.IsZero)
200+
{
201+
ret.X = X.ExportKeyParameter(ret.Q.Length);
202+
}
203+
204+
return ret;
150205
}
151206

152207
/// <summary>
@@ -164,30 +219,11 @@ public void Dispose()
164219
/// <param name="disposing"><see langword="true"/> to release both managed and unmanaged resources; <see langword="false"/> to release only unmanaged resources.</param>
165220
protected virtual void Dispose(bool disposing)
166221
{
167-
if (_isDisposed)
168-
{
169-
return;
170-
}
171-
172222
if (disposing)
173223
{
174-
var digitalSignature = _digitalSignature;
175-
if (digitalSignature != null)
176-
{
177-
digitalSignature.Dispose();
178-
_digitalSignature = null;
179-
}
180-
181-
_isDisposed = true;
224+
_digitalSignature?.Dispose();
225+
DSA.Dispose();
182226
}
183227
}
184-
185-
/// <summary>
186-
/// Finalizes an instance of the <see cref="DsaKey"/> class.
187-
/// </summary>
188-
~DsaKey()
189-
{
190-
Dispose(disposing: false);
191-
}
192228
}
193229
}

src/Renci.SshNet/Security/Cryptography/RsaKey.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ public override string ToString()
9292
public BigInteger InverseQ { get; }
9393

9494
/// <summary>
95-
/// Gets the length of the key.
95+
/// Gets the length of the key in bits.
9696
/// </summary>
9797
/// <value>
98-
/// The length of the key.
98+
/// The bit-length of the key.
9999
/// </value>
100100
public override int KeyLength
101101
{

test/Data/Key.SSH2.DSA.pub

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ssh-dss AAAAB3NzaC1kc3MAAACBAI8gyHFchkVhkPiwkhkjFDqN6w2nFWTqVy9sLjFs38oEWLMpAw9+c132erUptAhNQ6JZUAVZGllv/3V5hksSDyChe9WY5IfsOlh6X0dcZCwBKysEzQlPyMFqAtbc9uv7oUWNzBfvEbtV6WN/VmcmXf7dyo3EBVXbBFdPl1NKC7W9AAAAFQDY1+bTt7s2iNmYoBE4C9hdWRCyeQAAAIAEtj09ugx/Tdl6bo7X6mX17hcgVgIxcYj5VNONg2k6IHmRFriLviYaS68mIB4SG3jmvvxbXAGqR1bWBUrv90n0wpxxcuuNoCFylJQyuqUkzSsUHb0WMcncZ/tBQt+NJnRB1Zp9sw8n20ocpg3WVPdaXTtc4pk83NYB6ywG6UFPvgAAAIAX+De5dwo33LMl9W8IvA4dY8Q1wshdycAGJzhy+qYF9dCcwD1Pg+4EbPjYPmzJopsVrK97v9QhxyYcXMr/iHhngGwd9nYNzzSKx665vkSjzyeJWpeQ+fvNV3CLItP01ypbUreM+s+Vz1wor5joLKcDS4X0oQ0RIVZNEHnekuLuFg==
1+
ssh-dss AAAAB3NzaC1kc3MAAACBAOCSGuOxxY/DQBowG7fkS30AJmwN4fDPXToyTaAaxwpOWnGJXFhgP4Il+GSKlpaxnUWkajKpMc1b1hhawPWN4sxoT8QRb6SAW30ErnT/pUtsxqoFlkFla4xvWSgNAuHAVkUBrgPsJ4sHehSbNFn3I6q8Rgle2jyHDHTOqPj2KEXhAAAAFQC740YkVJdVpTJRTxd9Myi0Nx3t4wAAAIArvP7AGh5jY+zxeQRb52zxcUamRBkVYL/ferdJNi9hoM8ZaO4++Xgs8wMbpmoEch9DsXdtufjqXWpk7ywlPjcdhhsb3MxJAeEeFtTRsu2/IUTKqKPHIOgoiPzs8q69AxWhV10aDDUdYWLkqPV/tMGl6S/jC7vTJLmhZum4BUv8MQAAAIEAt19oHPIPyv/8mbMaYpu8I6kvj1/97Ejw0neN7Cd9cGZLqIPBwTyUHEQvKSAm4BvNP0Le3JDn3RFayhRmf+8RrAmS4d1MjrCOs6fmeyLnk2kTpRPFZ2x0H1udIRAhzRehyfj6OsAHn7Jclr+mqDhoq7nIfC3tSgWxFH5g948+7ks= imported-openssh-key

test/Data/Key.SSH2.DSA.txt

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----
2+
Comment: "imported-openssh-key"
23
P2/56wAAAgIAAAAmZGwtbW9kcHtzaWdue2RzYS1uaXN0LXNoYTF9LGRoe3BsYWlufX0AAA
3-
AEbm9uZQAAAcQAAAHAAAAAAAAABACPIMhxXIZFYZD4sJIZIxQ6jesNpxVk6lcvbC4xbN/K
4-
BFizKQMPfnNd9nq1KbQITUOiWVAFWRpZb/91eYZLEg8goXvVmOSH7DpYel9HXGQsASsrBM
5-
0JT8jBagLW3Pbr+6FFjcwX7xG7Veljf1ZnJl3+3cqNxAVV2wRXT5dTSgu1vQAAA/sEtj09
6-
ugx/Tdl6bo7X6mX17hcgVgIxcYj5VNONg2k6IHmRFriLviYaS68mIB4SG3jmvvxbXAGqR1
7-
bWBUrv90n0wpxxcuuNoCFylJQyuqUkzSsUHb0WMcncZ/tBQt+NJnRB1Zp9sw8n20ocpg3W
8-
VPdaXTtc4pk83NYB6ywG6UFPvgAAAKDY1+bTt7s2iNmYoBE4C9hdWRCyeQAAA/0X+De5dw
9-
o33LMl9W8IvA4dY8Q1wshdycAGJzhy+qYF9dCcwD1Pg+4EbPjYPmzJopsVrK97v9QhxyYc
10-
XMr/iHhngGwd9nYNzzSKx665vkSjzyeJWpeQ+fvNV3CLItP01ypbUreM+s+Vz1wor5joLK
11-
cDS4X0oQ0RIVZNEHnekuLuFgAAAJ4j+lpXSvEZexhbiACKenUniZ/Qkg==
4+
AEbm9uZQAAAcQAAAHAAAAAAAAABADgkhrjscWPw0AaMBu35Et9ACZsDeHwz106Mk2gGscK
5+
TlpxiVxYYD+CJfhkipaWsZ1FpGoyqTHNW9YYWsD1jeLMaE/EEW+kgFt9BK50/6VLbMaqBZ
6+
ZBZWuMb1koDQLhwFZFAa4D7CeLB3oUmzRZ9yOqvEYJXto8hwx0zqj49ihF4QAAA/4rvP7A
7+
Gh5jY+zxeQRb52zxcUamRBkVYL/ferdJNi9hoM8ZaO4++Xgs8wMbpmoEch9DsXdtufjqXW
8+
pk7ywlPjcdhhsb3MxJAeEeFtTRsu2/IUTKqKPHIOgoiPzs8q69AxWhV10aDDUdYWLkqPV/
9+
tMGl6S/jC7vTJLmhZum4BUv8MQAAAKC740YkVJdVpTJRTxd9Myi0Nx3t4wAABAC3X2gc8g
10+
/K//yZsxpim7wjqS+PX/3sSPDSd43sJ31wZkuog8HBPJQcRC8pICbgG80/Qt7ckOfdEVrK
11+
FGZ/7xGsCZLh3UyOsI6zp+Z7IueTaROlE8VnbHQfW50hECHNF6HJ+Po6wAefslyWv6aoOG
12+
iruch8Le1KBbEUfmD3jz7uSwAAAJ9mUEtdk3zSMZJ1umUnNSo5zC+UxA==
1213
---- END SSH2 ENCRYPTED PRIVATE KEY ----

test/Renci.SshNet.IntegrationTests/HostKeyAlgorithmTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public void TearDown()
2626
[TestMethod]
2727
public void SshDss()
2828
{
29-
DoTest(HostKeyAlgorithm.SshDss, HostKeyFile.Dsa, 2048);
29+
DoTest(HostKeyAlgorithm.SshDss, HostKeyFile.Dsa, 1024);
3030
}
3131

3232
[TestMethod]

test/Renci.SshNet.IntegrationTests/HostKeyFile.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
public sealed class HostKeyFile
44
{
55
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 });
6-
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 });
6+
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 });
77
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 });
88
public const string Ecdsa = "/etc/ssh/ssh_host_ecdsa_key";
99

0 commit comments

Comments
 (0)