diff --git a/Asn1Parser/Universal/Asn1ObjectIdentifier.cs b/Asn1Parser/Universal/Asn1ObjectIdentifier.cs index cf43428..a159a4f 100644 --- a/Asn1Parser/Universal/Asn1ObjectIdentifier.cs +++ b/Asn1Parser/Universal/Asn1ObjectIdentifier.cs @@ -12,6 +12,8 @@ namespace SysadminsLV.Asn1Parser.Universal; /// public sealed class Asn1ObjectIdentifier : Asn1Universal { const Asn1Type TYPE = Asn1Type.OBJECT_IDENTIFIER; + const Byte ITU_T_ROOT = 0; + const Byte ISO_ROOT = 1; /// /// Initializes a new instance of the Asn1ObjectIdentifier class from an existing @@ -21,7 +23,6 @@ public sealed class Asn1ObjectIdentifier : Asn1Universal { /// /// The current state of ASN1 object is not object identifier. /// - /// public Asn1ObjectIdentifier(Asn1Reader asn) : base(asn, TYPE) { Value = new Oid(decode(asn)); } @@ -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; } @@ -77,17 +78,29 @@ void m_encode(Oid oid) { static Byte[] encode(IList tokens) { var rawOid = new List(); - 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); @@ -99,23 +112,18 @@ static Byte[] encode(IList 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 { @@ -123,13 +131,24 @@ static String decode(Asn1Reader asn) { 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(); } @@ -143,8 +162,16 @@ static Boolean validateOidString(String oid, out List 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 { diff --git a/tests/Asn1Parser.Tests/Asn1OidTests.cs b/tests/Asn1Parser.Tests/Asn1OidTests.cs index 34c8305..1c34a29 100644 --- a/tests/Asn1Parser.Tests/Asn1OidTests.cs +++ b/tests/Asn1Parser.Tests/Asn1OidTests.cs @@ -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); + } }