2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 using System.Collections.Generic;
33 * An AsnElt instance represents a decoded ASN.1 DER object. It is
40 * Universal tag values.
42 public const int BOOLEAN = 1;
43 public const int INTEGER = 2;
44 public const int BIT_STRING = 3;
45 public const int OCTET_STRING = 4;
46 public const int NULL = 5;
47 public const int OBJECT_IDENTIFIER = 6;
48 public const int Object_Descriptor = 7;
49 public const int EXTERNAL = 8;
50 public const int REAL = 9;
51 public const int ENUMERATED = 10;
52 public const int EMBEDDED_PDV = 11;
53 public const int UTF8String = 12;
54 public const int RELATIVE_OID = 13;
55 public const int SEQUENCE = 16;
56 public const int SET = 17;
57 public const int NumericString = 18;
58 public const int PrintableString = 19;
59 public const int T61String = 20;
60 public const int TeletexString = 20;
61 public const int VideotexString = 21;
62 public const int IA5String = 22;
63 public const int UTCTime = 23;
64 public const int GeneralizedTime = 24;
65 public const int GraphicString = 25;
66 public const int VisibleString = 26;
67 public const int GeneralString = 27;
68 public const int UniversalString = 28;
69 public const int CHARACTER_STRING = 29;
70 public const int BMPString = 30;
75 public const int UNIVERSAL = 0;
76 public const int APPLICATION = 1;
77 public const int CONTEXT = 2;
78 public const int PRIVATE = 3;
84 * Instances are immutable. They reference an internal buffer
85 * that they never modify. The buffer is never shown to the
86 * outside; when decoding and creating, copies are performed
89 * If the instance was created by decoding, then:
90 * objBuf points to the array containing the complete object
91 * objOff start offset for the object header
92 * objLen complete object length
93 * valOff offset for the first value byte
94 * valLen value length (excluding the null-tag, if applicable)
95 * hasEncodedHeader is true
97 * If the instance was created from an explicit value or from
99 * objBuf contains the value, or is null
101 * objLen is -1, or contains the computed object length
103 * valLen is -1, or contains the computed value length
104 * hasEncodedHeader is false
106 * If objBuf is null, then the object is necessarily constructed
107 * (Sub is not null). If objBuf is not null, then the encoded
108 * value is known (the object may be constructed or primitive),
109 * and valOff/valLen identify the actual value within objBuf.
111 * Tag class and value, and sub-elements, are referenced from
112 * specific properties.
120 bool hasEncodedHeader;
127 * The tag class for this element.
130 public int TagClass {
140 * The tag value for this element.
143 public int TagValue {
153 * The sub-elements. This is null if this element is primitive.
154 * DO NOT MODIFY this array.
157 public AsnElt[] Sub {
167 * The "constructed" flag: true for an elements with sub-elements,
168 * false for a primitive element.
170 public bool Constructed {
177 * The value length. When the object is BER-encoded with an
178 * indefinite length, the value length includes all the sub-objects
179 * but NOT the formal null-tag marker.
181 public int ValueLength {
186 foreach (AsnElt a in Sub) {
187 vlen += a.EncodedLength;
191 valLen = objBuf.Length;
199 * The encoded object length (complete with header).
201 public int EncodedLength {
204 int vlen = ValueLength;
205 objLen = TagLength(TagValue)
206 + LengthLength(vlen) + vlen;
213 * Check that this element is constructed. An exception is thrown
214 * if this is not the case.
216 public void CheckConstructed()
219 throw new AsnException("not constructed");
224 * Check that this element is primitive. An exception is thrown
225 * if this is not the case.
227 public void CheckPrimitive()
230 throw new AsnException("not primitive");
235 * Get a sub-element. This method throws appropriate exceptions
236 * if this element is not constructed, or the requested index
239 public AsnElt GetSub(int n)
242 if (n < 0 || n >= Sub.Length) {
243 throw new AsnException("no such sub-object: n=" + n);
249 * Check that the tag is UNIVERSAL with the provided value.
251 public void CheckTag(int tv)
253 CheckTag(UNIVERSAL, tv);
257 * Check that the tag has the specified class and value.
259 public void CheckTag(int tc, int tv)
261 if (TagClass != tc || TagValue != tv) {
262 throw new AsnException("unexpected tag: " + TagString);
267 * Check that this element is constructed and contains exactly
270 public void CheckNumSub(int n)
273 if (Sub.Length != n) {
274 throw new AsnException("wrong number of sub-elements: "
275 + Sub.Length + " (expected: " + n + ")");
280 * Check that this element is constructed and contains at least
283 public void CheckNumSubMin(int n)
286 if (Sub.Length < n) {
287 throw new AsnException("not enough sub-elements: "
288 + Sub.Length + " (minimum: " + n + ")");
293 * Check that this element is constructed and contains no more
294 * than 'n' sub-elements.
296 public void CheckNumSubMax(int n)
299 if (Sub.Length > n) {
300 throw new AsnException("too many sub-elements: "
301 + Sub.Length + " (maximum: " + n + ")");
306 * Get a string representation of the tag class and value.
308 public string TagString {
310 return TagToString(TagClass, TagValue);
314 static string TagToString(int tc, int tv)
320 return "APPLICATION:" + tv;
322 return "CONTEXT:" + tv;
324 return "PRIVATE:" + tv;
326 return String.Format("INVALID:{0}/{1}", tc, tv);
330 case BOOLEAN: return "BOOLEAN";
331 case INTEGER: return "INTEGER";
332 case BIT_STRING: return "BIT_STRING";
333 case OCTET_STRING: return "OCTET_STRING";
334 case NULL: return "NULL";
335 case OBJECT_IDENTIFIER: return "OBJECT_IDENTIFIER";
336 case Object_Descriptor: return "Object_Descriptor";
337 case EXTERNAL: return "EXTERNAL";
338 case REAL: return "REAL";
339 case ENUMERATED: return "ENUMERATED";
340 case EMBEDDED_PDV: return "EMBEDDED_PDV";
341 case UTF8String: return "UTF8String";
342 case RELATIVE_OID: return "RELATIVE_OID";
343 case SEQUENCE: return "SEQUENCE";
344 case SET: return "SET";
345 case NumericString: return "NumericString";
346 case PrintableString: return "PrintableString";
347 case TeletexString: return "TeletexString";
348 case VideotexString: return "VideotexString";
349 case IA5String: return "IA5String";
350 case UTCTime: return "UTCTime";
351 case GeneralizedTime: return "GeneralizedTime";
352 case GraphicString: return "GraphicString";
353 case VisibleString: return "VisibleString";
354 case GeneralString: return "GeneralString";
355 case UniversalString: return "UniversalString";
356 case CHARACTER_STRING: return "CHARACTER_STRING";
357 case BMPString: return "BMPString";
359 return String.Format("UNIVERSAL:" + tv);
364 * Get the encoded length for a tag.
366 static int TagLength(int tv)
380 * Get the encoded length for a length.
382 static int LengthLength(int len)
396 * Decode an ASN.1 object. The provided buffer is internally
397 * copied. Trailing garbage is not tolerated.
399 public static AsnElt Decode(byte[] buf)
401 return Decode(buf, 0, buf.Length, true);
405 * Decode an ASN.1 object. The provided buffer is internally
406 * copied. Trailing garbage is not tolerated.
408 public static AsnElt Decode(byte[] buf, int off, int len)
410 return Decode(buf, off, len, true);
414 * Decode an ASN.1 object. The provided buffer is internally
415 * copied. If 'exactLength' is true, then trailing garbage is
416 * not tolerated (it triggers an exception).
418 public static AsnElt Decode(byte[] buf, bool exactLength)
420 return Decode(buf, 0, buf.Length, exactLength);
424 * Decode an ASN.1 object. The provided buffer is internally
425 * copied. If 'exactLength' is true, then trailing garbage is
426 * not tolerated (it triggers an exception).
428 public static AsnElt Decode(byte[] buf, int off, int len,
431 int tc, tv, valOff, valLen, objLen;
433 objLen = Decode(buf, off, len,
434 out tc, out tv, out cons,
435 out valOff, out valLen);
436 if (exactLength && objLen != len) {
437 throw new AsnException("trailing garbage");
439 byte[] nbuf = new byte[objLen];
440 Array.Copy(buf, off, nbuf, 0, objLen);
441 return DecodeNoCopy(nbuf, 0, objLen);
445 * Internal recursive decoder. The provided array is NOT copied.
446 * Trailing garbage is ignored (caller should use the 'objLen'
447 * field to learn the total object length).
449 static AsnElt DecodeNoCopy(byte[] buf, int off, int len)
451 int tc, tv, valOff, valLen, objLen;
453 objLen = Decode(buf, off, len,
454 out tc, out tv, out cons,
455 out valOff, out valLen);
456 AsnElt a = new AsnElt();
464 a.hasEncodedHeader = true;
466 List<AsnElt> subs = new List<AsnElt>();
468 int lim = valOff + valLen;
470 AsnElt b = DecodeNoCopy(buf, off, lim - off);
474 a.Sub = subs.ToArray();
482 * Decode the tag and length, and get the value offset and length.
483 * Returned value if the total object length.
484 * Note: when an object has indefinite length, the terminated
485 * "null tag" will NOT be considered part of the "value length".
487 static int Decode(byte[] buf, int off, int maxLen,
488 out int tc, out int tv, out bool cons,
489 out int valOff, out int valLen)
491 int lim = off + maxLen;
499 cons = (tv & 0x20) != 0;
508 throw new AsnException(
509 "tag value overflow");
511 tv = (tv << 7) | (c & 0x7F);
512 if ((c & 0x80) == 0) {
522 int vlen = buf[off ++];
525 * Indefinite length. This is not strict DER, but
526 * we allow it nonetheless; we must check that
527 * the value was tagged as constructed, though.
531 throw new AsnException("indefinite length"
532 + " but not constructed");
534 } else if (vlen > 0x80) {
535 int lenlen = vlen - 0x80;
536 CheckOff(off + lenlen - 1, lim);
538 while (lenlen -- > 0) {
539 if (vlen > 0x7FFFFF) {
540 throw new AsnException(
543 vlen = (vlen << 8) + buf[off ++];
548 * Length was decoded, so the value starts here.
553 * If length is indefinite then we must explore sub-objects
554 * to get the value length.
558 int tc2, tv2, valOff2, valLen2;
562 slen = Decode(buf, off, lim - off,
563 out tc2, out tv2, out cons2,
564 out valOff2, out valLen2);
565 if (tc2 == 0 && tv2 == 0) {
566 if (cons2 || valLen2 != 0) {
567 throw new AsnException(
570 valLen = off - valOff;
578 if (vlen > (lim - off)) {
579 throw new AsnException("value overflow");
582 valLen = off - valOff;
588 static void CheckOff(int off, int lim)
591 throw new AsnException("offset overflow");
596 * Get a specific byte from the value. This provided offset is
597 * relative to the value start (first value byte has offset 0).
599 public int ValueByte(int off)
602 throw new AsnException("invalid value offset: " + off);
604 if (objBuf == null) {
606 foreach (AsnElt a in Sub) {
607 int slen = a.EncodedLength;
608 if ((k + slen) > off) {
609 return a.ValueByte(off - k);
614 return objBuf[valOff + off];
617 throw new AsnException(String.Format(
618 "invalid value offset {0} (length = {1})",
623 * Encode this object into a newly allocated array.
625 public byte[] Encode()
627 byte[] r = new byte[EncodedLength];
633 * Encode this object into the provided array. Encoded object
634 * length is returned.
636 public int Encode(byte[] dst, int off)
638 return Encode(0, Int32.MaxValue, dst, off);
642 * Encode this object into the provided array. Only bytes
643 * at offset between 'start' (inclusive) and 'end' (exclusive)
644 * are actually written. The number of written bytes is returned.
645 * Offsets are relative to the object start (first tag byte).
647 int Encode(int start, int end, byte[] dst, int dstOff)
650 * If the encoded value is already known, then we just
653 if (hasEncodedHeader) {
654 int from = objOff + Math.Max(0, start);
655 int to = objOff + Math.Min(objLen, end);
658 Array.Copy(objBuf, from, dst, dstOff, len);
670 int fb = (TagClass << 6) + (Constructed ? 0x20 : 0x00);
671 if (TagValue < 0x1F) {
672 fb |= (TagValue & 0x1F);
673 if (start <= off && off < end) {
674 dst[dstOff ++] = (byte)fb;
679 if (start <= off && off < end) {
680 dst[dstOff ++] = (byte)fb;
684 for (int v = TagValue; v > 0; v >>= 7, k += 7);
687 int v = (TagValue >> k) & 0x7F;
691 if (start <= off && off < end) {
692 dst[dstOff ++] = (byte)v;
701 int vlen = ValueLength;
703 if (start <= off && off < end) {
704 dst[dstOff ++] = (byte)vlen;
709 for (int v = vlen; v > 0; v >>= 8, k += 8);
710 if (start <= off && off < end) {
711 dst[dstOff ++] = (byte)(0x80 + (k >> 3));
716 if (start <= off && off < end) {
717 dst[dstOff ++] = (byte)(vlen >> k);
724 * Encode value. We must adjust the start/end window to
725 * make it relative to the value.
727 EncodeValue(start - off, end - off, dst, dstOff);
731 * Compute copied length.
733 return Math.Max(0, Math.Min(off, end) - Math.Max(0, start));
737 * Encode the value into the provided buffer. Only value bytes
738 * at offsets between 'start' (inclusive) and 'end' (exclusive)
739 * are written. Actual number of written bytes is returned.
740 * Offsets are relative to the start of the value.
742 int EncodeValue(int start, int end, byte[] dst, int dstOff)
745 if (objBuf == null) {
747 foreach (AsnElt a in Sub) {
748 int slen = a.EncodedLength;
749 dstOff += a.Encode(start - k, end - k,
754 int from = Math.Max(0, start);
755 int to = Math.Min(valLen, end);
758 Array.Copy(objBuf, valOff + from,
763 return dstOff - orig;
767 * Copy a value chunk. The provided offset ('off') and length ('len')
768 * define the chunk to copy; the offset is relative to the value
769 * start (first value byte has offset 0). If the requested window
770 * exceeds the value boundaries, an exception is thrown.
772 public void CopyValueChunk(int off, int len, byte[] dst, int dstOff)
774 int vlen = ValueLength;
775 if (off < 0 || len < 0 || len > (vlen - off)) {
776 throw new AsnException(String.Format(
777 "invalid value window {0}:{1}"
778 + " (value length = {2})", off, len, vlen));
780 EncodeValue(off, off + len, dst, dstOff);
784 * Copy the value into the specified array. The value length is
787 public int CopyValue(byte[] dst, int off)
789 return EncodeValue(0, Int32.MaxValue, dst, off);
793 * Get a copy of the value as a freshly allocated array.
795 public byte[] CopyValue()
797 byte[] r = new byte[ValueLength];
798 EncodeValue(0, r.Length, r, 0);
803 * Get the value. This may return a shared buffer, that MUST NOT
806 byte[] GetValue(out int off, out int len)
808 if (objBuf == null) {
810 * We can modify objBuf because CopyValue()
811 * called ValueLength, thus valLen has been
814 objBuf = CopyValue();
825 * Interpret the value as a BOOLEAN.
827 public bool GetBoolean()
830 throw new AsnException(
831 "invalid BOOLEAN (constructed)");
833 int vlen = ValueLength;
835 throw new AsnException(String.Format(
836 "invalid BOOLEAN (length = {0})", vlen));
838 return ValueByte(0) != 0;
842 * Interpret the value as an INTEGER. An exception is thrown if
843 * the value does not fit in a 'long'.
845 public long GetInteger()
848 throw new AsnException(
849 "invalid INTEGER (constructed)");
851 int vlen = ValueLength;
853 throw new AsnException("invalid INTEGER (length = 0)");
855 int v = ValueByte(0);
857 if ((v & 0x80) != 0) {
859 for (int k = 0; k < vlen; k ++) {
860 if (x < ((-1L) << 55)) {
861 throw new AsnException(
862 "integer overflow (negative)");
864 x = (x << 8) + (long)ValueByte(k);
868 for (int k = 0; k < vlen; k ++) {
869 if (x >= (1L << 55)) {
870 throw new AsnException(
871 "integer overflow (positive)");
873 x = (x << 8) + (long)ValueByte(k);
880 * Interpret the value as an INTEGER. An exception is thrown if
881 * the value is outside of the provided range.
883 public long GetInteger(long min, long max)
885 long v = GetInteger();
886 if (v < min || v > max) {
887 throw new AsnException("integer out of allowed range");
893 * Interpret the value as an INTEGER. Return its hexadecimal
894 * representation (uppercase), preceded by a '0x' or '-0x'
895 * header, depending on the integer sign. The number of
896 * hexadecimal digits is even. Leading zeroes are returned (but
897 * one may remain, to ensure an even number of digits). If the
898 * integer has value 0, then 0x00 is returned.
900 public string GetIntegerHex()
903 throw new AsnException(
904 "invalid INTEGER (constructed)");
906 int vlen = ValueLength;
908 throw new AsnException("invalid INTEGER (length = 0)");
910 StringBuilder sb = new StringBuilder();
911 byte[] tmp = CopyValue();
912 if (tmp[0] >= 0x80) {
915 for (int i = tmp.Length - 1; i >= 0; i --) {
916 int v = ((~tmp[i]) & 0xFF) + cc;
922 while (k < tmp.Length && tmp[k] == 0) {
925 if (k == tmp.Length) {
929 while (k < tmp.Length) {
930 sb.AppendFormat("{0:X2}", tmp[k ++]);
932 return sb.ToString();
936 * Interpret the value as an OCTET STRING. The value bytes are
937 * returned. This method supports constructed values and performs
940 public byte[] GetOctetString()
942 int len = GetOctetString(null, 0);
943 byte[] r = new byte[len];
944 GetOctetString(r, 0);
949 * Interpret the value as an OCTET STRING. The value bytes are
950 * written in dst[], starting at offset 'off', and the total value
951 * length is returned. If 'dst' is null, then no byte is written
952 * anywhere, but the total length is still returned. This method
953 * supports constructed values and performs the reassembly.
955 public int GetOctetString(byte[] dst, int off)
959 foreach (AsnElt ae in Sub) {
960 ae.CheckTag(AsnElt.OCTET_STRING);
961 off += ae.GetOctetString(dst, off);
966 return CopyValue(dst, off);
973 * Interpret the value as a BIT STRING. The bits are returned,
974 * with the "ignored bits" cleared.
976 public byte[] GetBitString()
979 return GetBitString(out bitLength);
983 * Interpret the value as a BIT STRING. The bits are returned,
984 * with the "ignored bits" cleared. The actual bit length is
985 * written in 'bitLength'.
987 public byte[] GetBitString(out int bitLength)
991 * TODO: support constructed BIT STRING values.
993 throw new AsnException(
994 "invalid BIT STRING (constructed)");
996 int vlen = ValueLength;
998 throw new AsnException(
999 "invalid BIT STRING (length = 0)");
1001 int fb = ValueByte(0);
1002 if (fb > 7 || (vlen == 1 && fb != 0)) {
1003 throw new AsnException(String.Format(
1004 "invalid BIT STRING (start = 0x{0:X2})", fb));
1006 byte[] r = new byte[vlen - 1];
1007 CopyValueChunk(1, vlen - 1, r, 0);
1009 r[r.Length - 1] &= (byte)(0xFF << fb);
1011 bitLength = (r.Length << 3) - fb;
1016 * Interpret the value as a NULL.
1018 public void CheckNull()
1021 throw new AsnException(
1022 "invalid NULL (constructed)");
1024 if (ValueLength != 0) {
1025 throw new AsnException(String.Format(
1026 "invalid NULL (length = {0})", ValueLength));
1031 * Interpret the value as an OBJECT IDENTIFIER, and return it
1032 * (in decimal-dotted string format).
1034 public string GetOID()
1038 throw new AsnException("zero-length OID");
1040 int v = objBuf[valOff];
1042 throw new AsnException(
1043 "invalid OID: first byte = " + v);
1045 StringBuilder sb = new StringBuilder();
1051 for (int i = 1; i < valLen; i ++) {
1052 v = objBuf[valOff + i];
1053 if ((acc >> 56) != 0) {
1054 throw new AsnException(
1055 "invalid OID: integer overflow");
1057 acc = (acc << 7) + (long)(v & 0x7F);
1058 if ((v & 0x80) == 0) {
1068 throw new AsnException(
1069 "invalid OID: truncated");
1071 return sb.ToString();
1075 * Get the object value as a string. The string type is inferred
1078 public string GetString()
1080 if (TagClass != UNIVERSAL) {
1081 throw new AsnException(String.Format(
1082 "cannot infer string type: {0}:{1}",
1083 TagClass, TagValue));
1085 return GetString(TagValue);
1089 * Get the object value as a string. The string type is provided
1090 * (universal tag value). Supported string types include
1091 * NumericString, PrintableString, IA5String, TeletexString
1092 * (interpreted as ISO-8859-1), UTF8String, BMPString and
1093 * UniversalString; the "time types" (UTCTime and GeneralizedTime)
1094 * are also supported, though, in their case, the internal
1095 * contents are not checked (they are decoded as PrintableString).
1097 public string GetString(int type)
1100 throw new AsnException(
1101 "invalid string (constructed)");
1105 case PrintableString:
1109 case GeneralizedTime:
1110 return DecodeMono(objBuf, valOff, valLen, type);
1112 return DecodeUTF8(objBuf, valOff, valLen);
1114 return DecodeUTF16(objBuf, valOff, valLen);
1115 case UniversalString:
1116 return DecodeUTF32(objBuf, valOff, valLen);
1118 throw new AsnException(
1119 "unsupported string type: " + type);
1123 static string DecodeMono(byte[] buf, int off, int len, int type)
1125 char[] tc = new char[len];
1126 for (int i = 0; i < len; i ++) {
1127 tc[i] = (char)buf[off + i];
1129 VerifyChars(tc, type);
1130 return new string(tc);
1133 static string DecodeUTF8(byte[] buf, int off, int len)
1138 if (len >= 3 && buf[off] == 0xEF
1139 && buf[off + 1] == 0xBB && buf[off + 2] == 0xBF)
1145 for (int k = 0; k < 2; k ++) {
1147 for (int i = 0; i < len; i ++) {
1148 int c = buf[off + i];
1152 } else if (c < 0xC0) {
1153 throw BadByte(c, UTF8String);
1154 } else if (c < 0xE0) {
1157 } else if (c < 0xF0) {
1160 } else if (c < 0xF8) {
1164 throw BadByte(c, UTF8String);
1168 throw new AsnException(
1169 "invalid UTF-8 string");
1171 int d = buf[off + i];
1172 if (d < 0x80 || d > 0xBF) {
1173 throw BadByte(d, UTF8String);
1175 c = (c << 6) + (d & 0x3F);
1178 throw BadChar(c, UTF8String);
1182 int hi = 0xD800 + (c >> 10);
1183 int lo = 0xDC00 + (c & 0x3FF);
1185 tc[tcOff] = (char)hi;
1186 tc[tcOff + 1] = (char)lo;
1191 tc[tcOff] = (char)c;
1197 tc = new char[tcOff];
1200 VerifyChars(tc, UTF8String);
1201 return new string(tc);
1204 static string DecodeUTF16(byte[] buf, int off, int len)
1206 if ((len & 1) != 0) {
1207 throw new AsnException(
1208 "invalid UTF-16 string: length = " + len);
1216 int lo = buf[off + 1];
1217 if (hi == 0xFE && lo == 0xFF) {
1220 } else if (hi == 0xFF && lo == 0xFE) {
1225 char[] tc = new char[len];
1226 for (int i = 0; i < len; i ++) {
1227 int b0 = buf[off ++];
1228 int b1 = buf[off ++];
1230 tc[i] = (char)((b0 << 8) + b1);
1232 tc[i] = (char)((b1 << 8) + b0);
1235 VerifyChars(tc, BMPString);
1236 return new string(tc);
1239 static string DecodeUTF32(byte[] buf, int off, int len)
1241 if ((len & 3) != 0) {
1242 throw new AsnException(
1243 "invalid UTF-32 string: length = " + len);
1250 if (buf[off] == 0x00
1251 && buf[off + 1] == 0x00
1252 && buf[off + 2] == 0xFE
1253 && buf[off + 3] == 0xFF)
1257 } else if (buf[off] == 0xFF
1258 && buf[off + 1] == 0xFE
1259 && buf[off + 2] == 0x00
1260 && buf[off + 3] == 0x00)
1268 for (int k = 0; k < 2; k ++) {
1270 for (int i = 0; i < len; i ++) {
1271 uint b0 = buf[off + 0];
1272 uint b1 = buf[off + 1];
1273 uint b2 = buf[off + 2];
1274 uint b3 = buf[off + 3];
1277 c = (b0 << 24) | (b1 << 16)
1280 c = (b3 << 24) | (b2 << 16)
1284 throw BadChar((int)c, UniversalString);
1288 int hi = 0xD800 + (int)(c >> 10);
1289 int lo = 0xDC00 + (int)(c & 0x3FF);
1291 tc[tcOff] = (char)hi;
1292 tc[tcOff + 1] = (char)lo;
1297 tc[tcOff] = (char)c;
1303 tc = new char[tcOff];
1306 VerifyChars(tc, UniversalString);
1307 return new string(tc);
1310 static void VerifyChars(char[] tc, int type)
1314 foreach (char c in tc) {
1316 throw BadChar(c, type);
1320 case PrintableString:
1322 case GeneralizedTime:
1323 foreach (char c in tc) {
1324 if (!IsPrintable(c)) {
1325 throw BadChar(c, type);
1330 foreach (char c in tc) {
1332 throw BadChar(c, type);
1337 foreach (char c in tc) {
1339 throw BadChar(c, type);
1346 * For Unicode string types (UTF-8, BMP...).
1348 for (int i = 0; i < tc.Length; i ++) {
1350 if (c >= 0xFDD0 && c <= 0xFDEF) {
1351 throw BadChar(c, type);
1353 if (c == 0xFFFE || c == 0xFFFF) {
1354 throw BadChar(c, type);
1356 if (c < 0xD800 || c > 0xDFFF) {
1360 throw BadChar(c, type);
1363 if (++ i >= tc.Length) {
1364 throw BadChar(c, type);
1367 if (c < 0xDC00 || c > 0xDFFF) {
1368 throw BadChar(c, type);
1371 c = 0x10000 + lo + (hi << 10);
1372 if ((c & 0xFFFE) == 0xFFFE) {
1373 throw BadChar(c, type);
1378 static bool IsNum(int c)
1380 return c == ' ' || (c >= '0' && c <= '9');
1383 internal static bool IsPrintable(int c)
1385 if (c >= 'A' && c <= 'Z') {
1388 if (c >= 'a' && c <= 'z') {
1391 if (c >= '0' && c <= '9') {
1395 case ' ': case '(': case ')': case '+':
1396 case ',': case '-': case '.': case '/':
1397 case ':': case '=': case '?': case '\'':
1404 static bool IsIA5(int c)
1409 static bool IsLatin1(int c)
1414 static AsnException BadByte(int c, int type)
1416 return new AsnException(String.Format(
1417 "unexpected byte 0x{0:X2} in string of type {1}",
1421 static AsnException BadChar(int c, int type)
1423 return new AsnException(String.Format(
1424 "unexpected character U+{0:X4} in string of type {1}",
1429 * Decode the value as a date/time. Returned object is in UTC.
1430 * Type of date is inferred from the tag value.
1432 public DateTime GetTime()
1434 if (TagClass != UNIVERSAL) {
1435 throw new AsnException(String.Format(
1436 "cannot infer date type: {0}:{1}",
1437 TagClass, TagValue));
1439 return GetTime(TagValue);
1443 * Decode the value as a date/time. Returned object is in UTC.
1444 * The time string type is provided as parameter (UTCTime or
1447 public DateTime GetTime(int type)
1453 case GeneralizedTime:
1457 throw new AsnException(
1458 "unsupported date type: " + type);
1460 string s = GetString(type);
1464 * UTCTime has format:
1465 * YYMMDDhhmm[ss](Z|(+|-)hhmm)
1467 * GeneralizedTime has format:
1468 * YYYYMMDDhhmmss[.uu*][Z|(+|-)hhmm]
1470 * Differences between the two types:
1471 * -- UTCTime encodes year over two digits; GeneralizedTime
1472 * uses four digits. UTCTime years map to 1950..2049 (00 is
1474 * -- Seconds are optional with UTCTime, mandatory with
1476 * -- GeneralizedTime can have fractional seconds (optional).
1477 * -- Time zone is optional for GeneralizedTime. However,
1478 * a missing time zone means "local time" which depends on
1479 * the locality, so this is discouraged.
1482 * -- If there is a fractional second, then it must include
1483 * at least one digit. This implementation processes the
1484 * first three digits, and ignores the rest (if present).
1485 * -- Time zone offset ranges from -23:59 to +23:59.
1486 * -- The calendar computations are delegated to .NET's
1487 * DateTime (and DateTimeOffset) so this implements a
1488 * Gregorian calendar, even for dates before 1589. Year 0
1495 foreach (char c in s) {
1496 if (c >= '0' && c <= '9') {
1499 if (c == '.' || c == '+' || c == '-' || c == 'Z') {
1502 throw BadTime(type, orig);
1508 * Parse the time zone.
1514 if (s.EndsWith("Z")) {
1515 s = s.Substring(0, s.Length - 1);
1517 int j = s.IndexOf('+');
1525 string t = s.Substring(j + 1);
1526 s = s.Substring(0, j);
1527 if (t.Length != 4) {
1528 throw BadTime(type, orig);
1530 tzHours = Dec2(t, 0, ref good);
1531 tzMinutes = Dec2(t, 2, ref good);
1532 if (tzHours < 0 || tzHours > 23
1533 || tzMinutes < 0 || tzMinutes > 59)
1535 throw BadTime(type, orig);
1541 * Lack of time zone is allowed only for GeneralizedTime.
1543 if (noTZ && !isGen) {
1544 throw BadTime(type, orig);
1548 * Parse the date elements.
1551 throw BadTime(type, orig);
1553 int year = Dec2(s, 0, ref good);
1555 year = year * 100 + Dec2(s, 2, ref good);
1564 int month = Dec2(s, 0, ref good);
1565 int day = Dec2(s, 2, ref good);
1566 int hour = Dec2(s, 4, ref good);
1567 int minute = Dec2(s, 6, ref good);
1569 int millisecond = 0;
1571 second = Dec2(s, 8, ref good);
1572 if (s.Length >= 12 && s[10] == '.') {
1573 s = s.Substring(11);
1574 foreach (char c in s) {
1575 if (c < '0' || c > '9') {
1581 millisecond = 10 * Dec2(s, 0, ref good)
1582 + Dec2(s, 2, ref good) / 10;
1583 } else if (s.Length != 10) {
1591 second = Dec2(s, 8, ref good);
1594 throw BadTime(type, orig);
1599 * Parsing is finished; if any error occurred, then
1600 * the 'good' flag has been cleared.
1603 throw BadTime(type, orig);
1607 * Leap seconds are not supported by .NET, so we claim
1608 * they do not occur.
1615 * .NET implementation performs all the checks (including
1616 * checks on month length depending on year, as per the
1617 * proleptic Gregorian calendar).
1621 DateTime dt = new DateTime(year, month, day,
1622 hour, minute, second, millisecond,
1623 DateTimeKind.Local);
1624 return dt.ToUniversalTime();
1626 TimeSpan tzOff = new TimeSpan(tzHours, tzMinutes, 0);
1628 tzOff = tzOff.Negate();
1630 DateTimeOffset dto = new DateTimeOffset(
1631 year, month, day, hour, minute, second,
1632 millisecond, tzOff);
1633 return dto.UtcDateTime;
1634 } catch (Exception e) {
1635 throw BadTime(type, orig, e);
1639 static int Dec2(string s, int off, ref bool good)
1641 if (off < 0 || off >= (s.Length - 1)) {
1646 char c2 = s[off + 1];
1647 if (c1 < '0' || c1 > '9' || c2 < '0' || c2 > '9') {
1651 return 10 * (c1 - '0') + (c2 - '0');
1654 static AsnException BadTime(int type, string s)
1656 return BadTime(type, s, null);
1659 static AsnException BadTime(int type, string s, Exception e)
1661 string tt = (type == UTCTime) ? "UTCTime" : "GeneralizedTime";
1662 string msg = String.Format("invalid {0} string: '{1}'", tt, s);
1664 return new AsnException(msg);
1666 return new AsnException(msg, e);
1670 /* =============================================================== */
1673 * Create a new element for a primitive value. The provided buffer
1674 * is internally copied.
1676 public static AsnElt MakePrimitive(int tagValue, byte[] val)
1678 return MakePrimitive(UNIVERSAL, tagValue, val, 0, val.Length);
1682 * Create a new element for a primitive value. The provided buffer
1683 * is internally copied.
1685 public static AsnElt MakePrimitive(int tagValue,
1686 byte[] val, int off, int len)
1688 return MakePrimitive(UNIVERSAL, tagValue, val, off, len);
1692 * Create a new element for a primitive value. The provided buffer
1693 * is internally copied.
1695 public static AsnElt MakePrimitive(
1696 int tagClass, int tagValue, byte[] val)
1698 return MakePrimitive(tagClass, tagValue, val, 0, val.Length);
1702 * Create a new element for a primitive value. The provided buffer
1703 * is internally copied.
1705 public static AsnElt MakePrimitive(int tagClass, int tagValue,
1706 byte[] val, int off, int len)
1708 byte[] nval = new byte[len];
1709 Array.Copy(val, off, nval, 0, len);
1710 return MakePrimitiveInner(tagClass, tagValue, nval, 0, len);
1714 * Like MakePrimitive(), but the provided array is NOT copied.
1715 * This is for other factory methods that already allocate a
1718 static AsnElt MakePrimitiveInner(int tagValue, byte[] val)
1720 return MakePrimitiveInner(UNIVERSAL, tagValue,
1721 val, 0, val.Length);
1724 static AsnElt MakePrimitiveInner(int tagValue,
1725 byte[] val, int off, int len)
1727 return MakePrimitiveInner(UNIVERSAL, tagValue, val, off, len);
1730 static AsnElt MakePrimitiveInner(int tagClass, int tagValue, byte[] val)
1732 return MakePrimitiveInner(tagClass, tagValue,
1733 val, 0, val.Length);
1736 static AsnElt MakePrimitiveInner(int tagClass, int tagValue,
1737 byte[] val, int off, int len)
1739 AsnElt a = new AsnElt();
1740 a.objBuf = new byte[len];
1741 Array.Copy(val, off, a.objBuf, 0, len);
1746 a.hasEncodedHeader = false;
1747 if (tagClass < 0 || tagClass > 3) {
1748 throw new AsnException(
1749 "invalid tag class: " + tagClass);
1752 throw new AsnException(
1753 "invalid tag value: " + tagValue);
1755 a.TagClass = tagClass;
1756 a.TagValue = tagValue;
1762 * Create a new INTEGER value for the provided integer.
1764 public static AsnElt MakeInteger(long x)
1767 return MakeInteger((ulong)x);
1770 for (long w = x; w <= -(long)0x80; w >>= 8) {
1773 byte[] v = new byte[k];
1774 for (long w = x; k > 0; w >>= 8) {
1777 return MakePrimitiveInner(INTEGER, v);
1781 * Create a new INTEGER value for the provided integer.
1783 public static AsnElt MakeInteger(ulong x)
1786 for (ulong w = x; w >= 0x80; w >>= 8) {
1789 byte[] v = new byte[k];
1790 for (ulong w = x; k > 0; w >>= 8) {
1793 return MakePrimitiveInner(INTEGER, v);
1797 * Create a new INTEGER value for the provided integer. The x[]
1798 * array uses _unsigned_ big-endian encoding.
1800 public static AsnElt MakeInteger(byte[] x)
1802 int xLen = x.Length;
1804 while (j < xLen && x[j] == 0x00) {
1808 return MakePrimitiveInner(INTEGER, new byte[] { 0x00 });
1812 v = new byte[xLen - j];
1813 Array.Copy(x, j, v, 0, v.Length);
1815 v = new byte[1 + xLen - j];
1816 Array.Copy(x, j, v, 1, v.Length - 1);
1818 return MakePrimitiveInner(INTEGER, v);
1822 * Create a new INTEGER value for the provided integer. The x[]
1823 * array uses _signed_ big-endian encoding.
1825 public static AsnElt MakeIntegerSigned(byte[] x)
1827 int xLen = x.Length;
1829 throw new AsnException(
1830 "Invalid signed integer (empty)");
1834 while (j < (xLen - 1)
1836 && x[j + 1] >= 0x80)
1841 while (j < (xLen - 1)
1848 byte[] v = new byte[xLen - j];
1849 Array.Copy(x, j, v, 0, v.Length);
1850 return MakePrimitiveInner(INTEGER, v);
1854 * Create a BIT STRING from the provided value. The number of
1855 * "unused bits" is set to 0.
1857 public static AsnElt MakeBitString(byte[] buf)
1859 return MakeBitString(buf, 0, buf.Length);
1862 public static AsnElt MakeBitString(byte[] buf, int off, int len)
1864 byte[] tmp = new byte[len + 1];
1865 Array.Copy(buf, off, tmp, 1, len);
1866 return MakePrimitiveInner(BIT_STRING, tmp);
1870 * Create a BIT STRING from the provided value. The number of
1871 * "unused bits" is specified.
1873 public static AsnElt MakeBitString(int unusedBits, byte[] buf)
1875 return MakeBitString(unusedBits, buf, 0, buf.Length);
1878 public static AsnElt MakeBitString(int unusedBits,
1879 byte[] buf, int off, int len)
1881 if (unusedBits < 0 || unusedBits > 7
1882 || (unusedBits != 0 && len == 0))
1884 throw new AsnException(
1885 "Invalid number of unused bits in BIT STRING: "
1888 byte[] tmp = new byte[len + 1];
1889 tmp[0] = (byte)unusedBits;
1890 Array.Copy(buf, off, tmp, 1, len);
1892 tmp[len - 1] &= (byte)(0xFF << unusedBits);
1894 return MakePrimitiveInner(BIT_STRING, tmp);
1898 * Create an OCTET STRING from the provided value.
1900 public static AsnElt MakeBlob(byte[] buf)
1902 return MakeBlob(buf, 0, buf.Length);
1905 public static AsnElt MakeBlob(byte[] buf, int off, int len)
1907 return MakePrimitive(OCTET_STRING, buf, off, len);
1911 * Create a new constructed elements, by providing the relevant
1914 public static AsnElt Make(int tagValue, params AsnElt[] subs)
1916 return Make(UNIVERSAL, tagValue, subs);
1920 * Create a new constructed elements, by providing the relevant
1923 public static AsnElt Make(int tagClass, int tagValue,
1924 params AsnElt[] subs)
1926 AsnElt a = new AsnElt();
1932 a.hasEncodedHeader = false;
1933 if (tagClass < 0 || tagClass > 3) {
1934 throw new AsnException(
1935 "invalid tag class: " + tagClass);
1938 throw new AsnException(
1939 "invalid tag value: " + tagValue);
1941 a.TagClass = tagClass;
1942 a.TagValue = tagValue;
1944 a.Sub = new AsnElt[0];
1946 a.Sub = new AsnElt[subs.Length];
1947 Array.Copy(subs, 0, a.Sub, 0, subs.Length);
1953 * Create a SET OF: sub-elements are automatically sorted by
1954 * lexicographic order of their DER encodings. Identical elements
1957 public static AsnElt MakeSetOf(params AsnElt[] subs)
1959 AsnElt a = new AsnElt();
1965 a.hasEncodedHeader = false;
1966 a.TagClass = UNIVERSAL;
1969 a.Sub = new AsnElt[0];
1971 SortedDictionary<byte[], AsnElt> d =
1972 new SortedDictionary<byte[], AsnElt>(
1973 COMPARER_LEXICOGRAPHIC);
1974 foreach (AsnElt ax in subs) {
1975 d[ax.Encode()] = ax;
1977 AsnElt[] tmp = new AsnElt[d.Count];
1979 foreach (AsnElt ax in d.Values) {
1987 static IComparer<byte[]> COMPARER_LEXICOGRAPHIC =
1988 new ComparerLexicographic();
1990 class ComparerLexicographic : IComparer<byte[]> {
1992 public int Compare(byte[] x, byte[] y)
1994 int xLen = x.Length;
1995 int yLen = y.Length;
1996 int cLen = Math.Min(xLen, yLen);
1997 for (int i = 0; i < cLen; i ++) {
1999 return (int)x[i] - (int)y[i];
2007 * Wrap an element into an explicit tag.
2009 public static AsnElt MakeExplicit(int tagClass, int tagValue, AsnElt x)
2011 return Make(tagClass, tagValue, x);
2015 * Wrap an element into an explicit CONTEXT tag.
2017 public static AsnElt MakeExplicit(int tagValue, AsnElt x)
2019 return Make(CONTEXT, tagValue, x);
2023 * Apply an implicit tag to a value. The source AsnElt object
2024 * is unmodified; a new object is returned.
2026 public static AsnElt MakeImplicit(int tagClass, int tagValue, AsnElt x)
2028 if (x.Constructed) {
2029 return Make(tagClass, tagValue, x.Sub);
2031 AsnElt a = new AsnElt();
2032 a.objBuf = x.GetValue(out a.valOff, out a.valLen);
2035 a.hasEncodedHeader = false;
2036 a.TagClass = tagClass;
2037 a.TagValue = tagValue;
2042 public static AsnElt NULL_V = AsnElt.MakePrimitive(
2045 public static AsnElt BOOL_TRUE = AsnElt.MakePrimitive(
2046 BOOLEAN, new byte[] { 0xFF });
2047 public static AsnElt BOOL_FALSE = AsnElt.MakePrimitive(
2048 BOOLEAN, new byte[] { 0x00 });
2051 * Create an OBJECT IDENTIFIER from its string representation.
2052 * This function tolerates extra leading zeros.
2054 public static AsnElt MakeOID(string str)
2056 List<long> r = new List<long>();
2059 for (int i = 0; i < n; i ++) {
2063 throw new AsnException(
2064 "invalid OID (empty element)");
2070 if (c < '0' || c > '9') {
2071 throw new AsnException(String.Format(
2072 "invalid character U+{0:X4} in OID",
2077 } else if (x > ((Int64.MaxValue - 9) / 10)) {
2078 throw new AsnException("OID element overflow");
2080 x = x * (long)10 + (long)(c - '0');
2083 throw new AsnException(
2084 "invalid OID (empty element)");
2088 throw new AsnException(
2089 "invalid OID (not enough elements)");
2091 if (r[0] > 2 || r[1] > 40) {
2092 throw new AsnException(
2093 "invalid OID (first elements out of range)");
2096 MemoryStream ms = new MemoryStream();
2097 ms.WriteByte((byte)(40 * (int)r[0] + (int)r[1]));
2098 for (int i = 2; i < r.Count; i ++) {
2101 ms.WriteByte((byte)v);
2105 for (long w = v; w != 0; w >>= 7, k += 7);
2106 ms.WriteByte((byte)(0x80 + (int)(v >> k)));
2107 for (k -= 7; k >= 0; k -= 7) {
2108 int z = (int)(v >> k) & 0x7F;
2112 ms.WriteByte((byte)z);
2115 byte[] buf = ms.ToArray();
2116 return MakePrimitiveInner(OBJECT_IDENTIFIER,
2117 buf, 0, buf.Length);
2121 * Create a string of the provided type and contents. The string
2122 * type is a universal tag value for one of the string or time
2125 public static AsnElt MakeString(int type, string str)
2127 VerifyChars(str.ToCharArray(), type);
2131 case PrintableString:
2133 case GeneralizedTime:
2136 buf = EncodeMono(str);
2139 buf = EncodeUTF8(str);
2142 buf = EncodeUTF16(str);
2144 case UniversalString:
2145 buf = EncodeUTF32(str);
2148 throw new AsnException(
2149 "unsupported string type: " + type);
2151 return MakePrimitiveInner(type, buf);
2154 static byte[] EncodeMono(string str)
2156 byte[] r = new byte[str.Length];
2158 foreach (char c in str) {
2165 * Get the code point at offset 'off' in the string. Either one
2166 * or two 'char' slots are used; 'off' is updated accordingly.
2168 static int CodePoint(string str, ref int off)
2170 int c = str[off ++];
2171 if (c >= 0xD800 && c < 0xDC00 && off < str.Length) {
2173 if (d >= 0xDC00 && d < 0xE000) {
2174 c = ((c & 0x3FF) << 10)
2175 + (d & 0x3FF) + 0x10000;
2182 static byte[] EncodeUTF8(string str)
2186 MemoryStream ms = new MemoryStream();
2188 int cp = CodePoint(str, ref k);
2190 ms.WriteByte((byte)cp);
2191 } else if (cp < 0x800) {
2192 ms.WriteByte((byte)(0xC0 + (cp >> 6)));
2193 ms.WriteByte((byte)(0x80 + (cp & 63)));
2194 } else if (cp < 0x10000) {
2195 ms.WriteByte((byte)(0xE0 + (cp >> 12)));
2196 ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
2197 ms.WriteByte((byte)(0x80 + (cp & 63)));
2199 ms.WriteByte((byte)(0xF0 + (cp >> 18)));
2200 ms.WriteByte((byte)(0x80 + ((cp >> 12) & 63)));
2201 ms.WriteByte((byte)(0x80 + ((cp >> 6) & 63)));
2202 ms.WriteByte((byte)(0x80 + (cp & 63)));
2205 return ms.ToArray();
2208 static byte[] EncodeUTF16(string str)
2210 byte[] buf = new byte[str.Length << 1];
2212 foreach (char c in str) {
2213 buf[k ++] = (byte)(c >> 8);
2214 buf[k ++] = (byte)c;
2219 static byte[] EncodeUTF32(string str)
2223 MemoryStream ms = new MemoryStream();
2225 int cp = CodePoint(str, ref k);
2226 ms.WriteByte((byte)(cp >> 24));
2227 ms.WriteByte((byte)(cp >> 16));
2228 ms.WriteByte((byte)(cp >> 8));
2229 ms.WriteByte((byte)cp);
2231 return ms.ToArray();
2235 * Create a time value of the specified type (UTCTime or
2238 public static AsnElt MakeTime(int type, DateTime dt)
2240 dt = dt.ToUniversalTime();
2245 if (year < 1950 || year >= 2050) {
2246 throw new AsnException(String.Format(
2247 "cannot encode year {0} as UTCTime",
2251 str = String.Format(
2252 "{0:d2}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}Z",
2253 year, dt.Month, dt.Day,
2254 dt.Hour, dt.Minute, dt.Second);
2256 case GeneralizedTime:
2257 str = String.Format(
2258 "{0:d4}{1:d2}{2:d2}{3:d2}{4:d2}{5:d2}",
2259 dt.Year, dt.Month, dt.Day,
2260 dt.Hour, dt.Minute, dt.Second);
2261 int millis = dt.Millisecond;
2263 if (millis % 100 == 0) {
2264 str = String.Format("{0}.{1:d1}",
2266 } else if (millis % 10 == 0) {
2267 str = String.Format("{0}.{1:d2}",
2270 str = String.Format("{0}.{1:d3}",
2277 throw new AsnException(
2278 "unsupported time type: " + type);
2280 return MakeString(type, str);
2284 * Create a time value of the specified type (UTCTime or
2287 public static AsnElt MakeTime(int type, DateTimeOffset dto)
2289 return MakeTime(type, dto.UtcDateTime);
2293 * Create a time value with an automatic type selection
2294 * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
2297 public static AsnElt MakeTimeAuto(DateTime dt)
2299 dt = dt.ToUniversalTime();
2300 return MakeTime((dt.Year >= 1950 && dt.Year <= 2049)
2301 ? UTCTime : GeneralizedTime, dt);
2305 * Create a time value with an automatic type selection
2306 * (UTCTime if year is in the 1950..2049 range, GeneralizedTime
2309 public static AsnElt MakeTimeAuto(DateTimeOffset dto)
2311 return MakeTimeAuto(dto.UtcDateTime);