1823 lines
60 KiB
C#
1823 lines
60 KiB
C#
//+-------------------------------------------------------------------------------+
|
||
//| 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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|