//****************************************************************************************************** // DerWriter.cs - Gbtc // // Copyright © 2018, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 05/27/2018 - Steven E. Chisholm // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Text; namespace GSF.Security.Cryptography.X509 { [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable")] internal class DerWriter { private enum SectionMode { Sequence, Set, BitString, Tagged, Octet } private class Section : IDisposable { private DerWriter m_caller; private SectionMode m_mode; public Section(DerWriter caller, SectionMode mode) { m_caller = caller; m_mode = mode; } public void Dispose() { m_caller?.EndSection(m_mode); m_caller = null; } } private Stack m_stack = new Stack(); private const int Integer = 0x02; private const int BitString = 0x03; private const int OctetString = 0x04; private const int Null = 0x05; private const int ObjectIdentifier = 0x06; private const int Sequence = 0x10; private const int Set = 0x11; private const int GeneralizedTime = 0x18; private const int Utf8String = 0x0c; private const int Constructed = 0x20; private const int Tagged = 0x80; private MemoryStream m_ms; public DerWriter() { m_ms = new MemoryStream(); } private void EndSection(SectionMode mode) { byte[] data = m_ms.ToArray(); m_ms = m_stack.Pop(); switch (mode) { case SectionMode.Sequence: WriteSequence(data); break; case SectionMode.Set: WriteSet(data); break; case SectionMode.BitString: Write(data); break; case SectionMode.Octet: WriteOctetString(data); break; case SectionMode.Tagged: WriteLength(data.Length); m_ms.Write(data, 0, data.Length); break; default: throw new ArgumentOutOfRangeException(nameof(mode), mode, null); } } private void WriteLength(int length) { if (length > 127) { int size = 1; uint val = (uint)length; while ((val >>= 8) != 0) { size++; } m_ms.WriteByte((byte)(size | 0x80)); for (int i = (size - 1) * 8; i >= 0; i -= 8) { m_ms.WriteByte((byte)(length >> i)); } } else { m_ms.WriteByte((byte)length); } } private void WriteEncoded(int tag, byte[] bytes) { m_ms.WriteByte((byte)tag); WriteLength(bytes.Length); m_ms.Write(bytes, 0, bytes.Length); } public void Write(byte[] bitString) { m_ms.WriteByte(BitString); WriteLength(bitString.Length + 1); m_ms.WriteByte((byte)0); m_ms.Write(bitString, 0, bitString.Length); } public void Write(DateTime time) { WriteEncoded(GeneralizedTime, Encoding.ASCII.GetBytes(time.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture) + "Z")); } public void WriteInteger(byte[] bigInt) { for (int x = 0; x < bigInt.Length; x++) { if (bigInt[x] != 0) { if (bigInt[x] > 127) //Value is negative x--; byte[] newData = new byte[bigInt.Length - x]; if (x == -1) { Array.Copy(bigInt, 0, newData, 1, newData.Length-1); } else { Array.Copy(bigInt, x, newData, 0, newData.Length); } WriteEncoded(Integer, newData); return; } } //The value is 0 WriteEncoded(Integer, new byte[1]); } public void WriteInteger(long value) { WriteInteger(ToBytes(value)); } private static byte[] ToBytes(long value) { if (value < 0) throw new Exception("Value must be positive"); byte[] rv = BitConverter.GetBytes(value); if (BitConverter.IsLittleEndian) { Array.Reverse(rv); } return rv; } public void WriteNull() { WriteEncoded(Null, new byte[0]); } public IDisposable BeginTaggedObject(int tag) { m_ms.WriteByte((byte)(Constructed | Tagged | tag)); m_stack.Push(m_ms); m_ms = new MemoryStream(); return new Section(this, SectionMode.Tagged); } public IDisposable BeginSequence() { m_stack.Push(m_ms); m_ms = new MemoryStream(); return new Section(this, SectionMode.Sequence); } public IDisposable BeginOctetString() { m_stack.Push(m_ms); m_ms = new MemoryStream(); return new Section(this, SectionMode.Octet); } public void WriteOctetString(byte[] data) { m_ms.WriteByte(OctetString); WriteLength(data.Length); m_ms.Write(data, 0, data.Length); } public IDisposable BeginBitString() { m_stack.Push(m_ms); m_ms = new MemoryStream(); return new Section(this, SectionMode.BitString); } public IDisposable BeginSet() { m_stack.Push(m_ms); m_ms = new MemoryStream(); return new Section(this, SectionMode.Set); } public void WriteOID(string identifier) { string[] items = identifier.Split('.'); var ms = new MemoryStream(); for (int x = 1; x < items.Length; x++) { long value; if (x == 1) value = int.Parse(items[0]) * 40 + int.Parse(items[1]); else value = int.Parse(items[x]); WriteField(ms, value); } WriteEncoded(ObjectIdentifier, ms.ToArray()); } private void WriteField(MemoryStream stream, long fieldValue) { //Write a 7-bit big endian value. byte[] result = new byte[9]; int pos = 8; result[pos] = (byte)(fieldValue & 127); while (fieldValue >= 128) { fieldValue >>= 7; pos--; result[pos] = (byte)((fieldValue & 127) | 128); } stream.Write(result, pos, 9 - pos); } private void WriteSequence(byte[] data) { WriteEncoded(Sequence | Constructed, data); } private void WriteSet(byte[] data) { WriteEncoded(Set | Constructed, data); } public void Write(string value) { WriteEncoded(Utf8String, Encoding.UTF8.GetBytes(value)); } public byte[] ToArray() { return m_ms.ToArray(); } } }