Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 48 additions & 21 deletions Asn1Parser/Universal/Asn1ObjectIdentifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace SysadminsLV.Asn1Parser.Universal;
/// </summary>
public sealed class Asn1ObjectIdentifier : Asn1Universal {
const Asn1Type TYPE = Asn1Type.OBJECT_IDENTIFIER;
const Byte ITU_T_ROOT = 0;
const Byte ISO_ROOT = 1;

/// <summary>
/// Initializes a new instance of the <strong>Asn1ObjectIdentifier</strong> class from an existing
Expand All @@ -21,7 +23,6 @@ public sealed class Asn1ObjectIdentifier : Asn1Universal {
/// <exception cref="Asn1InvalidTagException">
/// The current state of <strong>ASN1</strong> object is not object identifier.
/// </exception>
///
public Asn1ObjectIdentifier(Asn1Reader asn) : base(asn, TYPE) {
Value = new Oid(decode(asn));
}
Expand Down Expand Up @@ -61,7 +62,7 @@ public Asn1ObjectIdentifier(Oid oid) : base(TYPE) {

void m_encode(Oid oid) {
if (String.IsNullOrWhiteSpace(oid.Value)) {
Initialize(new Asn1Reader(new Byte[] { Tag, 0 }));
Initialize(new Asn1Reader([Tag, 0]));
Value = new Oid();
return;
}
Expand All @@ -77,17 +78,29 @@ void m_encode(Oid oid) {

static Byte[] encode(IList<BigInteger> tokens) {
var rawOid = new List<Byte>();
for (Int32 token = 0; token < tokens.Count; token++) {
// first two arcs are encoded in a single byte
switch (token) {
for (Int32 tokenIndex = 0; tokenIndex < tokens.Count; tokenIndex++) {
BigInteger token = tokens[tokenIndex];
BigInteger temp = token;
// first two arcs are encoded as a single arc
switch (tokenIndex) {
case 0:
rawOid.Add((Byte)(40 * tokens[token] + tokens[token + 1]));
continue;
//
token = 40 * token + tokens[tokenIndex + 1];
// if first two arcs can be encoded using 7 bits (single byte where most significant bit is 0),
// then nothing fancy, simply add it as single byte.
if (token < 0x80) {
rawOid.Add((Byte)token);
continue;
}
// otherwise first two arcs are encoded using multiple bytes, and we have to go through
// standard OID arc encoding routine.
temp = token;
break;
// we already handled 2nd arc, so skip its processing.
case 1:
continue;
}
Int16 bitLength = 0;
BigInteger temp = tokens[token];
// calculate how many bits are occupied by the current integer value
do {
temp = (BigInteger)Math.Floor((Double)temp / 2);
Expand All @@ -99,37 +112,43 @@ static Byte[] encode(IList<BigInteger> tokens) {
// http://msdn.microsoft.com/en-us/library/bb540809(v=vs.85).aspx
// loop may not execute if arc value is less than 128.
for (Int32 index = (bitLength - 1) / 7; index > 0; index--) {
rawOid.Add((Byte)(0x80 | ((tokens[token] >> (index * 7)) & 0x7f)));
rawOid.Add((Byte)(0x80 | ((token >> (index * 7)) & 0x7f)));
}
rawOid.Add((Byte)(tokens[token] & 0x7f));
rawOid.Add((Byte)(token & 0x7f));
}
return rawOid.ToArray();
}
static String decode(Asn1Reader asn) {
var SB = new StringBuilder();
Int32 token = 0;
Boolean topArcsProcessed = false;
for (Int32 i = 0; i < asn.PayloadLength; i++) {
Int32 pi = asn.PayloadStartOffset + i;
if (token == 0) {
SB.Append(asn[pi] / 40);
SB.Append("." + asn[pi] % 40);
token++;
continue;
}

BigInteger value = 0;
Boolean proceed;
do {
value <<= 7;
value += asn[pi] & 0x7f;
proceed = (asn[pi] & 0x80) > 0;
if (proceed) {
token++;
i++;
pi++;
}
} while (proceed);
if (!topArcsProcessed) {
topArcsProcessed = true;
// max value for first two arcs in ITU-T and ISO is 79 (OID=1.39). If this value is larger, then
// it belongs to 'joint-iso-itu-t' (OID=2.x)
if (value >= 80) {
SB.Append("2.").Append(value - 80);
} else {
SB.Append(value / 40);
SB.Append("." + value % 40);
}
continue;
}

SB.Append("." + value);
token++;
}
return SB.ToString();
}
Expand All @@ -143,8 +162,16 @@ static Boolean validateOidString(String oid, out List<BigInteger> tokens) {
for (Int32 index = 0; index < strTokens.Length; index++) {
try {
var value = BigInteger.Parse(strTokens[index]);
if (index == 0 && value > 2 || index == 1 && value > 39) {
return false;
if (index == 0) {
// check if root arc is 0, 1, or 2
if (value > 2) {
return false;
}
var secondArc = BigInteger.Parse(strTokens[1]);
// check if 2nd arc under ITU-T and ISO is <=39
if ((Byte)value is ITU_T_ROOT or ISO_ROOT && secondArc > 39) {
return false;
}
}
tokens.Add(value);
} catch {
Expand Down
70 changes: 54 additions & 16 deletions tests/Asn1Parser.Tests/Asn1OidTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,71 @@ namespace Asn1Parser.Tests;

[TestClass]
public class Asn1OidTests {
[TestMethod, Description("Test if at least two arcs are encoded.")]
public void TestMinStringLengthEncode() {
var oid = new Asn1ObjectIdentifier("0.0");
Assert.AreEqual("0.0", oid.Value.Value);
String encodedB64 = Convert.ToBase64String(oid.GetRawData());
Assert.AreEqual("BgEA", encodedB64);
}
[TestMethod, Description("Test if at least two arcs are required.")]
public void TestMinStringLengthDecode() {
Byte[] rawData = Convert.FromBase64String("BgEA");
var oid = new Asn1ObjectIdentifier(rawData);
Assert.AreEqual("0.0", oid.Value.Value);
String encodedB64 = Convert.ToBase64String(oid.GetRawData());
Assert.AreEqual("BgEA", encodedB64);
public void TestMinStringLength() {
// must be 06 01 00
testOidBiDirectional("0.0", "BgEA");
}
[TestMethod, Description("Test if single arc encoding fails.")]
[ExpectedException(typeof(InvalidDataException))]
public void TestSingleArcEncodeFail() {
new Asn1ObjectIdentifier("0");
}
[TestMethod, Description("Test if 2nd arc under 'ITU' root node encoded up to 39.")]
[TestMethod, Description("Test if 2nd arc under 'itu-t' root node encoded up to 39.")]
public void TestItuRootArcConstraintsPass() {
new Asn1ObjectIdentifier("1.39");
// must be 06 01 27
testOidBiDirectional("0.39", "BgEn");
}
[TestMethod, Description("Test if 2nd arc under 'ITU' root node >39 fails.")]
[TestMethod, Description("Test if 2nd arc under 'itu-t' root node >39 fails.")]
[ExpectedException(typeof(InvalidDataException))]
public void TestItuRootArcConstraintsFail() {
new Asn1ObjectIdentifier("0.40");
}
[TestMethod, Description("Test if 2nd arc under 'iso' root node encoded up to 39.")]
public void TestIsoRootArcConstraintsPass() {
// must be 06 01 4f
testOidBiDirectional("1.39", "BgFP");
}
[TestMethod, Description("Test if 2nd arc under 'iso' root node >39 fails.")]
[ExpectedException(typeof(InvalidDataException))]
public void TestIsoRootArcConstraintsFail() {
new Asn1ObjectIdentifier("1.40");
}
[TestMethod, Description("Test if 2nd arc under 'joint-iso-itu-t' root do not impose 2nd arc limits.")]
public void TestJointIsoItuRootArcPass() {
// must be 06 01 78
testOidBiDirectional("2.40", "BgF4");
}
[TestMethod, Description("Test random cert template OID, which includes short and long arcs")]
public void TestCertTemplateOid() {
testOidBiDirectional("1.3.6.1.4.1.311.21.8.149510.7314491.15746959.9320746.3700693.37.1.25", "Bh8rBgEEAYI3FQiJkAaDvrg7h8GPD4S48iqB4e9VJQEZ");
}
[TestMethod, Description("Test if first first two arcs can span multiple bytes if first byte >= 128")]
public void TestLargeTopArcs() {
// must be 06 01 50
// OID 2.0 is identical to invalid 1.40, which is prohibited
testOidBiDirectional("2.0", "BgFQ");
// must be 06 02 88 37
testOidBiDirectional("2.999", "BgKINw==");
// must be 06 03 88 37 03
testOidBiDirectional("2.999.3", "BgOINwM=");
// must be 06 04 82 4B 09 79
testOidBiDirectional("2.251.9.121", "BgSCSwl5");
// must be 06 04 88 37 89 52
testOidBiDirectional("2.999.1234", "BgSIN4lS");
// must be 06 04 82 00 09 79
testOidBiDirectional("2.176.9.121", "BgSCAAl5");
// must be 06 04 79 00 09 79
testOidBiDirectional("2.81.0.9.121", "BgWBIQAJeQ==");
}

static void testOidBiDirectional(String oidString, String expectedB64) {
// test OID string -> binary encoding process
var oid = new Asn1ObjectIdentifier(oidString);
String encodedB64 = Convert.ToBase64String(oid.GetRawData());
Assert.AreEqual(expectedB64, encodedB64);
// test binary -> OID string decoding process
oid = new Asn1ObjectIdentifier(Convert.FromBase64String(expectedB64));
Assert.AreEqual(oidString, oid.Value.Value);
}
}