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 EXTERNAL = 0x08; //private const byte ENUMERATED = 0x0a; private const byte Sequence = 0x10; //private const byte SEQUENCEOF = 0x10; // for completeness //private const byte SET = 0x11; //private const byte SETOF = 0x11; // for completeness //private const byte NUMERICSTRING = 0x12; //private const byte PRINTABLESTRING = 0x13; //private const byte T61STRING = 0x14; //private const byte VIDEOTEXSTRING = 0x15; //private const byte IA5STRING = 0x16; //private const byte UTCTIME = 0x17; //private const byte GENERALIZEDTIME = 0x18; //private const byte GRAPHICSTRING = 0x19; //private const byte VISIBLESTRING = 0x1a; //private const byte GENERALSTRING = 0x1b; //private const byte UNIVERSALSTRING = 0x1c; //private const byte BMPSTRING = 0x1e; //private const byte UTF8STRING = 0x0c; //private const byte APPLICATION = 0x40; //private const byte TAGGED = 0x80; private readonly List _data; private int _readerIndex; private readonly int _lastIndex; /// /// Gets a value indicating whether end of data is reached. /// /// /// true if end of data is reached; otherwise, false. /// 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. public DerData(byte[] data) { _data = new List(data); 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("Invalid data type, INTEGER(02) is expected."); 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("Invalid data type, INTEGER(02) is expected."); 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 (int)(data[0] << 56 | data[1] << 48 | data[2] << 40 | data[3] << 32 | data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]); return result; } /// /// 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 NULL data into internal buffer. /// public void WriteNull() { _data.Add(Null); _data.Add(0); } /// /// Writes DerData data into internal buffer. /// /// DerData data to write. public void Write(DerData data) { var bytes = data.Encode(); _data.AddRange(bytes); } private static IEnumerable 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}; } private 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"); //if (length >= limit) // after all we must have read at least 1 byte // throw new IOException("Corrupted stream - out of bounds length found"); } return length; } private void WriteBytes(IEnumerable data) { _data.AddRange(data); } private byte ReadByte() { if (_readerIndex > _data.Count) throw new InvalidOperationException("Read out of boundaries."); return _data[_readerIndex++]; } private 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; } } }