lib_unity_purchase/Runtime/Security/Asn1Processor/Asn1Node.cs

1823 lines
60 KiB
C#
Raw Permalink Blame History

//+-------------------------------------------------------------------------------+
//| Copyright (c) 2003 Liping Dai. All rights reserved. |
//| Web: www.lipingshare.com |
//| Email: lipingshare@yahoo.com |
//| |
//| Copyright and Permission Details: |
//| ================================= |
//| 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, and/or sell copies of the |
//| Software, subject to the following conditions: |
//| |
//| 1. Redistributions of source code must retain the above copyright notice, this|
//| list of conditions and the following disclaimer. |
//| |
//| 2. Redistributions in binary form must reproduce the above copyright notice, |
//| this list of conditions and the following disclaimer in the documentation |
//| and/or other materials provided with the distribution. |
//| |
//| THE SOFTWARE PRODUCT IS PROVIDED <20>AS IS<49> WITHOUT WARRANTY OF ANY KIND, |
//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR |
//| A PARTICULAR PURPOSE. |
//+-------------------------------------------------------------------------------+
using System;
using System.IO;
using System.Collections;
using System.Linq;
using System.Text;
using UnityEngine;
namespace LipingShare.LCLib.Asn1Processor
{
/// <summary>
/// Asn1Node, implemented IAsn1Node interface.
/// </summary>
internal class Asn1Node : IAsn1Node
{
// PrivateMembers
private byte tag;
private long dataOffset;
private long dataLength;
private long lengthFieldBytes;
private byte[] data;
private ArrayList childNodeList;
private byte unusedBits;
private long deepness;
private string path = "";
private const int indentStep = 3;
private Asn1Node parentNode;
private bool requireRecalculatePar = true;
private bool isIndefiniteLength = false;
private bool parseEncapsulatedData = true;
/// <summary>
/// Default Asn1Node text line length.
/// </summary>
public const int defaultLineLen = 80;
/// <summary>
/// Minium line length.
/// </summary>
public const int minLineLen = 60;
const int k_EndOfStream = -1;
const int k_InvalidIndeterminateContentLength = -1;
const int k_IndefiniteLengthFooterSize = 2;
private Asn1Node(Asn1Node parentNode, long dataOffset)
{
Init();
deepness = parentNode.Deepness + 1;
this.parentNode = parentNode;
this.dataOffset = dataOffset;
}
private void Init()
{
childNodeList = new ArrayList();
data = null;
dataLength = 0;
lengthFieldBytes = 0;
unusedBits = 0;
tag = Asn1Tag.SEQUENCE | Asn1TagClasses.CONSTRUCTED;
childNodeList.Clear();
deepness = 0;
parentNode = null;
}
private string GetHexPrintingStr(Asn1Node startNode, string baseLine,
string lStr, int lineLen)
{
string nodeStr = "";
string iStr = GetIndentStr(startNode);
string dataStr = Asn1Util.ToHexString(data);
if (dataStr.Length > 0)
{
if (baseLine.Length + dataStr.Length < lineLen)
{
nodeStr += baseLine + "'" + dataStr + "'";
}
else
{
nodeStr += baseLine + FormatLineHexString(
lStr,
iStr.Length,
lineLen,
dataStr
);
}
}
else
{
nodeStr += baseLine;
}
return nodeStr + "\r\n";
}
private string FormatLineString(string lStr, int indent, int lineLen, string msg)
{
string retval = "";
indent += indentStep;
int realLen = lineLen - indent;
int sLen = indent;
int currentp;
for (currentp = 0; currentp < msg.Length; currentp += realLen)
{
if (currentp + realLen > msg.Length)
{
retval += "\r\n" + lStr + Asn1Util.GenStr(sLen, ' ') +
"'" + msg.Substring(currentp, msg.Length - currentp) + "'";
}
else
{
retval += "\r\n" + lStr + Asn1Util.GenStr(sLen, ' ') + "'" +
msg.Substring(currentp, realLen) + "'";
}
}
return retval;
}
private string FormatLineHexString(string lStr, int indent, int lineLen, string msg)
{
string retval = "";
indent += indentStep;
int realLen = lineLen - indent;
int sLen = indent;
int currentp;
for (currentp = 0; currentp < msg.Length; currentp += realLen)
{
if (currentp + realLen > msg.Length)
{
retval += "\r\n" + lStr + Asn1Util.GenStr(sLen, ' ') +
msg.Substring(currentp, msg.Length - currentp);
}
else
{
retval += "\r\n" + lStr + Asn1Util.GenStr(sLen, ' ') +
msg.Substring(currentp, realLen);
}
}
return retval;
}
//PublicMembers
/// <summary>
/// Constructor, initialize all the members.
/// </summary>
public Asn1Node()
{
Init();
dataOffset = 0;
}
/// <summary>
/// Get/Set isIndefiniteLength.
/// </summary>
public bool IsIndefiniteLength
{
get
{
return isIndefiniteLength;
}
set
{
isIndefiniteLength = value;
}
}
/// <summary>
/// Clone a new Asn1Node by current node.
/// </summary>
/// <returns>new node.</returns>
public Asn1Node Clone()
{
MemoryStream ms = new MemoryStream();
this.SaveData(ms);
ms.Position = 0;
Asn1Node node = new Asn1Node();
node.LoadData(ms);
return node;
}
/// <summary>
/// Get/Set tag value.
/// </summary>
public byte Tag
{
get
{
return tag;
}
set
{
tag = value;
}
}
public byte MaskedTag
{
get
{
return (byte)(tag & Asn1Tag.TAG_MASK);
}
}
/// <summary>
/// Load data from byte[].
/// </summary>
/// <param name="byteData">byte[]</param>
/// <returns>true:Succeed; false:failed.</returns>
public bool LoadData(byte[] byteData)
{
bool retval = true;
try
{
MemoryStream ms = new MemoryStream(byteData);
ms.Position = 0;
retval = LoadData(ms);
ms.Close();
}
catch
{
retval = false;
}
return retval;
}
/// <summary>
/// Retrieve all the node count in the node subtree.
/// </summary>
/// <param name="node">starting node.</param>
/// <returns>long integer node count in the node subtree.</returns>
public static long GetDescendantNodeCount(Asn1Node node)
{
long count = 0;
count += node.ChildNodeCount;
for (int i = 0; i < node.ChildNodeCount; i++)
{
count += GetDescendantNodeCount(node.GetChildNode(i));
}
return count;
}
/// <summary>
/// Load data from Stream. Start from current position.
/// This function sets requireRecalculatePar to false then calls InternalLoadData
/// to complish the task.
/// </summary>
/// <param name="xdata">Stream</param>
/// <returns>true:Succeed; false:failed.</returns>
public bool LoadData(Stream xdata)
{
bool retval = false;
try
{
RequireRecalculatePar = false;
retval = InternalLoadData(xdata);
return retval;
}
finally
{
RequireRecalculatePar = true;
RecalculateTreePar();
}
}
/// <summary>
/// Call SaveData and return byte[] as result instead stream.
/// </summary>
/// <returns></returns>
public byte[] GetRawData()
{
MemoryStream ms = new MemoryStream();
SaveData(ms);
byte[] retval = new byte[ms.Length];
ms.Position = 0;
ms.Read(retval, 0, (int)ms.Length);
ms.Close();
return retval;
}
/// <summary>
/// Get if data is empty.
/// </summary>
public bool IsEmptyData
{
get
{
if (data == null) return true;
if (data.Length < 1)
return true;
else
return false;
}
}
/// <summary>
/// Save node data into Stream.
/// </summary>
/// <param name="xdata">Stream.</param>
/// <returns>true:Succeed; false:failed.</returns>
public bool SaveData(Stream xdata)
{
bool retval = true;
long nodeCount = ChildNodeCount;
xdata.WriteByte(tag);
Asn1Util.DERLengthEncode(xdata, (ulong)dataLength);
if ((tag) == Asn1Tag.BIT_STRING)
{
xdata.WriteByte(unusedBits);
}
if (nodeCount == 0)
{
if (data != null)
{
xdata.Write(data, 0, data.Length);
}
}
else
{
Asn1Node tempNode;
int i;
for (i = 0; i < nodeCount; i++)
{
tempNode = GetChildNode(i);
retval = tempNode.SaveData(xdata);
}
}
return retval;
}
/// <summary>
/// Clear data and children list.
/// </summary>
public void ClearAll()
{
data = null;
Asn1Node tempNode;
for (int i = 0; i < childNodeList.Count; i++)
{
tempNode = (Asn1Node)childNodeList[i];
tempNode.ClearAll();
}
childNodeList.Clear();
RecalculateTreePar();
}
/// <summary>
/// Add child node at the end of children list.
/// </summary>
/// <param name="xdata">the node that will be add in.</param>
public void AddChild(Asn1Node xdata)
{
childNodeList.Add(xdata);
RecalculateTreePar();
}
/// <summary>
/// Insert a node in the children list before the pointed index.
/// </summary>
/// <param name="xdata">Asn1Node</param>
/// <param name="index">0 based index.</param>
/// <returns>New node index.</returns>
public int InsertChild(Asn1Node xdata, int index)
{
childNodeList.Insert(index, xdata);
RecalculateTreePar();
return index;
}
/// <summary>
/// Insert a node in the children list before the pointed node.
/// </summary>
/// <param name="xdata">Asn1Node that will be instered in the children list.</param>
/// <param name="indexNode">Index node.</param>
/// <returns>New node index.</returns>
public int InsertChild(Asn1Node xdata, Asn1Node indexNode)
{
int index = childNodeList.IndexOf(indexNode);
childNodeList.Insert(index, xdata);
RecalculateTreePar();
return index;
}
/// <summary>
/// Insert a node in the children list after the pointed node.
/// </summary>
/// <param name="xdata">Asn1Node</param>
/// <param name="indexNode">Index node.</param>
/// <returns>New node index.</returns>
public int InsertChildAfter(Asn1Node xdata, Asn1Node indexNode)
{
int index = childNodeList.IndexOf(indexNode) + 1;
childNodeList.Insert(index, xdata);
RecalculateTreePar();
return index;
}
/// <summary>
/// Insert a node in the children list after the pointed node.
/// </summary>
/// <param name="xdata">Asn1Node that will be instered in the children list.</param>
/// <param name="index">0 based index.</param>
/// <returns>New node index.</returns>
public int InsertChildAfter(Asn1Node xdata, int index)
{
int xindex = index + 1;
childNodeList.Insert(xindex, xdata);
RecalculateTreePar();
return xindex;
}
/// <summary>
/// Remove a child from children node list by index.
/// </summary>
/// <param name="index">0 based index.</param>
/// <returns>The Asn1Node just removed from the list.</returns>
public Asn1Node RemoveChild(int index)
{
Asn1Node retval = null;
if (index < (childNodeList.Count - 1))
{
retval = (Asn1Node)childNodeList[index + 1];
}
childNodeList.RemoveAt(index);
if (retval == null)
{
if (childNodeList.Count > 0)
{
retval = GetLastChild();
}
else
{
retval = this;
}
}
RecalculateTreePar();
return retval;
}
Asn1Node GetLastChild()
{
return (Asn1Node)childNodeList[childNodeList.Count - 1];
}
/// <summary>
/// Remove the child from children node list.
/// </summary>
/// <param name="node">The node needs to be removed.</param>
/// <returns></returns>
public Asn1Node RemoveChild(Asn1Node node)
{
Asn1Node retval = null;
int i = childNodeList.IndexOf(node);
retval = RemoveChild(i);
return retval;
}
/// <summary>
/// Get child node count.
/// </summary>
public long ChildNodeCount
{
get
{
return childNodeList.Count;
}
}
/// <summary>
/// Retrieve child node by index.
/// </summary>
/// <param name="index">0 based index.</param>
/// <returns>0 based index.</returns>
public Asn1Node GetChildNode(int index)
{
Asn1Node retval = null;
if (index < ChildNodeCount)
{
retval = (Asn1Node)childNodeList[index];
}
return retval;
}
/// <summary>
/// Get tag name.
/// </summary>
public string TagName
{
get
{
return Asn1Util.GetTagName(tag);
}
}
/// <summary>
/// Get parent node.
/// </summary>
public Asn1Node ParentNode
{
get
{
return parentNode;
}
}
/// <summary>
/// Get the node and all the descendents text description.
/// </summary>
/// <param name="startNode">starting node.</param>
/// <param name="lineLen">line length.</param>
/// <returns></returns>
public string GetText(Asn1Node startNode, int lineLen)
{
string nodeStr = "";
string baseLine = "";
string dataStr = "";
const string lStr = " | | | ";
string oid, oidName;
switch (tag)
{
case Asn1Tag.BIT_STRING:
baseLine =
String.Format("{0,6}|{1,6}|{2,7}|{3} {4} UnusedBits:{5} : ",
dataOffset,
dataLength,
lengthFieldBytes,
GetIndentStr(startNode),
TagName,
unusedBits
);
dataStr = Asn1Util.ToHexString(data);
if (baseLine.Length + dataStr.Length < lineLen)
{
if (dataStr.Length < 1)
{
nodeStr += baseLine + "\r\n";
}
else
{
nodeStr += baseLine + "'" + dataStr + "'\r\n";
}
}
else
{
nodeStr += baseLine + FormatLineHexString(
lStr,
GetIndentStr(startNode).Length,
lineLen,
dataStr + "\r\n"
);
}
break;
case Asn1Tag.OBJECT_IDENTIFIER:
Oid xoid = new Oid();
oid = xoid.Decode(new MemoryStream(data));
oidName = xoid.GetOidName(oid);
nodeStr += String.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5} [{6}]\r\n",
dataOffset,
dataLength,
lengthFieldBytes,
GetIndentStr(startNode),
TagName,
oidName,
oid
);
break;
case Asn1Tag.RELATIVE_OID:
RelativeOid xiod = new RelativeOid();
oid = xiod.Decode(new MemoryStream(data));
oidName = "";
nodeStr += String.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5} [{6}]\r\n",
dataOffset,
dataLength,
lengthFieldBytes,
GetIndentStr(startNode),
TagName,
oidName,
oid
);
break;
case Asn1Tag.PRINTABLE_STRING:
case Asn1Tag.IA5_STRING:
case Asn1Tag.UNIVERSAL_STRING:
case Asn1Tag.VISIBLE_STRING:
case Asn1Tag.NUMERIC_STRING:
case Asn1Tag.UTC_TIME:
case Asn1Tag.UTF8_STRING:
case Asn1Tag.BMPSTRING:
case Asn1Tag.GENERAL_STRING:
case Asn1Tag.GENERALIZED_TIME:
baseLine =
String.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ",
dataOffset,
dataLength,
lengthFieldBytes,
GetIndentStr(startNode),
TagName
);
if (tag == Asn1Tag.UTF8_STRING)
{
UTF8Encoding unicode = new UTF8Encoding();
dataStr = unicode.GetString(data);
}
else
{
dataStr = Asn1Util.BytesToString(data);
}
if (baseLine.Length + dataStr.Length < lineLen)
{
nodeStr += baseLine + "'" + dataStr + "'\r\n";
}
else
{
nodeStr += baseLine + FormatLineString(
lStr,
GetIndentStr(startNode).Length,
lineLen,
dataStr) + "\r\n";
}
break;
case Asn1Tag.INTEGER:
if (data != null && dataLength < 8)
{
nodeStr += String.Format("{0,6}|{1,6}|{2,7}|{3} {4} : {5}\r\n",
dataOffset,
dataLength,
lengthFieldBytes,
GetIndentStr(startNode),
TagName,
Asn1Util.BytesToLong(data).ToString()
);
}
else
{
baseLine =
String.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ",
dataOffset,
dataLength,
lengthFieldBytes,
GetIndentStr(startNode),
TagName
);
nodeStr += GetHexPrintingStr(startNode, baseLine, lStr, lineLen);
}
break;
default:
if ((tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate
{
baseLine =
String.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ",
dataOffset,
dataLength,
lengthFieldBytes,
GetIndentStr(startNode),
TagName
);
dataStr = Asn1Util.BytesToString(data);
if (baseLine.Length + dataStr.Length < lineLen)
{
nodeStr += baseLine + "'" + dataStr + "'\r\n";
}
else
{
nodeStr += baseLine + FormatLineString(
lStr,
GetIndentStr(startNode).Length,
lineLen,
dataStr) + "\r\n";
}
}
else
{
baseLine =
String.Format("{0,6}|{1,6}|{2,7}|{3} {4} : ",
dataOffset,
dataLength,
lengthFieldBytes,
GetIndentStr(startNode),
TagName
);
nodeStr += GetHexPrintingStr(startNode, baseLine, lStr, lineLen);
}
break;
};
if (childNodeList.Count >= 0)
{
nodeStr += GetListStr(startNode, lineLen);
}
return nodeStr;
}
/// <summary>
/// Get the path string of the node.
/// </summary>
public string Path
{
get
{
return path;
}
}
/// <summary>
/// Retrieve the node description.
/// </summary>
/// <param name="pureHexMode">true:Return hex string only;
/// false:Convert to more readable string depending on the node tag.</param>
/// <returns>string</returns>
public string GetDataStr(bool pureHexMode)
{
const int lineLen = 32;
string dataStr = "";
if (pureHexMode)
{
dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2);
}
else
{
switch (tag)
{
case Asn1Tag.BIT_STRING:
dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2);
break;
case Asn1Tag.OBJECT_IDENTIFIER:
Oid xoid = new Oid();
dataStr = xoid.Decode(new MemoryStream(data));
break;
case Asn1Tag.RELATIVE_OID:
RelativeOid roid = new RelativeOid();
dataStr = roid.Decode(new MemoryStream(data));
break;
case Asn1Tag.PRINTABLE_STRING:
case Asn1Tag.IA5_STRING:
case Asn1Tag.UNIVERSAL_STRING:
case Asn1Tag.VISIBLE_STRING:
case Asn1Tag.NUMERIC_STRING:
case Asn1Tag.UTC_TIME:
case Asn1Tag.BMPSTRING:
case Asn1Tag.GENERAL_STRING:
case Asn1Tag.GENERALIZED_TIME:
dataStr = Asn1Util.BytesToString(data);
break;
case Asn1Tag.UTF8_STRING:
UTF8Encoding utf8 = new UTF8Encoding();
dataStr = utf8.GetString(data);
break;
case Asn1Tag.INTEGER:
dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2);
break;
default:
if ((tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate
{
dataStr = Asn1Util.BytesToString(data);
}
else
{
dataStr = Asn1Util.FormatString(Asn1Util.ToHexString(data), lineLen, 2);
}
break;
};
}
return dataStr;
}
/// <summary>
/// Get node label string.
/// </summary>
/// <param name="mask">
/// <code>
/// SHOW_OFFSET
/// SHOW_DATA
/// USE_HEX_OFFSET
/// SHOW_TAG_NUMBER
/// SHOW_PATH</code>
/// </param>
/// <returns>string</returns>
public string GetLabel(uint mask)
{
string nodeStr = "";
string dataStr = "";
string offsetStr = "";
if ((mask & TagTextMask.USE_HEX_OFFSET) != 0)
{
if ((mask & TagTextMask.SHOW_TAG_NUMBER) != 0)
offsetStr = String.Format("(0x{0:X2},0x{1:X6},0x{2:X4})", tag, dataOffset, dataLength);
else
offsetStr = String.Format("(0x{0:X6},0x{1:X4})", dataOffset, dataLength);
}
else
{
if ((mask & TagTextMask.SHOW_TAG_NUMBER) != 0)
offsetStr = String.Format("({0},{1},{2})", tag, dataOffset, dataLength);
else
offsetStr = String.Format("({0},{1})", dataOffset, dataLength);
}
string oid, oidName;
switch (tag)
{
case Asn1Tag.BIT_STRING:
if ((mask & TagTextMask.SHOW_OFFSET) != 0)
{
nodeStr += offsetStr;
}
nodeStr += " " + TagName + " UnusedBits: " + unusedBits.ToString();
if ((mask & TagTextMask.SHOW_DATA) != 0)
{
dataStr = Asn1Util.ToHexString(data);
nodeStr += ((dataStr.Length > 0) ? " : '" + dataStr + "'" : "");
}
break;
case Asn1Tag.OBJECT_IDENTIFIER:
Oid xoid = new Oid();
oid = xoid.Decode(data);
oidName = xoid.GetOidName(oid);
if ((mask & TagTextMask.SHOW_OFFSET) != 0)
{
nodeStr += offsetStr;
}
nodeStr += " " + TagName;
nodeStr += " : " + oidName;
if ((mask & TagTextMask.SHOW_DATA) != 0)
{
nodeStr += ((oid.Length > 0) ? " : '" + oid + "'" : "");
}
break;
case Asn1Tag.RELATIVE_OID:
RelativeOid roid = new RelativeOid();
oid = roid.Decode(data);
oidName = "";
if ((mask & TagTextMask.SHOW_OFFSET) != 0)
{
nodeStr += offsetStr;
}
nodeStr += " " + TagName;
nodeStr += " : " + oidName;
if ((mask & TagTextMask.SHOW_DATA) != 0)
{
nodeStr += ((oid.Length > 0) ? " : '" + oid + "'" : "");
}
break;
case Asn1Tag.PRINTABLE_STRING:
case Asn1Tag.IA5_STRING:
case Asn1Tag.UNIVERSAL_STRING:
case Asn1Tag.VISIBLE_STRING:
case Asn1Tag.NUMERIC_STRING:
case Asn1Tag.UTC_TIME:
case Asn1Tag.UTF8_STRING:
case Asn1Tag.BMPSTRING:
case Asn1Tag.GENERAL_STRING:
case Asn1Tag.GENERALIZED_TIME:
if ((mask & TagTextMask.SHOW_OFFSET) != 0)
{
nodeStr += offsetStr;
}
nodeStr += " " + TagName;
if ((mask & TagTextMask.SHOW_DATA) != 0)
{
if (tag == Asn1Tag.UTF8_STRING)
{
UTF8Encoding unicode = new UTF8Encoding();
dataStr = unicode.GetString(data);
}
else
{
dataStr = Asn1Util.BytesToString(data);
}
nodeStr += ((dataStr.Length > 0) ? " : '" + dataStr + "'" : "");
}
break;
case Asn1Tag.INTEGER:
if ((mask & TagTextMask.SHOW_OFFSET) != 0)
{
nodeStr += offsetStr;
}
nodeStr += " " + TagName;
if ((mask & TagTextMask.SHOW_DATA) != 0)
{
if (data != null && dataLength < 8)
{
dataStr = Asn1Util.BytesToLong(data).ToString();
}
else
{
dataStr = Asn1Util.ToHexString(data);
}
nodeStr += ((dataStr.Length > 0) ? " : '" + dataStr + "'" : "");
}
break;
default:
if ((mask & TagTextMask.SHOW_OFFSET) != 0)
{
nodeStr += offsetStr;
}
nodeStr += " " + TagName;
if ((mask & TagTextMask.SHOW_DATA) != 0)
{
if ((tag & Asn1Tag.TAG_MASK) == 6) // Visible string for certificate
{
dataStr = Asn1Util.BytesToString(data);
}
else
{
dataStr = Asn1Util.ToHexString(data);
}
nodeStr += ((dataStr.Length > 0) ? " : '" + dataStr + "'" : "");
}
break;
};
if ((mask & TagTextMask.SHOW_PATH) != 0)
{
nodeStr = "(" + path + ") " + nodeStr;
}
return nodeStr;
}
/// <summary>
/// Get data length. Not included the unused bits byte for BITSTRING.
/// </summary>
public long DataLength
{
get
{
return dataLength;
}
}
/// <summary>
/// Get the length field bytes.
/// </summary>
public long LengthFieldBytes
{
get
{
return lengthFieldBytes;
}
}
/// <summary>
/// Get/Set node data by byte[], the data length field content and all the
/// node in the parent chain will be adjusted.
/// <br></br>
/// It return all the child data for constructed node.
/// </summary>
public byte[] Data
{
get
{
MemoryStream xdata = new MemoryStream();
long nodeCount = ChildNodeCount;
if (nodeCount == 0)
{
if (data != null)
{
xdata.Write(data, 0, data.Length);
}
}
else
{
Asn1Node tempNode;
for (int i = 0; i < nodeCount; i++)
{
tempNode = GetChildNode(i);
tempNode.SaveData(xdata);
}
}
byte[] tmpData = new byte[xdata.Length];
xdata.Position = 0;
xdata.Read(tmpData, 0, (int)xdata.Length);
xdata.Close();
return tmpData;
}
set
{
SetData(value);
}
}
/// <summary>
/// Get the deepness of the node.
/// </summary>
public long Deepness
{
get
{
return deepness;
}
}
/// <summary>
/// Get data offset.
/// </summary>
public long DataOffset
{
get
{
return dataOffset;
}
}
/// <summary>
/// Get unused bits for BITSTRING.
/// </summary>
public byte UnusedBits
{
get
{
return unusedBits;
}
set
{
unusedBits = value;
}
}
/// <summary>
/// Get descendant node by node path.
/// </summary>
/// <param name="nodePath">relative node path that refer to current node.</param>
/// <returns></returns>
public Asn1Node GetDescendantNodeByPath(string nodePath)
{
Asn1Node retval = this;
if (nodePath == null) return retval;
nodePath = nodePath.TrimEnd().TrimStart();
if (nodePath.Length < 1) return retval;
string[] route = nodePath.Split('/');
try
{
for (int i = 1; i < route.Length; i++)
{
retval = retval.GetChildNode(Convert.ToInt32(route[i]));
}
}
catch
{
retval = null;
}
return retval;
}
/// <summary>
/// Get node by OID.
/// </summary>
/// <param name="oid">OID.</param>
/// <param name="startNode">Starting node.</param>
/// <returns>Null or Asn1Node.</returns>
static public Asn1Node GetDecendantNodeByOid(string oid, Asn1Node startNode)
{
Asn1Node retval = null;
Oid xoid = new Oid();
for (int i = 0; i < startNode.ChildNodeCount; i++)
{
Asn1Node childNode = startNode.GetChildNode(i);
int tmpTag = childNode.tag & Asn1Tag.TAG_MASK;
if (tmpTag == Asn1Tag.OBJECT_IDENTIFIER)
{
if (oid == xoid.Decode(childNode.Data))
{
retval = childNode;
break;
}
}
retval = GetDecendantNodeByOid(oid, childNode);
if (retval != null) break;
}
return retval;
}
/// <summary>
/// Constant of tag field length.
/// </summary>
public const int TagLength = 1;
/// <summary>
/// Constant of unused bits field length.
/// </summary>
public const int BitStringUnusedFiledLength = 1;
/// <summary>
/// Tag text generation mask definition.
/// </summary>
public class TagTextMask
{
/// <summary>
/// Show offset.
/// </summary>
public const uint SHOW_OFFSET = 0x01;
/// <summary>
/// Show decoded data.
/// </summary>
public const uint SHOW_DATA = 0x02;
/// <summary>
/// Show offset in hex format.
/// </summary>
public const uint USE_HEX_OFFSET = 0x04;
/// <summary>
/// Show tag.
/// </summary>
public const uint SHOW_TAG_NUMBER = 0x08;
/// <summary>
/// Show node path.
/// </summary>
public const uint SHOW_PATH = 0x10;
}
/// <summary>
/// Set/Get requireRecalculatePar. RecalculateTreePar function will not do anything
/// if it is set to false.
/// </summary>
protected bool RequireRecalculatePar
{
get
{
return requireRecalculatePar;
}
set
{
requireRecalculatePar = value;
}
}
//ProtectedMembers
/// <summary>
/// Find root node and recalculate entire tree length field,
/// path, offset and deepness.
/// </summary>
protected void RecalculateTreePar()
{
if (!requireRecalculatePar) return;
Asn1Node rootNode;
for (rootNode = this; rootNode.ParentNode != null;)
{
rootNode = rootNode.ParentNode;
}
ResetBranchDataLength(rootNode);
rootNode.dataOffset = 0;
rootNode.deepness = 0;
long subOffset = rootNode.dataOffset + TagLength + rootNode.lengthFieldBytes;
ResetChildNodePar(rootNode, subOffset);
}
/// <summary>
/// Recursively set all the node data length.
/// </summary>
/// <param name="node"></param>
/// <returns>node data length.</returns>
protected static long ResetBranchDataLength(Asn1Node node)
{
long retval = 0;
long childDataLength = 0;
if (node.ChildNodeCount < 1)
{
if (node.data != null)
childDataLength += node.data.Length;
}
else
{
for (int i = 0; i < node.ChildNodeCount; i++)
{
childDataLength += ResetBranchDataLength(node.GetChildNode(i));
}
}
node.dataLength = childDataLength;
if (node.tag == Asn1Tag.BIT_STRING)
node.dataLength += BitStringUnusedFiledLength;
ResetDataLengthFieldWidth(node);
retval = node.dataLength + TagLength + node.lengthFieldBytes;
return retval;
}
/// <summary>
/// Encode the node data length field and set lengthFieldBytes and dataLength.
/// </summary>
/// <param name="node">The node needs to be reset.</param>
protected static void ResetDataLengthFieldWidth(Asn1Node node)
{
MemoryStream tempStream = new MemoryStream();
Asn1Util.DERLengthEncode(tempStream, (ulong)node.dataLength);
node.lengthFieldBytes = tempStream.Length;
tempStream.Close();
}
/// <summary>
/// Recursively set all the child parameters, except dataLength.
/// dataLength is set by ResetBranchDataLength.
/// </summary>
/// <param name="xNode">Starting node.</param>
/// <param name="subOffset">Starting node offset.</param>
protected void ResetChildNodePar(Asn1Node xNode, long subOffset)
{
int i;
if (xNode.tag == Asn1Tag.BIT_STRING)
{
subOffset++;
}
Asn1Node tempNode;
for (i = 0; i < xNode.ChildNodeCount; i++)
{
tempNode = xNode.GetChildNode(i);
tempNode.parentNode = xNode;
tempNode.dataOffset = subOffset;
tempNode.deepness = xNode.deepness + 1;
tempNode.path = xNode.path + '/' + i.ToString();
subOffset += TagLength + tempNode.lengthFieldBytes;
ResetChildNodePar(tempNode, subOffset);
subOffset += tempNode.dataLength;
}
}
/// <summary>
/// Generate all the child text from childNodeList.
/// </summary>
/// <param name="startNode">Starting node.</param>
/// <param name="lineLen">Line length.</param>
/// <returns>Text string.</returns>
protected string GetListStr(Asn1Node startNode, int lineLen)
{
string nodeStr = "";
int i;
Asn1Node tempNode;
for (i = 0; i < childNodeList.Count; i++)
{
tempNode = (Asn1Node)childNodeList[i];
nodeStr += tempNode.GetText(startNode, lineLen);
}
return nodeStr;
}
/// <summary>
/// Generate the node indent string.
/// </summary>
/// <param name="startNode">The node.</param>
/// <returns>Text string.</returns>
protected string GetIndentStr(Asn1Node startNode)
{
string retval = "";
long startLen = 0;
if (startNode != null)
{
startLen = startNode.Deepness;
}
for (long i = 0; i < deepness - startLen; i++)
{
retval += " ";
}
return retval;
}
/// <summary>
/// Decode ASN.1 encoded node Stream data.
/// </summary>
/// <param name="xdata">Stream data.</param>
/// <returns>true:Succeed, false:Failed.</returns>
protected bool GeneralDecode(Stream xdata)
{
long nodeMaxLen = xdata.Length - xdata.Position;
tag = (byte)xdata.ReadByte();
long start = xdata.Position;
dataLength = Asn1Util.DerLengthDecode(xdata, ref isIndefiniteLength);
if (AreTagsOk())
{
if (isIndefiniteLength)
{
return GeneralDecodeIndefiniteLength(xdata, nodeMaxLen - k_IndefiniteLengthFooterSize);
}
else
{
return GeneralDecodeKnownLengthWithChecks(xdata, start, nodeMaxLen);
}
}
else
{
return false;
}
}
private bool AreTagsOk()
{
if (ParentNode == null || ((ParentNode.tag & Asn1TagClasses.CONSTRUCTED) == 0))
{
if ((tag & Asn1Tag.TAG_MASK) <= 0 || (tag & Asn1Tag.TAG_MASK) > 0x1E)
{
return false;
}
}
return true;
}
private bool GeneralDecodeKnownLengthWithChecks(Stream xdata, long start, long nodeMaxLen)
{
if (IsGeneralStreamLengthOk(xdata, start, nodeMaxLen))
{
return GeneralDecodeKnownLength(xdata);
}
return false;
}
private bool IsGeneralStreamLengthOk(Stream xdata, long start, long nodeMaxLen)
{
if (dataLength >= 0)
{
lengthFieldBytes = xdata.Position - start;
if (nodeMaxLen >= (dataLength + TagLength + lengthFieldBytes))
{
return true;
}
}
return false;
}
private bool GeneralDecodeKnownLength(Stream xdata)
{
if (tag == Asn1Tag.BIT_STRING)
{
// First byte of BIT_STRING is unused bits.
// BIT_STRING data does not include this byte.
// Fixed by Gustaf Bj<42>rklund.
if (dataLength < 1)
{
return false;
}
unusedBits = (byte)xdata.ReadByte();
ReadStreamDataDefiniteLength(xdata, (int)(dataLength - 1));
}
else
{
ReadStreamDataDefiniteLength(xdata, (int)(dataLength));
}
return true;
}
private void ReadStreamDataDefiniteLength(Stream xdata, int length)
{
data = new byte[length];
xdata.Read(data, 0, (int)(length));
}
private bool GeneralDecodeIndefiniteLength(Stream xdata, long nodeMaxLen)
{
if (tag == Asn1Tag.BIT_STRING)
{
unusedBits = (byte)xdata.ReadByte();
nodeMaxLen--;
}
return ReadStreamDataIndefiniteLength(xdata, nodeMaxLen);
}
bool ReadStreamDataIndefiniteLength(Stream xdata, long nodeMaxLen)
{
var streamPosition = xdata.Position;
long contentLength = MeasureContentLength(xdata);
if (contentLength != k_InvalidIndeterminateContentLength && contentLength <= nodeMaxLen)
{
ReadMeasuredLengthDataFromStart(xdata, streamPosition, contentLength);
return true;
}
else
{
return false;
}
}
long MeasureContentLength(Stream xdata)
{
long contentLength = 0;
bool firstEocByteFound = false;
bool foundEoc = false;
while (!foundEoc)
{
var currentByte = xdata.ReadByte();
if (currentByte == k_EndOfStream)
{
foundEoc = true;
contentLength = k_InvalidIndeterminateContentLength;
}
else if (currentByte == Asn1Tag.TAG_END_OF_CONTENTS)
{
if (firstEocByteFound)
{
foundEoc = true;
}
else
{
firstEocByteFound = true;
}
}
else
{
if (firstEocByteFound)
{
firstEocByteFound = false;
contentLength++;
}
contentLength++;
}
}
return contentLength;
}
void ReadMeasuredLengthDataFromStart(Stream xdata, long startPosition, long length)
{
xdata.Seek(startPosition, SeekOrigin.Begin);
data = new byte[length];
xdata.Read(data, 0, (int)(length));
}
/// <summary>
/// Decode ASN.1 encoded complex data type Stream data.
/// </summary>
/// <param name="xdata">Stream data.</param>
/// <returns>true:Succeed, false:Failed.</returns>
protected bool ListDecode(Stream xdata)
{
bool retval = false;
long originalPosition = xdata.Position;
try
{
tag = (byte)xdata.ReadByte();
long start = xdata.Position;
dataLength = Asn1Util.DerLengthDecode(xdata, ref isIndefiniteLength);
long childNodeMaxLen = xdata.Length - xdata.Position;
if (isIndefiniteLength)
{
retval = ListDecodeIndefiniteLength(xdata, start, childNodeMaxLen - k_IndefiniteLengthFooterSize);
}
else
{
retval = ListDecodeKnownLengthWithChecks(xdata, start, childNodeMaxLen);
}
}
finally
{
if (!retval)
{
xdata.Position = originalPosition;
ClearAll();
}
}
return retval;
}
private bool ListDecodeKnownLengthWithChecks(Stream xdata, long start, long childNodeMaxLen)
{
if (IsListStreamLengthOk(xdata, childNodeMaxLen))
{
return ListDecodeKnownLength(xdata, start);
}
return false;
}
private bool IsListStreamLengthOk(Stream xdata, long childNodeMaxLen)
{
return (dataLength >= 0 && childNodeMaxLen >= dataLength);
}
private bool ListDecodeKnownLength(Stream xdata, long start)
{
long offset = CalculateListEncodeFieldBytesAndOffset(xdata, start);
HandleBitStringTag(xdata, ref offset);
if (dataLength > 0)
{
return ListDecodeKnownLengthInternal(xdata, offset);
}
return false;
}
private long CalculateListEncodeFieldBytesAndOffset(Stream xdata, long start)
{
lengthFieldBytes = xdata.Position - start;
return dataOffset + TagLength + lengthFieldBytes;
}
bool HandleBitStringTag(Stream xdata, ref long offset)
{
if (tag == Asn1Tag.BIT_STRING)
{
// First byte of BIT_STRING is unused bits.
// BIT_STRING data does not include this byte.
unusedBits = (byte)xdata.ReadByte();
dataLength--;
offset++;
return true;
}
return false;
}
private bool ListDecodeKnownLengthInternal(Stream xdata, long offset)
{
Stream secData = CreateAndPrepareListDecodeMemoryStreamKnownLength(xdata);
return ListDecodeChildNodesWithKnownLength(secData, offset);
}
private Stream CreateAndPrepareListDecodeMemoryStreamKnownLength(Stream xdata)
{
Stream secData = new MemoryStream((int)dataLength);
byte[] secByte = new byte[dataLength];
xdata.Read(secByte, 0, (int)(dataLength));
if (tag == Asn1Tag.BIT_STRING)
{
dataLength++;
}
secData.Write(secByte, 0, secByte.Length);
secData.Position = 0;
return secData;
}
private bool ListDecodeChildNodesWithKnownLength(Stream secData, long offset)
{
while (secData.Position < secData.Length)
{
if (!CreateAndAddChildNode(secData, ref offset))
{
return false;
}
}
return true;
}
private bool CreateAndAddChildNode(Stream secData, ref long offset)
{
var node = new Asn1Node(this, offset);
node.parseEncapsulatedData = this.parseEncapsulatedData;
long start = secData.Position;
if (!node.InternalLoadData(secData))
{
return false;
}
AddChild(node);
offset += secData.Position - start;
return true;
}
private bool ListDecodeIndefiniteLength(Stream xdata, long start, long childNodeMaxLen)
{
long offset = CalculateListEncodeFieldBytesAndOffset(xdata, start);
if (HandleBitStringTag(xdata, ref offset))
{
childNodeMaxLen--;
}
return ListDecodeIndefiniteLengthInternal(xdata, offset, childNodeMaxLen);
}
bool ListDecodeIndefiniteLengthInternal(Stream xdata, long offset, long childNodeMaxLen)
{
bool doneReading = false;
while (!doneReading)
{
var oldOffset = offset;
doneReading = ReadNextChildNodeOrEndFooterOfIndefiniteListClearIfInvalid(xdata, ref offset, childNodeMaxLen);
childNodeMaxLen -= (offset - oldOffset);
}
return ChildNodeCount > 0;
}
bool ReadNextChildNodeOrEndFooterOfIndefiniteListClearIfInvalid(Stream xdata, ref long offset, long childNodeMaxLen)
{
bool doneReading;
var byteChecks = DetectEndOfIndefiniteListContents(xdata);
if (byteChecks == Asn1EndOfIndefiniteLengthNodeType.NotEnd)
{
doneReading = !ReadNextChildNodeOfIndefiniteListClearIfInvalid(xdata, ref offset, childNodeMaxLen);
}
else
{
if (byteChecks == Asn1EndOfIndefiniteLengthNodeType.EndOfStream && ChildNodeCount > 0)
{
//DEVELOPERS' NOTE:
//End of Stream hit parsing an Indeterminate Length List Node.
//This Indeterminate Length List Node is a false flag that just happens to have initial bytes matching the pattern of such a node.
ClearAll();
}
doneReading = true;
}
return doneReading;
}
Asn1EndOfIndefiniteLengthNodeType DetectEndOfIndefiniteListContents(Stream xdata)
{
var tagByte = xdata.ReadByte();
if (tagByte != k_EndOfStream)
{
var lengthByte = xdata.ReadByte();
if (lengthByte != k_EndOfStream)
{
if (tagByte == Asn1Tag.TAG_END_OF_CONTENTS && lengthByte == Asn1Tag.TAG_END_OF_CONTENTS)
{
return Asn1EndOfIndefiniteLengthNodeType.EndOfNodeFooter;
}
else
{
return Asn1EndOfIndefiniteLengthNodeType.NotEnd;
}
}
}
return Asn1EndOfIndefiniteLengthNodeType.EndOfStream;
}
bool ReadNextChildNodeOfIndefiniteListClearIfInvalid(Stream xdata, ref long offset, long childNodeMaxLen)
{
xdata.Position -= k_IndefiniteLengthFooterSize;
bool validChildNode = false;
if (CreateAndAddChildNode(xdata, ref offset))
{
validChildNode = GetLastChild().DataLength < childNodeMaxLen;
}
if (ChildNodeCount > 0 && !validChildNode)
{
//DEVELOPERS' NOTE:
//Invalid sequential child Asn1Node found in an Indeterminate Length List Node.
//This Indeterminate Length List Node is a false flag that just happens to have initial bytes matching the pattern of such a node.
ClearAll();
}
return validChildNode;
}
/// <summary>
/// Set the node data and recalculate the entire tree parameters.
/// </summary>
/// <param name="xdata">byte[] data.</param>
protected void SetData(byte[] xdata)
{
if (childNodeList.Count > 0)
{
throw new Exception("Constructed node can't hold simple data.");
}
else
{
data = xdata;
if (data != null)
{
dataLength = data.Length;
}
else
{
dataLength = 0;
}
RecalculateTreePar();
}
}
/// <summary>
/// Load data from Stream. Start from current position.
/// </summary>
/// <param name="xdata">Stream</param>
/// <returns>true:Succeed; false:failed.</returns>
protected bool InternalLoadData(Stream xdata)
{
bool retval = true;
ClearAll();
byte xtag;
long curPosition = xdata.Position;
xtag = (byte)xdata.ReadByte();
xdata.Position = curPosition;
int maskedTag = xtag & Asn1Tag.TAG_MASK;
if (((xtag & Asn1TagClasses.CONSTRUCTED) != 0)
|| (parseEncapsulatedData
&& ((maskedTag == Asn1Tag.BIT_STRING)
|| (maskedTag == Asn1Tag.EXTERNAL)
|| (maskedTag == Asn1Tag.GENERAL_STRING)
|| (maskedTag == Asn1Tag.GENERALIZED_TIME)
|| (maskedTag == Asn1Tag.GRAPHIC_STRING)
|| (maskedTag == Asn1Tag.IA5_STRING)
|| (maskedTag == Asn1Tag.OCTET_STRING)
|| (maskedTag == Asn1Tag.PRINTABLE_STRING)
|| (maskedTag == Asn1Tag.SEQUENCE)
|| (maskedTag == Asn1Tag.SET)
|| (maskedTag == Asn1Tag.T61_STRING)
|| (maskedTag == Asn1Tag.UNIVERSAL_STRING)
|| (maskedTag == Asn1Tag.UTF8_STRING)
|| (maskedTag == Asn1Tag.VIDEOTEXT_STRING)
|| (maskedTag == Asn1Tag.VISIBLE_STRING)))
)
{
if (!ListDecode(xdata))
{
if (!GeneralDecode(xdata))
{
retval = false;
}
}
}
else
{
if (!GeneralDecode(xdata)) retval = false;
}
return retval;
}
/// <summary>
/// Get/Set parseEncapsulatedData. This property will be inherited by the
/// child nodes when loading data.
/// </summary>
public bool ParseEncapsulatedData
{
get
{
return parseEncapsulatedData;
}
set
{
if (parseEncapsulatedData == value) return;
byte[] tmpData = Data;
parseEncapsulatedData = value;
ClearAll();
if ((tag & Asn1TagClasses.CONSTRUCTED) != 0 || parseEncapsulatedData)
{
MemoryStream ms = new MemoryStream(tmpData);
ms.Position = 0;
bool isLoaded = true;
while (ms.Position < ms.Length)
{
Asn1Node tempNode = new Asn1Node();
tempNode.ParseEncapsulatedData = parseEncapsulatedData;
if (!tempNode.LoadData(ms))
{
ClearAll();
isLoaded = false;
break;
}
AddChild(tempNode);
}
if (!isLoaded)
{
Data = tmpData;
}
}
else
{
Data = tmpData;
}
}
}
}
}