mirror of
https://github.com/SabreTools/SabreTools.ASN1.git
synced 2026-02-07 13:54:33 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca1cb5a66e | ||
|
|
4c6ef33af2 | ||
|
|
eb519af488 | ||
|
|
402fe65e20 | ||
|
|
c22f0f2c6e | ||
|
|
92d5a097a8 | ||
|
|
d722f1fad2 | ||
|
|
a570f9a96a | ||
|
|
d4a118ba04 | ||
|
|
9ceea4f85a | ||
|
|
a0a7f23b6c | ||
|
|
bca1754d42 | ||
|
|
a6a281deb8 | ||
|
|
37bc010762 | ||
|
|
0a217e5e6b | ||
|
|
73e7b035b8 | ||
|
|
7276ab2199 | ||
|
|
fef4d30add | ||
|
|
e632f8537b | ||
|
|
ce8c4b1831 | ||
|
|
2bdb56aca2 | ||
|
|
ce6ffd3f7f | ||
|
|
c06618c66c | ||
|
|
eb356483b4 | ||
|
|
0e76552df8 | ||
|
|
070274e33f | ||
|
|
dd64d6f843 | ||
|
|
69992f64be | ||
|
|
e63921fbc2 | ||
|
|
69dc21c814 | ||
|
|
4c07f24e3c | ||
|
|
b6b43d9e1b | ||
|
|
7c972c1ea2 | ||
|
|
7c40583898 | ||
|
|
ef479c783a | ||
|
|
1ebd9f82ee | ||
|
|
774597b17c | ||
|
|
29437475fb | ||
|
|
214d4e4f9e | ||
|
|
06c742cd15 | ||
|
|
bc591f367f | ||
|
|
8c6b962bd6 |
6
.github/workflows/build_nupkg.yml
vendored
6
.github/workflows/build_nupkg.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'Nuget Package'
|
||||
path: 'bin/Release/*.nupkg'
|
||||
path: 'SabreTools.ASN1/bin/Release/*.nupkg'
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: 'bin/Release/*.nupkg'
|
||||
artifacts: 'SabreTools.ASN1/bin/Release/*.nupkg'
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
|
||||
2
.github/workflows/check_pr.yml
vendored
2
.github/workflows/check_pr.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
@@ -1,31 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SabreTools.ASN1
|
||||
{
|
||||
/// <summary>
|
||||
/// ASN.1 Parser
|
||||
/// </summary>
|
||||
public static class AbstractSyntaxNotationOne
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse a byte array into a DER-encoded ASN.1 structure
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the data</param>
|
||||
/// <param name="pointer">Current pointer into the data</param>
|
||||
/// <returns></returns>
|
||||
public static List<TypeLengthValue> Parse(byte[] data, int pointer)
|
||||
{
|
||||
// Create the output list to return
|
||||
var topLevelValues = new List<TypeLengthValue>();
|
||||
|
||||
// Loop through the data and return all top-level values
|
||||
while (pointer < data.Length)
|
||||
{
|
||||
var topLevelValue = new TypeLengthValue(data, ref pointer);
|
||||
topLevelValues.Add(topLevelValue);
|
||||
}
|
||||
|
||||
return topLevelValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
SabreTools.ASN1.Test/AbstractSyntaxNotationOneTests.cs
Normal file
61
SabreTools.ASN1.Test/AbstractSyntaxNotationOneTests.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.ASN1.Test
|
||||
{
|
||||
public class AbstractSyntaxNotationOneTests
|
||||
{
|
||||
[Fact]
|
||||
public void Parse_EmptyArray_Throws()
|
||||
{
|
||||
byte[] data = [];
|
||||
Assert.Throws<InvalidDataException>(() => AbstractSyntaxNotationOne.Parse(data, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ValidArrayNegativeIndex_Throws()
|
||||
{
|
||||
byte[] data = [0x00];
|
||||
Assert.Throws<IndexOutOfRangeException>(() => AbstractSyntaxNotationOne.Parse(data, -1));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ValidArrayOverIndex_Throws()
|
||||
{
|
||||
byte[] data = [0x00];
|
||||
Assert.Throws<IndexOutOfRangeException>(() => AbstractSyntaxNotationOne.Parse(data, 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ValidMinimalArray()
|
||||
{
|
||||
byte[] data = [0x00];
|
||||
var tlvs = AbstractSyntaxNotationOne.Parse(data, 0);
|
||||
|
||||
var tlv = Assert.Single(tlvs);
|
||||
Assert.Equal(ASN1Type.V_ASN1_EOC, tlv.Type);
|
||||
Assert.Equal(default, tlv.Length);
|
||||
Assert.Null(tlv.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_EmptyStream_Throws()
|
||||
{
|
||||
Stream data = new MemoryStream([], 0, 0, false, false);
|
||||
Assert.Throws<InvalidDataException>(() => AbstractSyntaxNotationOne.Parse(data));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_ValidMinimalStream()
|
||||
{
|
||||
Stream data = new MemoryStream([0x00]);
|
||||
var tlvs = AbstractSyntaxNotationOne.Parse(data);
|
||||
|
||||
var tlv = Assert.Single(tlvs);
|
||||
Assert.Equal(ASN1Type.V_ASN1_EOC, tlv.Type);
|
||||
Assert.Equal(default, tlv.Length);
|
||||
Assert.Null(tlv.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
110
SabreTools.ASN1.Test/ObjectIdentifierTests.cs
Normal file
110
SabreTools.ASN1.Test/ObjectIdentifierTests.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.ASN1.Test
|
||||
{
|
||||
// These tests are known to be incomplete due to the sheer number
|
||||
// of possible OIDs that exist. The tests below are a minimal
|
||||
// representation of functionality to guarantee proper behavior
|
||||
// not necessarily absolute outputs
|
||||
public class ObjectIdentifierTests
|
||||
{
|
||||
#region ASN.1
|
||||
|
||||
[Fact]
|
||||
public void ASN1Notation_AlwaysNull()
|
||||
{
|
||||
ulong[]? values = null;
|
||||
string? actual = ObjectIdentifier.ParseOIDToASN1Notation(values);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Dot Notation
|
||||
|
||||
[Fact]
|
||||
public void DotNotation_NullValues_Null()
|
||||
{
|
||||
ulong[]? values = null;
|
||||
string? actual = ObjectIdentifier.ParseOIDToDotNotation(values);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DotNotation_EmptyValues_Null()
|
||||
{
|
||||
ulong[]? values = [];
|
||||
string? actual = ObjectIdentifier.ParseOIDToDotNotation(values);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DotNotation_Values_Formatted()
|
||||
{
|
||||
string expected = "0.1.2.3";
|
||||
ulong[]? values = [0, 1, 2, 3];
|
||||
string? actual = ObjectIdentifier.ParseOIDToDotNotation(values);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modified OID-IRI
|
||||
|
||||
[Fact]
|
||||
public void ModifiedOIDIRI_NullValues_Null()
|
||||
{
|
||||
ulong[]? values = null;
|
||||
string? actual = ObjectIdentifier.ParseOIDToModifiedOIDIRI(values);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModifiedOIDIRI_EmptyValues_Null()
|
||||
{
|
||||
ulong[]? values = [];
|
||||
string? actual = ObjectIdentifier.ParseOIDToModifiedOIDIRI(values);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModifiedOIDIRI_Values_Formatted()
|
||||
{
|
||||
string expected = "/ITU-T/[question]/2/3";
|
||||
ulong[]? values = [0, 1, 2, 3];
|
||||
string? actual = ObjectIdentifier.ParseOIDToModifiedOIDIRI(values);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OID-IRI
|
||||
|
||||
[Fact]
|
||||
public void OIDIRI_NullValues_Null()
|
||||
{
|
||||
ulong[]? values = null;
|
||||
string? actual = ObjectIdentifier.ParseOIDToOIDIRINotation(values);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OIDIRI_EmptyValues_Null()
|
||||
{
|
||||
ulong[]? values = [];
|
||||
string? actual = ObjectIdentifier.ParseOIDToOIDIRINotation(values);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OIDIRI_Values_Formatted()
|
||||
{
|
||||
string expected = "/ITU-T/1/2/3";
|
||||
ulong[]? values = [0, 1, 2, 3];
|
||||
string? actual = ObjectIdentifier.ParseOIDToOIDIRINotation(values);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
30
SabreTools.ASN1.Test/SabreTools.ASN1.Test.csproj
Normal file
30
SabreTools.ASN1.Test/SabreTools.ASN1.Test.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<IsPackable>false</IsPackable>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<NoWarn>NU1903</NoWarn>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SabreTools.ASN1\SabreTools.ASN1.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
322
SabreTools.ASN1.Test/TypeLengthValueTests.cs
Normal file
322
SabreTools.ASN1.Test/TypeLengthValueTests.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.ASN1.Test
|
||||
{
|
||||
public class TypeLengthValueTests
|
||||
{
|
||||
#region Construction
|
||||
|
||||
[Fact]
|
||||
public void Constructor_EmptyArray_Throws()
|
||||
{
|
||||
int index = 0;
|
||||
byte[] data = [];
|
||||
Assert.Throws<InvalidDataException>(() => new TypeLengthValue(data, ref index));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ValidArrayNegativeIndex_Throws()
|
||||
{
|
||||
int index = -1;
|
||||
byte[] data = [0x00];
|
||||
Assert.Throws<IndexOutOfRangeException>(() => new TypeLengthValue(data, ref index));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ValidArrayOverIndex_Throws()
|
||||
{
|
||||
int index = 10;
|
||||
byte[] data = [0x00];
|
||||
Assert.Throws<IndexOutOfRangeException>(() => new TypeLengthValue(data, ref index));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ValidMinimalArray()
|
||||
{
|
||||
int index = 0;
|
||||
byte[] data = [0x00];
|
||||
var tlv = new TypeLengthValue(data, ref index);
|
||||
|
||||
Assert.Equal(ASN1Type.V_ASN1_EOC, tlv.Type);
|
||||
Assert.Equal(default, tlv.Length);
|
||||
Assert.Null(tlv.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_EmptyStream_Throws()
|
||||
{
|
||||
Stream data = new MemoryStream([], 0, 0, false, false);
|
||||
Assert.Throws<InvalidDataException>(() => new TypeLengthValue(data));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ValidMinimalStream()
|
||||
{
|
||||
Stream data = new MemoryStream([0x00]);
|
||||
var tlv = new TypeLengthValue(data);
|
||||
|
||||
Assert.Equal(ASN1Type.V_ASN1_EOC, tlv.Type);
|
||||
Assert.Equal(default, tlv.Length);
|
||||
Assert.Null(tlv.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_ValidBoolean()
|
||||
{
|
||||
Stream data = new MemoryStream([0x01, 0x01, 0x01]);
|
||||
var tlv = new TypeLengthValue(data);
|
||||
|
||||
Assert.Equal(ASN1Type.V_ASN1_BOOLEAN, tlv.Type);
|
||||
Assert.Equal(1UL, tlv.Length);
|
||||
Assert.NotNull(tlv.Value);
|
||||
|
||||
byte[]? valueAsArray = tlv.Value as byte[];
|
||||
Assert.NotNull(valueAsArray);
|
||||
byte actual = Assert.Single(valueAsArray);
|
||||
Assert.Equal(0x01, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[] { 0x26, 0x81, 0x03, 0x01, 0x01, 0x01 })]
|
||||
[InlineData(new byte[] { 0x26, 0x82, 0x00, 0x03, 0x01, 0x01, 0x01 })]
|
||||
[InlineData(new byte[] { 0x26, 0x83, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
|
||||
[InlineData(new byte[] { 0x26, 0x84, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
|
||||
[InlineData(new byte[] { 0x26, 0x85, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
|
||||
[InlineData(new byte[] { 0x26, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
|
||||
[InlineData(new byte[] { 0x26, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
|
||||
[InlineData(new byte[] { 0x26, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x01, 0x01 })]
|
||||
public void Constructor_ComplexValue(byte[] arr)
|
||||
{
|
||||
Stream data = new MemoryStream(arr);
|
||||
var tlv = new TypeLengthValue(data);
|
||||
|
||||
Assert.Equal(ASN1Type.V_ASN1_CONSTRUCTED | ASN1Type.V_ASN1_OBJECT, tlv.Type);
|
||||
Assert.Equal(3UL, tlv.Length);
|
||||
Assert.NotNull(tlv.Value);
|
||||
|
||||
TypeLengthValue[]? valueAsArray = tlv.Value as TypeLengthValue[];
|
||||
Assert.NotNull(valueAsArray);
|
||||
TypeLengthValue actual = Assert.Single(valueAsArray);
|
||||
|
||||
Assert.Equal(ASN1Type.V_ASN1_BOOLEAN, actual.Type);
|
||||
Assert.Equal(1UL, actual.Length);
|
||||
Assert.NotNull(actual.Value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new byte[] { 0x26, 0x80 })]
|
||||
[InlineData(new byte[] { 0x26, 0x89 })]
|
||||
public void Constructor_ComplexValueInvalidLength_Throws(byte[] arr)
|
||||
{
|
||||
Stream data = new MemoryStream(arr);
|
||||
Assert.Throws<InvalidOperationException>(() => new TypeLengthValue(data));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Formatting
|
||||
|
||||
[Fact]
|
||||
public void Format_EOC()
|
||||
{
|
||||
string expected = "Type: V_ASN1_EOC";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_EOC, 0, null);
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ZeroLength()
|
||||
{
|
||||
string expected = "Type: V_ASN1_NULL, Length: 0";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_NULL, 0, null);
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_InvalidConstructed()
|
||||
{
|
||||
string expected = "Type: V_ASN1_OBJECT, V_ASN1_CONSTRUCTED, Length: 1, Value: [INVALID DATA TYPE]";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT | ASN1Type.V_ASN1_CONSTRUCTED, 1, (object?)false);
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidConstructed()
|
||||
{
|
||||
string expected = "Type: V_ASN1_OBJECT, V_ASN1_CONSTRUCTED, Length: 3, Value:\n Type: V_ASN1_BOOLEAN, Length: 1, Value: True";
|
||||
var boolTlv = new TypeLengthValue(ASN1Type.V_ASN1_BOOLEAN, 1, new byte[] { 0x01 });
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT | ASN1Type.V_ASN1_CONSTRUCTED, 3, new TypeLengthValue[] { boolTlv });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_InvalidDataType()
|
||||
{
|
||||
string expected = "Type: V_ASN1_OBJECT, Length: 1, Value: [INVALID DATA TYPE]";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT, 1, (object?)false);
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_InvalidLength()
|
||||
{
|
||||
string expected = "Type: V_ASN1_NULL, Length: 1, Value: [NO DATA]";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_NULL, 1, Array.Empty<byte>());
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_InvalidBooleanLength()
|
||||
{
|
||||
string expected = "Type: V_ASN1_BOOLEAN, Length: 2 [Expected length of 1], Value: True";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BOOLEAN, 2, new byte[] { 0x01 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_InvalidBooleanArrayLength()
|
||||
{
|
||||
string expected = "Type: V_ASN1_BOOLEAN, Length: 1 [Expected value length of 1], Value: True";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BOOLEAN, 1, new byte[] { 0x01, 0x00 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidBoolean()
|
||||
{
|
||||
string expected = "Type: V_ASN1_BOOLEAN, Length: 1, Value: True";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BOOLEAN, 1, new byte[] { 0x01 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidInteger()
|
||||
{
|
||||
string expected = "Type: V_ASN1_INTEGER, Length: 1, Value: 1";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_INTEGER, 1, new byte[] { 0x01 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidBitString_NoBits()
|
||||
{
|
||||
string expected = "Type: V_ASN1_BIT_STRING, Length: 1, Value with 0 unused bits";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BIT_STRING, 1, new byte[] { 0x00 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidBitString_Bits()
|
||||
{
|
||||
string expected = "Type: V_ASN1_BIT_STRING, Length: 1, Value with 1 unused bits: 01";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BIT_STRING, 1, new byte[] { 0x01, 0x01 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidOctetString()
|
||||
{
|
||||
string expected = "Type: V_ASN1_OCTET_STRING, Length: 1, Value: 01";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OCTET_STRING, 1, new byte[] { 0x01 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidObject()
|
||||
{
|
||||
string expected = "Type: V_ASN1_OBJECT, Length: 3, Value: 0.1.2.3 (/ITU-T/1/2/3)";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT, 3, new byte[] { 0x01, 0x02, 0x03 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidUTF8String()
|
||||
{
|
||||
string expected = "Type: V_ASN1_UTF8STRING, Length: 3, Value: ABC";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_UTF8STRING, 3, new byte[] { 0x41, 0x42, 0x43 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidPrintableString()
|
||||
{
|
||||
string expected = "Type: V_ASN1_PRINTABLESTRING, Length: 3, Value: ABC";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_PRINTABLESTRING, 3, new byte[] { 0x41, 0x42, 0x43 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidTeletexString()
|
||||
{
|
||||
string expected = "Type: V_ASN1_TELETEXSTRING, Length: 3, Value: ABC";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_TELETEXSTRING, 3, new byte[] { 0x41, 0x42, 0x43 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidIA5String()
|
||||
{
|
||||
string expected = "Type: V_ASN1_IA5STRING, Length: 3, Value: ABC";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_IA5STRING, 3, new byte[] { 0x41, 0x42, 0x43 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_InvalidUTCTime()
|
||||
{
|
||||
string expected = "Type: V_ASN1_UTCTIME, Length: 3, Value: ABC";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_UTCTIME, 3, new byte[] { 0x41, 0x42, 0x43 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidUTCTime()
|
||||
{
|
||||
string expected = "Type: V_ASN1_UTCTIME, Length: 3, Value: 1980-01-01 00:00:00";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_UTCTIME, 3, new byte[] { 0x31, 0x39, 0x38, 0x30, 0x2D, 0x30, 0x31, 0x2D, 0x30, 0x31, 0x20, 0x30, 0x30, 0x3A, 0x30, 0x30, 0x3A, 0x30, 0x30 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidBmpString()
|
||||
{
|
||||
string expected = "Type: V_ASN1_BMPSTRING, Length: 6, Value: ABC";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_BMPSTRING, 6, new byte[] { 0x41, 0x00, 0x42, 0x00, 0x43, 0x00 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_ValidUnformatted()
|
||||
{
|
||||
string expected = "Type: V_ASN1_OBJECT_DESCRIPTOR, Length: 1, Value: 01";
|
||||
var tlv = new TypeLengthValue(ASN1Type.V_ASN1_OBJECT_DESCRIPTOR, 1, new byte[] { 0x01 });
|
||||
string actual = tlv.Format();
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.3.0</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Serialization and deserialization helpers for various types</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2022-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.ASN1</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>asn asn1 dot oid</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath=""/>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))">
|
||||
<PackageReference Include="Net30.LinqBridge" Version="1.3.0" />
|
||||
<PackageReference Include="NetLegacySupport.Numerics" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.ASN1", "SabreTools.ASN1.csproj", "{88EA40DD-E313-479C-94EA-0AB948DB4720}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.ASN1", "SabreTools.ASN1\SabreTools.ASN1.csproj", "{88EA40DD-E313-479C-94EA-0AB948DB4720}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.ASN1.Test", "SabreTools.ASN1.Test\SabreTools.ASN1.Test.csproj", "{5C740920-9505-4B58-AC17-2967559F6AE7}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -18,5 +20,9 @@ Global
|
||||
{88EA40DD-E313-479C-94EA-0AB948DB4720}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88EA40DD-E313-479C-94EA-0AB948DB4720}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88EA40DD-E313-479C-94EA-0AB948DB4720}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5C740920-9505-4B58-AC17-2967559F6AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5C740920-9505-4B58-AC17-2967559F6AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5C740920-9505-4B58-AC17-2967559F6AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5C740920-9505-4B58-AC17-2967559F6AE7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
55
SabreTools.ASN1/AbstractSyntaxNotationOne.cs
Normal file
55
SabreTools.ASN1/AbstractSyntaxNotationOne.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.ASN1
|
||||
{
|
||||
/// <summary>
|
||||
/// ASN.1 Parser
|
||||
/// </summary>
|
||||
public static class AbstractSyntaxNotationOne
|
||||
{
|
||||
/// <summary>
|
||||
/// Parse a byte array into a DER-encoded ASN.1 structure
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the data</param>
|
||||
/// <param name="pointer">Current pointer into the data</param>
|
||||
public static List<TypeLengthValue> Parse(byte[] data, int pointer)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data.Length == 0)
|
||||
throw new InvalidDataException(nameof(data));
|
||||
if (pointer < 0 || pointer >= data.Length)
|
||||
throw new IndexOutOfRangeException(nameof(pointer));
|
||||
|
||||
using var stream = new MemoryStream(data);
|
||||
stream.Seek(pointer, SeekOrigin.Begin);
|
||||
return Parse(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a stream into a DER-encoded ASN.1 structure
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the data</param>
|
||||
public static List<TypeLengthValue> Parse(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data.Length == 0 || !data.CanRead)
|
||||
throw new InvalidDataException(nameof(data));
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
throw new IndexOutOfRangeException(nameof(data));
|
||||
|
||||
// Create the output list to return
|
||||
var topLevelValues = new List<TypeLengthValue>();
|
||||
|
||||
// Loop through the data and return all top-level values
|
||||
while (data.Position < data.Length)
|
||||
{
|
||||
var topLevelValue = new TypeLengthValue(data);
|
||||
topLevelValues.Add(topLevelValue);
|
||||
}
|
||||
|
||||
return topLevelValues;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,17 +8,6 @@ namespace SabreTools.ASN1
|
||||
[Flags]
|
||||
public enum ASN1Type : byte
|
||||
{
|
||||
#region Modifiers
|
||||
|
||||
V_ASN1_UNIVERSAL = 0x00,
|
||||
V_ASN1_PRIMITIVE_TAG = 0x1F,
|
||||
V_ASN1_CONSTRUCTED = 0x20,
|
||||
V_ASN1_APPLICATION = 0x40,
|
||||
V_ASN1_CONTEXT_SPECIFIC = 0x80,
|
||||
V_ASN1_PRIVATE = 0xC0,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Types
|
||||
|
||||
V_ASN1_EOC = 0x00,
|
||||
@@ -51,5 +40,19 @@ namespace SabreTools.ASN1
|
||||
V_ASN1_BMPSTRING = 0x1E,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modifiers
|
||||
|
||||
// Commented out because it is the default
|
||||
// and can interfere with V_ASN1_EOC
|
||||
// V_ASN1_UNIVERSAL = 0x00,
|
||||
|
||||
V_ASN1_PRIMITIVE_TAG = 0x1F,
|
||||
V_ASN1_CONSTRUCTED = 0x20,
|
||||
V_ASN1_APPLICATION = 0x40,
|
||||
V_ASN1_CONTEXT_SPECIFIC = 0x80,
|
||||
V_ASN1_PRIVATE = 0xC0,
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,9 @@ namespace SabreTools.ASN1
|
||||
/// Parse an OID in separated-value notation into ASN.1 notation
|
||||
/// </summary>
|
||||
/// <param name="values">List of values to check against</param>
|
||||
/// <param name="index">Current index into the list</param>
|
||||
/// <returns>ASN.1 formatted string, if possible</returns>
|
||||
/// <remarks>
|
||||
public static string? ParseOIDToASN1Notation(ulong[]? values, ref int index)
|
||||
public static string? ParseOIDToASN1Notation(ulong[]? values)
|
||||
{
|
||||
// TODO: Once the modified OID-IRI formatting is done, make an ASN.1 notation version
|
||||
return null;
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
@@ -48,7 +48,14 @@ namespace SabreTools.ASN1
|
||||
|
||||
// Add trailing items as just values
|
||||
nameBuilder.Append("/");
|
||||
nameBuilder.Append(string.Join("/", values.Skip(index).Select(v => v.ToString()).ToArray()));
|
||||
|
||||
// Get the remaining values in a new array
|
||||
var remainingValues = new ulong[values.Length - index];
|
||||
Array.Copy(values, index, remainingValues, 0, remainingValues.Length);
|
||||
|
||||
// Convert the values and append to the builder
|
||||
var stringValues = Array.ConvertAll(remainingValues, v => v.ToString());
|
||||
nameBuilder.Append(string.Join("/", stringValues));
|
||||
|
||||
// Create and return the string
|
||||
return nameBuilder.ToString();
|
||||
@@ -77,7 +84,7 @@ namespace SabreTools.ASN1
|
||||
|
||||
#region Start
|
||||
|
||||
var oidPath = $"/{values[index]}";
|
||||
var oidPath = string.Empty;
|
||||
switch (values[index++])
|
||||
{
|
||||
case 0: goto oid_0;
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.ASN1
|
||||
@@ -41,7 +41,14 @@ namespace SabreTools.ASN1
|
||||
|
||||
// Add trailing items as just values
|
||||
nameBuilder.Append("/");
|
||||
nameBuilder.Append(string.Join("/", values.Skip(index).Select(index => index.ToString()).ToArray()));
|
||||
|
||||
// Get the remaining values in a new array
|
||||
var remainingValues = new ulong[values.Length - index];
|
||||
Array.Copy(values, index, remainingValues, 0, remainingValues.Length);
|
||||
|
||||
// Convert the values and append to the builder
|
||||
var stringValues = Array.ConvertAll(remainingValues, v => v.ToString());
|
||||
nameBuilder.Append(string.Join("/", stringValues));
|
||||
|
||||
// Create and return the string
|
||||
return nameBuilder.ToString();
|
||||
@@ -66,7 +73,7 @@ namespace SabreTools.ASN1
|
||||
|
||||
#region Start
|
||||
|
||||
var oidPath = $"/{values[index]}";
|
||||
var oidPath = string.Empty;
|
||||
switch (values[index++])
|
||||
{
|
||||
case 0: goto oid_0;
|
||||
@@ -24,7 +24,7 @@ namespace SabreTools.ASN1
|
||||
int firstNode = Math.DivRem(data[0], 40, out int secondNode);
|
||||
|
||||
// Create a list for all nodes
|
||||
List<ulong> nodes = new List<ulong> { (ulong)firstNode, (ulong)secondNode };
|
||||
List<ulong> nodes = [(ulong)firstNode, (ulong)secondNode];
|
||||
|
||||
// All other nodes are encoded uniquely
|
||||
int offset = 1;
|
||||
@@ -65,7 +65,7 @@ namespace SabreTools.ASN1
|
||||
nodes.Add(dotValue);
|
||||
}
|
||||
|
||||
return nodes.ToArray();
|
||||
return [.. nodes];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace SabreTools.ASN1
|
||||
{
|
||||
@@ -18,7 +18,8 @@ namespace SabreTools.ASN1
|
||||
if (values == null || values.Length == 0)
|
||||
return null;
|
||||
|
||||
return string.Join(".", values.Select(v => v.ToString()).ToArray());
|
||||
var stringValues = Array.ConvertAll(values, v => v.ToString());
|
||||
return string.Join(".", [.. stringValues]);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
SabreTools.ASN1/SabreTools.ASN1.csproj
Normal file
40
SabreTools.ASN1/SabreTools.ASN1.csproj
Normal file
@@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.5.0</Version>
|
||||
|
||||
<!-- Package Properties -->
|
||||
<Authors>Matt Nadareski</Authors>
|
||||
<Description>Serialization and deserialization helpers for various types</Description>
|
||||
<Copyright>Copyright (c) Matt Nadareski 2022-2024</Copyright>
|
||||
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<RepositoryUrl>https://github.com/SabreTools/SabreTools.ASN1</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>asn asn1 dot oid</PackageTags>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="SabreTools.ASN1.Test" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Support for old .NET versions -->
|
||||
<ItemGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))">
|
||||
<PackageReference Include="NetLegacySupport.Numerics" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using SabreTools.IO;
|
||||
using SabreTools.IO.Extensions;
|
||||
|
||||
namespace SabreTools.ASN1
|
||||
{
|
||||
@@ -27,6 +27,16 @@ namespace SabreTools.ASN1
|
||||
/// </summary>
|
||||
public object? Value { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Manual constructor
|
||||
/// </summary>
|
||||
public TypeLengthValue(ASN1Type type, ulong length, object? value)
|
||||
{
|
||||
Type = type;
|
||||
Length = length;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read from the source data array at an index
|
||||
/// </summary>
|
||||
@@ -34,38 +44,26 @@ namespace SabreTools.ASN1
|
||||
/// <param name="index">Index within the array to read at</param>
|
||||
public TypeLengthValue(byte[] data, ref int index)
|
||||
{
|
||||
// Get the type and modifiers
|
||||
this.Type = (ASN1Type)data[index++];
|
||||
// If the data is invalid
|
||||
if (data.Length == 0)
|
||||
throw new InvalidDataException(nameof(data));
|
||||
if (index < 0 || index >= data.Length)
|
||||
throw new IndexOutOfRangeException(nameof(index));
|
||||
|
||||
// If we have an end indicator, we just return
|
||||
if (this.Type == ASN1Type.V_ASN1_EOC)
|
||||
return;
|
||||
using var stream = new MemoryStream(data);
|
||||
stream.Seek(index, SeekOrigin.Begin);
|
||||
if (!Parse(stream))
|
||||
throw new InvalidDataException(nameof(data));
|
||||
}
|
||||
|
||||
// Get the length of the value
|
||||
this.Length = ReadLength(data, ref index);
|
||||
|
||||
// Read the value
|
||||
#if NET20 || NET35
|
||||
if ((this.Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
|
||||
#else
|
||||
if (this.Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
|
||||
#endif
|
||||
{
|
||||
var valueList = new List<TypeLengthValue>();
|
||||
|
||||
int currentIndex = index;
|
||||
while (index < currentIndex + (int)this.Length)
|
||||
{
|
||||
valueList.Add(new TypeLengthValue(data, ref index));
|
||||
}
|
||||
|
||||
this.Value = valueList.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Get more granular based on type
|
||||
this.Value = data.ReadBytes(ref index, (int)this.Length);
|
||||
}
|
||||
/// <summary>
|
||||
/// Read from the source data stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing data to read</param>
|
||||
public TypeLengthValue(Stream data)
|
||||
{
|
||||
if (!Parse(data))
|
||||
throw new InvalidDataException(nameof(data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -78,31 +76,27 @@ namespace SabreTools.ASN1
|
||||
// Create the left-padding string
|
||||
string padding = new(' ', paddingLevel);
|
||||
|
||||
// If we have an invalid item
|
||||
if (this.Type == 0)
|
||||
return $"{padding}UNKNOWN TYPE";
|
||||
|
||||
// Create the string builder
|
||||
var formatBuilder = new StringBuilder();
|
||||
|
||||
// Append the type
|
||||
formatBuilder.Append($"{padding}Type: {this.Type}");
|
||||
if (this.Type == ASN1Type.V_ASN1_EOC)
|
||||
formatBuilder.Append($"{padding}Type: {Type}");
|
||||
if (Type == ASN1Type.V_ASN1_EOC)
|
||||
return formatBuilder.ToString();
|
||||
|
||||
// Append the length
|
||||
formatBuilder.Append($", Length: {this.Length}");
|
||||
if (this.Length == 0)
|
||||
formatBuilder.Append($", Length: {Length}");
|
||||
if (Length == 0)
|
||||
return formatBuilder.ToString();
|
||||
|
||||
// If we have a constructed type
|
||||
#if NET20 || NET35
|
||||
if ((this.Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
|
||||
if ((Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
|
||||
#else
|
||||
if (this.Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
|
||||
if (Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
|
||||
#endif
|
||||
{
|
||||
if (this.Value is not TypeLengthValue[] valueAsObjectArray)
|
||||
if (Value is not TypeLengthValue[] valueAsObjectArray)
|
||||
{
|
||||
formatBuilder.Append(", Value: [INVALID DATA TYPE]");
|
||||
return formatBuilder.ToString();
|
||||
@@ -120,19 +114,26 @@ namespace SabreTools.ASN1
|
||||
}
|
||||
|
||||
// Get the value as a byte array
|
||||
if (this.Value is not byte[] valueAsByteArray)
|
||||
if (Value is not byte[] valueAsByteArray)
|
||||
{
|
||||
formatBuilder.Append(", Value: [INVALID DATA TYPE]");
|
||||
return formatBuilder.ToString();
|
||||
}
|
||||
else if (valueAsByteArray.Length == 0)
|
||||
{
|
||||
formatBuilder.Append(", Value: [NO DATA]");
|
||||
return formatBuilder.ToString();
|
||||
}
|
||||
|
||||
// If we have a primitive type
|
||||
switch (this.Type)
|
||||
switch (Type)
|
||||
{
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-boolean"/>
|
||||
case ASN1Type.V_ASN1_BOOLEAN:
|
||||
if (this.Length > 1 || valueAsByteArray.Length > 1)
|
||||
if (Length > 1)
|
||||
formatBuilder.Append($" [Expected length of 1]");
|
||||
else if (valueAsByteArray.Length > 1)
|
||||
formatBuilder.Append($" [Expected value length of 1]");
|
||||
|
||||
bool booleanValue = valueAsByteArray[0] != 0x00;
|
||||
formatBuilder.Append($", Value: {booleanValue}");
|
||||
@@ -149,7 +150,10 @@ namespace SabreTools.ASN1
|
||||
case ASN1Type.V_ASN1_BIT_STRING:
|
||||
// TODO: Read into a BitArray and print that out instead?
|
||||
int unusedBits = valueAsByteArray[0];
|
||||
formatBuilder.Append($", Value with {unusedBits} unused bits: {BitConverter.ToString(valueAsByteArray.Skip(1).ToArray()).Replace('-', ' ')}");
|
||||
if (unusedBits == 0)
|
||||
formatBuilder.Append($", Value with {unusedBits} unused bits");
|
||||
else
|
||||
formatBuilder.Append($", Value with {unusedBits} unused bits: {BitConverter.ToString(valueAsByteArray, 1).Replace('-', ' ')}");
|
||||
break;
|
||||
|
||||
/// <see href="https://learn.microsoft.com/en-us/windows/win32/seccertenroll/about-octet-string"/>
|
||||
@@ -161,7 +165,7 @@ namespace SabreTools.ASN1
|
||||
/// <see cref="http://snmpsharpnet.com/index.php/2009/03/02/ber-encoding-and-decoding-oid-values/"/>
|
||||
case ASN1Type.V_ASN1_OBJECT:
|
||||
// Derive array of values
|
||||
ulong[] objectNodes = ObjectIdentifier.ParseDERIntoArray(valueAsByteArray, this.Length);
|
||||
ulong[] objectNodes = ObjectIdentifier.ParseDERIntoArray(valueAsByteArray, Length);
|
||||
|
||||
// Append the dot and modified OID-IRI notations
|
||||
string? dotNotationString = ObjectIdentifier.ParseOIDToDotNotation(objectNodes);
|
||||
@@ -192,7 +196,7 @@ namespace SabreTools.ASN1
|
||||
case ASN1Type.V_ASN1_UTCTIME:
|
||||
string utctimeString = Encoding.ASCII.GetString(valueAsByteArray);
|
||||
if (DateTime.TryParse(utctimeString, out DateTime utctimeDateTime))
|
||||
formatBuilder.Append($", Value: {utctimeDateTime}");
|
||||
formatBuilder.Append($", Value: {utctimeDateTime:yyyy-MM-dd HH:mm:ss}");
|
||||
else
|
||||
formatBuilder.Append($", Value: {utctimeString}");
|
||||
break;
|
||||
@@ -203,7 +207,7 @@ namespace SabreTools.ASN1
|
||||
break;
|
||||
|
||||
default:
|
||||
formatBuilder.Append($", Value (Unknown Format): {BitConverter.ToString(this.Value as byte[] ?? []).Replace('-', ' ')}");
|
||||
formatBuilder.Append($", Value: {BitConverter.ToString(valueAsByteArray).Replace('-', ' ')}");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -211,20 +215,70 @@ namespace SabreTools.ASN1
|
||||
return formatBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a stream into TLV data
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing data to read</param>
|
||||
/// <returns>Indication if parsing was successful</returns>
|
||||
private bool Parse(Stream data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data.Length == 0 || !data.CanRead)
|
||||
return false;
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
throw new IndexOutOfRangeException(nameof(data));
|
||||
|
||||
// Get the type and modifiers
|
||||
Type = (ASN1Type)data.ReadByteValue();
|
||||
|
||||
// If we have an end indicator, we just return
|
||||
if (Type == ASN1Type.V_ASN1_EOC)
|
||||
return true;
|
||||
|
||||
// Get the length of the value
|
||||
Length = ReadLength(data);
|
||||
|
||||
// Read the value
|
||||
#if NET20 || NET35
|
||||
if ((Type & ASN1Type.V_ASN1_CONSTRUCTED) != 0)
|
||||
#else
|
||||
if (Type.HasFlag(ASN1Type.V_ASN1_CONSTRUCTED))
|
||||
#endif
|
||||
{
|
||||
var valueList = new List<TypeLengthValue>();
|
||||
|
||||
long currentIndex = data.Position;
|
||||
while (data.Position < currentIndex + (long)Length)
|
||||
{
|
||||
valueList.Add(new TypeLengthValue(data));
|
||||
}
|
||||
|
||||
Value = valueList.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Get more granular based on type
|
||||
Value = data.ReadBytes((int)Length);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the length field for a type
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing data to read</param>
|
||||
/// <param name="index">Index within the array to read at</param>
|
||||
/// <param name="data">Stream representing data to read</param>
|
||||
/// <returns>The length value read from the array</returns>
|
||||
private static ulong ReadLength(byte[] data, ref int index)
|
||||
private static ulong ReadLength(Stream data)
|
||||
{
|
||||
// If we have invalid data, throw an exception
|
||||
if (data == null || index < 0 && index >= data.Length)
|
||||
throw new ArgumentException();
|
||||
// If the data is invalid
|
||||
if (data.Length == 0 || !data.CanRead)
|
||||
throw new InvalidDataException(nameof(data));
|
||||
if (data.Position < 0 || data.Position >= data.Length)
|
||||
throw new IndexOutOfRangeException(nameof(data));
|
||||
|
||||
// Read the first byte, assuming it's the length
|
||||
byte length = data[index++];
|
||||
byte length = data.ReadByteValue();
|
||||
|
||||
// If the bit 7 is not set, then use the value as it is
|
||||
if ((length & 0x80) == 0)
|
||||
@@ -232,34 +286,48 @@ namespace SabreTools.ASN1
|
||||
|
||||
// Otherwise, use the value as the number of remaining bytes to read
|
||||
int bytesToRead = length & ~0x80;
|
||||
byte[]? bytesRead = data.ReadBytes(ref index, bytesToRead) ?? throw new InvalidOperationException();
|
||||
|
||||
// TODO: Write extensions to read big-endian
|
||||
|
||||
// Reverse the bytes to be in big-endian order
|
||||
Array.Reverse(bytesRead);
|
||||
|
||||
switch (bytesRead.Length)
|
||||
// Assemble the length based on byte count
|
||||
ulong fullLength = 0;
|
||||
switch (bytesToRead)
|
||||
{
|
||||
case 1:
|
||||
return bytesRead[0];
|
||||
case 2:
|
||||
return BitConverter.ToUInt16(bytesRead, 0);
|
||||
case 3:
|
||||
Array.Resize(ref bytesRead, 4);
|
||||
case 8:
|
||||
fullLength |= data.ReadByteValue();
|
||||
fullLength <<= 8;
|
||||
goto case 7;
|
||||
case 7:
|
||||
fullLength |= data.ReadByteValue();
|
||||
fullLength <<= 8;
|
||||
goto case 6;
|
||||
case 6:
|
||||
fullLength |= data.ReadByteValue();
|
||||
fullLength <<= 8;
|
||||
goto case 5;
|
||||
case 5:
|
||||
fullLength |= data.ReadByteValue();
|
||||
fullLength <<= 8;
|
||||
goto case 4;
|
||||
case 4:
|
||||
return BitConverter.ToUInt32(bytesRead, 0);
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
Array.Resize(ref bytesRead, 8);
|
||||
goto case 8;
|
||||
case 8:
|
||||
return BitConverter.ToUInt64(bytesRead, 0);
|
||||
fullLength |= data.ReadByteValue();
|
||||
fullLength <<= 8;
|
||||
goto case 3;
|
||||
case 3:
|
||||
fullLength |= data.ReadByteValue();
|
||||
fullLength <<= 8;
|
||||
goto case 2;
|
||||
case 2:
|
||||
fullLength |= data.ReadByteValue();
|
||||
fullLength <<= 8;
|
||||
goto case 1;
|
||||
case 1:
|
||||
fullLength |= data.ReadByteValue();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return fullLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
publish-nix.sh
Executable file
36
publish-nix.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This batch file assumes the following:
|
||||
# - .NET 9.0 (or newer) SDK is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
NO_BUILD=false
|
||||
while getopts "b" OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
b)
|
||||
NO_BUILD=true
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option provided"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set the current directory as a variable
|
||||
BUILD_FOLDER=$PWD
|
||||
|
||||
# Only build if requested
|
||||
if [ $NO_BUILD = false ]
|
||||
then
|
||||
# Restore Nuget packages for all builds
|
||||
echo "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack SabreTools.ASN1/SabreTools.ASN1.csproj --output $BUILD_FOLDER
|
||||
fi
|
||||
26
publish-win.ps1
Normal file
26
publish-win.ps1
Normal file
@@ -0,0 +1,26 @@
|
||||
# This batch file assumes the following:
|
||||
# - .NET 9.0 (or newer) SDK is installed and in PATH
|
||||
#
|
||||
# If any of these are not satisfied, the operation may fail
|
||||
# in an unpredictable way and result in an incomplete output.
|
||||
|
||||
# Optional parameters
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[Alias("NoBuild")]
|
||||
[switch]$NO_BUILD
|
||||
)
|
||||
|
||||
# Set the current directory as a variable
|
||||
$BUILD_FOLDER = $PSScriptRoot
|
||||
|
||||
# Only build if requested
|
||||
if (!$NO_BUILD.IsPresent)
|
||||
{
|
||||
# Restore Nuget packages for all builds
|
||||
Write-Host "Restoring Nuget packages"
|
||||
dotnet restore
|
||||
|
||||
# Create Nuget Package
|
||||
dotnet pack SabreTools.ASN1\SabreTools.ASN1.csproj --output $BUILD_FOLDER
|
||||
}
|
||||
Reference in New Issue
Block a user