Skip to content

Commit

Permalink
Make keys immutable (#1264)
Browse files Browse the repository at this point in the history
This makes it easier to reason about Key instances in e.g.
DigitalSignature implementations, because we know that the
Key is initialised with its data and will not change.

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>
Rob-Hague and WojciechNagorski authored Dec 21, 2023
1 parent e998d87 commit 24838e6
Showing 15 changed files with 295 additions and 516 deletions.
2 changes: 1 addition & 1 deletion src/Renci.SshNet/Common/SshData.cs
Original file line number Diff line number Diff line change
@@ -231,7 +231,7 @@ protected ulong ReadUInt64()
/// <returns>
/// The <see cref="string"/> that was read.
/// </returns>
protected string ReadString(Encoding encoding)
protected string ReadString(Encoding encoding = null)
{
return _stream.ReadString(encoding);
}
6 changes: 4 additions & 2 deletions src/Renci.SshNet/Common/SshDataStream.cs
Original file line number Diff line number Diff line change
@@ -249,12 +249,14 @@ public ulong ReadUInt64()
/// <summary>
/// Reads the next <see cref="string"/> data type from the SSH data stream.
/// </summary>
/// <param name="encoding">The character encoding to use.</param>
/// <param name="encoding">The character encoding to use. Defaults to <see cref="Encoding.UTF8"/>.</param>
/// <returns>
/// The <see cref="string"/> read from the SSH data stream.
/// </returns>
public string ReadString(Encoding encoding)
public string ReadString(Encoding encoding = null)
{
encoding ??= Encoding.UTF8;

var length = ReadUInt32();

if (length > int.MaxValue)
16 changes: 8 additions & 8 deletions src/Renci.SshNet/ConnectionInfo.cs
Original file line number Diff line number Diff line change
@@ -396,16 +396,16 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy

HostKeyAlgorithms = new Dictionary<string, Func<byte[], KeyHostAlgorithm>>
{
{ "ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(), data) },
{ "ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(), data) },
{ "ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(), data) },
{ "ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(), data) },
{ "ssh-ed25519", data => new KeyHostAlgorithm("ssh-ed25519", new ED25519Key(new SshKeyData(data))) },
{ "ecdsa-sha2-nistp256", data => new KeyHostAlgorithm("ecdsa-sha2-nistp256", new EcdsaKey(new SshKeyData(data))) },
{ "ecdsa-sha2-nistp384", data => new KeyHostAlgorithm("ecdsa-sha2-nistp384", new EcdsaKey(new SshKeyData(data))) },
{ "ecdsa-sha2-nistp521", data => new KeyHostAlgorithm("ecdsa-sha2-nistp521", new EcdsaKey(new SshKeyData(data))) },
#pragma warning disable SA1107 // Code should not contain multiple statements on one line
{ "rsa-sha2-512", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-512", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); } },
{ "rsa-sha2-256", data => { var key = new RsaKey(); return new KeyHostAlgorithm("rsa-sha2-256", key, data, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); } },
{ "rsa-sha2-512", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-512", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA512)); } },
{ "rsa-sha2-256", data => { var key = new RsaKey(new SshKeyData(data)); return new KeyHostAlgorithm("rsa-sha2-256", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA256)); } },
#pragma warning restore SA1107 // Code should not contain multiple statements on one line
{ "ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data) },
{ "ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(), data) },
{ "ssh-rsa", data => new KeyHostAlgorithm("ssh-rsa", new RsaKey(new SshKeyData(data))) },
{ "ssh-dss", data => new KeyHostAlgorithm("ssh-dss", new DsaKey(new SshKeyData(data))) },
};

CompressionAlgorithms = new Dictionary<string, Type>
10 changes: 6 additions & 4 deletions src/Renci.SshNet/PrivateKeyFile.cs
Original file line number Diff line number Diff line change
@@ -573,12 +573,14 @@ private static Key ParseOpenSshV1Key(byte[] keyFileData, string passPhrase)
switch (keyType)
{
case "ssh-ed25519":
// public key
publicKey = privateKeyReader.ReadBignum2();
// https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-11#section-3.2.3

// private key
// ENC(A)
_ = privateKeyReader.ReadBignum2();

// k || ENC(A)
unencryptedPrivateKey = privateKeyReader.ReadBignum2();
parsedKey = new ED25519Key(publicKey.Reverse(), unencryptedPrivateKey);
parsedKey = new ED25519Key(unencryptedPrivateKey);
break;
case "ecdsa-sha2-nistp256":
case "ecdsa-sha2-nistp384":
107 changes: 53 additions & 54 deletions src/Renci.SshNet/Security/Cryptography/DsaKey.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;

using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;

@@ -15,57 +16,27 @@ public class DsaKey : Key, IDisposable
/// <summary>
/// Gets the P.
/// </summary>
public BigInteger P
{
get
{
return _privateKey[0];
}
}
public BigInteger P { get; }

/// <summary>
/// Gets the Q.
/// </summary>
public BigInteger Q
{
get
{
return _privateKey[1];
}
}
public BigInteger Q { get; }

/// <summary>
/// Gets the G.
/// </summary>
public BigInteger G
{
get
{
return _privateKey[2];
}
}
public BigInteger G { get; }

/// <summary>
/// Gets public key Y.
/// </summary>
public BigInteger Y
{
get
{
return _privateKey[3];
}
}
public BigInteger Y { get; }

/// <summary>
/// Gets private key X.
/// </summary>
public BigInteger X
{
get
{
return _privateKey[4];
}
}
public BigInteger X { get; }

/// <summary>
/// Gets the length of the key.
@@ -94,46 +65,70 @@ protected internal override DigitalSignature DigitalSignature
}

/// <summary>
/// Gets or sets the public.
/// Gets the DSA public key.
/// </summary>
/// <value>
/// The public.
/// 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 };
}
set
{
if (value.Length != 4)
{
throw new InvalidOperationException("Invalid public key.");
}

_privateKey = value;
}
}

/// <summary>
/// Initializes a new instance of the <see cref="DsaKey"/> class.
/// </summary>
public DsaKey()
/// <param name="publicKeyData">The encoded public key data.</param>
public DsaKey(SshKeyData publicKeyData)
{
_privateKey = new BigInteger[5];
if (publicKeyData is null)
{
throw new ArgumentNullException(nameof(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];
}

/// <summary>
/// Initializes a new instance of the <see cref="DsaKey"/> class.
/// </summary>
/// <param name="data">DER encoded private key data.</param>
public DsaKey(byte[] data)
: base(data)
/// <param name="privateKeyData">DER encoded private key data.</param>
public DsaKey(byte[] privateKeyData)
{
if (_privateKey.Length != 5)
if (privateKeyData is null)
{
throw new ArgumentNullException(nameof(privateKeyData));
}

var der = new DerData(privateKeyData);
_ = der.ReadBigInteger(); // skip version

P = der.ReadBigInteger();
Q = der.ReadBigInteger();
G = der.ReadBigInteger();
Y = der.ReadBigInteger();
X = der.ReadBigInteger();

if (!der.IsEndOfData)
{
throw new InvalidOperationException("Invalid private key.");
throw new InvalidOperationException("Invalid private key (expected EOF).");
}
}

@@ -147,7 +142,11 @@ public DsaKey(byte[] data)
/// <param name="x">The x.</param>
public DsaKey(BigInteger p, BigInteger q, BigInteger g, BigInteger y, BigInteger x)
{
_privateKey = new BigInteger[5] { p, q, g, y, x };
P = p;
Q = q;
G = g;
Y = y;
X = x;
}

/// <summary>
68 changes: 26 additions & 42 deletions src/Renci.SshNet/Security/Cryptography/ED25519Key.cs
Original file line number Diff line number Diff line change
@@ -11,13 +11,7 @@ namespace Renci.SshNet.Security
/// </summary>
public class ED25519Key : Key, IDisposable
{
#pragma warning disable IDE1006 // Naming Styles
#pragma warning disable SX1309 // Field names should begin with underscore
private readonly byte[] privateKey = new byte[Ed25519.ExpandedPrivateKeySizeInBytes];
#pragma warning restore SX1309 // Field names should begin with underscore
#pragma warning restore IDE1006 // Naming Styles
private ED25519DigitalSignature _digitalSignature;
private byte[] _publicKey = new byte[Ed25519.PublicKeySizeInBytes];
private bool _isDisposed;

/// <summary>
@@ -32,20 +26,16 @@ public override string ToString()
}

/// <summary>
/// Gets or sets the public.
/// Gets the Ed25519 public key.
/// </summary>
/// <value>
/// The public.
/// An array with <see cref="PublicKey"/> encoded at index 0.
/// </value>
public override BigInteger[] Public
{
get
{
return new BigInteger[] { _publicKey.ToBigInteger2() };
}
set
{
_publicKey = value[0].ToByteArray().Reverse().TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
return new BigInteger[] { PublicKey.ToBigInteger2() };
}
}

@@ -78,52 +68,46 @@ protected internal override DigitalSignature DigitalSignature
/// <summary>
/// Gets the PublicKey Bytes.
/// </summary>
public byte[] PublicKey
{
get
{
return _publicKey;
}
}
public byte[] PublicKey { get; }

/// <summary>
/// Gets the PrivateKey Bytes.
/// </summary>
public byte[] PrivateKey
{
get
{
return privateKey;
}
}
public byte[] PrivateKey { get; }

/// <summary>
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
/// </summary>
public ED25519Key()
/// <param name="publicKeyData">The encoded public key data.</param>
public ED25519Key(SshKeyData publicKeyData)
{
}
if (publicKeyData is null)
{
throw new ArgumentNullException(nameof(publicKeyData));
}

/// <summary>
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
/// </summary>
/// <param name="pk">pk data.</param>
public ED25519Key(byte[] pk)
{
_publicKey = pk.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
if (publicKeyData.Name != "ssh-ed25519" || publicKeyData.Keys.Length != 1)
{
throw new ArgumentException($"Invalid Ed25519 public key data ({publicKeyData.Name}, {publicKeyData.Keys.Length}).", nameof(publicKeyData));
}

PublicKey = publicKeyData.Keys[0].ToByteArray().Reverse().TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
PrivateKey = new byte[Ed25519.ExpandedPrivateKeySizeInBytes];
}

/// <summary>
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
/// </summary>
/// <param name="pk">pk data.</param>
/// <param name="sk">sk data.</param>
public ED25519Key(byte[] pk, byte[] sk)
/// <param name="privateKeyData">
/// The private key data <c>k || ENC(A)</c> as described in RFC 8032.
/// </param>
public ED25519Key(byte[] privateKeyData)
{
_publicKey = pk.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
var seed = new byte[Ed25519.PrivateKeySeedSizeInBytes];
Buffer.BlockCopy(sk, 0, seed, 0, seed.Length);
Ed25519.KeyPairFromSeed(out _publicKey, out privateKey, seed);
Buffer.BlockCopy(privateKeyData, 0, seed, 0, seed.Length);
Ed25519.KeyPairFromSeed(out var publicKey, out var privateKey, seed);
PublicKey = publicKey;
PrivateKey = privateKey;
}

/// <summary>
31 changes: 20 additions & 11 deletions src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs
Original file line number Diff line number Diff line change
@@ -140,10 +140,11 @@ protected internal override DigitalSignature DigitalSignature
}

/// <summary>
/// Gets or sets the public.
/// Gets the ECDSA public key.
/// </summary>
/// <value>
/// The public.
/// An array with the ASCII-encoded curve identifier (e.g. "nistp256")
/// at index 0, and the public curve point Q at index 1.
/// </value>
public override BigInteger[] Public
{
@@ -213,14 +214,6 @@ public override BigInteger[] Public
// returns Curve-Name and x/y as ECPoint
return new[] { new BigInteger(curve.Reverse()), new BigInteger(q.Reverse()) };
}
set
{
var curve_s = Encoding.ASCII.GetString(value[0].ToByteArray().Reverse());
var curve_oid = GetCurveOid(curve_s);

var publickey = value[1].ToByteArray().Reverse();
Import(curve_oid, publickey, privatekey: null);
}
}

/// <summary>
@@ -236,8 +229,24 @@ public override BigInteger[] Public
/// <summary>
/// Initializes a new instance of the <see cref="EcdsaKey"/> class.
/// </summary>
public EcdsaKey()
/// <param name="publicKeyData">The encoded public key data.</param>
public EcdsaKey(SshKeyData publicKeyData)
{
if (publicKeyData is null)
{
throw new ArgumentNullException(nameof(publicKeyData));
}

if (!publicKeyData.Name.StartsWith("ecdsa-sha2-", StringComparison.Ordinal) || publicKeyData.Keys.Length != 2)
{
throw new ArgumentException($"Invalid ECDSA public key data. ({publicKeyData.Name}, {publicKeyData.Keys.Length}).", nameof(publicKeyData));
}

var curve_s = Encoding.ASCII.GetString(publicKeyData.Keys[0].ToByteArray().Reverse());
var curve_oid = GetCurveOid(curve_s);

var publickey = publicKeyData.Keys[1].ToByteArray().Reverse();
Import(curve_oid, publickey, privatekey: null);
}

/// <summary>
46 changes: 3 additions & 43 deletions src/Renci.SshNet/Security/Cryptography/Key.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;

using Renci.SshNet.Common;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;

namespace Renci.SshNet.Security
@@ -11,25 +8,18 @@ namespace Renci.SshNet.Security
/// </summary>
public abstract class Key
{
/// <summary>
/// Specifies array of big integers that represent private key.
/// </summary>
#pragma warning disable SA1401 // Fields should be private
protected BigInteger[] _privateKey;
#pragma warning restore SA1401 // Fields should be private

/// <summary>
/// Gets the default digital signature implementation for this key.
/// </summary>
protected internal abstract DigitalSignature DigitalSignature { get; }

/// <summary>
/// Gets or sets the public key.
/// Gets the public key.
/// </summary>
/// <value>
/// The public.
/// </value>
public abstract BigInteger[] Public { get; set; }
public abstract BigInteger[] Public { get; }

/// <summary>
/// Gets the length of the key.
@@ -44,36 +34,6 @@ public abstract class Key
/// </summary>
public string Comment { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="Key"/> class.
/// </summary>
/// <param name="data">DER encoded private key data.</param>
protected Key(byte[] data)
{
if (data is null)
{
throw new ArgumentNullException(nameof(data));
}

var der = new DerData(data);
_ = der.ReadBigInteger(); // skip version

var keys = new List<BigInteger>();
while (!der.IsEndOfData)
{
keys.Add(der.ReadBigInteger());
}

_privateKey = keys.ToArray();
}

/// <summary>
/// Initializes a new instance of the <see cref="Key"/> class.
/// </summary>
protected Key()
{
}

/// <summary>
/// Signs the specified data with the key.
/// </summary>
171 changes: 56 additions & 115 deletions src/Renci.SshNet/Security/Cryptography/RsaKey.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;

using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;

@@ -29,141 +30,63 @@ public override string ToString()
/// <value>
/// The modulus.
/// </value>
public BigInteger Modulus
{
get
{
return _privateKey[0];
}
}
public BigInteger Modulus { get; }

/// <summary>
/// Gets the exponent.
/// </summary>
/// <value>
/// The exponent.
/// </value>
public BigInteger Exponent
{
get
{
return _privateKey[1];
}
}
public BigInteger Exponent { get; }

/// <summary>
/// Gets the D.
/// </summary>
/// <value>
/// The D.
/// </value>
public BigInteger D
{
get
{
if (_privateKey.Length > 2)
{
return _privateKey[2];
}

return BigInteger.Zero;
}
}
public BigInteger D { get; }

/// <summary>
/// Gets the P.
/// </summary>
/// <value>
/// The P.
/// </value>
public BigInteger P
{
get
{
if (_privateKey.Length > 3)
{
return _privateKey[3];
}

return BigInteger.Zero;
}
}
public BigInteger P { get; }

/// <summary>
/// Gets the Q.
/// </summary>
/// <value>
/// The Q.
/// </value>
public BigInteger Q
{
get
{
if (_privateKey.Length > 4)
{
return _privateKey[4];
}

return BigInteger.Zero;
}
}
public BigInteger Q { get; }

/// <summary>
/// Gets the DP.
/// </summary>
/// <value>
/// The DP.
/// </value>
public BigInteger DP
{
get
{
if (_privateKey.Length > 5)
{
return _privateKey[5];
}

return BigInteger.Zero;
}
}
public BigInteger DP { get; }

/// <summary>
/// Gets the DQ.
/// </summary>
/// <value>
/// The DQ.
/// </value>
public BigInteger DQ
{
get
{
if (_privateKey.Length > 6)
{
return _privateKey[6];
}

return BigInteger.Zero;
}
}
public BigInteger DQ { get; }

/// <summary>
/// Gets the inverse Q.
/// </summary>
/// <value>
/// The inverse Q.
/// </value>
public BigInteger InverseQ
{
get
{
if (_privateKey.Length > 7)
{
return _privateKey[7];
}

return BigInteger.Zero;
}
}
public BigInteger InverseQ { get; }

/// <summary>
/// Gets the length of the key.
@@ -196,45 +119,66 @@ protected internal override DigitalSignature DigitalSignature
}

/// <summary>
/// Gets or sets the public.
/// Gets the RSA public key.
/// </summary>
/// <value>
/// The public.
/// An array with <see cref="Exponent"/> at index 0, and <see cref="Modulus"/>
/// at index 1.
/// </value>
public override BigInteger[] Public
{
get
{
return new[] { Exponent, Modulus };
}
set
{
if (value.Length != 2)
{
throw new InvalidOperationException("Invalid private key.");
}

_privateKey = new[] { value[1], value[0] };
}
}

/// <summary>
/// Initializes a new instance of the <see cref="RsaKey"/> class.
/// </summary>
public RsaKey()
/// <param name="publicKeyData">The encoded public key data.</param>
public RsaKey(SshKeyData publicKeyData)
{
if (publicKeyData is null)
{
throw new ArgumentNullException(nameof(publicKeyData));
}

if (publicKeyData.Name != "ssh-rsa" || publicKeyData.Keys.Length != 2)
{
throw new ArgumentException($"Invalid RSA public key data. ({publicKeyData.Name}, {publicKeyData.Keys.Length}).", nameof(publicKeyData));
}

Exponent = publicKeyData.Keys[0];
Modulus = publicKeyData.Keys[1];
}

/// <summary>
/// Initializes a new instance of the <see cref="RsaKey"/> class.
/// </summary>
/// <param name="data">DER encoded private key data.</param>
public RsaKey(byte[] data)
: base(data)
/// <param name="privateKeyData">DER encoded private key data.</param>
public RsaKey(byte[] privateKeyData)
{
if (_privateKey.Length != 8)
if (privateKeyData is null)
{
throw new InvalidOperationException("Invalid private key.");
throw new ArgumentNullException(nameof(privateKeyData));
}

var der = new DerData(privateKeyData);
_ = der.ReadBigInteger(); // skip version

Modulus = der.ReadBigInteger();
Exponent = der.ReadBigInteger();
D = der.ReadBigInteger();
P = der.ReadBigInteger();
Q = der.ReadBigInteger();
DP = der.ReadBigInteger();
DQ = der.ReadBigInteger();
InverseQ = der.ReadBigInteger();

if (!der.IsEndOfData)
{
throw new InvalidOperationException("Invalid private key (expected EOF).");
}
}

@@ -249,22 +193,19 @@ public RsaKey(byte[] data)
/// <param name="inverseQ">The inverse Q.</param>
public RsaKey(BigInteger modulus, BigInteger exponent, BigInteger d, BigInteger p, BigInteger q, BigInteger inverseQ)
{
_privateKey = new BigInteger[8]
{
modulus,
exponent,
d,
p,
q,
PrimeExponent(d, p),
PrimeExponent(d, q),
inverseQ
};
Modulus = modulus;
Exponent = exponent;
D = d;
P = p;
Q = q;
DP = PrimeExponent(d, p);
DQ = PrimeExponent(d, q);
InverseQ = inverseQ;
}

private static BigInteger PrimeExponent(BigInteger privateExponent, BigInteger prime)
{
var pe = prime - new BigInteger(1);
var pe = prime - BigInteger.One;
return privateExponent % pe;
}

173 changes: 6 additions & 167 deletions src/Renci.SshNet/Security/KeyHostAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System.Collections.Generic;
using System.Text;
using System.Text;

using Renci.SshNet.Common;
using Renci.SshNet.Security.Chaos.NaCl;
using Renci.SshNet.Security.Cryptography;

namespace Renci.SshNet.Security
@@ -15,17 +13,11 @@ public class KeyHostAlgorithm : HostAlgorithm
/// <summary>
/// Gets the key used in this host key algorithm.
/// </summary>
/// <value>
/// The key used in this host key algorithm.
/// </value>
public Key Key { get; private set; }

/// <summary>
/// Gets the signature implementation used in this host key algorithm.
/// </summary>
/// <value>
/// The signature implementation used in this host key algorithm.
/// </value>
public DigitalSignature DigitalSignature { get; private set; }

/// <summary>
@@ -47,11 +39,7 @@ public override byte[] Data
/// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class.
/// </summary>
/// <param name="name">The signature format identifier.</param>
/// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
/// <remarks>
/// This constructor is typically passed a private key in order to create an encoded signature for later
/// verification by the host.
/// </remarks>
/// <param name="key">The key used in this host key algorithm.</param>
public KeyHostAlgorithm(string name, Key key)
: base(name)
{
@@ -63,13 +51,9 @@ public KeyHostAlgorithm(string name, Key key)
/// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class.
/// </summary>
/// <param name="name">The signature format identifier.</param>
/// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
/// <param name="digitalSignature"><inheritdoc cref="DigitalSignature" path="/summary"/></param>
/// <param name="key">The key used in this host key algorithm.</param>
/// <param name="digitalSignature">The signature implementation used in this host key algorithm.</param>
/// <remarks>
/// <para>
/// This constructor is typically passed a private key in order to create an encoded signature for later
/// verification by the host.
/// </para>
/// The key used by <paramref name="digitalSignature"/> is intended to be equal to <paramref name="key"/>.
/// This is not verified.
/// </remarks>
@@ -80,59 +64,6 @@ public KeyHostAlgorithm(string name, Key key, DigitalSignature digitalSignature)
DigitalSignature = digitalSignature;
}

/// <summary>
/// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class
/// with the given encoded public key data. The data will be decoded into <paramref name="key"/>.
/// </summary>
/// <param name="name">The signature format identifier.</param>
/// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
/// <param name="data">Host key encoded data.</param>
/// <remarks>
/// This constructor is typically passed a new or reusable <see cref="Security.Key"/> instance in
/// order to verify an encoded signature sent by the host, created by the private counterpart
/// to the host's public key, which is encoded in <paramref name="data"/>.
/// </remarks>
public KeyHostAlgorithm(string name, Key key, byte[] data)
: base(name)
{
Key = key;

var sshKey = new SshKeyData();
sshKey.Load(data);
Key.Public = sshKey.Keys;

DigitalSignature = key.DigitalSignature;
}

/// <summary>
/// Initializes a new instance of the <see cref="KeyHostAlgorithm"/> class
/// with the given encoded public key data. The data will be decoded into <paramref name="key"/>.
/// </summary>
/// <param name="name">The signature format identifier.</param>
/// <param name="key"><inheritdoc cref="Key" path="/summary"/></param>
/// <param name="data">Host key encoded data.</param>
/// <param name="digitalSignature"><inheritdoc cref="DigitalSignature" path="/summary"/></param>
/// <remarks>
/// <para>
/// This constructor is typically passed a new or reusable <see cref="Security.Key"/> instance in
/// order to verify an encoded signature sent by the host, created by the private counterpart
/// to the host's public key, which is encoded in <paramref name="data"/>.
/// </para>
/// The key used by <paramref name="digitalSignature"/> is intended to be equal to <paramref name="key"/>.
/// This is not verified.
/// </remarks>
public KeyHostAlgorithm(string name, Key key, byte[] data, DigitalSignature digitalSignature)
: base(name)
{
Key = key;

var sshKey = new SshKeyData();
sshKey.Load(data);
Key.Public = sshKey.Keys;

DigitalSignature = digitalSignature;
}

/// <summary>
/// Signs and encodes the specified data.
/// </summary>
@@ -162,98 +93,6 @@ public override bool VerifySignature(byte[] data, byte[] signature)
return DigitalSignature.Verify(data, signatureData.Signature);
}

private sealed class SshKeyData : SshData
{
private byte[] _name;
private List<byte[]> _keys;

public BigInteger[] Keys
{
get
{
var keys = new BigInteger[_keys.Count];

for (var i = 0; i < _keys.Count; i++)
{
var key = _keys[i];
keys[i] = key.ToBigInteger2();
}

return keys;
}
private set
{
_keys = new List<byte[]>(value.Length);

foreach (var key in value)
{
var keyData = key.ToByteArray().Reverse();
if (Name == "ssh-ed25519")
{
keyData = keyData.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
}

_keys.Add(keyData);
}
}
}

private string Name
{
get { return Utf8.GetString(_name, 0, _name.Length); }
set { _name = Utf8.GetBytes(value); }
}

protected override int BufferCapacity
{
get
{
var capacity = base.BufferCapacity;
capacity += 4; // Name length
capacity += _name.Length; // Name

foreach (var key in _keys)
{
capacity += 4; // Key length
capacity += key.Length; // Key
}

return capacity;
}
}

public SshKeyData()
{
}

public SshKeyData(string name, params BigInteger[] keys)
{
Name = name;
Keys = keys;
}

protected override void LoadData()
{
_name = ReadBinary();
_keys = new List<byte[]>();

while (!IsEndOfData)
{
_keys.Add(ReadBinary());
}
}

protected override void SaveData()
{
WriteBinaryString(_name);

foreach (var key in _keys)
{
WriteBinaryString(key);
}
}
}

internal sealed class SignatureKeyData : SshData
{
/// <summary>
@@ -306,7 +145,7 @@ public SignatureKeyData(string name, byte[] signature)
/// </summary>
protected override void LoadData()
{
AlgorithmName = Encoding.UTF8.GetString(ReadBinary());
AlgorithmName = ReadString();
Signature = ReadBinary();
}

@@ -315,7 +154,7 @@ protected override void LoadData()
/// </summary>
protected override void SaveData()
{
WriteBinaryString(Encoding.UTF8.GetBytes(AlgorithmName));
Write(AlgorithmName);
WriteBinaryString(Signature);
}
}
98 changes: 98 additions & 0 deletions src/Renci.SshNet/Security/SshKeyData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System.Collections.Generic;
using System.Text;

using Renci.SshNet.Common;
using Renci.SshNet.Security.Chaos.NaCl;

namespace Renci.SshNet.Security
{
/// <summary>
/// Facilitates (de)serializing encoded public key data in the format
/// specified by RFC 4253 section 6.6.
/// </summary>
/// <remarks>
/// See https://datatracker.ietf.org/doc/html/rfc4253#section-6.6.
/// </remarks>
public sealed class SshKeyData : SshData
{
/// <summary>
/// Gets the public key format identifier.
/// </summary>
public string Name { get; private set; }

/// <summary>
/// Gets the public key constituents.
/// </summary>
public BigInteger[] Keys { get; private set; }

/// <inheritdoc/>
protected override int BufferCapacity
{
get
{
var capacity = base.BufferCapacity;
capacity += 4; // Name length
capacity += Encoding.UTF8.GetByteCount(Name); // Name

foreach (var key in Keys)
{
capacity += 4; // Key length
capacity += key.BitLength / 8; // Key
}

return capacity;
}
}

/// <summary>
/// Initializes a new instance of the <see cref="SshKeyData"/> class.
/// </summary>
/// <param name="data">The encoded public key data.</param>
public SshKeyData(byte[] data)
{
Load(data);
}

/// <summary>
/// Initializes a new instance of the <see cref="SshKeyData"/> class.
/// </summary>
/// <param name="name">The public key format identifer.</param>
/// <param name="keys">The public key constituents.</param>
public SshKeyData(string name, BigInteger[] keys)
{
Name = name;
Keys = keys;
}

/// <inheritdoc/>
protected override void LoadData()
{
Name = ReadString();
var keys = new List<BigInteger>();

while (!IsEndOfData)
{
keys.Add(ReadBinary().ToBigInteger2());
}

Keys = keys.ToArray();
}

/// <inheritdoc/>
protected override void SaveData()
{
Write(Name);

foreach (var key in Keys)
{
var keyData = key.ToByteArray().Reverse();
if (Name == "ssh-ed25519")
{
keyData = keyData.TrimLeadingZeros().Pad(Ed25519.PublicKeySizeInBytes);
}

WriteBinaryString(keyData);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using BenchmarkDotNet.Attributes;

using Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers;
using Renci.SshNet.Common;
using Renci.SshNet.Security;

@@ -18,7 +17,7 @@ public HostKeyEventArgsBenchmarks()
}
private static KeyHostAlgorithm GetKeyHostAlgorithm()
{
using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
using (var s = typeof(HostKeyEventArgsBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
{
var privateKey = new PrivateKeyFile(s);
return (KeyHostAlgorithm) privateKey.HostKeyAlgorithms.First();

This file was deleted.

Original file line number Diff line number Diff line change
@@ -57,10 +57,7 @@ public void Sha1_SignAndVerify()
//Assert.IsTrue(digitalSignature.Verify(data, signedBytes));

// 'Workaround': use a key with no private key information
var digitalSignaturePublic = new RsaDigitalSignature(new RsaKey()
{
Public = rsaKey.Public
});
var digitalSignaturePublic = new RsaDigitalSignature(new RsaKey(rsaKey.Modulus, rsaKey.Exponent, default, default, default, default));
Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
}

@@ -101,10 +98,9 @@ public void Sha256_SignAndVerify()
//Assert.IsTrue(digitalSignature.Verify(data, signedBytes));

// 'Workaround': use a key with no private key information
var digitalSignaturePublic = new RsaDigitalSignature(new RsaKey()
{
Public = rsaKey.Public
}, HashAlgorithmName.SHA256);
var digitalSignaturePublic = new RsaDigitalSignature(
new RsaKey(rsaKey.Modulus, rsaKey.Exponent, default, default, default, default),
HashAlgorithmName.SHA256);
Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
}

@@ -144,18 +140,17 @@ public void Sha512_SignAndVerify()
//Assert.IsTrue(digitalSignature.Verify(data, signedBytes));

// 'Workaround': use a key with no private key information
var digitalSignaturePublic = new RsaDigitalSignature(new RsaKey()
{
Public = rsaKey.Public
}, HashAlgorithmName.SHA512);
var digitalSignaturePublic = new RsaDigitalSignature(
new RsaKey(rsaKey.Modulus, rsaKey.Exponent, default, default, default, default),
HashAlgorithmName.SHA512);
Assert.IsTrue(digitalSignaturePublic.Verify(data, signedBytes));
}

[TestMethod]
public void Constructor_InvalidHashAlgorithm_ThrowsArgumentException()
{
ArgumentException exception = Assert.ThrowsException<ArgumentException>(
() => new RsaDigitalSignature(new RsaKey(), new HashAlgorithmName("invalid")));
() => new RsaDigitalSignature(GetRsaKey(), new HashAlgorithmName("invalid")));

Assert.AreEqual("hashAlgorithmName", exception.ParamName);
}
10 changes: 5 additions & 5 deletions test/Renci.SshNet.Tests/Classes/Security/KeyAlgorithmTest.cs
Original file line number Diff line number Diff line change
@@ -86,7 +86,7 @@ public void SshRsa_SignAndVerify()

CollectionAssert.AreEqual(expectedEncodedSignatureBytes, keyAlgorithm.Sign(data));

keyAlgorithm = new KeyHostAlgorithm("ssh-rsa", new RsaKey(), GetRsaPublicKeyBytes());
keyAlgorithm = new KeyHostAlgorithm("ssh-rsa", new RsaKey(new SshKeyData(GetRsaPublicKeyBytes())));
Assert.IsTrue(keyAlgorithm.VerifySignature(data, expectedEncodedSignatureBytes));
}

@@ -126,8 +126,8 @@ public void RsaSha256_SignAndVerify()

CollectionAssert.AreEqual(expectedEncodedSignatureBytes, keyAlgorithm.Sign(data));

key = new RsaKey();
keyAlgorithm = new KeyHostAlgorithm("rsa-sha2-256", key, GetRsaPublicKeyBytes(), new RsaDigitalSignature(key, HashAlgorithmName.SHA256));
key = new RsaKey(new SshKeyData(GetRsaPublicKeyBytes()));
keyAlgorithm = new KeyHostAlgorithm("rsa-sha2-256", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA256));
Assert.IsTrue(keyAlgorithm.VerifySignature(data, expectedEncodedSignatureBytes));
}

@@ -167,8 +167,8 @@ public void RsaSha512_SignAndVerify()

CollectionAssert.AreEqual(expectedEncodedSignatureBytes, keyAlgorithm.Sign(data));

key = new RsaKey();
keyAlgorithm = new KeyHostAlgorithm("rsa-sha2-512", key, GetRsaPublicKeyBytes(), new RsaDigitalSignature(key, HashAlgorithmName.SHA512));
key = new RsaKey(new SshKeyData(GetRsaPublicKeyBytes()));
keyAlgorithm = new KeyHostAlgorithm("rsa-sha2-512", key, new RsaDigitalSignature(key, HashAlgorithmName.SHA512));
Assert.IsTrue(keyAlgorithm.VerifySignature(data, expectedEncodedSignatureBytes));
}

0 comments on commit 24838e6

Please sign in to comment.