From b12c7d1dced123fbf63eeff9add86b8883af60bb Mon Sep 17 00:00:00 2001 From: Robert Hague Date: Fri, 8 Dec 2023 15:54:21 +0100 Subject: [PATCH] Make keys immutable 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. --- src/Renci.SshNet/Common/SshData.cs | 2 +- src/Renci.SshNet/Common/SshDataStream.cs | 6 +- src/Renci.SshNet/ConnectionInfo.cs | 16 +- .../Security/Cryptography/DsaKey.cs | 107 ++++++----- .../Security/Cryptography/ED25519Key.cs | 61 +++--- .../Security/Cryptography/EcdsaKey.cs | 31 ++-- src/Renci.SshNet/Security/Cryptography/Key.cs | 46 +---- .../Security/Cryptography/RsaKey.cs | 171 ++++++----------- src/Renci.SshNet/Security/KeyHostAlgorithm.cs | 173 +----------------- src/Renci.SshNet/Security/SshKeyData.cs | 98 ++++++++++ .../Common/HostKeyEventArgsBenchmarks.cs | 3 +- .../Ciphers/RsaCipherBenchmarks.cs | 49 ----- .../Cryptography/RsaDigitalSignatureTest.cs | 21 +-- .../Classes/Security/KeyAlgorithmTest.cs | 10 +- 14 files changed, 286 insertions(+), 508 deletions(-) create mode 100644 src/Renci.SshNet/Security/SshKeyData.cs delete mode 100644 test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs diff --git a/src/Renci.SshNet/Common/SshData.cs b/src/Renci.SshNet/Common/SshData.cs index 4e7ff0438..6082af3ca 100644 --- a/src/Renci.SshNet/Common/SshData.cs +++ b/src/Renci.SshNet/Common/SshData.cs @@ -243,7 +243,7 @@ protected ulong ReadUInt64() /// /// The that was read. /// - protected string ReadString(Encoding encoding) + protected string ReadString(Encoding encoding = null) { return _stream.ReadString(encoding); } diff --git a/src/Renci.SshNet/Common/SshDataStream.cs b/src/Renci.SshNet/Common/SshDataStream.cs index 6bad5f21f..4adeb8576 100644 --- a/src/Renci.SshNet/Common/SshDataStream.cs +++ b/src/Renci.SshNet/Common/SshDataStream.cs @@ -207,12 +207,14 @@ public ulong ReadUInt64() /// /// Reads the next data type from the SSH data stream. /// - /// The character encoding to use. + /// The character encoding to use. Defaults to . /// /// The read from the SSH data stream. /// - public string ReadString(Encoding encoding) + public string ReadString(Encoding encoding = null) { + encoding ??= Encoding.UTF8; + var length = ReadUInt32(); if (length > int.MaxValue) diff --git a/src/Renci.SshNet/ConnectionInfo.cs b/src/Renci.SshNet/ConnectionInfo.cs index 6ffbf5979..f5c4f1167 100644 --- a/src/Renci.SshNet/ConnectionInfo.cs +++ b/src/Renci.SshNet/ConnectionInfo.cs @@ -394,16 +394,16 @@ public ConnectionInfo(string host, int port, string username, ProxyTypes proxyTy HostKeyAlgorithms = new Dictionary> { - { "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 diff --git a/src/Renci.SshNet/Security/Cryptography/DsaKey.cs b/src/Renci.SshNet/Security/Cryptography/DsaKey.cs index 65c6fceec..b0f915cc0 100644 --- a/src/Renci.SshNet/Security/Cryptography/DsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/DsaKey.cs @@ -1,4 +1,5 @@ using System; + using Renci.SshNet.Common; using Renci.SshNet.Security.Cryptography; @@ -15,57 +16,27 @@ public class DsaKey : Key, IDisposable /// /// Gets the P. /// - public BigInteger P - { - get - { - return _privateKey[0]; - } - } + public BigInteger P { get; } /// /// Gets the Q. /// - public BigInteger Q - { - get - { - return _privateKey[1]; - } - } + public BigInteger Q { get; } /// /// Gets the G. /// - public BigInteger G - { - get - { - return _privateKey[2]; - } - } + public BigInteger G { get; } /// /// Gets public key Y. /// - public BigInteger Y - { - get - { - return _privateKey[3]; - } - } + public BigInteger Y { get; } /// /// Gets private key X. /// - public BigInteger X - { - get - { - return _privateKey[4]; - } - } + public BigInteger X { get; } /// /// Gets the length of the key. @@ -94,10 +65,16 @@ protected internal override DigitalSignature DigitalSignature } /// - /// Gets or sets the public. + /// Gets the DSA public key. /// /// - /// The public. + /// An array whose values are: + /// + /// 0 + /// 1 + /// 2 + /// 3 + /// /// public override BigInteger[] Public { @@ -105,35 +82,53 @@ public override BigInteger[] Public { return new[] { P, Q, G, Y }; } - set - { - if (value.Length != 4) - { - throw new InvalidOperationException("Invalid public key."); - } - - _privateKey = value; - } } /// /// Initializes a new instance of the class. /// - public DsaKey() + /// The encoded public key data. + 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]; } /// /// Initializes a new instance of the class. /// - /// DER encoded private key data. - public DsaKey(byte[] data) - : base(data) + /// DER encoded private key data. + 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) /// The x. 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; } /// diff --git a/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs b/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs index bb0037945..51958caa0 100644 --- a/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs +++ b/src/Renci.SshNet/Security/Cryptography/ED25519Key.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using Renci.SshNet.Common; using Renci.SshNet.Security.Chaos.NaCl; @@ -11,13 +12,7 @@ namespace Renci.SshNet.Security /// 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; /// @@ -32,20 +27,16 @@ public override string ToString() } /// - /// Gets or sets the public. + /// Gets the Ed25519 public key. /// /// - /// The public. + /// An array with encoded at index 0. /// 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,39 +69,31 @@ protected internal override DigitalSignature DigitalSignature /// /// Gets the PublicKey Bytes. /// - public byte[] PublicKey - { - get - { - return _publicKey; - } - } + public byte[] PublicKey { get; } /// /// Gets the PrivateKey Bytes. /// - public byte[] PrivateKey - { - get - { - return privateKey; - } - } + public byte[] PrivateKey { get; } /// /// Initializes a new instance of the class. /// - public ED25519Key() + /// The encoded public key data. + public ED25519Key(SshKeyData publicKeyData) { - } + if (publicKeyData is null) + { + throw new ArgumentNullException(nameof(publicKeyData)); + } - /// - /// Initializes a new instance of the class. - /// - /// pk data. - 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]; } /// @@ -120,10 +103,12 @@ public ED25519Key(byte[] pk) /// sk data. 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); + Ed25519.KeyPairFromSeed(out var publicKey, out var privateKey, seed); + Debug.Assert(publicKey.IsEqualTo(pk)); + PublicKey = publicKey; + PrivateKey = privateKey; } /// diff --git a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs index 455bb1363..7898a92c9 100644 --- a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs @@ -140,10 +140,11 @@ protected internal override DigitalSignature DigitalSignature } /// - /// Gets or sets the public. + /// Gets the ECDSA public key. /// /// - /// 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. /// 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); - } } /// @@ -236,8 +229,24 @@ public override BigInteger[] Public /// /// Initializes a new instance of the class. /// - public EcdsaKey() + /// The encoded public key data. + 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); } /// diff --git a/src/Renci.SshNet/Security/Cryptography/Key.cs b/src/Renci.SshNet/Security/Cryptography/Key.cs index 81580a1ab..54ecac7e4 100644 --- a/src/Renci.SshNet/Security/Cryptography/Key.cs +++ b/src/Renci.SshNet/Security/Cryptography/Key.cs @@ -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 /// public abstract class Key { - /// - /// Specifies array of big integers that represent private key. - /// -#pragma warning disable SA1401 // Fields should be private - protected BigInteger[] _privateKey; -#pragma warning restore SA1401 // Fields should be private - /// /// Gets the default digital signature implementation for this key. /// protected internal abstract DigitalSignature DigitalSignature { get; } /// - /// Gets or sets the public key. + /// Gets the public key. /// /// /// The public. /// - public abstract BigInteger[] Public { get; set; } + public abstract BigInteger[] Public { get; } /// /// Gets the length of the key. @@ -44,36 +34,6 @@ public abstract class Key /// public string Comment { get; set; } - /// - /// Initializes a new instance of the class. - /// - /// DER encoded private key data. - 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(); - while (!der.IsEndOfData) - { - keys.Add(der.ReadBigInteger()); - } - - _privateKey = keys.ToArray(); - } - - /// - /// Initializes a new instance of the class. - /// - protected Key() - { - } - /// /// Signs the specified data with the key. /// diff --git a/src/Renci.SshNet/Security/Cryptography/RsaKey.cs b/src/Renci.SshNet/Security/Cryptography/RsaKey.cs index 19c8bedc7..f342db6c1 100644 --- a/src/Renci.SshNet/Security/Cryptography/RsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/RsaKey.cs @@ -1,4 +1,5 @@ using System; + using Renci.SshNet.Common; using Renci.SshNet.Security.Cryptography; @@ -29,13 +30,7 @@ public override string ToString() /// /// The modulus. /// - public BigInteger Modulus - { - get - { - return _privateKey[0]; - } - } + public BigInteger Modulus { get; } /// /// Gets the exponent. @@ -43,13 +38,7 @@ public BigInteger Modulus /// /// The exponent. /// - public BigInteger Exponent - { - get - { - return _privateKey[1]; - } - } + public BigInteger Exponent { get; } /// /// Gets the D. @@ -57,18 +46,7 @@ public BigInteger Exponent /// /// The D. /// - public BigInteger D - { - get - { - if (_privateKey.Length > 2) - { - return _privateKey[2]; - } - - return BigInteger.Zero; - } - } + public BigInteger D { get; } /// /// Gets the P. @@ -76,18 +54,7 @@ public BigInteger D /// /// The P. /// - public BigInteger P - { - get - { - if (_privateKey.Length > 3) - { - return _privateKey[3]; - } - - return BigInteger.Zero; - } - } + public BigInteger P { get; } /// /// Gets the Q. @@ -95,18 +62,7 @@ public BigInteger P /// /// The Q. /// - public BigInteger Q - { - get - { - if (_privateKey.Length > 4) - { - return _privateKey[4]; - } - - return BigInteger.Zero; - } - } + public BigInteger Q { get; } /// /// Gets the DP. @@ -114,18 +70,7 @@ public BigInteger Q /// /// The DP. /// - public BigInteger DP - { - get - { - if (_privateKey.Length > 5) - { - return _privateKey[5]; - } - - return BigInteger.Zero; - } - } + public BigInteger DP { get; } /// /// Gets the DQ. @@ -133,18 +78,7 @@ public BigInteger DP /// /// The DQ. /// - public BigInteger DQ - { - get - { - if (_privateKey.Length > 6) - { - return _privateKey[6]; - } - - return BigInteger.Zero; - } - } + public BigInteger DQ { get; } /// /// Gets the inverse Q. @@ -152,18 +86,7 @@ public BigInteger DQ /// /// The inverse Q. /// - public BigInteger InverseQ - { - get - { - if (_privateKey.Length > 7) - { - return _privateKey[7]; - } - - return BigInteger.Zero; - } - } + public BigInteger InverseQ { get; } /// /// Gets the length of the key. @@ -196,10 +119,11 @@ protected internal override DigitalSignature DigitalSignature } /// - /// Gets or sets the public. + /// Gets the RSA public key. /// /// - /// The public. + /// An array with at index 0, and + /// at index 1. /// public override BigInteger[] Public { @@ -207,34 +131,54 @@ public override BigInteger[] Public { return new[] { Exponent, Modulus }; } - set - { - if (value.Length != 2) - { - throw new InvalidOperationException("Invalid private key."); - } - - _privateKey = new[] { value[1], value[0] }; - } } /// /// Initializes a new instance of the class. /// - public RsaKey() + /// The encoded public key data. + 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]; } /// /// Initializes a new instance of the class. /// - /// DER encoded private key data. - public RsaKey(byte[] data) - : base(data) + /// DER encoded private key data. + 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) /// The inverse Q. 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; } diff --git a/src/Renci.SshNet/Security/KeyHostAlgorithm.cs b/src/Renci.SshNet/Security/KeyHostAlgorithm.cs index 0a0232a58..5611c9713 100644 --- a/src/Renci.SshNet/Security/KeyHostAlgorithm.cs +++ b/src/Renci.SshNet/Security/KeyHostAlgorithm.cs @@ -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 /// /// Gets the key used in this host key algorithm. /// - /// - /// The key used in this host key algorithm. - /// public Key Key { get; private set; } /// /// Gets the signature implementation used in this host key algorithm. /// - /// - /// The signature implementation used in this host key algorithm. - /// public DigitalSignature DigitalSignature { get; private set; } /// @@ -47,11 +39,7 @@ public override byte[] Data /// Initializes a new instance of the class. /// /// The signature format identifier. - /// - /// - /// This constructor is typically passed a private key in order to create an encoded signature for later - /// verification by the host. - /// + /// The key used in this host key algorithm. public KeyHostAlgorithm(string name, Key key) : base(name) { @@ -63,13 +51,9 @@ public KeyHostAlgorithm(string name, Key key) /// Initializes a new instance of the class. /// /// The signature format identifier. - /// - /// + /// The key used in this host key algorithm. + /// The signature implementation used in this host key algorithm. /// - /// - /// This constructor is typically passed a private key in order to create an encoded signature for later - /// verification by the host. - /// /// The key used by is intended to be equal to . /// This is not verified. /// @@ -80,59 +64,6 @@ public KeyHostAlgorithm(string name, Key key, DigitalSignature digitalSignature) DigitalSignature = digitalSignature; } - /// - /// Initializes a new instance of the class - /// with the given encoded public key data. The data will be decoded into . - /// - /// The signature format identifier. - /// - /// Host key encoded data. - /// - /// This constructor is typically passed a new or reusable 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 . - /// - 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; - } - - /// - /// Initializes a new instance of the class - /// with the given encoded public key data. The data will be decoded into . - /// - /// The signature format identifier. - /// - /// Host key encoded data. - /// - /// - /// - /// This constructor is typically passed a new or reusable 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 . - /// - /// The key used by is intended to be equal to . - /// This is not verified. - /// - 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; - } - /// /// Signs and encodes the specified data. /// @@ -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 _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(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(); - - while (!IsEndOfData) - { - _keys.Add(ReadBinary()); - } - } - - protected override void SaveData() - { - WriteBinaryString(_name); - - foreach (var key in _keys) - { - WriteBinaryString(key); - } - } - } - internal sealed class SignatureKeyData : SshData { /// @@ -306,7 +145,7 @@ public SignatureKeyData(string name, byte[] signature) /// protected override void LoadData() { - AlgorithmName = Encoding.UTF8.GetString(ReadBinary()); + AlgorithmName = ReadString(); Signature = ReadBinary(); } @@ -315,7 +154,7 @@ protected override void LoadData() /// protected override void SaveData() { - WriteBinaryString(Encoding.UTF8.GetBytes(AlgorithmName)); + Write(AlgorithmName); WriteBinaryString(Signature); } } diff --git a/src/Renci.SshNet/Security/SshKeyData.cs b/src/Renci.SshNet/Security/SshKeyData.cs new file mode 100644 index 000000000..6a3af835a --- /dev/null +++ b/src/Renci.SshNet/Security/SshKeyData.cs @@ -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 +{ + /// + /// Facilitates (de)serializing encoded public key data in the format + /// specified by RFC 4253 section 6.6. + /// + /// + /// See https://datatracker.ietf.org/doc/html/rfc4253#section-6.6. + /// + public sealed class SshKeyData : SshData + { + /// + /// Gets the public key format identifier. + /// + public string Name { get; private set; } + + /// + /// Gets the public key constituents. + /// + public BigInteger[] Keys { get; private set; } + + /// + 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; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The encoded public key data. + public SshKeyData(byte[] data) + { + Load(data); + } + + /// + /// Initializes a new instance of the class. + /// + /// The public key format identifer. + /// The public key constituents. + public SshKeyData(string name, BigInteger[] keys) + { + Name = name; + Keys = keys; + } + + /// + protected override void LoadData() + { + Name = ReadString(); + var keys = new List(); + + while (!IsEndOfData) + { + keys.Add(ReadBinary().ToBigInteger2()); + } + + Keys = keys.ToArray(); + } + + /// + 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); + } + } + } +} diff --git a/test/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs b/test/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs index 54900d046..c3b070884 100644 --- a/test/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs +++ b/test/Renci.SshNet.Benchmarks/Common/HostKeyEventArgsBenchmarks.cs @@ -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(); diff --git a/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs b/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs deleted file mode 100644 index 13c08edb1..000000000 --- a/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs +++ /dev/null @@ -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); - // } - } -} diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs index d7558c2c4..acfc16382 100644 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs @@ -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,10 +140,9 @@ 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)); } @@ -155,7 +150,7 @@ public void Sha512_SignAndVerify() public void Constructor_InvalidHashAlgorithm_ThrowsArgumentException() { ArgumentException exception = Assert.ThrowsException( - () => new RsaDigitalSignature(new RsaKey(), new HashAlgorithmName("invalid"))); + () => new RsaDigitalSignature(GetRsaKey(), new HashAlgorithmName("invalid"))); Assert.AreEqual("hashAlgorithmName", exception.ParamName); } diff --git a/test/Renci.SshNet.Tests/Classes/Security/KeyAlgorithmTest.cs b/test/Renci.SshNet.Tests/Classes/Security/KeyAlgorithmTest.cs index d024c05a6..d41199817 100644 --- a/test/Renci.SshNet.Tests/Classes/Security/KeyAlgorithmTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Security/KeyAlgorithmTest.cs @@ -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)); }