DSA is removed at compile time from OpenSSH 9.8 and higher.
That means we can no longer test it in our integration tests. It seems like a
good time to remove it. From the OpenSSH release notes:

    DSA, as specified in the SSHv2 protocol, is inherently weak - being
    limited to a 160 bit private key and use of the SHA1 digest. Its
    estimated security level is only 80 bits symmetric equivalent.

    OpenSSH has disabled DSA keys by default since 2015 but has retained
    run-time optional support for them. DSA was the only mandatory-to-
    implement algorithm in the SSHv2 RFCs, mostly because alternative
    algorithms were encumbered by patents when the SSHv2 protocol was
    specified.

    This has not been the case for decades at this point and better
    algorithms are well supported by all actively-maintained SSH
    implementations. We do not consider the costs of maintaining DSA
    in OpenSSH to be justified and hope that removing it from OpenSSH
    can accelerate its wider deprecation in supporting cryptography
    libraries.
This commit is contained in:
Rob Hague
2025-01-03 11:12:21 +01:00
committed by GitHub
parent 2e68828f61
commit 60f3cd803e
25 changed files with 6 additions and 749 deletions
-1
View File
@@ -407,7 +407,6 @@ namespace Renci.SshNet
hostAlgs.Add("rsa-sha2-512", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-512", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); });
hostAlgs.Add("rsa-sha2-256", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-256", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); });
hostAlgs.Add("ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(new SshKeyData(data))));
hostAlgs.Add("ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(new SshKeyData(data))));
#pragma warning restore SA1107 // Code should not contain multiple statements on one line
HostKeyAlgorithms = hostAlgs;
-2
View File
@@ -80,8 +80,6 @@ namespace Renci.SshNet
{
case "RSA PRIVATE KEY":
return new RsaKey(decryptedData);
case "DSA PRIVATE KEY":
return new DsaKey(decryptedData);
case "EC PRIVATE KEY":
return new EcdsaKey(decryptedData);
default:
-21
View File
@@ -54,27 +54,6 @@ namespace Renci.SshNet
return new RsaKey(key);
}
if (algorithmOid.Equals(X9ObjectIdentifiers.IdDsa))
{
var parameters = privateKeyInfo.PrivateKeyAlgorithm.Parameters.GetDerEncoded();
var parametersReader = new AsnReader(parameters, AsnEncodingRules.BER);
var sequenceReader = parametersReader.ReadSequence();
parametersReader.ThrowIfNotEmpty();
var p = sequenceReader.ReadInteger();
var q = sequenceReader.ReadInteger();
var g = sequenceReader.ReadInteger();
sequenceReader.ThrowIfNotEmpty();
var keyReader = new AsnReader(key, AsnEncodingRules.BER);
var x = keyReader.ReadInteger();
keyReader.ThrowIfNotEmpty();
var y = BigInteger.ModPow(g, x, p);
return new DsaKey(p, q, g, y, x);
}
if (algorithmOid.Equals(X9ObjectIdentifiers.IdECPublicKey))
{
var parameters = privateKeyInfo.PrivateKeyAlgorithm.Parameters.GetDerEncoded();
+2 -10
View File
@@ -184,20 +184,12 @@ namespace Renci.SshNet
var prv = privateKeyReader.ReadBignum2();
parsedKey = new EcdsaKey(curve, pub, prv);
break;
case "ssh-dss":
var p = publicKeyReader.ReadBignum();
var q = publicKeyReader.ReadBignum();
var g = publicKeyReader.ReadBignum();
var y = publicKeyReader.ReadBignum();
var x = privateKeyReader.ReadBignum();
parsedKey = new DsaKey(p, q, g, y, x);
break;
case "ssh-rsa":
var exponent = publicKeyReader.ReadBignum(); // e
var modulus = publicKeyReader.ReadBignum(); // n
var d = privateKeyReader.ReadBignum(); // d
p = privateKeyReader.ReadBignum(); // p
q = privateKeyReader.ReadBignum(); // q
var p = privateKeyReader.ReadBignum(); // p
var q = privateKeyReader.ReadBignum(); // q
var inverseQ = privateKeyReader.ReadBignum(); // iqmp
parsedKey = new RsaKey(modulus, exponent, d, p, q, inverseQ);
break;
-15
View File
@@ -83,21 +83,6 @@ namespace Renci.SshNet
var p = reader.ReadBigIntWithBits(); // q
return new RsaKey(modulus, exponent, d, p, q, inverseQ);
}
else if (keyType.Contains("dsa"))
{
var zero = reader.ReadUInt32();
if (zero != 0)
{
throw new SshException("Invalid private key");
}
var p = reader.ReadBigIntWithBits();
var g = reader.ReadBigIntWithBits();
var q = reader.ReadBigIntWithBits();
var y = reader.ReadBigIntWithBits();
var x = reader.ReadBigIntWithBits();
return new DsaKey(p, q, g, y, x);
}
throw new NotSupportedException(string.Format("Key type '{0}' is not supported.", keyType));
}
-4
View File
@@ -381,10 +381,6 @@ namespace Renci.SshNet
_hostAlgorithms.Add(new KeyHostAlgorithm("rsa-sha2-256", _key, new RsaDigitalSignature(rsaKey, HashAlgorithmName.SHA256)));
#pragma warning restore CA2000 // Dispose objects before losing scope
}
else if (_key is DsaKey)
{
_hostAlgorithms.Add(new KeyHostAlgorithm("ssh-dss", _key));
}
else
{
_hostAlgorithms.Add(new KeyHostAlgorithm(_key.ToString(), _key));
-3
View File
@@ -348,9 +348,6 @@ namespace Renci.SshNet.Security
case "ssh-rsa-cert-v01@openssh.com":
keyData = new SshKeyData("ssh-rsa", LoadPublicKeys(2));
return new RsaKey(keyData);
case "ssh-dss-cert-v01@openssh.com":
keyData = new SshKeyData("ssh-dss", LoadPublicKeys(4));
return new DsaKey(keyData);
case "ecdsa-sha2-nistp256-cert-v01@openssh.com":
case "ecdsa-sha2-nistp384-cert-v01@openssh.com":
case "ecdsa-sha2-nistp521-cert-v01@openssh.com":
@@ -1,86 +0,0 @@
#nullable enable
using System;
using System.Security.Cryptography;
using Renci.SshNet.Common;
namespace Renci.SshNet.Security.Cryptography
{
/// <summary>
/// Implements DSA digital signature algorithm.
/// </summary>
public class DsaDigitalSignature : DigitalSignature, IDisposable
{
private readonly DsaKey _key;
/// <summary>
/// Initializes a new instance of the <see cref="DsaDigitalSignature" /> class.
/// </summary>
/// <param name="key">The DSA key.</param>
/// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception>
public DsaDigitalSignature(DsaKey key)
{
ThrowHelper.ThrowIfNull(key);
_key = key;
}
/// <inheritdoc/>
public override bool Verify(byte[] input, byte[] signature)
{
#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())
{
var hash = sha1.ComputeHash(input);
return _key.DSA.VerifySignature(hash, signature);
}
#endif
}
/// <summary>
/// Creates the signature.
/// </summary>
/// <param name="input">The input.</param>
/// <returns>
/// Signed input data.
/// </returns>
/// <exception cref="SshException">Invalid DSA key.</exception>
public override byte[] Sign(byte[] input)
{
#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 hash = sha1.ComputeHash(input);
return _key.DSA.CreateSignature(hash);
}
#endif
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <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)
{
}
}
}
@@ -1,220 +0,0 @@
#nullable enable
using System;
using System.Formats.Asn1;
using System.Numerics;
using System.Security.Cryptography;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;
namespace Renci.SshNet.Security
{
/// <summary>
/// Contains DSA private and public key.
/// </summary>
public class DsaKey : Key, IDisposable
{
private DsaDigitalSignature? _digitalSignature;
internal DSA DSA { get; }
/// <summary>
/// Gets the P.
/// </summary>
public BigInteger P { get; }
/// <summary>
/// Gets the Q.
/// </summary>
public BigInteger Q { get; }
/// <summary>
/// Gets the G.
/// </summary>
public BigInteger G { get; }
/// <summary>
/// Gets public key Y.
/// </summary>
public BigInteger Y { get; }
/// <summary>
/// Gets private key X.
/// </summary>
public BigInteger X { get; }
/// <inheritdoc/>
public override int KeyLength
{
get
{
return (int)P.GetBitLength();
}
}
/// <summary>
/// Gets the digital signature.
/// </summary>
protected internal override DigitalSignature DigitalSignature
{
get
{
_digitalSignature ??= new DsaDigitalSignature(this);
return _digitalSignature;
}
}
/// <summary>
/// Gets the DSA public key.
/// </summary>
/// <value>
/// An array whose values are:
/// <list>
/// <item><term>0</term><description><see cref="P"/></description></item>
/// <item><term>1</term><description><see cref="Q"/></description></item>
/// <item><term>2</term><description><see cref="G"/></description></item>
/// <item><term>3</term><description><see cref="Y"/></description></item>
/// </list>
/// </value>
public override BigInteger[] Public
{
get
{
return new[] { P, Q, G, Y };
}
}
/// <summary>
/// Initializes a new instance of the <see cref="DsaKey"/> class.
/// </summary>
/// <param name="publicKeyData">The encoded public key data.</param>
public DsaKey(SshKeyData publicKeyData)
{
ThrowHelper.ThrowIfNull(publicKeyData);
if (publicKeyData.Name != "ssh-dss" || publicKeyData.Keys.Length != 4)
{
throw new ArgumentException($"Invalid DSA public key data. ({publicKeyData.Name}, {publicKeyData.Keys.Length}).", nameof(publicKeyData));
}
P = publicKeyData.Keys[0];
Q = publicKeyData.Keys[1];
G = publicKeyData.Keys[2];
Y = publicKeyData.Keys[3];
DSA = LoadDSA();
}
/// <summary>
/// Initializes a new instance of the <see cref="DsaKey"/> class.
/// </summary>
/// <param name="privateKeyData">DER encoded private key data.</param>
public DsaKey(byte[] privateKeyData)
{
ThrowHelper.ThrowIfNull(privateKeyData);
var keyReader = new AsnReader(privateKeyData, AsnEncodingRules.DER);
var sequenceReader = keyReader.ReadSequence();
keyReader.ThrowIfNotEmpty();
_ = sequenceReader.ReadInteger(); // skip version
P = sequenceReader.ReadInteger();
Q = sequenceReader.ReadInteger();
G = sequenceReader.ReadInteger();
Y = sequenceReader.ReadInteger();
X = sequenceReader.ReadInteger();
sequenceReader.ThrowIfNotEmpty();
DSA = LoadDSA();
}
/// <summary>
/// Initializes a new instance of the <see cref="DsaKey" /> class.
/// </summary>
/// <param name="p">The p.</param>
/// <param name="q">The q.</param>
/// <param name="g">The g.</param>
/// <param name="y">The y.</param>
/// <param name="x">The x.</param>
public DsaKey(BigInteger p, BigInteger q, BigInteger g, BigInteger y, BigInteger x)
{
P = p;
Q = q;
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>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <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 (disposing)
{
_digitalSignature?.Dispose();
DSA.Dispose();
}
}
}
}