/// <summary> /// Read value of given node. Underlying stream in BER reader has to be seekable to do that. /// </summary> /// <param name="node">Node whose value shall be read.</param> /// <param name="outputBuffer">Reader will read content to this parameter. /// The parameter need to be initialized beforehand with length of actual read node.</param> /// <remarks> /// Basically it can be used only on nodes that have definite length and are primitive, i.e. Integer,BitString,OctetString etc. /// Sequence and Set are construct types so they are excluded. /// </remarks> /// <returns>Content of the given node.</returns> public void ReadContentAsBuffer(InternalNode node, byte[] outputBuffer) { if (node == null) { throw new ArgumentNullException("node"); } if (outputBuffer == null) { throw new ArgumentNullException("outputBuffer"); } if (outputBuffer.Length != node.Length) { throw new ArgumentException("Parameter outputBuffer needs to be initialized to length of actual read node."); } if (node.NodeType != Asn1NodeType.Primitive && node.Identifier.Class != Asn1Class.ContextSpecific) { throw new InvalidOperationException("Content cannot be read at this position"); } if ((node.IsConstructed && node.Identifier.Class != Asn1Class.ContextSpecific) || node.HasIndefiniteLength) { throw new InvalidOperationException("Cannot read value from constructed type"); } if (node.Length > int.MaxValue) { throw new InvalidOperationException("Cannot read values larger than 2GB into buffer"); } _innerStream.Seek(node.DataOffsetToStream, SeekOrigin.Begin); _innerStream.Read(outputBuffer, 0, node.Length); }
/// <summary> /// Reads identifier octet from ASN.1 object. /// </summary> /// <returns></returns> private Identifier ReadIdentifierOctet() { // Identifier octets // read one byte from stream. This should represent Identifier Asn.1 tag (class and number) int identifierOctet = _innerStream.ReadByte(); if (identifierOctet == -1) { // reached end of stream _current = new InternalNode() { NodeType = Asn1NodeType.DocumentEnd, Identifier = new Identifier() }; return(_current.Identifier); } // | | ( 63 -------------------------------> | // | | 32 | ( 31 -----------------------> | // ----------------------------------------------------------------- // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | // | class | P/C | Tag Number | // ----------------------------------------------------------------- var tagStart = (byte)identifierOctet; //parse Tag // bit 6 contains P/C (Primitive/Constructed) flag. 6bit => value 0x20 (32 decimal) bool isConstructed = (tagStart & 0x20) != 0; // Tag number allows values from 0 to 31. 31 states long-form bool moreBytes = (tagStart & (int)Asn1Type.LongForm) == (int)Asn1Type.LongForm; while (moreBytes) { byte b = ReadByteOrThrow(_innerStream); // TODO parse multi byte tag throw new NotSupportedException(); moreBytes = (b & 0x80) != 0; } var classTag = (Asn1Class)(tagStart & ~63); var tagNumber = (Asn1Type)(tagStart & 31); var identifier = new Identifier { Class = classTag, Constructed = isConstructed, Tag = tagNumber }; return(identifier); }
/// <summary> /// Read value of given node. Underlying stream in BER reader has to be seekable to do that. /// </summary> /// <param name="node">Node whose value shall be read.</param> /// <returns>Content of the given node.</returns> public byte[] ReadContentAsBuffer(InternalNode node) { if (node == null) { throw new ArgumentNullException("node"); } var result = new byte[node.Length]; ReadContentAsBuffer(node, result); return(result); }
/// <summary> /// Get raw ASN.1 node data (whole TLV => Identifier, Length and Value octets). /// </summary> /// <param name="node">Node that shall be read.</param> /// <returns>Raw data of the given ASN.1 node including Identifier and Length octets.</returns> public byte[] ExtractAsn1NodeAsRawData(InternalNode node) { if (node == null) { throw new ArgumentNullException("node"); } byte[] outputBuffer = new byte[node.EndPosition - node.StartPosition]; if (node.Length > int.MaxValue) { throw new InvalidOperationException("Cannot read values larger than 2GB into buffer"); } _innerStream.Seek(node.StartPosition, SeekOrigin.Begin); _innerStream.Read(outputBuffer, 0, Convert.ToInt32(node.EndPosition - node.StartPosition)); return(outputBuffer); }
/// <summary> /// Parse ASN.1 node and sets the result to <see cref="InternalNode"/> class. /// <param name="readContent">Flag indicating that value of each node should be read as well.</param> /// </summary> private void InternalReadElement(bool readContent = false) { // This is the end of Asn1 constructed element. if (_nodeStack.Count > 0 && _innerStream.Position >= _nodeStack.Peek().EndPosition) { _current = new InternalNode() { Identifier = new Identifier { Class = _nodeStack.Peek().Identifier.Class, Constructed = _nodeStack.Peek().Identifier.Constructed, Tag = _nodeStack.Peek().Identifier.Tag }, EndPosition = _innerStream.Position, Length = 0, DataOffsetToStream = _innerStream.Position, NodeType = Asn1NodeType.ConstructedEnd }; return; } var nodeStartPosition = _innerStream.Position; // Identifier var identifier = ReadIdentifierOctet(); if (identifier == null) { // if no identifier was returned then it is the end of stream return; } // ReadIdentifierOctet(); may change _current to DocumentEnd type if (_current.NodeType == Asn1NodeType.DocumentEnd) { return; } var length = ReadLength(); if (length >= _innerStream.Length) { throw new Exception("Length of ASN.1 node exceeds length of current stream."); } var hasIndefiniteLengthForm = false; if (length == -1) { hasIndefiniteLengthForm = true; length = DetermineLengthOfIndefiniteLengthNode(); } // set what we parsed so far in internal node class _current = new InternalNode() { Identifier = identifier, StartPosition = nodeStartPosition, HasIndefiniteLength = hasIndefiniteLengthForm, EndPosition = _innerStream.Position + ((hasIndefiniteLengthForm) ? length + 2: length), Length = length, DataOffsetToStream = _innerStream.Position, NodeType = (identifier.Constructed) ? Asn1NodeType.ConstructedStart : Asn1NodeType.Primitive }; // if content should be read then be it but only if the node is Primitive. Context Specific value should be read manually when appropriate if (readContent) { _current.RawValue = (_current.NodeType == Asn1NodeType.Primitive) ? ReadContentAsBuffer(_current) : null; } }