From b6755325bd53cd53e92ec047ea06111e85e56501 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Tue, 10 Sep 2024 19:59:10 +0800 Subject: [PATCH 1/4] Ref System.Formats.Asn1 nuget package --- src/Renci.SshNet/Common/DerData.cs | 439 ------------------ src/Renci.SshNet/Renci.SshNet.csproj | 1 + .../Security/Cryptography/DsaKey.cs | 20 +- .../Security/Cryptography/EcdsaKey.cs | 77 +-- .../Security/Cryptography/RsaKey.cs | 26 +- 5 files changed, 31 insertions(+), 532 deletions(-) delete mode 100644 src/Renci.SshNet/Common/DerData.cs diff --git a/src/Renci.SshNet/Common/DerData.cs b/src/Renci.SshNet/Common/DerData.cs deleted file mode 100644 index cd77c0e08..000000000 --- a/src/Renci.SshNet/Common/DerData.cs +++ /dev/null @@ -1,439 +0,0 @@ -using System; -using System.Buffers.Binary; -using System.Collections.Generic; -using System.Numerics; - -namespace Renci.SshNet.Common -{ - /// - /// Base class for DER encoded data. - /// - public class DerData - { - private const byte Constructed = 0x20; - - private const byte Boolean = 0x01; - private const byte Integer = 0x02; - private const byte BITSTRING = 0x03; - private const byte Octetstring = 0x04; - private const byte Null = 0x05; - private const byte Objectidentifier = 0x06; - private const byte Sequence = 0x10; - - private readonly List _data; - private readonly int _lastIndex; - private int _readerIndex; - - /// - /// Gets a value indicating whether end of data is reached. - /// - /// - /// if end of data is reached; otherwise, . - /// - public bool IsEndOfData - { - get - { - return _readerIndex >= _lastIndex; - } - } - - /// - /// Initializes a new instance of the class. - /// - public DerData() - { - _data = new List(); - } - - /// - /// Initializes a new instance of the class. - /// - /// DER encoded data. - /// its a construct. - public DerData(byte[] data, bool construct = false) - { - _data = new List(data); - if (construct) - { - _lastIndex = _readerIndex + data.Length; - } - else - { - _ = ReadByte(); // skip dataType - var length = ReadLength(); - _lastIndex = _readerIndex + length; - } - } - - /// - /// Encodes written data as DER byte array. - /// - /// DER Encoded array. - public byte[] Encode() - { - var length = _data.Count; - var lengthBytes = GetLength(length); - - _data.InsertRange(0, lengthBytes); - _data.Insert(0, Constructed | Sequence); - - return _data.ToArray(); - } - - /// - /// Reads next mpint data type from internal buffer. - /// - /// mpint read. - public BigInteger ReadBigInteger() - { - var type = ReadByte(); - if (type != Integer) - { - throw new InvalidOperationException(string.Format("Invalid data type, INTEGER(02) is expected, but was {0}", type.ToString("X2"))); - } - - var length = ReadLength(); - - var data = ReadBytes(length); - -#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER - return new BigInteger(data, isBigEndian: true); -#else - return new BigInteger(data.Reverse()); -#endif - } - - /// - /// Reads next int data type from internal buffer. - /// - /// int read. - public int ReadInteger() - { - var type = ReadByte(); - if (type != Integer) - { - throw new InvalidOperationException(string.Format("Invalid data type, INTEGER(02) is expected, but was {0}", type.ToString("X2"))); - } - - var length = ReadLength(); - - var data = ReadBytes(length); - - if (length > 4) - { - throw new InvalidOperationException("Integer type cannot occupy more then 4 bytes"); - } - - var result = 0; - var shift = (length - 1) * 8; - for (var i = 0; i < length; i++) - { - result |= data[i] << shift; - shift -= 8; - } - - return result; - } - - /// - /// Reads next octetstring data type from internal buffer. - /// - /// data read. - public byte[] ReadOctetString() - { - var type = ReadByte(); - if (type != Octetstring) - { - throw new InvalidOperationException(string.Format("Invalid data type, OCTETSTRING(04) is expected, but was {0}", type.ToString("X2"))); - } - - var length = ReadLength(); - var data = ReadBytes(length); - return data; - } - - /// - /// Reads next bitstring data type from internal buffer. - /// - /// data read. - public byte[] ReadBitString() - { - var type = ReadByte(); - if (type != BITSTRING) - { - throw new InvalidOperationException(string.Format("Invalid data type, BITSTRING(03) is expected, but was {0}", type.ToString("X2"))); - } - - var length = ReadLength(); - var data = ReadBytes(length); - return data; - } - - /// - /// Reads next object data type from internal buffer. - /// - /// data read. - public byte[] ReadObject() - { - var type = ReadByte(); - if (type != Objectidentifier) - { - throw new InvalidOperationException(string.Format("Invalid data type, OBJECT(06) is expected, but was {0}", type.ToString("X2"))); - } - - var length = ReadLength(); - var data = ReadBytes(length); - return data; - } - - /// - /// Writes BOOLEAN data into internal buffer. - /// - /// UInt32 data to write. - public void Write(bool data) - { - _data.Add(Boolean); - _data.Add(1); - _data.Add((byte)(data ? 1 : 0)); - } - - /// - /// Writes UInt32 data into internal buffer. - /// - /// UInt32 data to write. - public void Write(uint data) - { - var bytes = new byte[sizeof(uint)]; - BinaryPrimitives.WriteUInt32BigEndian(bytes, data); - _data.Add(Integer); - var length = GetLength(bytes.Length); - WriteBytes(length); - WriteBytes(bytes); - } - - /// - /// Writes INTEGER data into internal buffer. - /// - /// BigInteger data to write. - public void Write(BigInteger data) - { - var bytes = data.ToByteArray(isBigEndian: true); - _data.Add(Integer); - var length = GetLength(bytes.Length); - WriteBytes(length); - WriteBytes(bytes); - } - - /// - /// Writes OCTETSTRING data into internal buffer. - /// - /// The data. - public void Write(byte[] data) - { - _data.Add(Octetstring); - var length = GetLength(data.Length); - WriteBytes(length); - WriteBytes(data); - } - - /// - /// Writes OBJECTIDENTIFIER data into internal buffer. - /// - /// The identifier. - public void Write(ObjectIdentifier identifier) - { - var temp = new ulong[identifier.Identifiers.Length - 1]; - temp[0] = (identifier.Identifiers[0] * 40) + identifier.Identifiers[1]; - Buffer.BlockCopy(identifier.Identifiers, 2 * sizeof(ulong), temp, 1 * sizeof(ulong), (identifier.Identifiers.Length - 2) * sizeof(ulong)); - var bytes = new List(); - foreach (var subidentifier in temp) - { - var item = subidentifier; - var buffer = new byte[8]; - var bufferIndex = buffer.Length - 1; - - var current = (byte)(item & 0x7F); - do - { - buffer[bufferIndex] = current; - if (bufferIndex < buffer.Length - 1) - { - buffer[bufferIndex] |= 0x80; - } - - item >>= 7; - current = (byte)(item & 0x7F); - bufferIndex--; - } - while (current > 0); - - for (var i = bufferIndex + 1; i < buffer.Length; i++) - { - bytes.Add(buffer[i]); - } - } - - _data.Add(Objectidentifier); - var length = GetLength(bytes.Count); - WriteBytes(length); - WriteBytes(bytes); - } - - /// - /// Writes DerData data into internal buffer. - /// - /// DerData data to write. - public void Write(DerData data) - { - var bytes = data.Encode(); - _data.AddRange(bytes); - } - - /// - /// Writes BITSTRING data into internal buffer. - /// - /// The data. - public void WriteBitstring(byte[] data) - { - _data.Add(BITSTRING); - var length = GetLength(data.Length); - WriteBytes(length); - WriteBytes(data); - } - - /// - /// Writes OBJECTIDENTIFIER data into internal buffer. - /// - /// The bytes. - public void WriteObjectIdentifier(byte[] bytes) - { - _data.Add(Objectidentifier); - var length = GetLength(bytes.Length); - WriteBytes(length); - WriteBytes(bytes); - } - - /// - /// Writes NULL data into internal buffer. - /// - public void WriteNull() - { - _data.Add(Null); - _data.Add(0); - } - - private static byte[] GetLength(int length) - { - if (length > 127) - { - var size = 1; - var val = length; - - while ((val >>= 8) != 0) - { - size++; - } - - var data = new byte[size]; - data[0] = (byte)(size | 0x80); - - for (int i = (size - 1) * 8, j = 1; i >= 0; i -= 8, j++) - { - data[j] = (byte)(length >> i); - } - - return data; - } - - return new[] { (byte)length }; - } - - /// - /// Gets Data Length. - /// - /// - /// The length. - /// - public int ReadLength() - { - int length = ReadByte(); - - if (length == 0x80) - { - throw new NotSupportedException("Indefinite-length encoding is not supported."); - } - - if (length > 127) - { - var size = length & 0x7f; - - // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here - if (size > 4) - { - throw new InvalidOperationException(string.Format("DER length is '{0}' and cannot be more than 4 bytes.", size)); - } - - length = 0; - for (var i = 0; i < size; i++) - { - int next = ReadByte(); - - length = (length << 8) + next; - } - - if (length < 0) - { - throw new InvalidOperationException("Corrupted data - negative length found"); - } - } - - return length; - } - - /// - /// Write Byte data into internal buffer. - /// - /// The data to write. - public void WriteBytes(IEnumerable data) - { - _data.AddRange(data); - } - - /// - /// Reads Byte data into internal buffer. - /// - /// - /// The data read. - /// - public byte ReadByte() - { - if (_readerIndex > _data.Count) - { - throw new InvalidOperationException("Read out of boundaries."); - } - - return _data[_readerIndex++]; - } - - /// - /// Reads lengths Bytes data into internal buffer. - /// - /// - /// The data read. - /// - /// amount of data to read. - public byte[] ReadBytes(int length) - { - if (_readerIndex + length > _data.Count) - { - throw new InvalidOperationException("Read out of boundaries."); - } - - var result = new byte[length]; - _data.CopyTo(_readerIndex, result, 0, length); - _readerIndex += length; - return result; - } - } -} diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index bea1b66af..61bc29e40 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -50,6 +50,7 @@ + diff --git a/src/Renci.SshNet/Security/Cryptography/DsaKey.cs b/src/Renci.SshNet/Security/Cryptography/DsaKey.cs index 2d7c35011..41f62f059 100644 --- a/src/Renci.SshNet/Security/Cryptography/DsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/DsaKey.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Formats.Asn1; using System.Numerics; using System.Security.Cryptography; @@ -118,19 +119,16 @@ public DsaKey(byte[] privateKeyData) throw new ArgumentNullException(nameof(privateKeyData)); } - var der = new DerData(privateKeyData); - _ = der.ReadBigInteger(); // skip version + var der = new AsnReader(privateKeyData, AsnEncodingRules.DER).ReadSequence(); + _ = der.ReadInteger(); // skip version - P = der.ReadBigInteger(); - Q = der.ReadBigInteger(); - G = der.ReadBigInteger(); - Y = der.ReadBigInteger(); - X = der.ReadBigInteger(); + P = der.ReadInteger(); + Q = der.ReadInteger(); + G = der.ReadInteger(); + Y = der.ReadInteger(); + X = der.ReadInteger(); - if (!der.IsEndOfData) - { - throw new InvalidOperationException("Invalid private key (expected EOF)."); - } + der.ThrowIfNotEmpty(); DSA = LoadDSA(); } diff --git a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs index 718d5170a..70e2e1a9d 100644 --- a/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/EcdsaKey.cs @@ -1,6 +1,7 @@ #nullable enable using System; using System.Diagnostics; +using System.Formats.Asn1; using System.Numerics; using System.Security.Cryptography; using System.Text; @@ -220,51 +221,20 @@ public EcdsaKey(string curve, byte[] publickey, byte[] privatekey) /// DER encoded private key data. public EcdsaKey(byte[] data) { - var der = new DerData(data); - _ = der.ReadBigInteger(); // skip version + var der = new AsnReader(data, AsnEncodingRules.DER).ReadSequence(); + _ = der.ReadInteger(); // skip version - // PrivateKey var privatekey = der.ReadOctetString().TrimLeadingZeros(); - // Construct - var s0 = der.ReadByte(); - if ((s0 & 0xe0) != 0xa0) - { - throw new SshException(string.Format("UnexpectedDER: wanted constructed tag (0xa0-0xbf), got: {0:X}", s0)); - } - - var tag = s0 & 0x1f; - if (tag != 0) - { - throw new SshException(string.Format("expected tag 0 in DER privkey, got: {0}", tag)); - } - - var construct = der.ReadBytes(der.ReadLength()); // object length - - // curve OID - var curve_der = new DerData(construct, construct: true); - var curve = curve_der.ReadObject(); - - // Construct - s0 = der.ReadByte(); - if ((s0 & 0xe0) != 0xa0) - { - throw new SshException(string.Format("UnexpectedDER: wanted constructed tag (0xa0-0xbf), got: {0:X}", s0)); - } - - tag = s0 & 0x1f; - if (tag != 1) - { - throw new SshException(string.Format("expected tag 1 in DER privkey, got: {0}", tag)); - } + var s0 = der.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true)); + var curve = s0.ReadObjectIdentifier(); - construct = der.ReadBytes(der.ReadLength()); // object length + var s1 = der.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true)); + var pubkey = s1.ReadBitString(out _); - // PublicKey - var pubkey_der = new DerData(construct, construct: true); - var pubkey = pubkey_der.ReadBitString().TrimLeadingZeros(); + der.ThrowIfNotEmpty(); - _impl = Import(OidByteArrayToString(curve), pubkey, privatekey); + _impl = Import(curve, pubkey, privatekey); } #pragma warning disable CA1859 // Use concrete types when possible for improved performance @@ -319,35 +289,6 @@ private static string GetCurveOid(string curve_s) throw new SshException("Unexpected Curve Name: " + curve_s); } - private static string OidByteArrayToString(byte[] oid) - { - var retVal = new StringBuilder(); - - for (var i = 0; i < oid.Length; i++) - { - if (i == 0) - { - var b = oid[0] % 40; - var a = (oid[0] - b) / 40; - _ = retVal.AppendFormat("{0}.{1}", a, b); - } - else - { - if (oid[i] < 128) - { - _ = retVal.AppendFormat(".{0}", oid[i]); - } - else - { - _ = retVal.AppendFormat(".{0}", ((oid[i] - 128) * 128) + oid[i + 1]); - i++; - } - } - } - - return retVal.ToString(); - } - /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/src/Renci.SshNet/Security/Cryptography/RsaKey.cs b/src/Renci.SshNet/Security/Cryptography/RsaKey.cs index 84fd14c91..d77d7bf3e 100644 --- a/src/Renci.SshNet/Security/Cryptography/RsaKey.cs +++ b/src/Renci.SshNet/Security/Cryptography/RsaKey.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Formats.Asn1; using System.Numerics; using System.Security.Cryptography; @@ -166,22 +167,19 @@ public RsaKey(byte[] privateKeyData) throw new ArgumentNullException(nameof(privateKeyData)); } - var der = new DerData(privateKeyData); - _ = der.ReadBigInteger(); // skip version + var der = new AsnReader(privateKeyData, AsnEncodingRules.DER).ReadSequence(); + _ = der.ReadInteger(); // 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(); + Modulus = der.ReadInteger(); + Exponent = der.ReadInteger(); + D = der.ReadInteger(); + P = der.ReadInteger(); + Q = der.ReadInteger(); + DP = der.ReadInteger(); + DQ = der.ReadInteger(); + InverseQ = der.ReadInteger(); - if (!der.IsEndOfData) - { - throw new InvalidOperationException("Invalid private key (expected EOF)."); - } + der.ThrowIfNotEmpty(); RSA = RSA.Create(); RSA.ImportParameters(GetRSAParameters()); From c8cdd8970486d2c9ee1e4198d9562a8befea2814 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Wed, 11 Sep 2024 07:32:12 +0800 Subject: [PATCH 2/4] Ref System.Formats.Asn1 only for net462, netstandard2.0 and netstandard2.1 Delete ObjectIdentifier.cs --- src/Renci.SshNet/Common/ObjectIdentifier.cs | 54 ------------------- src/Renci.SshNet/Renci.SshNet.csproj | 5 +- .../Classes/Common/ObjectIdentifierTest.cs | 38 ------------- 3 files changed, 2 insertions(+), 95 deletions(-) delete mode 100644 src/Renci.SshNet/Common/ObjectIdentifier.cs delete mode 100644 test/Renci.SshNet.Tests/Classes/Common/ObjectIdentifierTest.cs diff --git a/src/Renci.SshNet/Common/ObjectIdentifier.cs b/src/Renci.SshNet/Common/ObjectIdentifier.cs deleted file mode 100644 index 5aa310a54..000000000 --- a/src/Renci.SshNet/Common/ObjectIdentifier.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Linq; -using System.Security.Cryptography; - -namespace Renci.SshNet.Common -{ - /// - /// Describes object identifier for DER encoding. - /// -#pragma warning disable CA1815 // Override equals and operator equals on value types - public struct ObjectIdentifier -#pragma warning restore CA1815 // Override equals and operator equals on value types - { - /// - /// Gets the object identifier. - /// - public ulong[] Identifiers { get; private set; } - - /// - /// Initializes a new instance of the struct. - /// - /// The identifiers. - /// is . - /// has less than two elements. - public ObjectIdentifier(params ulong[] identifiers) - { - if (identifiers is null) - { - throw new ArgumentNullException(nameof(identifiers)); - } - - if (identifiers.Length < 2) - { - throw new ArgumentException("Must contain at least two elements.", nameof(identifiers)); - } - - Identifiers = identifiers; - } - - internal static ObjectIdentifier FromHashAlgorithmName(HashAlgorithmName hashAlgorithmName) - { - var oid = CryptoConfig.MapNameToOID(hashAlgorithmName.Name); - - if (oid is null) - { - throw new ArgumentException($"Could not map `{hashAlgorithmName}` to OID.", nameof(hashAlgorithmName)); - } - - var identifiers = oid.Split('.').Select(ulong.Parse).ToArray(); - - return new ObjectIdentifier(identifiers); - } - } -} diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index 61bc29e40..b047501c5 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -42,15 +42,14 @@ - + - + - diff --git a/test/Renci.SshNet.Tests/Classes/Common/ObjectIdentifierTest.cs b/test/Renci.SshNet.Tests/Classes/Common/ObjectIdentifierTest.cs deleted file mode 100644 index c0b74098b..000000000 --- a/test/Renci.SshNet.Tests/Classes/Common/ObjectIdentifierTest.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; - -using Microsoft.VisualStudio.TestTools.UnitTesting; - -using Renci.SshNet.Common; -using Renci.SshNet.Tests.Common; - -namespace Renci.SshNet.Tests.Classes.Common -{ - [TestClass] - public class ObjectIdentifierTest : TestBase - { - [TestMethod] - public void Constructor_IdentifiersIsNull() - { - const ulong[] identifiers = null; - - var actualException = Assert.ThrowsException(() => new ObjectIdentifier(identifiers)); - - Assert.AreEqual(typeof(ArgumentNullException), actualException.GetType()); - Assert.IsNull(actualException.InnerException); - Assert.AreEqual(nameof(identifiers), actualException.ParamName); - } - - [TestMethod] - public void Constructor_LengthOfIdentifiersIsLessThanTwo() - { - var identifiers = new[] { 5UL }; - - var actualException = Assert.ThrowsException(() => new ObjectIdentifier(identifiers)); - - Assert.AreEqual(typeof(ArgumentException), actualException.GetType()); - Assert.IsNull(actualException.InnerException); - ArgumentExceptionAssert.MessageEquals("Must contain at least two elements.", actualException); - Assert.AreEqual(nameof(identifiers), actualException.ParamName); - } - } -} From ed63050a59dcb82275e607882919d39e2e11bd75 Mon Sep 17 00:00:00 2001 From: Scott Xu Date: Wed, 11 Sep 2024 23:03:48 +0800 Subject: [PATCH 3/4] Consolidate package reference --- src/Renci.SshNet/Renci.SshNet.csproj | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index b047501c5..93d664486 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -33,23 +33,16 @@ true - - - - - - - - - - - + + + + + From 30082a65672f3206e6739a5665219679c181af01 Mon Sep 17 00:00:00 2001 From: Rob Hague Date: Wed, 11 Sep 2024 22:11:25 +0200 Subject: [PATCH 4/4] 100x improvement --- src/Renci.SshNet/Renci.SshNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Renci.SshNet.csproj b/src/Renci.SshNet/Renci.SshNet.csproj index a991a5ee6..5d853cf03 100644 --- a/src/Renci.SshNet/Renci.SshNet.csproj +++ b/src/Renci.SshNet/Renci.SshNet.csproj @@ -40,8 +40,8 @@ - +