using System; using System.Collections.Generic; namespace Renci.SshNet.Common { /// /// Base class for DER encoded data. /// public class DerData { private const byte Constructed = 0x20; private const byte Boolean = 0x01; private const byte Integer = 0x02; private const byte BITSTRING = 0x03; private const byte Octetstring = 0x04; private const byte Null = 0x05; private const byte Objectidentifier = 0x06; private const byte Sequence = 0x10; private readonly List _data; private readonly int _lastIndex; private int _readerIndex; /// /// Gets a value indicating whether end of data is reached. /// /// /// if end of data is reached; otherwise, . /// public bool IsEndOfData { get { return _readerIndex >= _lastIndex; } } /// /// Initializes a new instance of the class. /// public DerData() { _data = new List(); } /// /// Initializes a new instance of the class. /// /// DER encoded data. /// its a construct. public DerData(byte[] data, bool construct = false) { _data = new List(data); if (construct) { _lastIndex = _readerIndex + data.Length; } else { _ = ReadByte(); // skip dataType var length = ReadLength(); _lastIndex = _readerIndex + length; } } /// /// Encodes written data as DER byte array. /// /// DER Encoded array. public byte[] Encode() { var length = _data.Count; var lengthBytes = GetLength(length); _data.InsertRange(0, lengthBytes); _data.Insert(0, Constructed | Sequence); return _data.ToArray(); } /// /// Reads next mpint data type from internal buffer. /// /// mpint read. public BigInteger ReadBigInteger() { var type = ReadByte(); if (type != Integer) { throw new InvalidOperationException(string.Format("Invalid data type, INTEGER(02) is expected, but was {0}", type.ToString("X2"))); } var length = ReadLength(); var data = ReadBytes(length); return new BigInteger(data.Reverse()); } /// /// Reads next int data type from internal buffer. /// /// int read. public int ReadInteger() { var type = ReadByte(); if (type != Integer) { throw new InvalidOperationException(string.Format("Invalid data type, INTEGER(02) is expected, but was {0}", type.ToString("X2"))); } var length = ReadLength(); var data = ReadBytes(length); if (length > 4) { throw new InvalidOperationException("Integer type cannot occupy more then 4 bytes"); } var result = 0; var shift = (length - 1) * 8; for (var i = 0; i < length; i++) { result |= data[i] << shift; shift -= 8; } return result; } /// /// Reads next octetstring data type from internal buffer. /// /// data read. public byte[] ReadOctetString() { var type = ReadByte(); if (type != Octetstring) { throw new InvalidOperationException(string.Format("Invalid data type, OCTETSTRING(04) is expected, but was {0}", type.ToString("X2"))); } var length = ReadLength(); var data = ReadBytes(length); return data; } /// /// Reads next bitstring data type from internal buffer. /// /// data read. public byte[] ReadBitString() { var type = ReadByte(); if (type != BITSTRING) { throw new InvalidOperationException(string.Format("Invalid data type, BITSTRING(03) is expected, but was {0}", type.ToString("X2"))); } var length = ReadLength(); var data = ReadBytes(length); return data; } /// /// Reads next object data type from internal buffer. /// /// data read. public byte[] ReadObject() { var type = ReadByte(); if (type != Objectidentifier) { throw new InvalidOperationException(string.Format("Invalid data type, OBJECT(06) is expected, but was {0}", type.ToString("X2"))); } var length = ReadLength(); var data = ReadBytes(length); return data; } /// /// Writes BOOLEAN data into internal buffer. /// /// UInt32 data to write. public void Write(bool data) { _data.Add(Boolean); _data.Add(1); _data.Add((byte)(data ? 1 : 0)); } /// /// Writes UInt32 data into internal buffer. /// /// UInt32 data to write. public void Write(uint data) { var bytes = Pack.UInt32ToBigEndian(data); _data.Add(Integer); var length = GetLength(bytes.Length); WriteBytes(length); WriteBytes(bytes); } /// /// Writes INTEGER data into internal buffer. /// /// BigInteger data to write. public void Write(BigInteger data) { var bytes = data.ToByteArray().Reverse(); _data.Add(Integer); var length = GetLength(bytes.Length); WriteBytes(length); WriteBytes(bytes); } /// /// Writes OCTETSTRING data into internal buffer. /// /// The data. public void Write(byte[] data) { _data.Add(Octetstring); var length = GetLength(data.Length); WriteBytes(length); WriteBytes(data); } /// /// Writes OBJECTIDENTIFIER data into internal buffer. /// /// The identifier. public void Write(ObjectIdentifier identifier) { var temp = new ulong[identifier.Identifiers.Length - 1]; temp[0] = (identifier.Identifiers[0] * 40) + identifier.Identifiers[1]; Buffer.BlockCopy(identifier.Identifiers, 2 * sizeof(ulong), temp, 1 * sizeof(ulong), (identifier.Identifiers.Length - 2) * sizeof(ulong)); var bytes = new List(); foreach (var subidentifier in temp) { var item = subidentifier; var buffer = new byte[8]; var bufferIndex = buffer.Length - 1; var current = (byte)(item & 0x7F); do { buffer[bufferIndex] = current; if (bufferIndex < buffer.Length - 1) { buffer[bufferIndex] |= 0x80; } item >>= 7; current = (byte)(item & 0x7F); bufferIndex--; } while (current > 0); for (var i = bufferIndex + 1; i < buffer.Length; i++) { bytes.Add(buffer[i]); } } _data.Add(Objectidentifier); var length = GetLength(bytes.Count); WriteBytes(length); WriteBytes(bytes); } /// /// Writes DerData data into internal buffer. /// /// DerData data to write. public void Write(DerData data) { var bytes = data.Encode(); _data.AddRange(bytes); } /// /// Writes BITSTRING data into internal buffer. /// /// The data. public void WriteBitstring(byte[] data) { _data.Add(BITSTRING); var length = GetLength(data.Length); WriteBytes(length); WriteBytes(data); } /// /// Writes OBJECTIDENTIFIER data into internal buffer. /// /// The bytes. public void WriteObjectIdentifier(byte[] bytes) { _data.Add(Objectidentifier); var length = GetLength(bytes.Length); WriteBytes(length); WriteBytes(bytes); } /// /// Writes NULL data into internal buffer. /// public void WriteNull() { _data.Add(Null); _data.Add(0); } private static byte[] GetLength(int length) { if (length > 127) { var size = 1; var val = length; while ((val >>= 8) != 0) { size++; } var data = new byte[size]; data[0] = (byte)(size | 0x80); for (int i = (size - 1) * 8, j = 1; i >= 0; i -= 8, j++) { data[j] = (byte)(length >> i); } return data; } return new[] { (byte) length }; } /// /// Gets Data Length. /// /// /// The length. /// public int ReadLength() { int length = ReadByte(); if (length == 0x80) { throw new NotSupportedException("Indefinite-length encoding is not supported."); } if (length > 127) { var size = length & 0x7f; // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here if (size > 4) { throw new InvalidOperationException(string.Format("DER length is '{0}' and cannot be more than 4 bytes.", size)); } length = 0; for (var i = 0; i < size; i++) { int next = ReadByte(); length = (length << 8) + next; } if (length < 0) { throw new InvalidOperationException("Corrupted data - negative length found"); } } return length; } /// /// Write Byte data into internal buffer. /// /// The data to write. public void WriteBytes(IEnumerable data) { _data.AddRange(data); } /// /// Reads Byte data into internal buffer. /// /// /// The data read. /// public byte ReadByte() { if (_readerIndex > _data.Count) { throw new InvalidOperationException("Read out of boundaries."); } return _data[_readerIndex++]; } /// /// Reads lengths Bytes data into internal buffer. /// /// /// The data read. /// /// amount of data to read. public byte[] ReadBytes(int length) { if (_readerIndex + length > _data.Count) { throw new InvalidOperationException("Read out of boundaries."); } var result = new byte[length]; _data.CopyTo(_readerIndex, result, 0, length); _readerIndex += length; return result; } } }