mirror of
https://github.com/SabreTools/SabreTools.ASN1.git
synced 2026-02-04 05:36:00 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bdfc5426d | ||
|
|
827913d09c | ||
|
|
2d955c8ef7 | ||
|
|
ebdab151be | ||
|
|
a664cff34c | ||
|
|
8110cdd0cb | ||
|
|
7a10aab5d1 | ||
|
|
1516a8647a | ||
|
|
4ee64a2c49 | ||
|
|
9cdf10d481 | ||
|
|
04996584b5 | ||
|
|
98b930e231 | ||
|
|
2da26d2dca | ||
|
|
32a226ede6 | ||
|
|
e9abf681c3 | ||
|
|
1c10ebf648 | ||
|
|
ea6d89d2da | ||
|
|
f457e12d85 | ||
|
|
3c561a60e7 | ||
|
|
5830e2a2fd | ||
|
|
10163eb9eb | ||
|
|
4e45de3fab | ||
|
|
ec92bb22aa | ||
|
|
6a0826e95c | ||
|
|
b7010349a0 | ||
|
|
005a447e35 | ||
|
|
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 |
@@ -1,4 +1,4 @@
|
||||
name: Nuget Pack
|
||||
name: Build and Test
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -16,25 +16,28 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test
|
||||
|
||||
- name: Pack
|
||||
run: dotnet pack
|
||||
- name: Run publish script
|
||||
run: ./publish-nix.sh
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'Nuget Package'
|
||||
path: 'bin/Release/*.nupkg'
|
||||
path: "*.nupkg,*.snupkg"
|
||||
|
||||
- name: Upload to rolling
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
allowUpdates: True
|
||||
artifacts: 'bin/Release/*.nupkg'
|
||||
artifacts: "*.nupkg,*.snupkg"
|
||||
body: 'Last built commit: ${{ github.sha }}'
|
||||
name: 'Rolling Release'
|
||||
prerelease: True
|
||||
10
.github/workflows/check_pr.yml
vendored
10
.github/workflows/check_pr.yml
vendored
@@ -11,7 +11,13 @@ jobs:
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
run: dotnet build
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test
|
||||
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2018-2025 Matt Nadareski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
13
README.MD
13
README.MD
@@ -1,5 +1,18 @@
|
||||
# SabreTools.ASN1
|
||||
|
||||
[](https://github.com/SabreTools/SabreTools.ASN1/actions/workflows/build_and_test.yml)
|
||||
|
||||
**NOTICE:** This library has been deprecated. All functionality formerly in this library is in [SabreTools.Serialization](https://github.com/SabreTools/SabreTools.Serialization) as of version 1.9.6.
|
||||
|
||||
This library comprises of code to parse Abstract Syntax Notation One (ASN.1) codes and output them into various formats. This also performs some rudimentary validation based on the chains that are input.
|
||||
|
||||
**Note:** This code is known to be incomplete and will be added to over time.
|
||||
|
||||
Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTools.ASN1).
|
||||
|
||||
## Releases
|
||||
|
||||
For the most recent stable build, download the latest release here: [Releases Page](https://github.com/SabreTools/SabreTools.ASN1/releases)
|
||||
|
||||
For the latest WIP build here: [Rolling Release](https://github.com/SabreTools/SabreTools.ASN1/releases/rolling)
|
||||
|
||||
|
||||
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>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.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4">
|
||||
<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
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
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
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SabreTools.ASN1
|
||||
{
|
||||
@@ -12,16 +14,38 @@ namespace SabreTools.ASN1
|
||||
/// </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)
|
||||
{
|
||||
// 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 (pointer < data.Length)
|
||||
while (data.Position < data.Length)
|
||||
{
|
||||
var topLevelValue = new TypeLengthValue(data, ref pointer);
|
||||
var topLevelValue = new TypeLengthValue(data);
|
||||
topLevelValues.Add(topLevelValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,13 +9,12 @@ 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,8 +1,4 @@
|
||||
#if NET20 || NET35
|
||||
using System.Collections.Generic;
|
||||
#else
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
@@ -52,17 +48,14 @@ namespace SabreTools.ASN1
|
||||
|
||||
// Add trailing items as just values
|
||||
nameBuilder.Append("/");
|
||||
#if NET20 || NET35
|
||||
var stringValues = new List<string>();
|
||||
for (int i = index; i < values.Length; i++)
|
||||
{
|
||||
stringValues.Add(values[i].ToString());
|
||||
}
|
||||
|
||||
nameBuilder.Append(string.Join("/", [.. stringValues]));
|
||||
#else
|
||||
nameBuilder.Append(string.Join("/", values.Skip(index).Select(v => v.ToString()).ToArray()));
|
||||
#endif
|
||||
// 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();
|
||||
@@ -91,7 +84,7 @@ namespace SabreTools.ASN1
|
||||
|
||||
#region Start
|
||||
|
||||
var oidPath = $"/{values[index]}";
|
||||
var oidPath = string.Empty;
|
||||
switch (values[index++])
|
||||
{
|
||||
case 0: goto oid_0;
|
||||
@@ -18043,4 +18036,4 @@ namespace SabreTools.ASN1
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
#if NET20 || NET35
|
||||
using System.Collections.Generic;
|
||||
#else
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SabreTools.ASN1
|
||||
@@ -45,17 +41,14 @@ namespace SabreTools.ASN1
|
||||
|
||||
// Add trailing items as just values
|
||||
nameBuilder.Append("/");
|
||||
#if NET20 || NET35
|
||||
var stringValues = new List<string>();
|
||||
for (int i = index; i < values.Length; i++)
|
||||
{
|
||||
stringValues.Add(values[i].ToString());
|
||||
}
|
||||
|
||||
nameBuilder.Append(string.Join("/", [.. stringValues]));
|
||||
#else
|
||||
nameBuilder.Append(string.Join("/", values.Skip(index).Select(v => v.ToString()).ToArray()));
|
||||
#endif
|
||||
// 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();
|
||||
@@ -80,7 +73,7 @@ namespace SabreTools.ASN1
|
||||
|
||||
#region Start
|
||||
|
||||
var oidPath = $"/{values[index]}";
|
||||
var oidPath = string.Empty;
|
||||
switch (values[index++])
|
||||
{
|
||||
case 0: goto oid_0;
|
||||
@@ -107,7 +100,8 @@ namespace SabreTools.ASN1
|
||||
case 5: return "/ITU-R/R-Recommendation";
|
||||
case 9: return $"{oidPath}/Data";
|
||||
default: return $"{oidPath}/{values[index - 1]}";
|
||||
};
|
||||
}
|
||||
;
|
||||
|
||||
// recommendation
|
||||
#region 0.0.*
|
||||
@@ -910,4 +904,4 @@ namespace SabreTools.ASN1
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,8 +1,4 @@
|
||||
#if NET20 || NET35
|
||||
using System.Collections.Generic;
|
||||
#else
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System;
|
||||
|
||||
namespace SabreTools.ASN1
|
||||
{
|
||||
@@ -22,17 +18,8 @@ namespace SabreTools.ASN1
|
||||
if (values == null || values.Length == 0)
|
||||
return null;
|
||||
|
||||
#if NET20 || NET35
|
||||
var stringValues = new List<string>();
|
||||
foreach (ulong value in values)
|
||||
{
|
||||
stringValues.Add(value.ToString());
|
||||
}
|
||||
|
||||
var stringValues = Array.ConvertAll(values, v => v.ToString());
|
||||
return string.Join(".", [.. stringValues]);
|
||||
#else
|
||||
return string.Join(".", values.Select(v => v.ToString()).ToArray());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<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.3</Version>
|
||||
<PropertyGroup>
|
||||
<!-- Assembly Properties -->
|
||||
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0;netstandard2.0;netstandard2.1</TargetFrameworks>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.6.4</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>
|
||||
<!-- 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>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="SabreTools.ASN1.Test" />
|
||||
</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>
|
||||
<None Include="../README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SabreTools.IO" Version="1.4.12" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NetLegacySupport.Numerics" Version="1.0.1" Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`))" />
|
||||
<PackageReference Include="SabreTools.IO" Version="1.7.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#if NET40_OR_GREATER || NETCOREAPP
|
||||
using System.Linq;
|
||||
#endif
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
@@ -29,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>
|
||||
@@ -36,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>
|
||||
@@ -80,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();
|
||||
@@ -122,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}");
|
||||
@@ -151,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, 1).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"/>
|
||||
@@ -163,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);
|
||||
@@ -194,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;
|
||||
@@ -205,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;
|
||||
}
|
||||
|
||||
@@ -213,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)
|
||||
@@ -234,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.0 (or newer) SDK is installed and in PATH
|
||||
# - .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 "uba" OPTION
|
||||
while getopts "b" OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
b)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# This batch file assumes the following:
|
||||
# - .NET 8.0 (or newer) SDK is installed and in PATH
|
||||
# - .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.
|
||||
|
||||
Reference in New Issue
Block a user