diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9c5950fab..c5c4f4ada 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -20,7 +20,7 @@
-
+
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/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 9ddb79769..5d853cf03 100644
--- a/src/Renci.SshNet/Renci.SshNet.csproj
+++ b/src/Renci.SshNet/Renci.SshNet.csproj
@@ -33,18 +33,19 @@
true
-
+
+
-
+
-
-
+
+
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());
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);
- }
- }
-}