Make keys immutable (#1264)

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>
This commit is contained in:
Rob Hague
2023-12-21 22:12:15 +00:00
committed by GitHub
parent e998d875e0
commit 24838e6173
15 changed files with 316 additions and 537 deletions
+1 -1
View File
@@ -231,7 +231,7 @@ namespace Renci.SshNet.Common
/// <returns>
/// The <see cref="string"/> that was read.
/// </returns>
protected string ReadString(Encoding encoding)
protected string ReadString(Encoding encoding = null)
{
return _stream.ReadString(encoding);
}
+4 -2
View File
@@ -249,12 +249,14 @@ namespace Renci.SshNet.Common
/// <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)
+8 -8
View File
@@ -396,16 +396,16 @@ namespace Renci.SshNet
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>
+6 -4
View File
@@ -573,12 +573,14 @@ namespace Renci.SshNet
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":
@@ -1,4 +1,5 @@
using System;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;
@@ -15,57 +16,27 @@ namespace Renci.SshNet.Security
/// <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,10 +65,16 @@ namespace Renci.SshNet.Security
}
/// <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
{
@@ -105,35 +82,53 @@ namespace Renci.SshNet.Security
{
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>
/// <param name="publicKeyData">The encoded public key data.</param>
public DsaKey(SshKeyData publicKeyData)
{
if (publicKeyData is null)
{
throw new ArgumentNullException(nameof(publicKeyData));
}
}
/// <summary>
/// Initializes a new instance of the <see cref="DsaKey"/> class.
/// </summary>
public DsaKey()
{
_privateKey = new BigInteger[5];
}
/// <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)
{
if (_privateKey.Length != 5)
if (publicKeyData.Name != "ssh-dss" || publicKeyData.Keys.Length != 4)
{
throw new InvalidOperationException("Invalid private key.");
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="privateKeyData">DER encoded private key data.</param>
public DsaKey(byte[] privateKeyData)
{
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 (expected EOF).");
}
}
@@ -147,7 +142,11 @@ namespace Renci.SshNet.Security
/// <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>
@@ -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 @@ namespace Renci.SshNet.Security
}
/// <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 @@ namespace Renci.SshNet.Security
/// <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
public byte[] PrivateKey { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ED25519Key"/> class.
/// </summary>
/// <param name="publicKeyData">The encoded public key data.</param>
public ED25519Key(SshKeyData publicKeyData)
{
get
if (publicKeyData is null)
{
return privateKey;
throw new ArgumentNullException(nameof(publicKeyData));
}
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>
public ED25519Key()
/// <param name="privateKeyData">
/// The private key data <c>k || ENC(A)</c> as described in RFC 8032.
/// </param>
public ED25519Key(byte[] privateKeyData)
{
}
/// <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);
}
/// <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)
{
_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>
@@ -140,10 +140,11 @@ namespace Renci.SshNet.Security
}
/// <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 @@ namespace Renci.SshNet.Security
// 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 @@ namespace Renci.SshNet.Security
/// <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>
+3 -43
View File
@@ -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 @@ namespace Renci.SshNet.Security
/// </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>
+65 -124
View File
@@ -1,4 +1,5 @@
using System;
using Renci.SshNet.Common;
using Renci.SshNet.Security.Cryptography;
@@ -29,13 +30,7 @@ namespace Renci.SshNet.Security
/// <value>
/// The modulus.
/// </value>
public BigInteger Modulus
{
get
{
return _privateKey[0];
}
}
public BigInteger Modulus { get; }
/// <summary>
/// Gets the exponent.
@@ -43,13 +38,7 @@ namespace Renci.SshNet.Security
/// <value>
/// The exponent.
/// </value>
public BigInteger Exponent
{
get
{
return _privateKey[1];
}
}
public BigInteger Exponent { get; }
/// <summary>
/// Gets the D.
@@ -57,18 +46,7 @@ namespace Renci.SshNet.Security
/// <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.
@@ -76,18 +54,7 @@ namespace Renci.SshNet.Security
/// <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.
@@ -95,18 +62,7 @@ namespace Renci.SshNet.Security
/// <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.
@@ -114,18 +70,7 @@ namespace Renci.SshNet.Security
/// <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.
@@ -133,18 +78,7 @@ namespace Renci.SshNet.Security
/// <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.
@@ -152,18 +86,7 @@ namespace Renci.SshNet.Security
/// <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,10 +119,11 @@ namespace Renci.SshNet.Security
}
/// <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
{
@@ -207,34 +131,54 @@ namespace Renci.SshNet.Security
{
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>
/// <param name="publicKeyData">The encoded public key data.</param>
public RsaKey(SshKeyData publicKeyData)
{
if (publicKeyData is null)
{
throw new ArgumentNullException(nameof(publicKeyData));
}
}
/// <summary>
/// Initializes a new instance of the <see cref="RsaKey"/> class.
/// </summary>
public RsaKey()
{
}
/// <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)
{
if (_privateKey.Length != 8)
if (publicKeyData.Name != "ssh-rsa" || publicKeyData.Keys.Length != 2)
{
throw new InvalidOperationException("Invalid private key.");
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="privateKeyData">DER encoded private key data.</param>
public RsaKey(byte[] privateKeyData)
{
if (privateKeyData is null)
{
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 @@ namespace Renci.SshNet.Security
/// <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;
}
+6 -167
View File
@@ -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 @@ namespace Renci.SshNet.Security
/// <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 @@ namespace Renci.SshNet.Security
/// 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 @@ namespace Renci.SshNet.Security
/// 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 @@ namespace Renci.SshNet.Security
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 @@ namespace Renci.SshNet.Security
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 @@ namespace Renci.SshNet.Security
/// </summary>
protected override void LoadData()
{
AlgorithmName = Encoding.UTF8.GetString(ReadBinary());
AlgorithmName = ReadString();
Signature = ReadBinary();
}
@@ -315,7 +154,7 @@ namespace Renci.SshNet.Security
/// </summary>
protected override void SaveData()
{
WriteBinaryString(Encoding.UTF8.GetBytes(AlgorithmName));
Write(AlgorithmName);
WriteBinaryString(Signature);
}
}
+98
View File
@@ -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);
}
}
}
}
@@ -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 @@ namespace Renci.SshNet.Benchmarks.Common
}
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();
@@ -1,49 +0,0 @@
using BenchmarkDotNet.Attributes;
using Renci.SshNet.Security;
using Renci.SshNet.Security.Cryptography.Ciphers;
namespace Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers
{
[MemoryDiagnoser]
public class RsaCipherBenchmarks
{
private readonly RsaKey _privateKey;
private readonly RsaKey _publicKey;
private readonly byte[] _data;
public RsaCipherBenchmarks()
{
_data = new byte[128];
Random random = new(Seed: 12345);
random.NextBytes(_data);
using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
{
_privateKey = (RsaKey)new PrivateKeyFile(s).Key;
// The implementations of RsaCipher.Encrypt/Decrypt differ based on whether the supplied RsaKey has private key information
// or only public. So we extract out the public key information to a separate variable.
_publicKey = new RsaKey()
{
Public = _privateKey.Public
};
}
}
[Benchmark]
public byte[] Encrypt()
{
return new RsaCipher(_publicKey).Encrypt(_data);
}
// RSA Decrypt does not work
// [Benchmark]
// public byte[] Decrypt()
// {
// return new RsaCipher(_privateKey).Decrypt(_data);
// }
}
}
@@ -57,10 +57,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
//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 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
//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,10 +140,9 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
//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));
}
@@ -155,7 +150,7 @@ namespace Renci.SshNet.Tests.Classes.Security.Cryptography
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);
}
@@ -86,7 +86,7 @@ namespace Renci.SshNet.Tests.Classes.Security
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 @@ namespace Renci.SshNet.Tests.Classes.Security
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 @@ namespace Renci.SshNet.Tests.Classes.Security
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));
}