소스 검색

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>
Rob Hague 1 년 전
부모
커밋
24838e6173

+ 1 - 1
src/Renci.SshNet/Common/SshData.cs

@@ -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
src/Renci.SshNet/Common/SshDataStream.cs

@@ -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
src/Renci.SshNet/ConnectionInfo.cs

@@ -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
src/Renci.SshNet/PrivateKeyFile.cs

@@ -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":

+ 53 - 54
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 @@ 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>
-        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 @@ 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>

+ 26 - 42
src/Renci.SshNet/Security/Cryptography/ED25519Key.cs

@@ -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
-        {
-            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>

+ 20 - 11
src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs

@@ -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
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
     /// </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>

+ 56 - 115
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 @@ 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>
-        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 @@ 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
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 @@ 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 - 0
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
+{
+    /// <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 - 2
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 @@ 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();

+ 0 - 49
test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/RsaCipherBenchmarks.cs

@@ -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);
-        // }
-    }
-}

+ 8 - 13
test/Renci.SshNet.Tests/Classes/Security/Cryptography/RsaDigitalSignatureTest.cs

@@ -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);
         }

+ 5 - 5
test/Renci.SshNet.Tests/Classes/Security/KeyAlgorithmTest.cs

@@ -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));
         }