Ref System.Formats.Asn1 nuget package (#1490)

* Ref System.Formats.Asn1 nuget package

* Ref System.Formats.Asn1 only for net462, netstandard2.0 and netstandard2.1
Delete ObjectIdentifier.cs

* Consolidate package reference

* 100x improvement

---------

Co-authored-by: Rob Hague <rob.hague00@gmail.com>
This commit is contained in:
Scott Xu
2024-09-12 04:51:33 +08:00
committed by GitHub
parent dde5f67b2e
commit f4bf62b847
8 changed files with 36 additions and 629 deletions
+1 -1
View File
@@ -20,7 +20,7 @@
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.7.70-alpha" />
<PackageVersion Include="SonarAnalyzer.CSharp" Version="9.19.0.84025" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Formats.Asn1" Version="8.0.1" />
<PackageVersion Include="Testcontainers" Version="3.9.0" />
</ItemGroup>
</Project>
-439
View File
@@ -1,439 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Numerics;
namespace Renci.SshNet.Common
{
/// <summary>
/// Base class for DER encoded data.
/// </summary>
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<byte> _data;
private readonly int _lastIndex;
private int _readerIndex;
/// <summary>
/// Gets a value indicating whether end of data is reached.
/// </summary>
/// <value>
/// <see langword="true"/> if end of data is reached; otherwise, <see langword="false"/>.
/// </value>
public bool IsEndOfData
{
get
{
return _readerIndex >= _lastIndex;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="DerData"/> class.
/// </summary>
public DerData()
{
_data = new List<byte>();
}
/// <summary>
/// Initializes a new instance of the <see cref="DerData"/> class.
/// </summary>
/// <param name="data">DER encoded data.</param>
/// <param name="construct">its a construct.</param>
public DerData(byte[] data, bool construct = false)
{
_data = new List<byte>(data);
if (construct)
{
_lastIndex = _readerIndex + data.Length;
}
else
{
_ = ReadByte(); // skip dataType
var length = ReadLength();
_lastIndex = _readerIndex + length;
}
}
/// <summary>
/// Encodes written data as DER byte array.
/// </summary>
/// <returns>DER Encoded array.</returns>
public byte[] Encode()
{
var length = _data.Count;
var lengthBytes = GetLength(length);
_data.InsertRange(0, lengthBytes);
_data.Insert(0, Constructed | Sequence);
return _data.ToArray();
}
/// <summary>
/// Reads next mpint data type from internal buffer.
/// </summary>
/// <returns>mpint read.</returns>
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
}
/// <summary>
/// Reads next int data type from internal buffer.
/// </summary>
/// <returns>int read.</returns>
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;
}
/// <summary>
/// Reads next octetstring data type from internal buffer.
/// </summary>
/// <returns>data read.</returns>
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;
}
/// <summary>
/// Reads next bitstring data type from internal buffer.
/// </summary>
/// <returns>data read.</returns>
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;
}
/// <summary>
/// Reads next object data type from internal buffer.
/// </summary>
/// <returns>data read.</returns>
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;
}
/// <summary>
/// Writes BOOLEAN data into internal buffer.
/// </summary>
/// <param name="data">UInt32 data to write.</param>
public void Write(bool data)
{
_data.Add(Boolean);
_data.Add(1);
_data.Add((byte)(data ? 1 : 0));
}
/// <summary>
/// Writes UInt32 data into internal buffer.
/// </summary>
/// <param name="data">UInt32 data to write.</param>
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);
}
/// <summary>
/// Writes INTEGER data into internal buffer.
/// </summary>
/// <param name="data">BigInteger data to write.</param>
public void Write(BigInteger data)
{
var bytes = data.ToByteArray(isBigEndian: true);
_data.Add(Integer);
var length = GetLength(bytes.Length);
WriteBytes(length);
WriteBytes(bytes);
}
/// <summary>
/// Writes OCTETSTRING data into internal buffer.
/// </summary>
/// <param name="data">The data.</param>
public void Write(byte[] data)
{
_data.Add(Octetstring);
var length = GetLength(data.Length);
WriteBytes(length);
WriteBytes(data);
}
/// <summary>
/// Writes OBJECTIDENTIFIER data into internal buffer.
/// </summary>
/// <param name="identifier">The identifier.</param>
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<byte>();
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);
}
/// <summary>
/// Writes DerData data into internal buffer.
/// </summary>
/// <param name="data">DerData data to write.</param>
public void Write(DerData data)
{
var bytes = data.Encode();
_data.AddRange(bytes);
}
/// <summary>
/// Writes BITSTRING data into internal buffer.
/// </summary>
/// <param name="data">The data.</param>
public void WriteBitstring(byte[] data)
{
_data.Add(BITSTRING);
var length = GetLength(data.Length);
WriteBytes(length);
WriteBytes(data);
}
/// <summary>
/// Writes OBJECTIDENTIFIER data into internal buffer.
/// </summary>
/// <param name="bytes">The bytes.</param>
public void WriteObjectIdentifier(byte[] bytes)
{
_data.Add(Objectidentifier);
var length = GetLength(bytes.Length);
WriteBytes(length);
WriteBytes(bytes);
}
/// <summary>
/// Writes NULL data into internal buffer.
/// </summary>
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 };
}
/// <summary>
/// Gets Data Length.
/// </summary>
/// <returns>
/// The length.
/// </returns>
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;
}
/// <summary>
/// Write Byte data into internal buffer.
/// </summary>
/// <param name="data">The data to write.</param>
public void WriteBytes(IEnumerable<byte> data)
{
_data.AddRange(data);
}
/// <summary>
/// Reads Byte data into internal buffer.
/// </summary>
/// <returns>
/// The data read.
/// </returns>
public byte ReadByte()
{
if (_readerIndex > _data.Count)
{
throw new InvalidOperationException("Read out of boundaries.");
}
return _data[_readerIndex++];
}
/// <summary>
/// Reads lengths Bytes data into internal buffer.
/// </summary>
/// <returns>
/// The data read.
/// </returns>
/// <param name="length">amount of data to read.</param>
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;
}
}
}
@@ -1,54 +0,0 @@
using System;
using System.Linq;
using System.Security.Cryptography;
namespace Renci.SshNet.Common
{
/// <summary>
/// Describes object identifier for DER encoding.
/// </summary>
#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
{
/// <summary>
/// Gets the object identifier.
/// </summary>
public ulong[] Identifiers { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="ObjectIdentifier"/> struct.
/// </summary>
/// <param name="identifiers">The identifiers.</param>
/// <exception cref="ArgumentNullException"><paramref name="identifiers"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="identifiers"/> has less than two elements.</exception>
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);
}
}
}
+5 -4
View File
@@ -33,18 +33,19 @@
<PropertyGroup Condition=" $([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0')) ">
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" />
<PackageReference Include="Nerdbank.GitVersioning" PrivateAssets="all" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Formats.Asn1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" />
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.1'">
<PackageReference Include="System.Formats.Asn1" />
</ItemGroup>
<ItemGroup>
@@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Formats.Asn1;
using System.Numerics;
using System.Security.Cryptography;
@@ -118,19 +119,16 @@ namespace Renci.SshNet.Security
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();
}
@@ -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 @@ namespace Renci.SshNet.Security
/// <param name="data">DER encoded private key data.</param>
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 s0 = der.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0, isConstructed: true));
var curve = s0.ReadObjectIdentifier();
var tag = s0 & 0x1f;
if (tag != 0)
{
throw new SshException(string.Format("expected tag 0 in DER privkey, got: {0}", tag));
}
var s1 = der.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 1, isConstructed: true));
var pubkey = s1.ReadBitString(out _);
var construct = der.ReadBytes(der.ReadLength()); // object length
der.ThrowIfNotEmpty();
// 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));
}
construct = der.ReadBytes(der.ReadLength()); // object length
// PublicKey
var pubkey_der = new DerData(construct, construct: true);
var pubkey = pubkey_der.ReadBitString().TrimLeadingZeros();
_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 @@ namespace Renci.SshNet.Security
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();
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
@@ -1,5 +1,6 @@
#nullable enable
using System;
using System.Formats.Asn1;
using System.Numerics;
using System.Security.Cryptography;
@@ -166,22 +167,19 @@ namespace Renci.SshNet.Security
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());
@@ -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<ArgumentNullException>(() => 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<ArgumentException>(() => 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);
}
}
}