1 /**
2     Distinguished Encoding Rules (DER) is a standard for encoding ASN.1 data.
3     DER is often used for cryptgraphically-signed data, such as X.509
4     certificates, because DER's defining feature is that there is only one way
5     to encode each data type, which means that two encodings of the same data
6     could not have different cryptographic signatures. For this reason, DER
7     is generally regarded as the most secure encoding standard for ASN.1.
8     Like Basic Encoding Rules (BER), Canonical Encoding Rules (CER), and
9     Packed Encoding Rules (PER), Distinguished Encoding Rules (DER) is a
10     specification created by the
11     $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union),
12     and specified in
13     $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules)
14 
15     Authors:
16     $(UL
17         $(LI $(PERSON Jonathan M. Wilbur, jonathan@wilbur.space, http://jonathan.wilbur.space))
18     )
19     Copyright: Copyright (C) Jonathan M. Wilbur
20     License: $(LINK https://mit-license.org/, MIT License)
21     Standards:
22     $(UL
23         $(LI $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1)))
24         $(LI $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules))
25     )
26     See_Also:
27     $(UL
28         $(LI $(LINK https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One, The Wikipedia Page on ASN.1))
29         $(LI $(LINK https://en.wikipedia.org/wiki/X.690, The Wikipedia Page on X.690))
30         $(LI $(LINK https://www.strozhevsky.com/free_docs/asn1_in_simple_words.pdf, ASN.1 By Simple Words))
31         $(LI $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF, ASN.1: Communication Between Heterogeneous Systems))
32     )
33 */
34 module codecs.der;
35 public import codec;
36 public import interfaces : Byteable;
37 public import types.identification;
38 
39 ///
40 public alias derOID = distinguishedEncodingRulesObjectIdentifier;
41 ///
42 public alias derObjectID = distinguishedEncodingRulesObjectIdentifier;
43 ///
44 public alias derObjectIdentifier = distinguishedEncodingRulesObjectIdentifier;
45 /**
46     The object identifier assigned to the Distinguished Encoding Rules (DER), per the
47     $(LINK https://www.itu.int/en/pages/default.aspx,
48         International Telecommunications Union)'s,
49     $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules)
50 
51     $(MONO {joint-iso-itu-t asn1(1) ber-derived(2) distinguished-encoding(1)} )
52 */
53 public immutable OID distinguishedEncodingRulesObjectIdentifier = cast(immutable(OID)) new OID(2, 1, 2, 1);
54 
55 ///
56 public alias DERElement = DistinguishedEncodingRulesElement;
57 /**
58     The unit of encoding and decoding for Distinguished Encoding Rules (DER).
59 
60     There are three parts to an element encoded according to the Distinguished
61     Encoding Rules (DER):
62 
63     $(UL
64         $(LI A Type Tag, which specifies what data type is encoded)
65         $(LI A Length Tag, which specifies how many subsequent bytes encode the data)
66         $(LI The Encoded Value)
67     )
68 
69     They appear in the binary encoding in that order, and as such, the encoding
70     scheme is sometimes described as "TLV," which stands for Type-Length-Value.
71 
72     This class provides a properties for getting and setting bit fields of
73     the type tag, but most of it is functionality for encoding data per
74     the specification.
75 
76     As an example, this is what encoding a simple INTEGER looks like:
77 
78     ---
79     DERElement dv = new DERElement();
80     dv.tagNumber = 0x02u; // "2" means this is an INTEGER
81     dv.integer = 1433; // Now the data is encoded.
82     transmit(cast(ubyte[]) dv); // transmit() is a made-up function.
83     ---
84 
85     And this is what decoding looks like:
86 
87     ---
88     ubyte[] data = receive(); // receive() is a made-up function.
89     DERElement dv2 = new DERElement(data);
90 
91     long x;
92     if (dv.tagNumber == 0x02u) // it is an INTEGER
93     {
94         x = dv.integer;
95     }
96     // Now x is 1433!
97     ---
98 */
99 /* FIXME:
100     This class should be "final," but a bug in the DMD compiler produces
101     unlinkable objects if a final class inherits an alias to an internal
102     member of a parent class.
103 
104     I have reported this to the D Language Foundation's Bugzilla site on
105     17 October, 2017, and this bug can be viewed here:
106     https://issues.dlang.org/show_bug.cgi?id=17909
107 */
108 public
109 class DistinguishedEncodingRulesElement : ASN1Element!DERElement, Byteable
110 {
111     @system
112     unittest
113     {
114         writeln("Running unit tests for codec: " ~ typeof(this).stringof);
115     }
116 
117     ///
118     public ASN1TagClass tagClass;
119     ///
120     public ASN1Construction construction;
121     ///
122     public size_t tagNumber;
123 
124     /// The length of the value in octets
125     final public @property @safe nothrow
126     size_t length() const
127     {
128         return this.value.length;
129     }
130 
131     /// The octets of the encoded value.
132     public ubyte[] value;
133 
134     /**
135         "Decodes" an $(MONO END OF CONTENT), by which I mean: returns nothing, but
136         throws exceptions if the element is not correct.
137 
138         Throws:
139         $(UL
140             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
141             $(LI $(D ASN1ValueSizeException) if there are any content octets)
142         )
143     */
144     override public @property @safe
145     void endOfContent() const
146     {
147         if (this.construction != ASN1Construction.primitive)
148             throw new ASN1ConstructionException
149             (this.construction, "decode an END OF CONTENT");
150 
151         if (this.value.length != 0u)
152             throw new ASN1ValueSizeException
153             (0u, 0u, this.value.length, "decode an END OF CONTENT");
154     }
155 
156     /**
157         Decodes a $(D bool).
158 
159         A $(D 0xFF) byte will be interpreted as $(MONO TRUE). A zero byte
160         ($(D 0x00)) will be interpreted as $(MONO FALSE). Any other value,
161         or any length other than 1 byte will throw an exception.
162 
163         Throws:
164         $(UL
165             $(LI $(D ASN1ConstructionException)
166                 if the encoded value is not primitively-constructed)
167             $(LI $(D ASN1ValueSizeException)
168                 if the encoded value is not exactly 1 byte in size)
169             $(LI $(D ASN1ValueException)
170                 if the encoded value is not either 0xFF or 0x00)
171         )
172     */
173     override public @property @safe
174     bool boolean() const
175     {
176         if (this.construction != ASN1Construction.primitive)
177             throw new ASN1ConstructionException
178             (this.construction, "decode a BOOLEAN");
179 
180         if (this.value.length != 1u)
181             throw new ASN1ValueSizeException
182             (1u, 1u, this.value.length, "decode a BOOLEAN");
183 
184         switch (this.value[0])
185         {
186             case (0xFFu): return true;
187             case (0x00u): return false;
188             default:
189                 throw new ASN1ValueException
190                 (
191                     "This exception was thrown because you attempted to decode a BOOLEAN " ~
192                     "that was encoded on a byte that was not 0xFF or 0x00 using the DER " ~
193                     "codec. Any encoding of a boolean other than 0xFF (true) or 0x00 " ~
194                     "(false) is restricted by the DER codec. " ~ notWhatYouMeantText ~
195                     forMoreInformationText ~ debugInformationText ~ reportBugsText
196                 );
197         }
198     }
199 
200     /// Encodes a $(D bool)
201     override public @property @safe nothrow
202     void boolean(in bool value)
203     out
204     {
205         assert(this.value.length > 0u);
206     }
207     body
208     {
209         scope(success) this.construction = ASN1Construction.primitive;
210         this.value = [(value ? 0xFFu : 0x00u)];
211     }
212 
213     @safe
214     unittest
215     {
216         DERElement dv = new DERElement();
217         dv.value = [ 0xFFu ];
218         assert(dv.boolean == true);
219         dv.value = [ 0x00u ];
220         assert(dv.boolean == false);
221         dv.value = [ 0x01u, 0x00u ];
222         assertThrown!ASN1ValueSizeException(dv.boolean);
223         dv.value = [];
224         assertThrown!ASN1ValueSizeException(dv.boolean);
225         dv.value = [ 0x01u ];
226         assertThrown!ASN1ValueException(dv.boolean);
227     }
228 
229     /**
230         Decodes a signed integer.
231 
232         Throws:
233         $(UL
234             $(LI $(D ASN1ConstructionException)
235                 if the encoded value is not primitively-constructed)
236             $(LI $(D ASN1ValueSizeException)
237                 if the value is too big to decode to a signed integral type,
238                 or if the value is zero bytes)
239             $(LI $(D ASN1ValuePaddingException)
240                 if there are padding bytes)
241         )
242     */
243     public @property @system
244     T integer(T)() const
245     if (isIntegral!T && isSigned!T)
246     {
247         if (this.construction != ASN1Construction.primitive)
248             throw new ASN1ConstructionException
249             (this.construction, "decode an INTEGER");
250 
251         if (this.value.length == 1u)
252             return cast(T) cast(byte) this.value[0];
253 
254         if (this.value.length == 0u || this.value.length > T.sizeof)
255             throw new ASN1ValueSizeException
256             (1u, long.sizeof, this.value.length, "decode an INTEGER");
257 
258         if
259         (
260             (this.value[0] == 0x00u && (!(this.value[1] & 0x80u))) || // Unnecessary positive leading bytes
261             (this.value[0] == 0xFFu && (this.value[1] & 0x80u)) // Unnecessary negative leading bytes
262         )
263             throw new ASN1ValuePaddingException
264             (
265                 "This exception was thrown because you attempted to decode " ~
266                 "an INTEGER that was encoded on more than the minimum " ~
267                 "necessary bytes. " ~
268                 notWhatYouMeantText ~ forMoreInformationText ~
269                 debugInformationText ~ reportBugsText
270             );
271 
272         /* NOTE:
273             Because the INTEGER is stored in two's complement form, you
274             can't just apppend 0x00u to the big end of it until it is as long
275             as T in bytes, then cast to T. Instead, you have to first determine
276             if the encoded integer is negative or positive. If it is negative,
277             then you actually want to append 0xFFu to the big end until it is
278             as big as T, so you get the two's complement form of whatever T
279             you choose.
280 
281             The line immediately below this determines whether the padding byte
282             should be 0xFF or 0x00 based on the most significant bit of the
283             most significant byte (which, since DER encodes big-endian, will
284             always be the first byte). If set (1), the number is negative, and
285             hence, the padding byte should be 0xFF. If not, it is positive,
286             and the padding byte should be 0x00.
287         */
288         immutable ubyte paddingByte = ((this.value[0] & 0x80u) ? 0xFFu : 0x00u);
289         ubyte[] value = this.value.dup; // Duplication is necessary to prevent modifying the source bytes
290         while (value.length < T.sizeof)
291             value = (paddingByte ~ value);
292         version (LittleEndian) reverse(value);
293         version (unittest) assert(value.length == T.sizeof);
294         return *cast(T *) value.ptr;
295     }
296 
297     /// Encodes a signed integral type
298     public @property @system nothrow
299     void integer(T)(in T value)
300     if (isIntegral!T && isSigned!T)
301     out
302     {
303         assert(this.value.length > 0u);
304     }
305     body
306     {
307         scope(success) this.construction = ASN1Construction.primitive;
308         if (value <= byte.max && value >= byte.min)
309         {
310             this.value = [ cast(ubyte) cast(byte) value ];
311             return;
312         }
313 
314         ubyte[] ub;
315         ub.length = T.sizeof;
316         *cast(T *)&ub[0] = value;
317         version (LittleEndian) reverse(ub);
318 
319         /*
320             An INTEGER must be encoded on the fewest number of bytes than can
321             encode it. The loops below identify how many bytes can be
322             truncated from the start of the INTEGER, with one loop for positive
323             and another loop for negative numbers.
324 
325             From X.690, Section 8.3.2:
326 
327             If the contents octets of an integer value encoding consist of more
328             than one octet, then the bits of the first octet and bit 8 of the
329             second octet:
330                 a) shall not all be ones; and
331                 b) shall not all be zero.
332                 NOTE – These rules ensure that an integer value is always
333                 encoded in the smallest possible number of octets.
334         */
335         size_t startOfNonPadding = 0u;
336         if (T.sizeof > 1u)
337         {
338             if (value >= 0)
339             {
340                 for (size_t i = 0u; i < ub.length-1; i++)
341                 {
342                     if (ub[i] != 0x00u) break;
343                     if (!(ub[i+1] & 0x80u)) startOfNonPadding++;
344                 }
345             }
346             else
347             {
348                 for (size_t i = 0u; i < ub.length-1; i++)
349                 {
350                     if (ub[i] != 0xFFu) break;
351                     if (ub[i+1] & 0x80u) startOfNonPadding++;
352                 }
353             }
354         }
355 
356         this.value = ub[startOfNonPadding .. $];
357     }
358 
359     // Ensure that INTEGER 0 gets encoded on a single null byte.
360     @system
361     unittest
362     {
363         DERElement el = new DERElement();
364 
365         el.integer!byte = cast(byte) 0x00;
366         assert(el.value == [ 0x00u ]);
367 
368         el.integer!short = cast(short) 0x0000;
369         assert(el.value == [ 0x00u ]);
370 
371         el.integer!int = cast(int) 0;
372         assert(el.value == [ 0x00u ]);
373 
374         el.integer!long = cast(long) 0;
375         assert(el.value == [ 0x00u ]);
376 
377         el.value = [];
378         assertThrown!ASN1ValueSizeException(el.integer!byte);
379         assertThrown!ASN1ValueSizeException(el.integer!short);
380         assertThrown!ASN1ValueSizeException(el.integer!int);
381         assertThrown!ASN1ValueSizeException(el.integer!long);
382     }
383 
384     // Test encoding -0 for the sake of CVE-2016-2108
385     @system
386     unittest
387     {
388         DERElement el = new DERElement();
389 
390         el.integer!byte = -0;
391         assertNotThrown!RangeError(el.integer!byte);
392         assertNotThrown!ASN1Exception(el.integer!byte);
393 
394         el.integer!short = -0;
395         assertNotThrown!RangeError(el.integer!short);
396         assertNotThrown!ASN1Exception(el.integer!short);
397 
398         el.integer!int = -0;
399         assertNotThrown!RangeError(el.integer!int);
400         assertNotThrown!ASN1Exception(el.integer!int);
401 
402         el.integer!long = -0;
403         assertNotThrown!RangeError(el.integer!long);
404         assertNotThrown!ASN1Exception(el.integer!long);
405     }
406 
407     /**
408         Decodes an array of $(D bool)s representing a string of bits.
409 
410         Returns: an array of $(D bool)s, where each $(D bool) represents a bit
411             in the encoded bit string
412 
413         Throws:
414         $(UL
415             $(LI $(D ASN1ValueSizeException)
416                 if the any primitive contains 0 bytes)
417             $(LI $(D ASN1ValueException)
418                 if the first byte has a value greater
419                 than seven, or if the first byte indicates the presence of
420                 padding bits when no subsequent bytes exist, or if any primitive
421                 but the last in a constructed BIT STRING uses padding bits, or
422                 if any of the padding bits are set)
423             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
424         )
425     */
426     override public @property
427     bool[] bitString() const
428     {
429         if (this.construction != ASN1Construction.primitive)
430             throw new ASN1ConstructionException
431             (this.construction, "decode a BIT STRING");
432 
433         if (this.value.length == 0u)
434             throw new ASN1ValueSizeException
435             (1u, size_t.max, 0u, "decode a BIT STRING");
436 
437         if (this.value[0] > 0x07u)
438             throw new ASN1ValueException
439             (
440                 "In Distinguished Encoding Rules (DER), the first byte of the encoded " ~
441                 "binary value (after the type and length bytes, of course) " ~
442                 "is used to indicate how many unused bits there are at the " ~
443                 "end of the BIT STRING. Since everything is encoded in bytes " ~
444                 "in Distinguished Encoding Rules (DER), but a BIT STRING may not " ~
445                 "necessarily encode a number of bits, divisible by eight " ~
446                 "there may be bits at the end of the BIT STRING that will " ~
447                 "need to be identified as padding instead of meaningful data." ~
448                 "Since a byte is eight bits, the largest number that the " ~
449                 "first byte should encode is 7, since, if you have eight " ~
450                 "unused bits or more, you may as well truncate an entire " ~
451                 "byte from the encoded data. This exception was thrown because " ~
452                 "you attempted to decode a BIT STRING whose first byte " ~
453                 "had a value greater than seven. The value was: " ~
454                 text(this.value[0]) ~ ". " ~ notWhatYouMeantText ~
455                 forMoreInformationText ~ debugInformationText ~ reportBugsText
456             );
457 
458         if (this.value[0] > 0x00u && this.value.length <= 1u)
459             throw new ASN1ValueException
460             (
461                 "This exception was thrown because you attempted to decode a " ~
462                 "BIT STRING that had a misleading first byte, which indicated " ~
463                 "that there were more than zero padding bits, but there were " ~
464                 "no subsequent octets supplied, which contain the octet-" ~
465                 "aligned bits and padding. This may have been a mistake on " ~
466                 "the part of the encoder, but this looks really suspicious: " ~
467                 "it is likely that an attempt was made to hack your systems " ~
468                 "by inducing an out-of-bounds read from an array. " ~
469                 notWhatYouMeantText ~ forMoreInformationText ~
470                 debugInformationText ~ reportBugsText
471             );
472 
473         bool[] ret;
474         for (size_t i = 1; i < this.value.length; i++)
475         {
476             ret ~= [
477                 (this.value[i] & 0b10000000u ? true : false),
478                 (this.value[i] & 0b01000000u ? true : false),
479                 (this.value[i] & 0b00100000u ? true : false),
480                 (this.value[i] & 0b00010000u ? true : false),
481                 (this.value[i] & 0b00001000u ? true : false),
482                 (this.value[i] & 0b00000100u ? true : false),
483                 (this.value[i] & 0b00000010u ? true : false),
484                 (this.value[i] & 0b00000001u ? true : false)
485             ];
486         }
487 
488         foreach (immutable bit; ret[$-this.value[0] .. $])
489         {
490             if (bit == true)
491                 throw new ASN1ValueException
492                 (
493                     "This exception was thrown because you attempted to decode " ~
494                     "a BIT STRING whose padding bits were not entirely zeroes. " ~
495                     "If you were using the Basic Encoding Rules (BER), this " ~
496                     "would not be a problem, but under Distinguished Encoding " ~
497                     "Rules (DER), padding the BIT STRING with anything other " ~
498                     "zeroes is forbidden. " ~
499                     notWhatYouMeantText ~ forMoreInformationText ~
500                     debugInformationText ~ reportBugsText
501                 );
502         }
503 
504         ret.length -= this.value[0];
505         return ret;
506     }
507 
508     /// Encodes an array of $(D bool)s representing a string of bits.
509     override public @property
510     void bitString(in bool[] value)
511     out
512     {
513         assert(this.value.length > 0u);
514     }
515     body
516     {
517         scope(success) this.construction = ASN1Construction.primitive;
518         ubyte[] ub;
519         ub.length = ((value.length / 8u) + (value.length % 8u ? 1u : 0u));
520         for (size_t i = 0u; i < value.length; i++)
521         {
522             if (value[i] == false) continue;
523             ub[(i/8u)] |= (0b10000000u >> (i % 8u));
524         }
525         this.value = [ cast(ubyte) (8u - (value.length % 8u)) ] ~ ub;
526         if (this.value[0] == 0x08u) this.value[0] = 0x00u;
527     }
528 
529     // Ensure that 1s in the padding get PUNISHED with an exception
530     @system
531     unittest
532     {
533         DERElement el = new DERElement();
534         el.value = [ 0x07u, 0b11000000u ];
535         assertThrown!ASN1ValueException(el.bitString);
536 
537         el.value = [ 0x01u, 0b11111111u ];
538         assertThrown!ASN1ValueException(el.bitString);
539 
540         el.value = [ 0x00u, 0b11111111u ];
541         assertNotThrown!ASN1ValueException(el.bitString);
542     }
543 
544     // Test a BIT STRING with a deceptive first byte.
545     @system
546     unittest
547     {
548         DERElement el = new DERElement();
549         el.value = [ 0x01u ];
550         assertThrown!ASN1ValueException(el.bitString);
551     }
552 
553     /**
554         Decodes an $(MONO OCTET STRING) into an unsigned byte array.
555 
556         Throws:
557         $(UL
558             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
559         )
560     */
561     override public @property @safe
562     ubyte[] octetString() const
563     {
564         if (this.construction != ASN1Construction.primitive)
565             throw new ASN1ConstructionException
566             (this.construction, "decode an OCTET STRING");
567 
568         return this.value.dup;
569     }
570 
571     /// Encodes an $(MONO OCTET STRING) from an unsigned byte ($(D ubyte)) array.
572     override public @property @safe
573     void octetString(in ubyte[] value)
574     {
575         scope(success) this.construction = ASN1Construction.primitive;
576         this.value = value.dup;
577     }
578 
579     /**
580         "Decodes" a $(D null), by which I mean: returns nothing, but
581         throws exceptions if the element is not correct.
582 
583         Note:
584             I had to name this method $(D nill), because $(D null) is a keyword in D.
585 
586         Throws:
587         $(UL
588             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
589             $(LI $(D ASN1ValueSizeException) if there are any content octets)
590         )
591     */
592     override public @property @safe
593     void nill() const
594     {
595         if (this.construction != ASN1Construction.primitive)
596             throw new ASN1ConstructionException
597             (this.construction, "decode a NULL");
598 
599         if (this.value.length != 0u)
600             throw new ASN1ValueSizeException
601             (0u, 0u, this.value.length, "decode a NULL");
602     }
603 
604     /**
605         Decodes an $(MONO OBJECT IDENTIFIER).
606         See $(MONO source/types/universal/objectidentifier.d) for information about
607         the $(D ObjectIdentifier) class (aliased as $(D OID)).
608 
609         Throws:
610         $(UL
611             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
612             $(LI $(D ASN1ValueSizeException) if there are no value bytes)
613             $(LI $(D ASN1ValuePaddingException) if a single OID number is encoded with
614                 "leading zero bytes" ($(D 0x80u)))
615             $(LI $(D ASN1ValueOverflowException) if a single OID number is too big to
616                 decode to a $(D size_t))
617             $(LI $(D ASN1TruncationException) if a single OID number is "cut off")
618         )
619 
620         Standards:
621         $(UL
622             $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660))
623         )
624     */
625     override public @property @system
626     OID objectIdentifier() const
627     out (value)
628     {
629         assert(value.length >= 2u);
630     }
631     body
632     {
633         if (this.construction != ASN1Construction.primitive)
634             throw new ASN1ConstructionException
635             (this.construction, "decode an OBJECT IDENTIFIER");
636 
637         if (this.value.length == 0u)
638             throw new ASN1ValueSizeException
639             (1u, size_t.max, 0u, "decode an OBJECT IDENTIFIER");
640 
641         if (this.value.length >= 2u)
642         {
643             // Skip the first, because it is fine if it is 0x80
644             // Skip the last because it will be checked next
645             foreach (immutable octet; this.value[1 .. $-1])
646             {
647                 if (octet == 0x80u)
648                     throw new ASN1ValuePaddingException
649                     (
650                         "This exception was thrown because you attempted to decode " ~
651                         "an OBJECT IDENTIFIER that contained a number that was " ~
652                         "encoded on more than the minimum necessary octets. This " ~
653                         "is indicated by an occurrence of the octet 0x80, which " ~
654                         "is the encoded equivalent of a leading zero. " ~
655                         notWhatYouMeantText ~ forMoreInformationText ~
656                         debugInformationText ~ reportBugsText
657                     );
658             }
659 
660             if ((this.value[$-1] & 0x80u) == 0x80u)
661                 throw new ASN1TruncationException
662                 (size_t.max, this.value.length, "decode an OBJECT IDENTIFIER");
663         }
664 
665         size_t[] numbers;
666         if (this.value[0] >= 0x50u)
667         {
668             numbers = [ 2u, (this.value[0] - 0x50u) ];
669         }
670         else if (this.value[0] >= 0x28u)
671         {
672             numbers = [ 1u, (this.value[0] - 0x28u) ];
673         }
674         else
675         {
676             numbers = [ 0u, this.value[0] ];
677         }
678 
679         // Breaks bytes into groups, where each group encodes one OID component.
680         ubyte[][] byteGroups;
681         size_t lastTerminator = 1;
682         for (size_t i = 1; i < this.length; i++)
683         {
684             if (!(this.value[i] & 0x80u))
685             {
686                 byteGroups ~= cast(ubyte[]) this.value[lastTerminator .. i+1];
687                 lastTerminator = i+1;
688             }
689         }
690 
691         // Converts each group of bytes to a number.
692         foreach (const byteGroup; byteGroups)
693         {
694             if (byteGroup.length > size_t.sizeof)
695                 throw new ASN1ValueOverflowException
696                 (
697                     "This exception was thrown because you attempted to decode " ~
698                     "a OBJECT IDENTIFIER that encoded a number on more than " ~
699                     "size_t.sizeof bytes. " ~
700                     notWhatYouMeantText ~ forMoreInformationText ~
701                     debugInformationText ~ reportBugsText
702                 );
703 
704             numbers ~= 0u;
705             for (size_t i = 0u; i < byteGroup.length; i++)
706             {
707                 numbers[$-1] <<= 7;
708                 numbers[$-1] |= cast(size_t) (byteGroup[i] & 0x7Fu);
709             }
710         }
711 
712         // Constructs the array of OIDNodes from the array of numbers.
713         OIDNode[] nodes;
714         foreach (number; numbers)
715         {
716             nodes ~= OIDNode(number);
717         }
718 
719         return new OID(nodes);
720     }
721 
722     /**
723         Encodes an $(MONO OBJECT IDENTIFIER).
724         See $(MONO source/types/universal/objectidentifier.d) for information about
725         the $(D ObjectIdentifier) class (aliased as $(OID)).
726 
727         Standards:
728         $(UL
729             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
730             $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660))
731         )
732     */
733     override public @property @system
734     void objectIdentifier(in OID value)
735     in
736     {
737         assert(value.length >= 2u);
738         assert(value.numericArray[0] <= 2u);
739         if (value.numericArray[0] == 2u)
740             assert(value.numericArray[1] <= 175u);
741         else
742             assert(value.numericArray[1] <= 39u);
743     }
744     out
745     {
746         assert(this.value.length > 0u);
747     }
748     body
749     {
750         scope(success) this.construction = ASN1Construction.primitive;
751         size_t[] numbers = value.numericArray();
752         this.value = [ cast(ubyte) (numbers[0] * 40u + numbers[1]) ];
753         if (numbers.length > 2u)
754         {
755             foreach (number; numbers[2 .. $])
756             {
757                 if (number < 128u)
758                 {
759                     this.value ~= cast(ubyte) number;
760                     continue;
761                 }
762 
763                 ubyte[] encodedOIDNode;
764                 while (number != 0u)
765                 {
766                     ubyte[] numberBytes;
767                     numberBytes.length = size_t.sizeof;
768                     *cast(size_t *) numberBytes.ptr = number;
769                     if ((numberBytes[0] & 0x80u) == 0u) numberBytes[0] |= 0x80u;
770                     encodedOIDNode = numberBytes[0] ~ encodedOIDNode;
771                     number >>= 7u;
772                 }
773 
774                 encodedOIDNode[$-1] &= 0x7Fu;
775                 this.value ~= encodedOIDNode;
776             }
777         }
778     }
779 
780     @system
781     unittest
782     {
783         DERElement element = new DERElement();
784 
785         // All values of octet[0] should pass.
786         for (ubyte i = 0x00u; i < 0xFFu; i++)
787         {
788             element.value = [ i ];
789             assertNotThrown!Exception(element.objectIdentifier);
790         }
791 
792         // All values of octet[0] should pass.
793         for (ubyte i = 0x00u; i < 0xFFu; i++)
794         {
795             element.value = [ i, 0x14u ];
796             assertNotThrown!Exception(element.objectIdentifier);
797         }
798     }
799 
800     @system
801     unittest
802     {
803         DERElement element = new DERElement();
804 
805         // Tests for the "leading zero byte," 0x80
806         element.value = [ 0x29u, 0x80u, 0x14u ];
807         assertThrown!ASN1ValuePaddingException(element.objectIdentifier);
808         element.value = [ 0x29u, 0x80u, 0x80u ];
809         assertThrown!ASN1ValuePaddingException(element.objectIdentifier);
810         element.value = [ 0x80u, 0x80u, 0x80u ];
811         assertThrown!ASN1ValuePaddingException(element.objectIdentifier);
812 
813         // Test for non-terminating components
814         element.value = [ 0x29u, 0x81u ];
815         assertThrown!ASN1TruncationException(element.objectIdentifier);
816         element.value = [ 0x29u, 0x80u ];
817         assertThrown!ASN1TruncationException(element.objectIdentifier);
818         element.value = [ 0x29u, 0x14u, 0x81u ];
819         assertThrown!ASN1TruncationException(element.objectIdentifier);
820         element.value = [ 0x29u, 0x14u, 0x80u ];
821         assertThrown!ASN1TruncationException(element.objectIdentifier);
822 
823         // This one should not fail. 0x80u is valid for the first octet.
824         element.value = [ 0x80u, 0x14u, 0x14u ];
825         assertNotThrown!ASN1ValuePaddingException(element.objectIdentifier);
826     }
827 
828     /**
829         Decodes an $(D ObjectDescriptor), which is a string consisting of only
830         graphical characters. In fact, $(D ObjectDescriptor) is actually implicitly
831         just a $(MONO GraphicString)! The formal specification for an $(D ObjectDescriptor)
832         is:
833 
834         $(MONO ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString)
835 
836         $(MONO GraphicString) is just a string containing only characters between
837         and including $(D 0x20) and $(D 0x7E), therefore ObjectDescriptor is just
838         $(D 0x20) and $(D 0x7E).
839 
840         Throws:
841         $(UL
842             $(LI $(D ASN1ValueCharactersException)
843                 if the encoded value contains any character outside of
844                 $(D 0x20) to $(D 0x7E), which means any control characters or $(MONO DELETE))
845             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
846         )
847 
848         Citations:
849         $(UL
850             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
851                 $(I ASN.1: Communication between Heterogeneous Systems),
852                 Morgan Kaufmann, 2001, pp. 175-178.)
853             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
854             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
855         )
856     */
857     override public @property @system
858     string objectDescriptor() const
859     {
860         if (this.construction != ASN1Construction.primitive)
861             throw new ASN1ConstructionException
862             (this.construction, "decode an ObjectDescriptor");
863 
864         foreach (immutable character; this.value)
865         {
866             if ((!character.isGraphical) && (character != ' '))
867             {
868                 throw new ASN1ValueCharactersException
869                 ("all characters within the range 0x20 to 0x7E", character, "ObjectDescriptor");
870             }
871         }
872         return cast(string) this.value;
873     }
874 
875     /**
876         Encodes an $(D ObjectDescriptor), which is a string consisting of only
877         graphical characters. In fact, $(D ObjectDescriptor) is actually implicitly
878         just a $(MONO GraphicString)! The formal specification for an $(D ObjectDescriptor)
879         is:
880 
881         $(MONO ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString)
882 
883         $(MONO GraphicString) is just a string containing only characters between
884         and including $(D 0x20) and $(D 0x7E), therefore ObjectDescriptor is just
885         $(D 0x20) and $(D 0x7E).
886 
887         Throws:
888         $(UL
889             $(LI $(D ASN1ValueCharactersException)
890                 if the string value contains any character outside of
891                 $(D 0x20) to $(D 0x7E), which means any control characters or $(MONO DELETE))
892             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
893         )
894 
895         Citations:
896         $(UL
897             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
898                 $(I ASN.1: Communication between Heterogeneous Systems),
899                 Morgan Kaufmann, 2001, pp. 175-178.)
900             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
901             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
902         )
903     */
904     override public @property @system
905     void objectDescriptor(in string value)
906     {
907         scope(success) this.construction = ASN1Construction.primitive;
908         foreach (immutable character; value)
909         {
910             if ((!character.isGraphical) && (character != ' '))
911                 throw new ASN1ValueCharactersException
912                 ("all characters within the range 0x20 to 0x7E", character, "ObjectDescriptor");
913         }
914         this.value = cast(ubyte[]) value;
915     }
916 
917     /**
918         Decodes an $(MONO EXTERNAL).
919 
920         According to the
921         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
922         $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1)),
923         the abstract definition for an $(MONO EXTERNAL), after removing the comments in the
924         specification, is as follows:
925 
926         $(PRE
927             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
928                 identification CHOICE {
929                     syntaxes SEQUENCE {
930                         abstract OBJECT IDENTIFIER,
931                         transfer OBJECT IDENTIFIER },
932                     syntax OBJECT IDENTIFIER,
933                     presentation-context-id INTEGER,
934                     context-negotiation SEQUENCE {
935                         presentation-context-id INTEGER,
936                         transfer-syntax OBJECT IDENTIFIER },
937                     transfer-syntax OBJECT IDENTIFIER,
938                     fixed NULL },
939                 data-value-descriptor ObjectDescriptor OPTIONAL,
940                 data-value OCTET STRING }
941                     ( WITH COMPONENTS {
942                         ... ,
943                         identification ( WITH COMPONENTS {
944                             ... ,
945                             syntaxes ABSENT,
946                             transfer-syntax ABSENT,
947                             fixed ABSENT } ) } )
948         )
949 
950         Note that the abstract syntax resembles that of $(MONO EmbeddedPDV) and
951         $(MONO CharacterString), except with a $(MONO WITH COMPONENTS) constraint that removes some
952         of our choices of $(MONO identification).
953         As can be seen on page 303 of Olivier Dubuisson's
954         $(I $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF,
955             ASN.1: Communication Between Heterogeneous Systems)),
956         after applying the $(MONO WITH COMPONENTS) constraint, our reduced syntax becomes:
957 
958         $(PRE
959             EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE {
960                 identification CHOICE {
961                     syntax OBJECT IDENTIFIER,
962                     presentation-context-id INTEGER,
963                     context-negotiation SEQUENCE {
964                         presentation-context-id INTEGER,
965                         transfer-syntax OBJECT IDENTIFIER } },
966                 data-value-descriptor ObjectDescriptor OPTIONAL,
967                 data-value OCTET STRING }
968         )
969 
970         But, according to the
971         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
972         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules),
973         section 8.18, when encoded using Basic Encoding Rules (BER), is encoded as
974         follows, for compatibility reasons:
975 
976         $(PRE
977             EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE {
978                 direct-reference  OBJECT IDENTIFIER OPTIONAL,
979                 indirect-reference  INTEGER OPTIONAL,
980                 data-value-descriptor  ObjectDescriptor  OPTIONAL,
981                 encoding  CHOICE {
982                     single-ASN1-type  [0] ANY,
983                     octet-aligned     [1] IMPLICIT OCTET STRING,
984                     arbitrary         [2] IMPLICIT BIT STRING } }
985         )
986 
987         The definition above is the pre-1994 definition of $(MONO EXTERNAL). The $(MONO syntax)
988         field of the post-1994 definition maps to the $(MONO direct-reference) field of
989         the pre-1994 definition. The $(MONO presentation-context-id) field of the post-1994
990         definition maps to the $(MONO indirect-reference) field of the pre-1994 definition.
991         If $(MONO context-negotiation) is used, per the abstract syntax, then the
992         $(MONO presentation-context-id) field of the $(MONO context-negotiation) $(MONO SEQUENCE) in the
993         post-1994 definition maps to the $(MONO indirect-reference) field of the pre-1994
994         definition, and the $(MONO transfer-syntax) field of the $(MONO context-negotiation)
995         $(MONO SEQUENCE) maps to the $(MONO direct-reference) field of the pre-1994 definition.
996 
997         The following additional constraints are applied to the abstract syntax
998         when using Canonical Encoding Rules or Distinguished Encoding Rules,
999         which are also defined in the
1000         $(LINK https://www.itu.int/en/pages/default.aspx,
1001         International Telecommunications Union)'s
1002         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules):
1003 
1004         $(PRE
1005             EXTERNAL ( WITH COMPONENTS {
1006                 ... ,
1007                 identification ( WITH COMPONENTS {
1008                     ... ,
1009                     presentation-context-id ABSENT,
1010                     context-negotiation ABSENT } ) } )
1011         )
1012 
1013         The stated purpose of the constraints shown above is to restrict the use of
1014         the $(MONO presentation-context-id), either by itself or within the
1015         $(MONO context-negotiation), which makes the following the effective abstract
1016         syntax of $(MONO EXTERNAL) when using Canonical Encoding Rules or
1017         Distinguished Encoding Rules:
1018 
1019         $(PRE
1020             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1021                 identification CHOICE {
1022                     syntaxes SEQUENCE {
1023                         abstract OBJECT IDENTIFIER,
1024                         transfer OBJECT IDENTIFIER },
1025                     syntax OBJECT IDENTIFIER,
1026                     presentation-context-id INTEGER,
1027                     context-negotiation SEQUENCE {
1028                         presentation-context-id INTEGER,
1029                         transfer-syntax OBJECT IDENTIFIER },
1030                     transfer-syntax OBJECT IDENTIFIER,
1031                     fixed NULL },
1032                 data-value-descriptor ObjectDescriptor OPTIONAL,
1033                 data-value OCTET STRING }
1034                     ( WITH COMPONENTS {
1035                         ... ,
1036                         identification ( WITH COMPONENTS {
1037                             ... ,
1038                             syntaxes ABSENT,
1039                             presentation-context-id ABSENT,
1040                             context-negotiation ABSENT,
1041                             transfer-syntax ABSENT,
1042                             fixed ABSENT } ) } )
1043         )
1044 
1045         With the constraints applied, the abstract syntax for $(MONO EXTERNAL)s encoded
1046         using Canonical Encoding Rules or Distinguished Encoding Rules becomes:
1047 
1048         $(PRE
1049             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1050                 identification CHOICE {
1051                     syntax OBJECT IDENTIFIER },
1052                 data-value-descriptor ObjectDescriptor OPTIONAL,
1053                 data-value OCTET STRING }
1054         )
1055 
1056         Upon removing the $(MONO CHOICE) tag (since you have no choice but to use syntax
1057         at this point), the encoding definition when using
1058         Canonical Encoding Rules or Distinguished Encoding Rules:
1059 
1060         $(PRE
1061             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1062                 syntax OBJECT IDENTIFIER,
1063                 data-value-descriptor ObjectDescriptor OPTIONAL,
1064                 data-value OCTET STRING }
1065         )
1066 
1067         Returns: an instance of $(D types.universal.external.External)
1068 
1069         Throws:
1070         $(UL
1071             $(LI $(D ASN1ValueException)
1072                 if the SEQUENCE does not contain two to four elements)
1073             $(LI $(D ASN1RecursionException)
1074                 if using constructed form and the element
1075                 is constructed of too many nested constructed elements)
1076             $(LI $(D ASN1TagClassException)
1077                 if any nested primitives do not have the correct tag class)
1078             $(LI $(D ASN1ConstructionException)
1079                 if any element has the wrong construction)
1080             $(LI $(D ASN1TagNumberException)
1081                 if any nested primitives do not have the correct tag number)
1082             $(LI $(D ASN1ValueCharactersException)
1083                 if a data-value-descriptor is supplied with invalid characters)
1084             $(LI $(D ASN1ConstructionException) if the element is not marked as "constructed")
1085         )
1086     */
1087     deprecated override public @property @system
1088     External external() const
1089     {
1090         if (this.construction != ASN1Construction.constructed)
1091             throw new ASN1ConstructionException
1092             (this.construction, "decode an EXTERNAL");
1093 
1094         const DERElement[] components = this.sequence;
1095         External ext = External();
1096         ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID();
1097 
1098         if (components.length < 2u || components.length > 3u)
1099             throw new ASN1ValueException
1100             (
1101                 "This exception was thrown because you attempted to decode " ~
1102                 "an EXTERNAL that contained too many or too few elements. " ~
1103                 "An EXTERNAL should have either two and three elements: " ~
1104                 "a direct-reference (syntax), an optional " ~
1105                 "data-value-descriptor, and an encoding (data-value). " ~
1106                 notWhatYouMeantText ~ forMoreInformationText ~
1107                 debugInformationText ~ reportBugsText
1108             );
1109 
1110         // Every component except the last must be universal class
1111         foreach (const component; components[0 .. $-1])
1112         {
1113             if (component.tagClass != ASN1TagClass.universal)
1114                 throw new ASN1TagClassException
1115                 ([ ASN1TagClass.universal ], component.tagClass, "decode all but the last component of an EXTERNAL");
1116         }
1117 
1118         // The last tag must be context-specific class
1119         if (components[$-1].tagClass != ASN1TagClass.contextSpecific)
1120             throw new ASN1TagClassException
1121             ([ ASN1TagClass.contextSpecific ], components[$-1].tagClass, "decode the last component of an EXTERNAL");
1122 
1123         // The first component should always be primitive
1124         if (components[0].construction != ASN1Construction.primitive)
1125             throw new ASN1ConstructionException
1126             (components[0].construction, "decode the first component of an EXTERNAL");
1127 
1128         if (components[0].tagNumber != ASN1UniversalType.objectIdentifier)
1129             throw new ASN1TagNumberException
1130             ([ 2u, 6u ], components[0].tagNumber, "decode the first component of an EXTERNAL");
1131 
1132         identification.directReference = components[0].objectIdentifier;
1133         if (components.length == 3u)
1134         {
1135             if (components[1].tagNumber != ASN1UniversalType.objectDescriptor)
1136                 throw new ASN1TagNumberException
1137                 (
1138                     [ ASN1UniversalType.objectDescriptor ],
1139                     components[1].tagNumber,
1140                     "decode the second of three subcomponents of an EXTERNAL"
1141                 );
1142 
1143             ext.dataValueDescriptor = components[1].objectDescriptor;
1144         }
1145 
1146         switch (components[$-1].tagNumber)
1147         {
1148             case (0u): // single-ASN1-value
1149             {
1150                 ext.encoding = ASN1ExternalEncodingChoice.singleASN1Type;
1151                 break;
1152             }
1153             case (1u): // octet-aligned
1154             {
1155                 ext.encoding = ASN1ExternalEncodingChoice.octetAligned;
1156                 break;
1157             }
1158             case (2u): // arbitrary
1159             {
1160                 ext.encoding = ASN1ExternalEncodingChoice.arbitrary;
1161                 break;
1162             }
1163             default:
1164                 throw new ASN1TagNumberException
1165                 ([ 0u, 1u, 2u ], components[$-1].tagNumber, "decode an EXTERNAL identification");
1166         }
1167 
1168         ext.dataValue = components[$-1].value.dup;
1169         ext.identification = identification;
1170         return ext;
1171     }
1172 
1173     /**
1174         Encodes an $(MONO EXTERNAL).
1175 
1176         According to the
1177         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
1178         $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1)),
1179         the abstract definition for an $(MONO EXTERNAL), after removing the comments in the
1180         specification, is as follows:
1181 
1182         $(PRE
1183             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1184                 identification CHOICE {
1185                     syntaxes SEQUENCE {
1186                         abstract OBJECT IDENTIFIER,
1187                         transfer OBJECT IDENTIFIER },
1188                     syntax OBJECT IDENTIFIER,
1189                     presentation-context-id INTEGER,
1190                     context-negotiation SEQUENCE {
1191                         presentation-context-id INTEGER,
1192                         transfer-syntax OBJECT IDENTIFIER },
1193                     transfer-syntax OBJECT IDENTIFIER,
1194                     fixed NULL },
1195                 data-value-descriptor ObjectDescriptor OPTIONAL,
1196                 data-value OCTET STRING }
1197                     ( WITH COMPONENTS {
1198                         ... ,
1199                         identification ( WITH COMPONENTS {
1200                             ... ,
1201                             syntaxes ABSENT,
1202                             transfer-syntax ABSENT,
1203                             fixed ABSENT } ) } )
1204         )
1205 
1206         Note that the abstract syntax resembles that of $(MONO EmbeddedPDV) and
1207         $(MONO CharacterString), except with a $(MONO WITH COMPONENTS) constraint that removes some
1208         of our choices of $(MONO identification).
1209         As can be seen on page 303 of Olivier Dubuisson's
1210         $(I $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF,
1211             ASN.1: Communication Between Heterogeneous Systems)),
1212         after applying the $(MONO WITH COMPONENTS) constraint, our reduced syntax becomes:
1213 
1214         $(PRE
1215             EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE {
1216                 identification CHOICE {
1217                     syntax OBJECT IDENTIFIER,
1218                     presentation-context-id INTEGER,
1219                     context-negotiation SEQUENCE {
1220                         presentation-context-id INTEGER,
1221                         transfer-syntax OBJECT IDENTIFIER } },
1222                 data-value-descriptor ObjectDescriptor OPTIONAL,
1223                 data-value OCTET STRING }
1224         )
1225 
1226         But, according to the
1227         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
1228         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules),
1229         section 8.18, when encoded using Basic Encoding Rules (BER), is encoded as
1230         follows, for compatibility reasons:
1231 
1232         $(PRE
1233             EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE {
1234                 direct-reference  OBJECT IDENTIFIER OPTIONAL,
1235                 indirect-reference  INTEGER OPTIONAL,
1236                 data-value-descriptor  ObjectDescriptor  OPTIONAL,
1237                 encoding  CHOICE {
1238                     single-ASN1-type  [0] ANY,
1239                     octet-aligned     [1] IMPLICIT OCTET STRING,
1240                     arbitrary         [2] IMPLICIT BIT STRING } }
1241         )
1242 
1243         The definition above is the pre-1994 definition of $(MONO EXTERNAL). The $(MONO syntax)
1244         field of the post-1994 definition maps to the $(MONO direct-reference) field of
1245         the pre-1994 definition. The $(MONO presentation-context-id) field of the post-1994
1246         definition maps to the $(MONO indirect-reference) field of the pre-1994 definition.
1247         If $(MONO context-negotiation) is used, per the abstract syntax, then the
1248         $(MONO presentation-context-id) field of the $(MONO context-negotiation) $(MONO SEQUENCE) in the
1249         post-1994 definition maps to the $(MONO indirect-reference) field of the pre-1994
1250         definition, and the $(MONO transfer-syntax) field of the $(MONO context-negotiation)
1251         $(MONO SEQUENCE) maps to the $(MONO direct-reference) field of the pre-1994 definition.
1252 
1253         The following additional constraints are applied to the abstract syntax
1254         when using Canonical Encoding Rules or Distinguished Encoding Rules,
1255         which are also defined in the
1256         $(LINK https://www.itu.int/en/pages/default.aspx,
1257         International Telecommunications Union)'s
1258         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules):
1259 
1260         $(PRE
1261             EXTERNAL ( WITH COMPONENTS {
1262                 ... ,
1263                 identification ( WITH COMPONENTS {
1264                     ... ,
1265                     presentation-context-id ABSENT,
1266                     context-negotiation ABSENT } ) } )
1267         )
1268 
1269         The stated purpose of the constraints shown above is to restrict the use of
1270         the $(MONO presentation-context-id), either by itself or within the
1271         $(MONO context-negotiation), which makes the following the effective abstract
1272         syntax of $(MONO EXTERNAL) when using Canonical Encoding Rules or
1273         Distinguished Encoding Rules:
1274 
1275         $(PRE
1276             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1277                 identification CHOICE {
1278                     syntaxes SEQUENCE {
1279                         abstract OBJECT IDENTIFIER,
1280                         transfer OBJECT IDENTIFIER },
1281                     syntax OBJECT IDENTIFIER,
1282                     presentation-context-id INTEGER,
1283                     context-negotiation SEQUENCE {
1284                         presentation-context-id INTEGER,
1285                         transfer-syntax OBJECT IDENTIFIER },
1286                     transfer-syntax OBJECT IDENTIFIER,
1287                     fixed NULL },
1288                 data-value-descriptor ObjectDescriptor OPTIONAL,
1289                 data-value OCTET STRING }
1290                     ( WITH COMPONENTS {
1291                         ... ,
1292                         identification ( WITH COMPONENTS {
1293                             ... ,
1294                             syntaxes ABSENT,
1295                             presentation-context-id ABSENT,
1296                             context-negotiation ABSENT,
1297                             transfer-syntax ABSENT,
1298                             fixed ABSENT } ) } )
1299         )
1300 
1301         With the constraints applied, the abstract syntax for $(MONO EXTERNAL)s encoded
1302         using Canonical Encoding Rules or Distinguished Encoding Rules becomes:
1303 
1304         $(PRE
1305             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1306                 identification CHOICE {
1307                     syntax OBJECT IDENTIFIER },
1308                 data-value-descriptor ObjectDescriptor OPTIONAL,
1309                 data-value OCTET STRING }
1310         )
1311 
1312         Upon removing the $(MONO CHOICE) tag (since you have no choice but to use syntax
1313         at this point), the encoding definition when using
1314         Canonical Encoding Rules or Distinguished Encoding Rules:
1315 
1316         $(PRE
1317             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1318                 syntax OBJECT IDENTIFIER,
1319                 data-value-descriptor ObjectDescriptor OPTIONAL,
1320                 data-value OCTET STRING }
1321         )
1322 
1323         Returns: an instance of $(D types.universal.external.External)
1324 
1325         Throws:
1326         $(UL
1327             $(LI $(D ASN1ValueException)
1328                 if something other than $(D syntax) is used for $(MONO identification))
1329             $(LI $(D ASN1ValueCharactersException)
1330                 if a data-value-descriptor is supplied with invalid characters)
1331         )
1332     */
1333     deprecated override public @property @system
1334     void external(in External value)
1335     out
1336     {
1337         assert(this.value.length > 0u);
1338     }
1339     body
1340     {
1341         scope(success) this.construction = ASN1Construction.constructed;
1342         DERElement[] components = [];
1343 
1344         if (!(value.identification.syntax.isNull))
1345         {
1346             DERElement directReference = new DERElement();
1347             directReference.tagNumber = ASN1UniversalType.objectIdentifier;
1348             directReference.objectIdentifier = value.identification.directReference;
1349             components ~= directReference;
1350         }
1351         else // it must be the presentationContextID / indirectReference INTEGER
1352             throw new ASN1ValueException
1353             (
1354                 "This exception was thrown because you attempted to encode an " ~
1355                 "EXTERNAL that used something other than syntax as the CHOICE " ~
1356                 "of identification, which is not permitted when using " ~
1357                 "Distinguished Encoding Rules (DER). " ~
1358                 notWhatYouMeantText ~ forMoreInformationText ~
1359                 debugInformationText ~ reportBugsText
1360             );
1361 
1362         DERElement dataValueDescriptor = new DERElement();
1363         dataValueDescriptor.tagNumber = ASN1UniversalType.objectDescriptor;
1364         dataValueDescriptor.objectDescriptor = value.dataValueDescriptor;
1365         components ~= dataValueDescriptor;
1366 
1367         DERElement dataValue = new DERElement();
1368         dataValue.tagClass = ASN1TagClass.contextSpecific;
1369         dataValue.tagNumber = value.encoding;
1370         dataValue.value = value.dataValue.dup;
1371 
1372         components ~= dataValue;
1373         this.sequence = components;
1374     }
1375 
1376     /*
1377         Since a DER-encoded EXTERNAL can only use the syntax field for
1378         the CHOICE of identification, this unit test ensures that an
1379         exception if thrown if an alternative identification is supplied.
1380     */
1381     @system
1382     unittest
1383     {
1384         ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID();
1385         id.presentationContextID = 27L;
1386 
1387         External input = External();
1388         input.identification = id;
1389         input.dataValueDescriptor = "external";
1390         input.dataValue = [ 0x01u, 0x02u, 0x03u, 0x04u ];
1391 
1392         DERElement el = new DERElement();
1393         assertThrown!ASN1ValueException(el.external = input);
1394     }
1395 
1396     // Inspired by CVE-2017-9023
1397     @system
1398     unittest
1399     {
1400         ubyte[] external = [ // This is valid
1401             0x08u, 0x09u, // EXTERNAL, Length 9
1402                 0x02u, 0x01u, 0x1Bu, // INTEGER 27
1403                 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1404         ];
1405 
1406         // Valid values for octet[2]: 02 06
1407         for (ubyte i = 0x07u; i < 0x1Eu; i++)
1408         {
1409             external[2] = i;
1410             size_t x = 0u;
1411             DERElement el = new DERElement(x, external);
1412             assertThrown!ASN1Exception(el.external);
1413         }
1414 
1415         // Valid values for octet[5]: 80 - 82 (Anything else is an invalid value)
1416         for (ubyte i = 0x82u; i < 0x9Eu; i++)
1417         {
1418             external[5] = i;
1419             size_t x = 0u;
1420             DERElement el = new DERElement(x, external);
1421             assertThrown!ASN1Exception(el.external);
1422         }
1423     }
1424 
1425     // Assert that duplicate elements throw exceptions
1426     @system
1427     unittest
1428     {
1429         ubyte[] external;
1430 
1431         external = [ // This is invalid
1432             0x08u, 0x0Cu, // EXTERNAL, Length 12
1433                 0x02u, 0x01u, 0x1Bu, // INTEGER 27
1434                 0x02u, 0x01u, 0x1Bu, // INTEGER 27
1435                 0x81, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1436         ];
1437         assertThrown!ASN1Exception((new DERElement(external)).external);
1438 
1439         external = [ // This is invalid
1440             0x08u, 0x0Eu, // EXTERNAL, Length 14
1441                 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3
1442                 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3
1443                 0x81, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1444         ];
1445         assertThrown!ASN1Exception((new DERElement(external)).external);
1446 
1447         external = [ // This is invalid
1448             0x08u, 0x12u, // EXTERNAL, Length 18
1449                 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3
1450                 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE"
1451                 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE"
1452                 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1453         ];
1454         assertThrown!ASN1Exception((new DERElement(external)).external);
1455 
1456         external = [ // This is invalid
1457             0x08u, 0x14u, // EXTERNAL, Length 20
1458                 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3
1459                 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE"
1460                 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u, // OCTET STRING 1,2,3,4
1461                 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1462         ];
1463         assertThrown!ASN1Exception((new DERElement(external)).external);
1464     }
1465 
1466     /**
1467         Decodes a floating-point type.
1468 
1469         Note that this method assumes that your machine uses
1470         $(LINK http://ieeexplore.ieee.org/document/4610935/, IEEE 754-2008)
1471         floating point format.
1472 
1473         Throws:
1474         $(UL
1475             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
1476             $(LI $(D ASN1TruncationException) if the value appears to be "cut off")
1477             $(LI $(D ConvException) if character-encoding cannot be converted to
1478                 the selected floating-point type, T)
1479             $(LI $(D ConvOverflowException) if the character-encoding encodes a
1480                 number that is too big for the selected floating-point
1481                 type to express)
1482             $(LI $(D ASN1ValueSizeException) if the binary-encoding contains fewer
1483                 bytes than the information byte purports, or if the
1484                 binary-encoded mantissa is too big to be expressed by an
1485                 unsigned long integer)
1486             $(LI $(D ASN1ValueException) if a complicated-form exponent or a
1487                 non-zero-byte mantissa encodes a zero or even number, of if a base-10
1488                 (character-encoded) REAL is has something wrong that is not
1489                 covered by $(D ASN1ValueCharactersException) or
1490                 $(D ASN1ValuePaddingException))
1491             $(LI $(D ASN1ValueUndefinedException) if both bits indicating the base in the
1492                 information byte of a binary-encoded $(MONO REAL)'s information byte
1493                 are set, which would indicate an invalid base, or if a special
1494                 value has been indicated that is not defined by the specification)
1495             $(LI $(D ASN1ValuePaddingException) if a base-10 (character-encoded)
1496                 REAL is encoded with leading zeroes, or trailing zeroes on the
1497                 mantissa, or if the mantissa is encoded on more than the minimal
1498                 necessary octets)
1499             $(LI $(D ASN1ValueCharactersException) if a base-10 (character-encoded)
1500                 REAL is encoded with characters that are not numeric, a decimal, E,
1501                 or plus or minus)
1502         )
1503 
1504         Citations:
1505         $(UL
1506             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
1507                 $(I ASN.1: Communication between Heterogeneous Systems),
1508                 Morgan Kaufmann, 2001, pp. 400-402.)
1509             $(LI $(LINK https://www.iso.org/standard/12285.html, ISO 6093))
1510         )
1511     */
1512     public @property @system
1513     T realNumber(T)() const
1514     if (isFloatingPoint!T)
1515     {
1516         if (this.construction != ASN1Construction.primitive)
1517             throw new ASN1ConstructionException
1518             (this.construction, "decode a REAL");
1519 
1520         if (this.value.length == 0u) return cast(T) 0.0;
1521         switch (this.value[0] & 0b11000000u)
1522         {
1523             case (0b01000000u):
1524             {
1525                 if (this.value[0] == ASN1SpecialRealValue.notANumber) return T.nan;
1526                 if (this.value[0] == ASN1SpecialRealValue.minusZero) return -0.0;
1527                 if (this.value[0] == ASN1SpecialRealValue.plusInfinity) return T.infinity;
1528                 if (this.value[0] == ASN1SpecialRealValue.minusInfinity) return -T.infinity;
1529                 throw new ASN1ValueUndefinedException
1530                 (
1531                     "This exception was thrown because you attempted to decode " ~
1532                     "a REAL whose information byte indicated a special value " ~
1533                     "not recognized by the specification. The only special " ~
1534                     "values recognized by the specification are PLUS-INFINITY, " ~
1535                     "MINUS-INFINITY, NOT-A-NUMBER, and minus zero, identified " ~
1536                     "by information bytes of 0x40, 0x41 0x42, 0x43 respectively. " ~
1537                     notWhatYouMeantText ~ forMoreInformationText ~
1538                     debugInformationText ~ reportBugsText
1539                 );
1540             }
1541             case (0b00000000u): // Character Encoding
1542             {
1543                 /* NOTE:
1544                     Specification X.690 lays out very strict standards for the
1545                     Canonical Encoding Rules (CER) and Distinguished Encoding
1546                     Rules (DER) base-10-encoded REAL.
1547 
1548                     The character encoding form must be NR3, from ISO 6093, but
1549                     with even more restrictions applied.
1550 
1551                     It must be encoded like so:
1552                     * No whitespace whatsoever
1553                     * No leading zeroes under any circumstance.
1554                     * No trailing zeroes under any circumstance.
1555                     * No plus sign unless exponent is 0.
1556 
1557                     A valid encoding looks like this: 22.E-5
1558                 */
1559                 import std.conv : to;
1560                 import std.string : indexOf;
1561 
1562                 immutable string invalidNR3RealMessage =
1563                     "This exception was thrown because you attempted to decode " ~
1564                     "a base-10 encoded REAL that was encoded with improper " ~
1565                     "format. When using Canonical Encoding Rules (CER) or " ~
1566                     "Distinguished Encoding Rules (DER), the base-10 encoded " ~
1567                     "REAL must be encoded in the NR3 format specified in " ~
1568                     "ISO 6093. Further, there may be no whitespace, no leading " ~
1569                     "zeroes, no trailing zeroes on the mantissa, before or " ~
1570                     "after the decimal point, and no plus sign should ever " ~
1571                     "appear, unless the exponent is 0, in which case, the " ~
1572                     "exponent should read '+0'. Further, there must be a " ~
1573                     "decimal point, immediately followed by a capital 'E'." ~
1574                     "Your problem, in this case, was that your encoded value ";
1575 
1576                 // Smallest possible is '#.E#'. Decimal is necessary.
1577                 if (this.value.length < 5u)
1578                     throw new ASN1ValueSizeException
1579                     (5u, size_t.max, this.value.length, "decode a base-10 encoded REAL");
1580 
1581                 if (this.value[0] != 0b00000011u)
1582                     throw new ASN1ValueException
1583                     (invalidNR3RealMessage ~ "was not NR3 format at all.");
1584 
1585                 string valueString = cast(string) this.value[1 .. $];
1586 
1587                 foreach (character; valueString)
1588                 {
1589                     import std.ascii : isWhite;
1590                     if
1591                     (
1592                         character.isWhite ||
1593                         character == ',' ||
1594                         character == '_'
1595                     )
1596                         throw new ASN1ValueCharactersException
1597                         ("1234567890+-.E", character, "decode a base-10 encoded REAL");
1598                 }
1599 
1600                 if
1601                 (
1602                     valueString[0] == '0' ||
1603                     (valueString[0] == '-' && valueString[1] == '0')
1604                 )
1605                     throw new ASN1ValuePaddingException
1606                     (invalidNR3RealMessage ~ "contained a leading zero.");
1607 
1608                 ptrdiff_t indexOfDecimalPoint = valueString.indexOf(".");
1609                 if (indexOfDecimalPoint == -1)
1610                     throw new ASN1ValueException
1611                     (invalidNR3RealMessage ~ "contained no decimal point.");
1612 
1613                 if (valueString[indexOfDecimalPoint+1] != 'E')
1614                     throw new ASN1ValueException
1615                     (invalidNR3RealMessage ~ "contained no 'E'.");
1616 
1617                 if (valueString[indexOfDecimalPoint-1] == '0')
1618                     throw new ASN1ValuePaddingException
1619                     (invalidNR3RealMessage ~ "contained a trailing zero on the mantissa.");
1620 
1621                 if (valueString[$-2 .. $] != "+0" && canFind(valueString, '+'))
1622                     throw new ASN1ValueException
1623                     (invalidNR3RealMessage ~ "contained an illegitimate plus sign.");
1624 
1625                 if (canFind(valueString, "E0") || canFind(valueString, "E-0"))
1626                     throw new ASN1ValuePaddingException
1627                     (invalidNR3RealMessage ~ "contained a leading zero on the exponent.");
1628 
1629                 return to!(T)(valueString);
1630             }
1631             case 0b10000000u, 0b11000000u: // Binary Encoding
1632             {
1633                 ulong mantissa;
1634                 short exponent;
1635                 ubyte scale;
1636                 ubyte base;
1637                 size_t startOfMantissa;
1638 
1639                 switch (this.value[0] & 0b00000011u)
1640                 {
1641                     case 0b00000000u: // Exponent on the following octet
1642                     {
1643                         if (this.value.length < 3u)
1644                             throw new ASN1TruncationException
1645                             (3u, this.value.length, "decode a REAL exponent");
1646 
1647                         exponent = cast(short) cast(byte) this.value[1];
1648                         startOfMantissa = 2u;
1649                         break;
1650                     }
1651                     case 0b00000001u: // Exponent on the following two octets
1652                     {
1653                         if (this.value.length < 4u)
1654                             throw new ASN1TruncationException
1655                             (4u, this.value.length, "decode a REAL exponent");
1656 
1657                         ubyte[] exponentBytes = this.value[1 .. 3].dup;
1658                         version (LittleEndian) exponentBytes = [ exponentBytes[1], exponentBytes[0] ];
1659                         exponent = *cast(short *) exponentBytes.ptr;
1660 
1661                         if (exponent <= byte.max && exponent >= byte.min)
1662                             throw new ASN1ValuePaddingException
1663                             (
1664                                 "This exception was thrown because you attempted " ~
1665                                 "to decode a binary-encoded REAL whose exponent " ~
1666                                 "was encoded on more bytes than necessary. This " ~
1667                                 "would not be a problem if you were using the " ~
1668                                 "Basic Encoding Rules (BER), but the Canonical " ~
1669                                 "Encoding Rules (CER) and Distinguished Encoding " ~
1670                                 "Rules (DER) require that the exponent be " ~
1671                                 "encoded on the fewest possible bytes. " ~
1672                                 notWhatYouMeantText ~ forMoreInformationText ~
1673                                 debugInformationText ~ reportBugsText
1674                             );
1675 
1676                         startOfMantissa = 3u;
1677                         break;
1678                     }
1679                     case 0b00000010u: // Exponent on the following three octets
1680                     case 0b00000011u: // Complicated
1681                     {
1682                         throw new ASN1ValueOverflowException
1683                         (
1684                             "This exception was thrown because, according to " ~
1685                             "section 11.3.1 of specification X.690, a REAL's " ~
1686                             "exponent must be encoded on the fewest possible " ~
1687                             "octets, but you attempted to decode one that was " ~
1688                             "either too big to fit in an IEEE 754 floating " ~
1689                             "point type, or would have had unnecessary leading " ~
1690                             "bytes if it could. "
1691                         );
1692                     }
1693                     default: assert(0, "Impossible binary exponent encoding on REAL type");
1694                 }
1695 
1696                 if (this.value.length - startOfMantissa > ulong.sizeof)
1697                     throw new ASN1ValueOverflowException
1698                     (
1699                         "This exception was thrown because you attempted to " ~
1700                         "decode a REAL whose mantissa was encoded on too many " ~
1701                         "bytes to decode to the largest unsigned integral data " ~
1702                         "type. "
1703                     );
1704 
1705                 ubyte[] mantissaBytes = this.value[startOfMantissa .. $].dup;
1706 
1707                 if (mantissaBytes[0] == 0x00u)
1708                     throw new ASN1ValuePaddingException
1709                     (
1710                         "This exception was thrown because you attempted to decode " ~
1711                         "a REAL mantissa that was encoded on more than the minimum " ~
1712                         "necessary bytes. " ~
1713                         notWhatYouMeantText ~ forMoreInformationText ~
1714                         debugInformationText ~ reportBugsText
1715                     );
1716 
1717                 while (mantissaBytes.length < ulong.sizeof)
1718                     mantissaBytes = (0x00u ~ mantissaBytes);
1719                 version (LittleEndian) reverse(mantissaBytes);
1720                 version (unittest) assert(mantissaBytes.length == ulong.sizeof);
1721                 mantissa = *cast(ulong *) mantissaBytes.ptr;
1722 
1723                 if (mantissa == 0u)
1724                     throw new ASN1ValueException
1725                     (
1726                         "This exception was thrown because you attempted to " ~
1727                         "decode a REAL that was encoded on more than zero " ~
1728                         "bytes, but whose mantissa encoded a zero. This " ~
1729                         "is prohibited by specification X.690. If the " ~
1730                         "abstract value encoded is a real number of zero, " ~
1731                         "the REAL must be encoded upon zero bytes. " ~
1732                         notWhatYouMeantText ~ forMoreInformationText ~
1733                         debugInformationText ~ reportBugsText
1734                     );
1735 
1736 
1737 
1738                 switch (this.value[0] & 0b00110000u)
1739                 {
1740                     case (0b00000000u):
1741                     {
1742                         /* NOTE:
1743                             Section 11.3.1 of X.690 states that, for Canonical Encoding Rules
1744                             (CER) and Distinguished Encoding Rules (DER), the mantissa must be
1745                             zero or odd.
1746                         */
1747                         if (!(mantissa & 1u))
1748                             throw new ASN1ValueException
1749                             (
1750                                 "This exception was thrown because you attempted to " ~
1751                                 "decode a base-2 encoded REAL whose mantissa was " ~
1752                                 "not zero or odd. Both Canonical Encoding Rules (CER) " ~
1753                                 "and Distinguished Encoding Rules (DER) require that " ~
1754                                 "a base-2 encoded REAL's mantissa be shifted so that " ~
1755                                 "it is either zero or odd. "
1756                             );
1757 
1758                         base = 0x02u;
1759                         break;
1760                     }
1761                     case (0b00010000u): base = 0x08u; break;
1762                     case (0b00100000u): base = 0x10u; break;
1763                     default:
1764                         throw new ASN1ValueUndefinedException
1765                         (
1766                             "This exception was thrown because you attempted to " ~
1767                             "decode a REAL that had both base bits in the " ~
1768                             "information block set, the meaning of which is " ~
1769                             "not specified. " ~
1770                             notWhatYouMeantText ~ forMoreInformationText ~
1771                             debugInformationText ~ reportBugsText
1772                         );
1773                 }
1774 
1775                 if (this.value[0] & 0b00001100u)
1776                     throw new ASN1ValueException
1777                     (
1778                         "This exception was thrown because you attempted to " ~
1779                         "decode a REAL whose scale was not zero. This would " ~
1780                         "not be a problem if you were using the Basic " ~
1781                         "Encoding Rules (BER), but specification X.690 " ~
1782                         "says that, when using the Canonical Encoding Rules " ~
1783                         "(CER) or Distinguished Encoding Rules (DER), the " ~
1784                         "scale must be zero. " ~
1785                         notWhatYouMeantText ~ forMoreInformationText ~
1786                         debugInformationText ~ reportBugsText
1787                     );
1788 
1789                 /*
1790                     For some reason that I have yet to discover, you must
1791                     cast the exponent to T. If you do not, specifically
1792                     any usage of realNumber!T() outside of this library will
1793                     produce a "floating point exception 8" message and
1794                     crash. For some reason, all of the tests pass within
1795                     this library without doing this.
1796                 */
1797                 return (
1798                     ((this.value[0] & 0b01000000u) ? -1.0 : 1.0) *
1799                     cast(T) mantissa *
1800                     (cast(T) base)^^(cast(T) exponent) // base must be cast
1801                 );
1802             }
1803             default: assert(0, "Impossible information byte value appeared!");
1804         }
1805     }
1806 
1807     /**
1808         Encodes a floating-point type, using base-2 binary encoding.
1809 
1810         Note that this method assumes that your machine uses
1811         $(LINK http://ieeexplore.ieee.org/document/4610935/, IEEE 754-2008)
1812         floating point format.
1813 
1814         Citations:
1815         $(UL
1816             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
1817                 $(I ASN.1: Communication between Heterogeneous Systems),
1818                 Morgan Kaufmann, 2001, pp. 400-402.)
1819             $(LI $(LINK https://www.iso.org/standard/12285.html, ISO 6093))
1820         )
1821     */
1822     public @property @system
1823     void realNumber(T)(in T value)
1824     if (isFloatingPoint!T)
1825     {
1826         scope(success) this.construction = ASN1Construction.primitive;
1827         /* NOTE:
1828             You must use isIdentical() to compare FP types to negative zero,
1829             because the basic == operator does not distinguish between zero
1830             and negative zero.
1831 
1832             isNaN() must be used to compare NaNs, because comparison using ==
1833             does not work for that at all.
1834 
1835             Also, this cannot go in a switch statement, because FP types
1836             cannot be the switch value.
1837         */
1838         if (isIdentical(value, 0.0))
1839         {
1840             this.value = [];
1841             return;
1842         }
1843         else if (isIdentical(value, -0.0))
1844         {
1845             this.value = [ ASN1SpecialRealValue.minusZero ];
1846             return;
1847         }
1848         if (value.isNaN)
1849         {
1850             this.value = [ ASN1SpecialRealValue.notANumber ];
1851             return;
1852         }
1853         else if (value == T.infinity)
1854         {
1855             this.value = [ ASN1SpecialRealValue.plusInfinity ];
1856             return;
1857         }
1858         else if (value == -T.infinity)
1859         {
1860             this.value = [ ASN1SpecialRealValue.minusInfinity ];
1861             return;
1862         }
1863 
1864         real realValue = cast(real) value;
1865         bool positive = true;
1866         ulong mantissa;
1867         short exponent;
1868 
1869         /*
1870             Per the IEEE specifications, the exponent of a floating-point
1871             type is stored with a bias, meaning that the exponent counts
1872             up from a negative number, the reaches zero at the bias. We
1873             subtract the bias from the raw binary exponent to get the
1874             actual exponent encoded in the IEEE floating-point number.
1875             In the case of an x86 80-bit extended-precision floating-point
1876             type, the bias is 16383. In the case of double-precision, it is
1877             1023. For single-precision, it is 127.
1878 
1879             We then subtract the number of bits in the fraction from the
1880             exponent, which is equivalent to having had multiplied the
1881             fraction enough to have made it an integer represented by the
1882             same sequence of bits.
1883         */
1884         ubyte[] realBytes;
1885         realBytes.length = real.sizeof;
1886         *cast(real *)&realBytes[0] = realValue;
1887 
1888         version (BigEndian)
1889         {
1890             static if (real.sizeof > 10u) realBytes = realBytes[real.sizeof-10 .. $];
1891             positive = ((realBytes[0] & 0x80u) ? false : true);
1892         }
1893         else version (LittleEndian)
1894         {
1895             static if (real.sizeof > 10u) realBytes.length = 10u;
1896             positive = ((realBytes[$-1] & 0x80u) ? false : true);
1897         }
1898         else assert(0, "Could not determine endianness");
1899 
1900         static if (real.mant_dig == 64) // x86 Extended Precision
1901         {
1902             version (BigEndian)
1903             {
1904                 exponent = (((*cast(short *) &realBytes[0]) & 0x7FFF) - 16383 - 63); // 16383 is the bias
1905                 mantissa = *cast(ulong *) &realBytes[2];
1906             }
1907             else version (LittleEndian)
1908             {
1909                 exponent = (((*cast(short *) &realBytes[8]) & 0x7FFF) - 16383 - 63); // 16383 is the bias
1910                 mantissa = *cast(ulong *) &realBytes[0];
1911             }
1912             else assert(0, "Could not determine endianness");
1913         }
1914         else if (T.mant_dig == 53) // Double Precision
1915         {
1916             /*
1917                 The IEEE 754 double-precision floating point type only stores
1918                 the fractional part of the mantissa, because there is an
1919                 implicit 1 prior to the fractional part. To retrieve the actual
1920                 mantissa encoded, we flip the bit that comes just before the
1921                 most significant bit of the fractional part of the number.
1922             */
1923             version (BigEndian)
1924             {
1925                 exponent = (((*cast(short *) &realBytes[0]) & 0x7FFF) - 1023 - 53); // 1023 is the bias
1926                 mantissa = (((*cast(ulong *) &realBytes[2]) & 0x000FFFFFFFFFFFFFu) | 0x0010000000000000u);
1927             }
1928             else version (LittleEndian)
1929             {
1930                 exponent = (((*cast(short *) &realBytes[8]) & 0x7FFF) - 1023 - 53); // 1023 is the bias
1931                 mantissa = (((*cast(ulong *) &realBytes[0]) & 0x000FFFFFFFFFFFFFu) | 0x0010000000000000u);
1932             }
1933             else assert(0, "Could not determine endianness");
1934         }
1935         else if (T.mant_dig == 24) // Single Precision
1936         {
1937             /*
1938                 The IEEE 754 single-precision floating point type only stores
1939                 the fractional part of the mantissa, because there is an
1940                 implicit 1 prior to the fractional part. To retrieve the actual
1941                 mantissa encoded, we flip the bit that comes just before the
1942                 most significant bit of the fractional part of the number.
1943             */
1944             version (BigEndian)
1945             {
1946                 exponent = ((((*cast(short *) &realBytes[0]) & 0x7F80) >> 7) - 127 - 23); // 127 is the bias
1947                 mantissa = cast(ulong) (((*cast(uint *) &realBytes[2]) & 0x007FFFFFu) | 0x00800000u);
1948             }
1949             else version (LittleEndian)
1950             {
1951                 exponent = ((((*cast(short *) &realBytes[8]) & 0x7F80) >> 7) - 127 - 23); // 127 is the bias
1952                 mantissa = cast(ulong) (((*cast(uint *) &realBytes[0]) & 0x007FFFFFu) | 0x00800000u);
1953             }
1954             else assert(0, "Could not determine endianness");
1955         }
1956         else assert(0, "Unrecognized real floating-point format.");
1957 
1958         /* NOTE:
1959             Section 11.3.1 of X.690 states that, for Canonical Encoding Rules
1960             (CER) and Distinguished Encoding Rules (DER), the mantissa must be
1961             zero or odd.
1962         */
1963         if (mantissa != 0u)
1964         {
1965             while (!(mantissa & 1u))
1966             {
1967                 mantissa >>= 1;
1968                 exponent++;
1969             }
1970             version(unittest) assert(mantissa & 1u);
1971         }
1972 
1973         ubyte[] exponentBytes;
1974         exponentBytes.length = short.sizeof;
1975         *cast(short *)exponentBytes.ptr = exponent;
1976         version (LittleEndian) exponentBytes = [ exponentBytes[1], exponentBytes[0] ]; // Manual reversal (optimization)
1977         if
1978         (
1979             (exponentBytes[0] == 0x00u && (!(exponentBytes[1] & 0x80u))) || // Unnecessary positive leading bytes
1980             (exponentBytes[0] == 0xFFu && (exponentBytes[1] & 0x80u)) // Unnecessary negative leading bytes
1981         )
1982             exponentBytes = exponentBytes[1 .. 2];
1983 
1984         ubyte[] mantissaBytes;
1985         mantissaBytes.length = ulong.sizeof;
1986         *cast(ulong *)mantissaBytes.ptr = cast(ulong) mantissa;
1987         version (LittleEndian) reverse(mantissaBytes);
1988 
1989         size_t startOfNonPadding = 0u;
1990         for (size_t i = 0u; i < mantissaBytes.length-1; i++)
1991         {
1992             if (mantissaBytes[i] != 0x00u) break;
1993             startOfNonPadding++;
1994         }
1995         mantissaBytes = mantissaBytes[startOfNonPadding .. $];
1996 
1997         ubyte infoByte =
1998             0x80u | // First bit gets set for base2, base8, or base16 encoding
1999             (positive ? 0x00u : 0x40u) | // 1 = negative, 0 = positive
2000             // Scale = 0
2001             cast(ubyte) (exponentBytes.length == 1u ?
2002                 ASN1RealExponentEncoding.followingOctet :
2003                 ASN1RealExponentEncoding.following2Octets);
2004 
2005         this.value = (infoByte ~ exponentBytes ~ mantissaBytes);
2006     }
2007 
2008     @system
2009     unittest
2010     {
2011         DERElement el = new DERElement();
2012 
2013         // float
2014         el.realNumber!float = cast(float) float.nan;
2015         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2016         el.realNumber!double = cast(double) float.nan;
2017         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2018         el.realNumber!real = cast(real) float.nan;
2019         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2020 
2021         el.realNumber!float = cast(float) 0.0;
2022         assert(el.value == []);
2023         el.realNumber!double = cast(float) 0.0;
2024         assert(el.value == []);
2025         el.realNumber!real = cast(float) 0.0;
2026         assert(el.value == []);
2027 
2028         el.realNumber!float = cast(float) -0.0;
2029         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2030         el.realNumber!double = cast(float) -0.0;
2031         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2032         el.realNumber!real = cast(float) -0.0;
2033         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2034 
2035         el.realNumber!float = cast(float) float.infinity;
2036         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2037         el.realNumber!double = cast(double) float.infinity;
2038         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2039         el.realNumber!real = cast(real) float.infinity;
2040         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2041 
2042         el.realNumber!float = cast(float) -float.infinity;
2043         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2044         el.realNumber!double = cast(double) -float.infinity;
2045         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2046         el.realNumber!real = cast(real) -float.infinity;
2047         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2048 
2049         // double
2050         el.realNumber!float = cast(float) double.nan;
2051         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2052         el.realNumber!double = cast(double) double.nan;
2053         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2054         el.realNumber!real = cast(real) double.nan;
2055         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2056 
2057         el.realNumber!float = cast(double) 0.0;
2058         assert(el.value == []);
2059         el.realNumber!double = cast(double) 0.0;
2060         assert(el.value == []);
2061         el.realNumber!real = cast(double) 0.0;
2062         assert(el.value == []);
2063 
2064         el.realNumber!float = cast(double) -0.0;
2065         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2066         el.realNumber!double = cast(double) -0.0;
2067         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2068         el.realNumber!real = cast(double) -0.0;
2069         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2070 
2071         el.realNumber!float = cast(float) double.infinity;
2072         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2073         el.realNumber!double = cast(double) double.infinity;
2074         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2075         el.realNumber!real = cast(real) double.infinity;
2076         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2077 
2078         el.realNumber!float = cast(float) -double.infinity;
2079         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2080         el.realNumber!double = cast(double) -double.infinity;
2081         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2082         el.realNumber!real = cast(real) -double.infinity;
2083         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2084 
2085         // real
2086         el.realNumber!float = cast(float) real.nan;
2087         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2088         el.realNumber!double = cast(double) real.nan;
2089         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2090         el.realNumber!real = cast(real) real.nan;
2091         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2092 
2093         el.realNumber!float = cast(real) 0.0;
2094         assert(el.value == []);
2095         el.realNumber!double = cast(real) 0.0;
2096         assert(el.value == []);
2097         el.realNumber!real = cast(real) 0.0;
2098         assert(el.value == []);
2099 
2100         el.realNumber!float = cast(real) -0.0;
2101         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2102         el.realNumber!double = cast(real) -0.0;
2103         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2104         el.realNumber!real = cast(real) -0.0;
2105         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2106 
2107         el.realNumber!float = cast(float) real.infinity;
2108         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2109         el.realNumber!double = cast(double) real.infinity;
2110         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2111         el.realNumber!real = cast(real) real.infinity;
2112         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2113 
2114         el.realNumber!float = cast(float) -real.infinity;
2115         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2116         el.realNumber!double = cast(double) -real.infinity;
2117         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2118         el.realNumber!real = cast(real) -real.infinity;
2119         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2120     }
2121 
2122     // Positive Testing Base-10 (Character-Encoded) REALs
2123     @system
2124     unittest
2125     {
2126         immutable string[] tests = [
2127             "1.E1",
2128             "2.E10",
2129             "4.E100",
2130             "1.E-1",
2131             "2.E-10",
2132             "4.E-100",
2133             "-1.E1",
2134             "-2.E10",
2135             "-4.E100",
2136             "-1.E-1",
2137             "-2.E-10",
2138             "-4.E-100",
2139             "19.E1",
2140             "29.E10",
2141             "49.E100",
2142             "19.E-1",
2143             "29.E-10",
2144             "49.E-100",
2145             "-19.E1",
2146             "-29.E10",
2147             "-49.E100",
2148             "-19.E-1",
2149             "-29.E-10",
2150             "-49.E-100",
2151             "33.E+0"
2152         ];
2153 
2154         DERElement el = new DERElement();
2155 
2156         foreach (test; tests)
2157         {
2158             el.value = [ 0b00000011u ];
2159             el.value ~= cast(ubyte[]) test;
2160             assertNotThrown!ASN1ValueException(el.realNumber!float);
2161             assertNotThrown!ASN1ValueException(el.realNumber!double);
2162             assertNotThrown!ASN1ValueException(el.realNumber!real);
2163         }
2164     }
2165 
2166     // Negative Testing Base-10 (Character-Encoded) REALs
2167     @system
2168     unittest
2169     {
2170         immutable string[] tests = [
2171             " 1.E1", // Leading whitespace
2172             "1.E1 ", // Trailing whitespace
2173             "1 .E1", // Internal whitespace
2174             "1. E1", // Internal whitespace
2175             "1.E 1", // Internal whitespace
2176             "+1.E1", // Leading plus sign
2177             "01.E1", // Leading zero
2178             "10.E1", // Trailing zero
2179             "1.0E1", // Fractional zero
2180             "1.E+1", // Leading plus sign
2181             "1.E01", // Leading zero
2182             "1E100", // No decimal point
2183             "1.1",   // No 'E'
2184             ""       // Empty string
2185         ];
2186 
2187         DERElement el = new DERElement();
2188 
2189         foreach (test; tests)
2190         {
2191             el.value = [ 0b00000011u ];
2192             el.value ~= cast(ubyte[]) test;
2193             assertThrown!ASN1ValueException(el.realNumber!float);
2194             assertThrown!ASN1ValueException(el.realNumber!double);
2195             assertThrown!ASN1ValueException(el.realNumber!real);
2196         }
2197     }
2198 
2199     /**
2200         Decodes a signed integer, which represents a selection from an
2201         $(MONO ENUMERATION) of choices.
2202 
2203         Throws:
2204         $(UL
2205             $(LI $(D ASN1ConstructionException)
2206                 if the encoded value is not primitively-constructed)
2207             $(LI $(D ASN1ValueSizeException)
2208                 if the value is too big to decode to a signed integral type,
2209                 or if the value is zero bytes)
2210             $(LI $(D ASN1ValuePaddingException)
2211                 if there are padding bytes)
2212         )
2213     */
2214     public @property @system
2215     T enumerated(T)() const
2216     if (isIntegral!T && isSigned!T)
2217     {
2218         if (this.construction != ASN1Construction.primitive)
2219             throw new ASN1ConstructionException
2220             (this.construction, "decode an ENUMERATED");
2221 
2222         if (this.value.length == 1u)
2223             return cast(T) cast(byte) this.value[0];
2224 
2225         if (this.value.length == 0u || this.value.length > T.sizeof)
2226             throw new ASN1ValueSizeException
2227             (1u, long.sizeof, this.value.length, "decode an ENUMERATED");
2228 
2229         if
2230         (
2231             (this.value[0] == 0x00u && (!(this.value[1] & 0x80u))) || // Unnecessary positive leading bytes
2232             (this.value[0] == 0xFFu && (this.value[1] & 0x80u)) // Unnecessary negative leading bytes
2233         )
2234             throw new ASN1ValuePaddingException
2235             (
2236                 "This exception was thrown because you attempted to decode " ~
2237                 "an ENUMERATED that was encoded on more than the minimum " ~
2238                 "necessary bytes. " ~
2239                 notWhatYouMeantText ~ forMoreInformationText ~
2240                 debugInformationText ~ reportBugsText
2241             );
2242 
2243         /* NOTE:
2244             Because the ENUMERATED is stored in two's complement form, you
2245             can't just apppend 0x00u to the big end of it until it is as long
2246             as T in bytes, then cast to T. Instead, you have to first determine
2247             if the encoded integer is negative or positive. If it is negative,
2248             then you actually want to append 0xFFu to the big end until it is
2249             as big as T, so you get the two's complement form of whatever T
2250             you choose.
2251 
2252             The line immediately below this determines whether the padding byte
2253             should be 0xFF or 0x00 based on the most significant bit of the
2254             most significant byte (which, since DER encodes big-endian, will
2255             always be the first byte). If set (1), the number is negative, and
2256             hence, the padding byte should be 0xFF. If not, it is positive,
2257             and the padding byte should be 0x00.
2258         */
2259         immutable ubyte paddingByte = ((this.value[0] & 0x80u) ? 0xFFu : 0x00u);
2260         ubyte[] value = this.value.dup; // Duplication is necessary to prevent modifying the source bytes
2261         while (value.length < T.sizeof)
2262             value = (paddingByte ~ value);
2263         version (LittleEndian) reverse(value);
2264         version (unittest) assert(value.length == T.sizeof);
2265         return *cast(T *) value.ptr;
2266     }
2267 
2268     /// Encodes an $(MONO ENUMERATED) type from an integer.
2269     public @property @system nothrow
2270     void enumerated(T)(in T value)
2271     out
2272     {
2273         assert(this.value.length > 0u);
2274     }
2275     body
2276     {
2277         scope(success) this.construction = ASN1Construction.primitive;
2278         if (value <= byte.max && value >= byte.min)
2279         {
2280             this.value = [ cast(ubyte) cast(byte) value ];
2281             return;
2282         }
2283 
2284         ubyte[] ub;
2285         ub.length = T.sizeof;
2286         *cast(T *)&ub[0] = value;
2287         version (LittleEndian) reverse(ub);
2288 
2289         /*
2290             An ENUMERATED must be encoded on the fewest number of bytes than can
2291             encode it. The loops below identify how many bytes can be
2292             truncated from the start of the ENUMERATED, with one loop for positive
2293             and another loop for negative numbers. ENUMERATED is encoded in the
2294             same exact way that INTEGER is encoded.
2295 
2296             From X.690, Section 8.3.2:
2297 
2298             If the contents octets of an integer value encoding consist of more
2299             than one octet, then the bits of the first octet and bit 8 of the
2300             second octet:
2301                 a) shall not all be ones; and
2302                 b) shall not all be zero.
2303                 NOTE – These rules ensure that an integer value is always
2304                 encoded in the smallest possible number of octets.
2305         */
2306         size_t startOfNonPadding = 0u;
2307         if (T.sizeof > 1u)
2308         {
2309             if (value >= 0)
2310             {
2311                 for (size_t i = 0u; i < ub.length-1; i++)
2312                 {
2313                     if (ub[i] != 0x00u) break;
2314                     if (!(ub[i+1] & 0x80u)) startOfNonPadding++;
2315                 }
2316             }
2317             else
2318             {
2319                 for (size_t i = 0u; i < ub.length-1; i++)
2320                 {
2321                     if (ub[i] != 0xFFu) break;
2322                     if (ub[i+1] & 0x80u) startOfNonPadding++;
2323                 }
2324             }
2325         }
2326 
2327         this.value = ub[startOfNonPadding .. $];
2328     }
2329 
2330     // Ensure that ENUMERATED 0 gets encoded on a single null byte.
2331     @system
2332     unittest
2333     {
2334         DERElement el = new DERElement();
2335 
2336         el.enumerated!byte = cast(byte) 0x00;
2337         assert(el.value == [ 0x00u ]);
2338 
2339         el.enumerated!short = cast(short) 0x0000;
2340         assert(el.value == [ 0x00u ]);
2341 
2342         el.enumerated!int = cast(int) 0;
2343         assert(el.value == [ 0x00u ]);
2344 
2345         el.enumerated!long = cast(long) 0;
2346         assert(el.value == [ 0x00u ]);
2347 
2348         el.value = [];
2349         assertThrown!ASN1ValueSizeException(el.enumerated!byte);
2350         assertThrown!ASN1ValueSizeException(el.enumerated!short);
2351         assertThrown!ASN1ValueSizeException(el.enumerated!int);
2352         assertThrown!ASN1ValueSizeException(el.enumerated!long);
2353     }
2354 
2355     // Test encoding -0 for the sake of CVE-2016-2108
2356     @system
2357     unittest
2358     {
2359         DERElement el = new DERElement();
2360 
2361         el.enumerated!byte = -0;
2362         assertNotThrown!RangeError(el.enumerated!byte);
2363         assertNotThrown!ASN1Exception(el.enumerated!byte);
2364 
2365         el.enumerated!short = -0;
2366         assertNotThrown!RangeError(el.enumerated!short);
2367         assertNotThrown!ASN1Exception(el.enumerated!short);
2368 
2369         el.enumerated!int = -0;
2370         assertNotThrown!RangeError(el.enumerated!int);
2371         assertNotThrown!ASN1Exception(el.enumerated!int);
2372 
2373         el.enumerated!long = -0;
2374         assertNotThrown!RangeError(el.enumerated!long);
2375         assertNotThrown!ASN1Exception(el.enumerated!long);
2376     }
2377 
2378     /**
2379         Decodes an $(MONO EmbeddedPDV), which is a constructed data type, defined in
2380             the $(LINK https://www.itu.int, International Telecommunications Union)'s
2381             $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680).
2382 
2383         The specification defines $(MONO EmbeddedPDV) as:
2384 
2385         $(PRE
2386             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2387                 identification CHOICE {
2388                     syntaxes SEQUENCE {
2389                         abstract OBJECT IDENTIFIER,
2390                         transfer OBJECT IDENTIFIER },
2391                     syntax OBJECT IDENTIFIER,
2392                     presentation-context-id INTEGER,
2393                     context-negotiation SEQUENCE {
2394                         presentation-context-id INTEGER,
2395                         transfer-syntax OBJECT IDENTIFIER },
2396                     transfer-syntax OBJECT IDENTIFIER,
2397                     fixed NULL },
2398                 data-value-descriptor ObjectDescriptor OPTIONAL,
2399                 data-value OCTET STRING }
2400             (WITH COMPONENTS { ... , data-value-descriptor ABSENT })
2401         )
2402 
2403         This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification)
2404         choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5.
2405 
2406         The following additional constraints are applied to the abstract syntax
2407         when using Canonical Encoding Rules or Distinguished Encoding Rules,
2408         which are also defined in the
2409         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
2410         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules):
2411 
2412         $(PRE
2413             EmbeddedPDV ( WITH COMPONENTS {
2414                 ... ,
2415                 identification ( WITH COMPONENTS {
2416                     ... ,
2417                     presentation-context-id ABSENT,
2418                     context-negotiation ABSENT } ) } )
2419         )
2420 
2421         The stated purpose of the constraints shown above is to restrict the use of
2422         the $(MONO presentation-context-id), either by itself or within the
2423         context-negotiation, which makes the following the effective abstract
2424         syntax of $(MONO EmbeddedPDV) when using Canonical Encoding Rules or
2425         Distinguished Encoding Rules:
2426 
2427         $(PRE
2428             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2429                 identification CHOICE {
2430                     syntaxes SEQUENCE {
2431                         abstract OBJECT IDENTIFIER,
2432                         transfer OBJECT IDENTIFIER },
2433                     syntax OBJECT IDENTIFIER,
2434                     presentation-context-id INTEGER,
2435                     context-negotiation SEQUENCE {
2436                         presentation-context-id INTEGER,
2437                         transfer-syntax OBJECT IDENTIFIER },
2438                     transfer-syntax OBJECT IDENTIFIER,
2439                     fixed NULL },
2440                 data-value-descriptor ObjectDescriptor OPTIONAL,
2441                 data-value OCTET STRING }
2442                     ( WITH COMPONENTS {
2443                         ... ,
2444                         identification ( WITH COMPONENTS {
2445                             ... ,
2446                             presentation-context-id ABSENT,
2447                             context-negotiation ABSENT } ) } )
2448         )
2449 
2450         With the constraints applied, the abstract syntax for $(MONO EmbeddedPDV)s encoded
2451         using Canonical Encoding Rules or Distinguished Encoding Rules becomes:
2452 
2453         $(PRE
2454             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2455                 identification CHOICE {
2456                     syntaxes SEQUENCE {
2457                         abstract OBJECT IDENTIFIER,
2458                         transfer OBJECT IDENTIFIER },
2459                     syntax OBJECT IDENTIFIER,
2460                     transfer-syntax OBJECT IDENTIFIER,
2461                     fixed NULL },
2462                 data-value-descriptor ObjectDescriptor OPTIONAL,
2463                 data-value OCTET STRING }
2464         )
2465 
2466         Returns: an instance of $(D types.universal.embeddedpdv.EmbeddedPDV)
2467 
2468         Throws:
2469         $(UL
2470             $(LI $(D ASN1ValueException) if encoded EmbeddedPDV has too few or too many
2471                 elements, or if syntaxes or context-negotiation element has
2472                 too few or too many elements)
2473             $(LI $(D ASN1ValueSizeException) if encoded INTEGER is too large to decode)
2474             $(LI $(D ASN1RecursionException) if using constructed form and the element
2475                 is constructed of too many nested constructed elements)
2476             $(LI $(D ASN1TagClassException) if any nested primitives do not have the
2477                 correct tag class)
2478             $(LI $(D ASN1ConstructionException) if any element has the wrong construction)
2479             $(LI $(D ASN1TagNumberException) if any nested primitives do not have the
2480                 correct tag number)
2481         )
2482     */
2483     override public @property @system
2484     EmbeddedPDV embeddedPresentationDataValue() const
2485     {
2486         if (this.construction != ASN1Construction.constructed)
2487             throw new ASN1ConstructionException
2488             (this.construction, "decode a EmbeddedPDV");
2489 
2490         const DERElement[] components = this.sequence;
2491         ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID();
2492 
2493         if (components.length != 2u)
2494             throw new ASN1ValueException
2495             (
2496                 "This exception was thrown because you attempted to decode " ~
2497                 "an EmbeddedPDV that contained too many or too few elements. " ~
2498                 "An EmbeddedPDV should have only two elements: " ~
2499                 "an identification CHOICE, and a data-value OCTET STRING, " ~
2500                 "in that order. " ~
2501                 notWhatYouMeantText ~ forMoreInformationText ~
2502                 debugInformationText ~ reportBugsText
2503             );
2504 
2505         if (components[0].tagClass != ASN1TagClass.contextSpecific)
2506             throw new ASN1TagClassException
2507             (
2508                 [ ASN1TagClass.contextSpecific ],
2509                 components[0].tagClass,
2510                 "decode the first component of an EmbeddedPDV"
2511             );
2512 
2513         if (components[1].tagClass != ASN1TagClass.contextSpecific)
2514             throw new ASN1TagClassException
2515             (
2516                 [ ASN1TagClass.contextSpecific ],
2517                 components[1].tagClass,
2518                 "decode the second component of an EmbeddedPDV"
2519             );
2520 
2521         /* NOTE:
2522             See page 224 of Dubuisson, item 11:
2523             It sounds like, even if you have an ABSENT constraint applied,
2524             all automatically-tagged items still have the same numbers as
2525             though the constrained component were PRESENT.
2526         */
2527         if (components[0].tagNumber != 0u)
2528             throw new ASN1TagNumberException
2529             ([ 0u ], components[0].tagNumber, "decode the first component of an EmbeddedPDV");
2530 
2531         if (components[1].tagNumber != 2u)
2532             throw new ASN1TagNumberException
2533             ([ 2u ], components[1].tagNumber, "decode the second component of an EmbeddedPDV");
2534 
2535         ubyte[] bytes = components[0].value.dup;
2536         const DERElement identificationChoice = new DERElement(bytes);
2537         switch (identificationChoice.tagNumber)
2538         {
2539             case (0u): // syntaxes
2540             {
2541                 if (identificationChoice.construction != ASN1Construction.constructed)
2542                     throw new ASN1ConstructionException
2543                     (identificationChoice.construction, "decode the syntaxes component of an EmbeddedPDV");
2544 
2545                 const DERElement[] syntaxesComponents = identificationChoice.sequence;
2546 
2547                 if (syntaxesComponents.length != 2u)
2548                     throw new ASN1ValueException
2549                     (
2550                         "This exception was thrown because you attempted to " ~
2551                         "decode an EmbeddedPDV whose syntaxes component " ~
2552                         "contained an invalid number of elements. The " ~
2553                         "syntaxes component should contain abstract and transfer " ~
2554                         "syntax OBJECT IDENTIFIERS, in that order. " ~
2555                         notWhatYouMeantText ~ forMoreInformationText ~
2556                         debugInformationText ~ reportBugsText
2557                     );
2558 
2559                 // Class Validation
2560                 if (syntaxesComponents[0].tagClass != ASN1TagClass.contextSpecific)
2561                     throw new ASN1TagClassException
2562                     (
2563                         [ ASN1TagClass.contextSpecific ],
2564                         syntaxesComponents[0].tagClass,
2565                         "decode the first syntaxes component of an EmbeddedPDV"
2566                     );
2567 
2568                 if (syntaxesComponents[1].tagClass != ASN1TagClass.contextSpecific)
2569                     throw new ASN1TagClassException
2570                     (
2571                         [ ASN1TagClass.contextSpecific ],
2572                         syntaxesComponents[1].tagClass,
2573                         "decode the second syntaxes component of an EmbeddedPDV"
2574                     );
2575 
2576                 // Construction Validation
2577                 if (syntaxesComponents[0].construction != ASN1Construction.primitive)
2578                     throw new ASN1ConstructionException
2579                     (syntaxesComponents[0].construction, "decode the first syntaxes component of an EmbeddedPDV");
2580 
2581                 if (syntaxesComponents[1].construction != ASN1Construction.primitive)
2582                     throw new ASN1ConstructionException
2583                     (syntaxesComponents[1].construction, "decode the second syntaxes component of an EmbeddedPDV");
2584 
2585                 // Number Validation
2586                 if (syntaxesComponents[0].tagNumber != 0u)
2587                     throw new ASN1TagNumberException
2588                     (
2589                         [ 0u ],
2590                         syntaxesComponents[0].tagNumber,
2591                         "decode the first syntaxes component of an EmbeddedPDV"
2592                     );
2593 
2594                 if (syntaxesComponents[1].tagNumber != 1u)
2595                     throw new ASN1TagNumberException
2596                     (
2597                         [ 1u ],
2598                         syntaxesComponents[1].tagNumber,
2599                         "decode the second syntaxes component of an EmbeddedPDV"
2600                     );
2601 
2602                 identification.syntaxes  = ASN1Syntaxes(
2603                     syntaxesComponents[0].objectIdentifier,
2604                     syntaxesComponents[1].objectIdentifier
2605                 );
2606 
2607                 break;
2608             }
2609             case (1u): // syntax
2610             {
2611                 identification.syntax = identificationChoice.objectIdentifier;
2612                 break;
2613             }
2614             case (4u): // transfer-syntax
2615             {
2616                 identification.transferSyntax = identificationChoice.objectIdentifier;
2617                 break;
2618             }
2619             case (5u): // fixed
2620             {
2621                 identification.fixed = true;
2622                 break;
2623             }
2624             default:
2625                 throw new ASN1TagNumberException
2626                 (
2627                     [ 0u, 1u, 4u, 5u ],
2628                     identificationChoice.tagNumber,
2629                     "decode an EmbeddedPDV identification"
2630                 );
2631         }
2632 
2633         EmbeddedPDV pdv = EmbeddedPDV();
2634         pdv.identification = identification;
2635         pdv.dataValue = components[1].octetString;
2636         return pdv;
2637     }
2638 
2639     /**
2640         Encodes an $(MONO EmbeddedPDV), which is a constructed data type, defined in
2641             the $(LINK https://www.itu.int, International Telecommunications Union)'s
2642             $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680).
2643 
2644         The specification defines $(MONO EmbeddedPDV) as:
2645 
2646         $(PRE
2647             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2648                 identification CHOICE {
2649                     syntaxes SEQUENCE {
2650                         abstract OBJECT IDENTIFIER,
2651                         transfer OBJECT IDENTIFIER },
2652                     syntax OBJECT IDENTIFIER,
2653                     presentation-context-id INTEGER,
2654                     context-negotiation SEQUENCE {
2655                         presentation-context-id INTEGER,
2656                         transfer-syntax OBJECT IDENTIFIER },
2657                     transfer-syntax OBJECT IDENTIFIER,
2658                     fixed NULL },
2659                 data-value-descriptor ObjectDescriptor OPTIONAL,
2660                 data-value OCTET STRING }
2661             (WITH COMPONENTS { ... , data-value-descriptor ABSENT })
2662         )
2663 
2664         This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification)
2665         choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5.
2666 
2667         The following additional constraints are applied to the abstract syntax
2668         when using Canonical Encoding Rules or Distinguished Encoding Rules,
2669         which are also defined in the
2670         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
2671         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules):
2672 
2673         $(PRE
2674             EmbeddedPDV ( WITH COMPONENTS {
2675                 ... ,
2676                 identification ( WITH COMPONENTS {
2677                     ... ,
2678                     presentation-context-id ABSENT,
2679                     context-negotiation ABSENT } ) } )
2680         )
2681 
2682         The stated purpose of the constraints shown above is to restrict the use of
2683         the $(MONO presentation-context-id), either by itself or within the
2684         context-negotiation, which makes the following the effective abstract
2685         syntax of $(MONO EmbeddedPDV) when using Canonical Encoding Rules or
2686         Distinguished Encoding Rules:
2687 
2688         $(PRE
2689             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2690                 identification CHOICE {
2691                     syntaxes SEQUENCE {
2692                         abstract OBJECT IDENTIFIER,
2693                         transfer OBJECT IDENTIFIER },
2694                     syntax OBJECT IDENTIFIER,
2695                     presentation-context-id INTEGER,
2696                     context-negotiation SEQUENCE {
2697                         presentation-context-id INTEGER,
2698                         transfer-syntax OBJECT IDENTIFIER },
2699                     transfer-syntax OBJECT IDENTIFIER,
2700                     fixed NULL },
2701                 data-value-descriptor ObjectDescriptor OPTIONAL,
2702                 data-value OCTET STRING }
2703                     ( WITH COMPONENTS {
2704                         ... ,
2705                         identification ( WITH COMPONENTS {
2706                             ... ,
2707                             presentation-context-id ABSENT,
2708                             context-negotiation ABSENT } ) } )
2709         )
2710 
2711         With the constraints applied, the abstract syntax for $(MONO EmbeddedPDV)s encoded
2712         using Canonical Encoding Rules or Distinguished Encoding Rules becomes:
2713 
2714         $(PRE
2715             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2716                 identification CHOICE {
2717                     syntaxes SEQUENCE {
2718                         abstract OBJECT IDENTIFIER,
2719                         transfer OBJECT IDENTIFIER },
2720                     syntax OBJECT IDENTIFIER,
2721                     transfer-syntax OBJECT IDENTIFIER,
2722                     fixed NULL },
2723                 data-value-descriptor ObjectDescriptor OPTIONAL,
2724                 data-value OCTET STRING }
2725         )
2726 
2727         If the supplied $(MONO identification) for the EmbeddedPDV is a
2728         $(MONO presentation-context-id) or a $(MONO context-negotiation),
2729         no exception will be thrown; the $(MONO identification) will be set to
2730         $(MONO fixed) silently.
2731 
2732         Throws:
2733         $(UL
2734             $(LI $(D ASN1ValueException) if encoded ObjectDescriptor contains invalid characters)
2735         )
2736     */
2737     override public @property @system
2738     void embeddedPresentationDataValue(in EmbeddedPDV value)
2739     out
2740     {
2741         assert(this.value.length > 0u);
2742     }
2743     body
2744     {
2745         scope(success) this.construction = ASN1Construction.constructed;
2746         DERElement identification = new DERElement();
2747         identification.tagClass = ASN1TagClass.contextSpecific;
2748         identification.tagNumber = 0u; // CHOICE is EXPLICIT, even with automatic tagging.
2749 
2750         DERElement identificationChoice = new DERElement();
2751         identificationChoice.tagClass = ASN1TagClass.contextSpecific;
2752         if (!(value.identification.syntaxes.isNull))
2753         {
2754             DERElement abstractSyntax = new DERElement();
2755             abstractSyntax.tagClass = ASN1TagClass.contextSpecific;
2756             abstractSyntax.tagNumber = 0u;
2757             abstractSyntax.objectIdentifier = value.identification.syntaxes.abstractSyntax;
2758 
2759             DERElement transferSyntax = new DERElement();
2760             transferSyntax.tagClass = ASN1TagClass.contextSpecific;
2761             transferSyntax.tagNumber = 1u;
2762             transferSyntax.objectIdentifier = value.identification.syntaxes.transferSyntax;
2763 
2764             identificationChoice.construction = ASN1Construction.constructed;
2765             identificationChoice.tagNumber = 0u;
2766             identificationChoice.sequence = [ abstractSyntax, transferSyntax ];
2767         }
2768         else if (!(value.identification.syntax.isNull))
2769         {
2770             identificationChoice.tagNumber = 1u;
2771             identificationChoice.objectIdentifier = value.identification.syntax;
2772         }
2773         else if (!(value.identification.transferSyntax.isNull))
2774         {
2775             identificationChoice.tagNumber = 4u;
2776             identificationChoice.objectIdentifier = value.identification.transferSyntax;
2777         }
2778         else
2779         {
2780             identificationChoice.tagNumber = 5u;
2781             identificationChoice.value = [];
2782         }
2783 
2784         // This makes identification: [CONTEXT 0][L][CONTEXT #][L][V]
2785         identification.value = cast(ubyte[]) identificationChoice;
2786 
2787         DERElement dataValue = new DERElement();
2788         dataValue.tagClass = ASN1TagClass.contextSpecific;
2789         dataValue.tagNumber = 2u;
2790         dataValue.octetString = value.dataValue;
2791 
2792         this.sequence = [ identification, dataValue ];
2793     }
2794 
2795     /* NOTE:
2796         This unit test had to be moved out of ASN1Element because DER and CER
2797         do not support encoding of presentation-context-id in EmbeddedPDV.
2798 
2799         This unit test ensures that, if you attempt to create an EmbeddedPDV
2800         with presentation-context-id as the CHOICE of identification, the
2801         encoded EmbeddedPDV's identification defaults to fixed.
2802     */
2803     @system
2804     unittest
2805     {
2806         ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID();
2807         id.presentationContextID = 27L;
2808 
2809         EmbeddedPDV input = EmbeddedPDV();
2810         input.identification = id;
2811         input.dataValue = [ 0x01u, 0x02u, 0x03u, 0x04u ];
2812 
2813         DERElement el = new DERElement();
2814         el.tagNumber = 0x08u;
2815         el.embeddedPDV = input;
2816         EmbeddedPDV output = el.embeddedPDV;
2817         assert(output.identification.fixed == true);
2818         assert(output.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]);
2819     }
2820 
2821     /* NOTE:
2822         This unit test had to be moved out of ASN1Element because DER and CER
2823         do not support encoding of context-negotiation in EmbeddedPDV.
2824 
2825         This unit test ensures that, if you attempt to create an EmbeddedPDV
2826         with context-negotiation as the CHOICE of identification, the
2827         encoded EmbeddedPDV's identification defaults to fixed.
2828     */
2829     @system
2830     unittest
2831     {
2832         ASN1ContextNegotiation cn = ASN1ContextNegotiation();
2833         cn.presentationContextID = 27L;
2834         cn.transferSyntax = new OID(1, 3, 6, 4, 1, 256, 39);
2835 
2836         ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID();
2837         id.contextNegotiation = cn;
2838 
2839         EmbeddedPDV input = EmbeddedPDV();
2840         input.identification = id;
2841         input.dataValue = [ 0x13u, 0x15u, 0x17u, 0x19u ];
2842 
2843         DERElement el = new DERElement();
2844         el.embeddedPDV = input;
2845         EmbeddedPDV output = el.embeddedPDV;
2846         assert(output.identification.fixed == true);
2847         assert(output.dataValue == [ 0x13u, 0x15u, 0x17u, 0x19u ]);
2848     }
2849 
2850     // Inspired by CVE-2017-9023
2851     @system
2852     unittest
2853     {
2854         ubyte[] data = [ // This is valid.
2855             0x0Bu, 0x0Au, // EmbeddedPDV, Length 11
2856                 0x80u, 0x02u, // CHOICE
2857                     0x85u, 0x00u, // NULL
2858                 0x82u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u ]; // OCTET STRING
2859 
2860         // Valid values for data[2]: 80
2861         for (ubyte i = 0x81u; i < 0x9Eu; i++)
2862         {
2863             data[2] = i;
2864             size_t x = 0u;
2865             DERElement el = new DERElement(x, data);
2866             assertThrown!ASN1Exception(el.embeddedPDV);
2867         }
2868 
2869         // Valid values for data[4]: 80-85
2870         for (ubyte i = 0x86u; i < 0x9Eu; i++)
2871         {
2872             data[4] = i;
2873             size_t x = 0u;
2874             DERElement el = new DERElement(x, data);
2875             assertThrown!ASN1Exception(el.embeddedPDV);
2876         }
2877 
2878         // Valid values for data[6]: 82
2879         for (ubyte i = 0x83u; i < 0x9Eu; i++)
2880         {
2881             data[6] = i;
2882             size_t x = 0u;
2883             DERElement el = new DERElement(x, data);
2884             assertThrown!ASN1Exception(el.embeddedPDV);
2885         }
2886     }
2887 
2888     /**
2889         Decodes the value to UTF-8 characters.
2890 
2891         Throws:
2892         $(UL
2893             $(LI $(D UTF8Exception)
2894                 if the encoded value does not decode to UTF-8)
2895             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
2896         )
2897     */
2898     override public @property @system
2899     string unicodeTransformationFormat8String() const
2900     {
2901         if (this.construction != ASN1Construction.primitive)
2902             throw new ASN1ConstructionException
2903             (this.construction, "decode a UTF8String");
2904 
2905         return cast(string) this.value;
2906     }
2907 
2908     /// Encodes a UTF-8 string to bytes.
2909     override public @property @system nothrow
2910     void unicodeTransformationFormat8String(in string value)
2911     {
2912         scope(success) this.construction = ASN1Construction.primitive;
2913         this.value = cast(ubyte[]) value.dup;
2914     }
2915 
2916     /**
2917         Decodes a $(MONO RELATIVE OBJECT IDENTIFIER).
2918 
2919         Throws:
2920         $(UL
2921             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
2922             $(LI $(D ASN1ValuePaddingException) if a single OID number is encoded with
2923                 "leading zero bytes" ($(D 0x80u)))
2924             $(LI $(D ASN1ValueOverflowException) if a single OID number is too big to
2925                 decode to a $(D size_t))
2926             $(LI $(D ASN1TruncationException) if a single OID number is "cut off")
2927         )
2928 
2929         Standards:
2930         $(UL
2931             $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660))
2932         )
2933     */
2934     override public @property @system
2935     OIDNode[] relativeObjectIdentifier() const
2936     {
2937         if (this.construction != ASN1Construction.primitive)
2938             throw new ASN1ConstructionException
2939             (this.construction, "decode a RELATIVE OID");
2940 
2941         if (this.value.length == 0u) return [];
2942         foreach (immutable octet; this.value)
2943         {
2944             if (octet == 0x80u)
2945                 throw new ASN1ValuePaddingException
2946                 (
2947                     "This exception was thrown because you attempted to decode " ~
2948                     "a RELATIVE OID that contained a number that was " ~
2949                     "encoded on more than the minimum necessary octets. This " ~
2950                     "is indicated by an occurrence of the octet 0x80, which " ~
2951                     "is the encoded equivalent of a leading zero. " ~
2952                     notWhatYouMeantText ~ forMoreInformationText ~
2953                     debugInformationText ~ reportBugsText
2954                 );
2955         }
2956 
2957         if (this.value[$-1] > 0x80u)
2958             throw new ASN1TruncationException
2959             (size_t.max, this.value.length, "decode a RELATIVE OID");
2960 
2961         // Breaks bytes into groups, where each group encodes one OID component.
2962         ubyte[][] byteGroups;
2963         size_t lastTerminator = 0u;
2964         for (size_t i = 0u; i < this.length; i++)
2965         {
2966             if (!(this.value[i] & 0x80u))
2967             {
2968                 byteGroups ~= cast(ubyte[]) this.value[lastTerminator .. i+1];
2969                 lastTerminator = i+1;
2970             }
2971         }
2972 
2973         // Converts each group of bytes to a number.
2974         size_t[] numbers;
2975         foreach (byteGroup; byteGroups)
2976         {
2977             if (byteGroup.length > size_t.sizeof)
2978                 throw new ASN1ValueOverflowException
2979                 (
2980                     "This exception was thrown because you attempted to decode " ~
2981                     "a RELATIVE OID that encoded a number on more than " ~
2982                     "size_t.sizeof bytes. " ~
2983                     notWhatYouMeantText ~ forMoreInformationText ~
2984                     debugInformationText ~ reportBugsText
2985                 );
2986 
2987             numbers ~= 0u;
2988             for (size_t i = 0u; i < byteGroup.length; i++)
2989             {
2990                 numbers[$-1] <<= 7;
2991                 numbers[$-1] |= cast(size_t) (byteGroup[i] & 0x7Fu);
2992             }
2993         }
2994 
2995         // Constructs the array of OIDNodes from the array of numbers.
2996         OIDNode[] nodes;
2997         foreach (number; numbers)
2998         {
2999             nodes ~= OIDNode(number);
3000         }
3001 
3002         return nodes;
3003     }
3004 
3005     /**
3006         Encodes a $(MONO RELATIVE OBJECT IDENTIFIER).
3007 
3008         Standards:
3009             $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660)
3010     */
3011     override public @property @system nothrow
3012     void relativeObjectIdentifier(in OIDNode[] value)
3013     {
3014         scope(success) this.construction = ASN1Construction.primitive;
3015         foreach (node; value)
3016         {
3017             size_t number = node.number;
3018             if (number < 128u)
3019             {
3020                 this.value ~= cast(ubyte) number;
3021                 continue;
3022             }
3023 
3024             ubyte[] encodedOIDNode;
3025             while (number != 0u)
3026             {
3027                 ubyte[] numberBytes;
3028                 numberBytes.length = size_t.sizeof;
3029                 *cast(size_t *) numberBytes.ptr = number;
3030                 if ((numberBytes[0] & 0x80u) == 0u) numberBytes[0] |= 0x80u;
3031                 encodedOIDNode = numberBytes[0] ~ encodedOIDNode;
3032                 number >>= 7u;
3033             }
3034 
3035             encodedOIDNode[$-1] &= 0x7Fu;
3036             this.value ~= encodedOIDNode;
3037         }
3038     }
3039 
3040     @system
3041     unittest
3042     {
3043         DERElement element = new DERElement();
3044 
3045         // All values of octet[0] should pass.
3046         for (ubyte i = 0x00u; i < 0x80u; i++)
3047         {
3048             element.value = [ i ];
3049             assertNotThrown!Exception(element.roid);
3050         }
3051 
3052         // All values of octet[0] should pass.
3053         for (ubyte i = 0x81u; i < 0xFFu; i++)
3054         {
3055             element.value = [ i, 0x14u ];
3056             assertNotThrown!Exception(element.roid);
3057         }
3058     }
3059 
3060     @system
3061     unittest
3062     {
3063         DERElement element = new DERElement();
3064 
3065         // Tests for the "leading zero byte," 0x80
3066         element.value = [ 0x29u, 0x80u, 0x14u ];
3067         assertThrown!ASN1ValuePaddingException(element.roid);
3068         element.value = [ 0x29u, 0x80u, 0x80u ];
3069         assertThrown!ASN1ValuePaddingException(element.roid);
3070         element.value = [ 0x80u, 0x80u, 0x80u ];
3071         assertThrown!ASN1ValuePaddingException(element.roid);
3072         element.value = [ 0x29u, 0x14u, 0x80u ];
3073         assertThrown!ASN1ValuePaddingException(element.roid);
3074         element.value = [ 0x29u, 0x80u ];
3075         assertThrown!ASN1ValuePaddingException(element.roid);
3076 
3077         // Test for non-terminating components
3078         element.value = [ 0x29u, 0x81u ];
3079         assertThrown!ASN1TruncationException(element.roid);
3080         element.value = [ 0x29u, 0x14u, 0x81u ];
3081         assertThrown!ASN1TruncationException(element.roid);
3082     }
3083 
3084     /**
3085         Decodes a sequence of elements
3086 
3087         Throws:
3088         $(UL
3089             $(LI $(D ASN1ConstructionException) if the element is marked as "primitive")
3090             $(LI And all of the exceptions thrown by the constructor)
3091         )
3092     */
3093     override public @property @system
3094     DERElement[] sequence() const
3095     {
3096         if (this.construction != ASN1Construction.constructed)
3097             throw new ASN1ConstructionException
3098             (this.construction, "decode a SEQUENCE");
3099 
3100         ubyte[] data = this.value.dup;
3101         DERElement[] result;
3102         while (data.length > 0u)
3103             result ~= new DERElement(data);
3104         return result;
3105     }
3106 
3107     /// Encodes a sequence of elements
3108     override public @property @system
3109     void sequence(in DERElement[] value)
3110     {
3111         scope(success) this.construction = ASN1Construction.constructed;
3112         ubyte[] result;
3113         foreach (dv; value)
3114         {
3115             result ~= dv.toBytes;
3116         }
3117         this.value = result;
3118     }
3119 
3120     /**
3121         Decodes a set of elements
3122 
3123         Throws:
3124         $(UL
3125             $(LI $(D ASN1ConstructionException) if the element is marked as "primitive")
3126             $(LI And all of the exceptions thrown by the constructor)
3127         )
3128     */
3129     override public @property @system
3130     DERElement[] set() const
3131     {
3132         if (this.construction != ASN1Construction.primitive)
3133             throw new ASN1ConstructionException
3134             (this.construction, "decode a SET");
3135 
3136         ubyte[] data = this.value.dup;
3137         DERElement[] result;
3138         while (data.length > 0u)
3139             result ~= new DERElement(data);
3140         return result;
3141     }
3142 
3143     /// Encodes a set of elements
3144     override public @property @system
3145     void set(in DERElement[] value)
3146     {
3147         scope(success) this.construction = ASN1Construction.constructed;
3148         ubyte[] result;
3149         foreach (dv; value)
3150         {
3151             result ~= dv.toBytes;
3152         }
3153         this.value = result;
3154     }
3155 
3156     /**
3157         Decodes a string, where the characters of the string are limited to
3158         0 - 9 and $(MONO SPACE).
3159 
3160         Throws:
3161         $(UL
3162             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3163             $(LI $(D ASN1ValueCharactersException) if any character other than 0-9 or space is encoded.)
3164         )
3165     */
3166     override public @property @system
3167     string numericString() const
3168     {
3169         if (this.construction != ASN1Construction.primitive)
3170             throw new ASN1ConstructionException
3171             (this.construction, "decode a NumericString");
3172 
3173         foreach (immutable character; this.value)
3174         {
3175             if (!canFind(numericStringCharacters, character))
3176                 throw new ASN1ValueCharactersException
3177                 ("1234567890 ", character, "NumericString");
3178         }
3179         return cast(string) this.value;
3180     }
3181 
3182     /**
3183         Encodes a string, where the characters of the string are limited to
3184         0 - 9 and space.
3185 
3186         Throws:
3187         $(UL
3188             $(LI $(D ASN1ValueCharactersException) if any character other than 0-9 or space is supplied.)
3189         )
3190     */
3191     override public @property @system
3192     void numericString(in string value)
3193     {
3194         scope(success) this.construction = ASN1Construction.primitive;
3195         foreach (immutable character; value)
3196         {
3197             if (!canFind(numericStringCharacters, character))
3198                 throw new ASN1ValueCharactersException
3199                 ("1234567890 ", character, "NumericString");
3200         }
3201         this.value = cast(ubyte[]) value.dup;
3202     }
3203 
3204     /**
3205         Decodes a string that will only contain characters a-z, A-Z, 0-9,
3206         space, apostrophe, parentheses, comma, minus, plus, period,
3207         forward slash, colon, equals, and question mark.
3208 
3209         Throws:
3210         $(UL
3211             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3212             $(LI $(D ASN1ValueCharactersException)
3213                 if any character other than a-z, A-Z,
3214                 0-9, space, apostrophe, parentheses, comma, minus, plus,
3215                 period, forward slash, colon, equals, or question mark are
3216                 encoded)
3217         )
3218     */
3219     override public @property @system
3220     string printableString() const
3221     {
3222         if (this.construction != ASN1Construction.primitive)
3223             throw new ASN1ConstructionException
3224             (this.construction, "decode a PrintableString");
3225 
3226         foreach (immutable character; this.value)
3227         {
3228             if (!canFind(printableStringCharacters, character))
3229                 throw new ASN1ValueCharactersException
3230                 (printableStringCharacters, character, "PrintableString");
3231         }
3232         return cast(string) this.value;
3233     }
3234 
3235     /**
3236         Encodes a string that may only contain characters a-z, A-Z, 0-9,
3237         space, apostrophe, parentheses, comma, minus, plus, period,
3238         forward slash, colon, equals, and question mark.
3239 
3240         Throws:
3241             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3242             $(LI $(D ASN1ValueCharactersException) if any character other than a-z, A-Z,
3243                 0-9, space, apostrophe, parentheses, comma, minus, plus,
3244                 period, forward slash, colon, equals, or question mark are
3245                 supplied)
3246     */
3247     override public @property @system
3248     void printableString(in string value)
3249     {
3250         scope(success) this.construction = ASN1Construction.primitive;
3251         foreach (immutable character; value)
3252         {
3253             if (!canFind(printableStringCharacters, character))
3254                 throw new ASN1ValueCharactersException
3255                 (printableStringCharacters, character, "PrintableString");
3256         }
3257         this.value = cast(ubyte[]) value.dup;
3258     }
3259 
3260     /**
3261         Literally just returns the value bytes.
3262 
3263         Returns: an unsigned byte array, where each byte is a T.61 character.
3264 
3265         Throws:
3266         $(UL
3267             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3268         )
3269     */
3270     override public @property @safe
3271     ubyte[] teletexString() const
3272     {
3273         if (this.construction != ASN1Construction.primitive)
3274             throw new ASN1ConstructionException
3275             (this.construction, "decode a TeletexString (T61String)");
3276 
3277         return this.value.dup;
3278     }
3279 
3280     /// Literally just sets the value bytes.
3281     override public @property @safe nothrow
3282     void teletexString(in ubyte[] value)
3283     {
3284         scope(success) this.construction = ASN1Construction.primitive;
3285         this.value = value.dup;
3286     }
3287 
3288     /**
3289         Literally just returns the value bytes.
3290 
3291         Returns: an unsigned byte array, where each byte is a Videotex character.
3292 
3293         Throws:
3294         $(UL
3295             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3296         )
3297     */
3298     override public @property @safe
3299     ubyte[] videotexString() const
3300     {
3301         if (this.construction != ASN1Construction.primitive)
3302             throw new ASN1ConstructionException
3303             (this.construction, "decode a VideotexString");
3304 
3305         return this.value.dup;
3306     }
3307 
3308     /// Literally just sets the value bytes.
3309     override public @property @safe nothrow
3310     void videotexString(in ubyte[] value)
3311     {
3312         scope(success) this.construction = ASN1Construction.primitive;
3313         this.value = value.dup;
3314     }
3315 
3316     /**
3317         Decodes a string that only contains ASCII characters.
3318 
3319         $(MONO IA5String) differs from ASCII ever so slightly: IA5 is international,
3320         leaving 10 characters up to be locale-specific:
3321 
3322         $(TABLE
3323             $(TR $(TH Byte) $(TH ASCII Character))
3324             $(TR $(TD 0x40) $(TD @))
3325             $(TR $(TD 0x5B) $(TD [))
3326             $(TR $(TD 0x5C) $(TD \))
3327             $(TR $(TD 0x5D) $(TD ]))
3328             $(TR $(TD 0x5E) $(TD ^))
3329             $(TR $(TD 0x60) $(TD `))
3330             $(TR $(TD 0x7B) $(TD {))
3331             $(TR $(TD 0x7C) $(TD /))
3332             $(TR $(TD 0x7D) $(TD }))
3333             $(TR $(TD 0x7E) $(TD ~))
3334         )
3335 
3336         Throws:
3337         $(UL
3338             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3339             $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII)
3340         )
3341     */
3342     override public @property @system
3343     string internationalAlphabetNumber5String() const
3344     {
3345         if (this.construction != ASN1Construction.primitive)
3346             throw new ASN1ConstructionException
3347             (this.construction, "decode an IA5String");
3348 
3349         string ret = cast(string) this.value;
3350         foreach (immutable character; ret)
3351         {
3352             if (!character.isASCII)
3353                 throw new ASN1ValueCharactersException
3354                 ("all ASCII characters", character, "IA5String");
3355         }
3356         return ret;
3357     }
3358 
3359     /**
3360         Encodes a string that may only contain ASCII characters.
3361 
3362         $(MONO IA5String) differs from ASCII ever so slightly: IA5 is international,
3363         leaving 10 characters up to be locale-specific:
3364 
3365         $(TABLE
3366             $(TR $(TH Byte) $(TH ASCII Character))
3367             $(TR $(TD 0x40) $(TD @))
3368             $(TR $(TD 0x5B) $(TD [))
3369             $(TR $(TD 0x5C) $(TD \))
3370             $(TR $(TD 0x5D) $(TD ]))
3371             $(TR $(TD 0x5E) $(TD ^))
3372             $(TR $(TD 0x60) $(TD `))
3373             $(TR $(TD 0x7B) $(TD {))
3374             $(TR $(TD 0x7C) $(TD /))
3375             $(TR $(TD 0x7D) $(TD }))
3376             $(TR $(TD 0x7E) $(TD ~))
3377         )
3378 
3379         Throws:
3380         $(UL
3381             $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII)
3382         )
3383     */
3384     override public @property @system
3385     void internationalAlphabetNumber5String(in string value)
3386     {
3387         scope(success) this.construction = ASN1Construction.primitive;
3388         foreach (immutable character; value)
3389         {
3390             if (!character.isASCII)
3391                 throw new ASN1ValueCharactersException
3392                 ("all ASCII characters", character, "IA5String");
3393         }
3394         this.value = cast(ubyte[]) value.dup;
3395     }
3396 
3397     /**
3398         Decodes a $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime).
3399         The value is just the ASCII character representation of the UTC-formatted timestamp.
3400 
3401         An UTC Timestamp looks like:
3402         $(UL
3403             $(LI $(MONO 9912312359Z))
3404             $(LI $(MONO 991231235959+0200))
3405         )
3406 
3407         If the first digit of the two-digit year is 7, 6, 5, 4, 3, 2, 1, or 0,
3408         meaning that the date refers to the first 80 years of the century, this
3409         assumes we are talking about the 21st century and prepend '20' when
3410         creating the ISO Date String. Otherwise, it assumes we are talking
3411         about the 20th century, and prepend '19' when creating the string.
3412 
3413         See_Also:
3414         $(UL
3415             $(LI $(LINK https://www.obj-sys.com/asn1tutorial/node15.html, UTCTime))
3416             $(LI $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime))
3417         )
3418 
3419         Throws:
3420         $(UL
3421             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3422             $(LI $(D ASN1ValueException) if the encoded value does not end with a 'Z')
3423             $(LI $(D ASN1ValueCharactersException) if any character is not valid in a $(MONO Visiblestring))
3424             $(LI $(D DateTimeException) if the encoded string cannot be decoded to a DateTime)
3425         )
3426     */
3427     override public @property @system
3428     DateTime coordinatedUniversalTime() const
3429     {
3430         if (this.construction != ASN1Construction.primitive)
3431             throw new ASN1ConstructionException
3432             (this.construction, "decode a UTCTime");
3433 
3434         // Mandated in X.690, section 11.8.2
3435         if (this.value.length != 13u) // YYMMDDhhmmssZ
3436             throw new ASN1ValueSizeException(13u, 13u, this.value.length, "decode a UTCTime");
3437 
3438         // Mandated in X.690, section 11.8.1
3439         if (this.value[$-1] != 'Z')
3440             throw new ASN1ValueException
3441             (
3442                 "This exception was thrown because you attempted to decode " ~
3443                 "a UTCTime whose encoding did not terminate with a 'Z', as " ~
3444                 "is expected by the Canonical Encoding Rules (CER) and " ~
3445                 "Distinguished Encoding Rules (DER). " ~
3446                 notWhatYouMeantText ~ forMoreInformationText ~
3447                 debugInformationText ~ reportBugsText
3448             );
3449 
3450         /** NOTE:
3451             .fromISOString() MUST be called from SysTime, not DateTime. There
3452             is a subtle difference in how .fromISOString() works in both SysTime
3453             and DateTime: SysTime's accepts the "Z" at the end (indicating that
3454             the time is in GMT).
3455 
3456             If you use DateTime.fromISOString, you will get a DateTimeException
3457             whose cryptic message reads "Invalid ISO String: " followed,
3458             strangely, by only the last six characters of the string.
3459         */
3460         immutable string dt = (((this.value[0] <= '7') ? "20" : "19") ~ cast(string) this.value);
3461         return cast(DateTime) SysTime.fromISOString(dt[0 .. 8].idup ~ "T" ~ dt[8 .. $].idup);
3462     }
3463 
3464     /**
3465         Encodes a $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime).
3466         The value is just the ASCII character representation of
3467         the UTC-formatted timestamp.
3468 
3469         An UTC Timestamp looks like:
3470         $(UL
3471             $(LI $(MONO 9912312359Z))
3472             $(LI $(MONO 991231235959+0200))
3473         )
3474 
3475         See_Also:
3476             $(LINK https://www.obj-sys.com/asn1tutorial/node15.html, UTCTime)
3477     */
3478     override public @property @system
3479     void coordinatedUniversalTime(in DateTime value)
3480     out
3481     {
3482         // YYMMDDhhmmssZ is the only acceptable format for DER
3483         assert(this.value.length == 13u);
3484         assert(this.value[$-1] == 'Z');
3485     }
3486     body
3487     {
3488         scope(success) this.construction = ASN1Construction.primitive;
3489         immutable SysTime st = SysTime(value, UTC());
3490         this.value = cast(ubyte[]) ((st.toUTC()).toISOString()[2 .. $].replace("T", ""));
3491     }
3492 
3493     @system
3494     unittest
3495     {
3496         DERElement el = new DERElement();
3497         el.utcTime = DateTime(2017, 10, 3);
3498         assert(el.visibleString == "171003000000Z");
3499     }
3500 
3501     /**
3502         Decodes a $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime).
3503         The value is just the ASCII character representation of
3504         the $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601)-formatted timestamp.
3505 
3506         An ISO-8601 Timestamp looks like:
3507         $(UL
3508             $(LI $(MONO 19851106210627.3))
3509             $(LI $(MONO 19851106210627.3Z))
3510             $(LI $(MONO 19851106210627.3-0500))
3511         )
3512 
3513         Throws:
3514         $(UL
3515             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3516             $(LI $(D ASN1ValueException) if the encoded value did not end with a 'Z'
3517                 or contained a misplaced or unnecessary decimal point)
3518             $(LI $(D ASN1ValueCharactersException) if any character is not valid in a $(MONO Visiblestring))
3519             $(LI $(D DateTimeException) if the encoded string cannot be decoded to a DateTime)
3520             $(LI $(D ASN1ValuePaddingException) if the seconds fraction contains trailing zeroes)
3521             $(LI $(D ASN1RecursionException)
3522                 if using constructed form and the element
3523                 is constructed of too many nested constructed elements)
3524             $(LI $(D ASN1TagClassException)
3525                 if any nested primitives do not share the
3526                 same tag class as their outer constructed element)
3527             $(LI $(D ASN1TagNumberException)
3528                 if any nested primitives do not share the
3529                 same tag number as their outer constructed element)
3530         )
3531 
3532         Standards:
3533         $(UL
3534             $(LI $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601))
3535         )
3536     */
3537     override public @property @system
3538     DateTime generalizedTime() const
3539     {
3540         if (this.construction != ASN1Construction.primitive)
3541             throw new ASN1ConstructionException
3542             (this.construction, "decode a GeneralizedTime");
3543 
3544         if (this.value.length < 15u)
3545             throw new ASN1ValueSizeException(15u, size_t.max, this.value.length, "decode a GeneralizedTime");
3546 
3547         // Inferred, because YYYYMMDDhhmmss.Z could not be valid.
3548         if (this.value.length == 16u)
3549             throw new ASN1ValueException
3550             (
3551                 "This exception was thrown because you attempted to decode a " ~
3552                 "GeneralizedTime that was encoded on too few bytes to be " ~
3553                 "correct. A valid GeneralizedTime cannot be 16 bytes. " ~
3554                 notWhatYouMeantText ~ forMoreInformationText ~
3555                 debugInformationText ~ reportBugsText
3556             );
3557 
3558         // Mandated in X.690, section 11.7.1
3559         if (this.value[$-1] != 'Z')
3560             throw new ASN1ValueException
3561             (
3562                 "This exception was thrown because you attempted to decode " ~
3563                 "a GeneralizedTime whose encoding did not terminate with a 'Z', " ~
3564                 "as is expected by the Canonical Encoding Rules (CER) and " ~
3565                 "Distinguished Encoding Rules (DER). " ~
3566                 notWhatYouMeantText ~ forMoreInformationText ~
3567                 debugInformationText ~ reportBugsText
3568             );
3569 
3570         ptrdiff_t indexOfDecimalPoint = (cast(string) this.value).indexOf('.');
3571         if (indexOfDecimalPoint != -1)
3572         {
3573             if (this.value[$-2] == '0')
3574                 throw new ASN1ValuePaddingException
3575                 (
3576                     "This exception was thrown because you attempted to decode " ~
3577                     "a GeneralizedTime that contained trailing zeroes in the " ~
3578                     "fraction-of-seconds part. " ~
3579                     notWhatYouMeantText ~ forMoreInformationText ~
3580                     debugInformationText ~ reportBugsText
3581                 );
3582 
3583             if (indexOfDecimalPoint != 14)
3584                 throw new ASN1ValueException
3585                 (
3586                     "This exception was thrown because you attempted to decode " ~
3587                     "a GeneralizedTime whose decimal point was misplaced. " ~
3588                     notWhatYouMeantText ~ forMoreInformationText ~
3589                     debugInformationText ~ reportBugsText
3590                 );
3591         }
3592         else
3593         {
3594             if ((cast(string) this.value).indexOf(',') != -1)
3595                 throw new ASN1ValueCharactersException
3596                 ("1234567890Z.", ',', "decode a GeneralizedTime");
3597         }
3598 
3599         /** NOTE:
3600             .fromISOString() MUST be called from SysTime, not DateTime. There
3601             is a subtle difference in how .fromISOString() works in both SysTime
3602             and DateTime: SysTime's accepts the "Z" at the end (indicating that
3603             the time is in GMT).
3604 
3605             If you use DateTime.fromISOString, you will get a DateTimeException
3606             whose cryptic message reads "Invalid ISO String: " followed,
3607             strangely, by only the last six characters of the string.
3608         */
3609         immutable string dt = cast(string) this.value;
3610         return cast(DateTime) SysTime.fromISOString(dt[0 .. 8].idup ~ "T" ~ dt[8 .. $].idup);
3611     }
3612 
3613     /**
3614         Encodes a $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime).
3615 
3616         The value is just the ASCII character representation of
3617         the $(LINK https://www.iso.org/iso-8601-date-and-time-format.html,
3618         ISO 8601)-formatted timestamp.
3619 
3620         An ISO-8601 Timestamp looks like:
3621         $(UL
3622             $(LI $(MONO 19851106210627.3))
3623             $(LI $(MONO 19851106210627.3Z))
3624             $(LI $(MONO 19851106210627.3-0500))
3625         )
3626 
3627         Standards:
3628         $(UL
3629             $(LI $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601))
3630         )
3631     */
3632     override public @property @system
3633     void generalizedTime(in DateTime value)
3634     out
3635     {
3636         assert(this.value.length > 0u);
3637         assert((cast(string) this.value).indexOf("T") == -1);
3638     }
3639     body
3640     {
3641         scope(success) this.construction = ASN1Construction.primitive;
3642         immutable SysTime st = SysTime(value, UTC());
3643         this.value = cast(ubyte[]) ((st.toUTC()).toISOString().replace("T", ""));
3644     }
3645 
3646     // Positive tests
3647     @system
3648     unittest
3649     {
3650         DERElement el = new DERElement();
3651 
3652         el.generalizedTime = DateTime(2017, 10, 3);
3653         assert(el.visibleString == "20171003000000Z");
3654 
3655         el.visibleString = "20171003000000.003Z";
3656         assertNotThrown!Exception(el.generalizedTime);
3657 
3658         el.visibleString = "20171003000000.0000003Z";
3659         assertNotThrown!Exception(el.generalizedTime);
3660     }
3661 
3662     // Negative tests
3663     @system
3664     unittest
3665     {
3666         DERElement el = new DERElement();
3667 
3668         el.visibleString = "20171003000000.Z";
3669         assertThrown!ASN1Exception(el.generalizedTime);
3670 
3671         el.visibleString = "20171003000000.0Z";
3672         assertThrown!ASN1Exception(el.generalizedTime);
3673 
3674         el.visibleString = "20171003000000.000000Z";
3675         assertThrown!ASN1Exception(el.generalizedTime);
3676 
3677         el.visibleString = "0171003000000Z";
3678         assertThrown!ASN1Exception(el.generalizedTime);
3679 
3680         el.visibleString = "2017100300000Z"; // Missing 1 trailing zero.
3681         assertThrown!ASN1Exception(el.generalizedTime);
3682     }
3683 
3684     /**
3685         Decodes an ASCII string that contains only characters between and
3686         including $(D 0x20) and $(D 0x75). Deprecated, according to page 182 of the
3687         Dubuisson book.
3688 
3689         Throws:
3690         $(UL
3691             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3692             $(LI $(D ASN1ValueCharactersException) if any non-graphical character (including space) is encoded)
3693         )
3694 
3695         Citations:
3696         $(UL
3697             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
3698                 $(I ASN.1: Communication between Heterogeneous Systems),
3699                 Morgan Kaufmann, 2001, pp. 175-178.)
3700             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
3701             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
3702         )
3703     */
3704     override public @property @system
3705     string graphicString() const
3706     {
3707         if (this.construction != ASN1Construction.primitive)
3708             throw new ASN1ConstructionException
3709             (this.construction, "decode a GraphicString");
3710 
3711         string ret = cast(string) this.value;
3712         foreach (immutable character; ret)
3713         {
3714             if (!character.isGraphical && character != ' ')
3715                 throw new ASN1ValueCharactersException
3716                 ("all characters within the range 0x20 to 0x7E", character, "GraphicString");
3717         }
3718         return ret;
3719     }
3720 
3721     /**
3722         Encodes an ASCII string that may contain only characters between and
3723         including $(D 0x20) and $(D 0x75). Deprecated, according to page 182
3724         of the Dubuisson book.
3725 
3726         Throws:
3727         $(UL
3728             $(LI $(D ASN1ValueCharactersException) if any non-graphical character (including space) is supplied)
3729         )
3730 
3731         Citations:
3732         $(UL
3733             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
3734                 $(I ASN.1: Communication between Heterogeneous Systems),
3735                 Morgan Kaufmann, 2001, pp. 175-178.)
3736             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
3737             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
3738         )
3739     */
3740     override public @property @system
3741     void graphicString(in string value)
3742     {
3743         scope(success) this.construction = ASN1Construction.primitive;
3744         foreach (immutable character; value)
3745         {
3746             if (!character.isGraphical && character != ' ')
3747                 throw new ASN1ValueCharactersException
3748                 ("all characters within the range 0x20 to 0x7E", character, "GraphicString");
3749         }
3750         this.value = cast(ubyte[]) value.dup;
3751     }
3752 
3753     /**
3754         Decodes a string that only contains characters between and including
3755         $(D 0x20) and $(D 0x7E). (Honestly, I don't know how this differs from
3756         $(MONO GraphicalString).)
3757 
3758         Throws:
3759         $(UL
3760             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3761             $(LI $(D ASN1ValueCharactersException)
3762                 if any non-graphical character (including space) is encoded)
3763         )
3764     */
3765     override public @property @system
3766     string visibleString() const
3767     {
3768         if (this.construction != ASN1Construction.primitive)
3769             throw new ASN1ConstructionException
3770             (this.construction, "decode a VisibleString");
3771 
3772         string ret = cast(string) this.value;
3773         foreach (immutable character; ret)
3774         {
3775             if (!character.isGraphical && character != ' ')
3776                 throw new ASN1ValueCharactersException
3777                 ("all characters within the range 0x20 to 0x7E", character, "VisibleString");
3778         }
3779         return ret;
3780     }
3781 
3782     /**
3783         Encodes a string that only contains characters between and including
3784         $(D 0x20) and $(D 0x7E). (Honestly, I don't know how this differs from
3785         $(MONO GraphicalString).)
3786 
3787         Throws:
3788         $(UL
3789             $(LI $(D ASN1ValueCharactersException)
3790                 if any non-graphical character (including space) is supplied.)
3791         )
3792     */
3793     override public @property @system
3794     void visibleString(in string value)
3795     {
3796         scope(success) this.construction = ASN1Construction.primitive;
3797         foreach (immutable character; value)
3798         {
3799             if (!character.isGraphical && character != ' ')
3800                 throw new ASN1ValueCharactersException
3801                 ("all characters within the range 0x20 to 0x7E", character, "VisibleString");
3802         }
3803         this.value = cast(ubyte[]) value.dup;
3804     }
3805 
3806     /**
3807         Decodes a string containing only ASCII characters. Deprecated, according
3808         to page 182 of the Dubuisson book.
3809 
3810         Throws:
3811         $(UL
3812             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3813             $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII)
3814         )
3815 
3816         Citations:
3817         $(UL
3818             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
3819                 $(I ASN.1: Communication between Heterogeneous Systems),
3820                 Morgan Kaufmann, 2001, p. 182.)
3821             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
3822             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
3823         )
3824     */
3825     override public @property @system
3826     string generalString() const
3827     {
3828         if (this.construction != ASN1Construction.primitive)
3829             throw new ASN1ConstructionException
3830             (this.construction, "decode a GeneralString");
3831 
3832         string ret = cast(string) this.value;
3833         foreach (immutable character; ret)
3834         {
3835             if (!character.isASCII)
3836                 throw new ASN1ValueCharactersException
3837                 ("all ASCII characters", character, "GeneralString");
3838         }
3839         return ret;
3840     }
3841 
3842     /**
3843         Encodes a string containing only ASCII characters. Deprecated,
3844         according to page 182 of the Dubuisson book.
3845 
3846         Throws:
3847         $(UL
3848             $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII)
3849         )
3850 
3851         Citations:
3852         $(UL
3853             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
3854                 $(I ASN.1: Communication between Heterogeneous Systems),
3855                 Morgan Kaufmann, 2001, p. 182.)
3856         )
3857     */
3858     override public @property @system
3859     void generalString(in string value)
3860     {
3861         scope(success) this.construction = ASN1Construction.primitive;
3862         foreach (immutable character; value)
3863         {
3864             if (!character.isASCII)
3865                 throw new ASN1ValueCharactersException
3866                 ("all ASCII characters", character, "GeneralString");
3867         }
3868         this.value = cast(ubyte[]) value.dup;
3869     }
3870 
3871     /**
3872         Decodes a $(MONO dstring) of UTF-32 characters.
3873 
3874         Throws:
3875         $(UL
3876             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3877             $(LI $(D ASN1ValueException)
3878                 if the encoded bytes is not evenly divisible by four)
3879         )
3880     */
3881     override public @property @system
3882     dstring universalString() const
3883     {
3884         if (this.construction != ASN1Construction.primitive)
3885             throw new ASN1ConstructionException
3886             (this.construction, "decode a UniversalString");
3887 
3888         if (this.value.length == 0u) return ""d;
3889         if (this.value.length % 4u)
3890             throw new ASN1ValueException
3891             (
3892                 "This exception was thrown because you tried to decode " ~
3893                 "a UniversalString that contained a number of bytes that " ~
3894                 "is not divisible by four. " ~
3895                 notWhatYouMeantText ~ forMoreInformationText ~
3896                 debugInformationText ~ reportBugsText
3897             );
3898 
3899         version (BigEndian)
3900         {
3901             return cast(dstring) this.value;
3902         }
3903         else version (LittleEndian)
3904         {
3905             dstring ret;
3906             size_t i = 0u;
3907             while (i < this.value.length-3)
3908             {
3909                 ubyte[] character;
3910                 character.length = 4u;
3911                 character[3] = this.value[i++];
3912                 character[2] = this.value[i++];
3913                 character[1] = this.value[i++];
3914                 character[0] = this.value[i++];
3915                 ret ~= (*cast(dchar *) character.ptr);
3916             }
3917             return ret;
3918         }
3919         else
3920         {
3921             static assert(0, "Could not determine endianness!");
3922         }
3923     }
3924 
3925     /// Encodes a $(MONO dstring) of UTF-32 characters.
3926     override public @property @system
3927     void universalString(in dstring value)
3928     {
3929         scope(success) this.construction = ASN1Construction.primitive;
3930         version (BigEndian)
3931         {
3932             this.value = cast(ubyte[]) value.dup;
3933         }
3934         else version (LittleEndian)
3935         {
3936             foreach(immutable character; value)
3937             {
3938                 ubyte[] charBytes = cast(ubyte[]) *cast(char[4] *) &character;
3939                 reverse(charBytes);
3940                 this.value ~= charBytes;
3941             }
3942         }
3943         else
3944         {
3945             static assert(0, "Could not determine endianness!");
3946         }
3947     }
3948 
3949     /**
3950         Decodes a $(MONO CharacterString), which is a constructed data type, defined
3951         in the $(LINK https://www.itu.int, International Telecommunications Union)'s
3952             $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680).
3953 
3954         The specification defines $(MONO CharacterString) as:
3955 
3956         $(PRE
3957             CHARACTER STRING ::= [UNIVERSAL 29] SEQUENCE {
3958                 identification CHOICE {
3959                     syntaxes SEQUENCE {
3960                         abstract OBJECT IDENTIFIER,
3961                         transfer OBJECT IDENTIFIER },
3962                     syntax OBJECT IDENTIFIER,
3963                     presentation-context-id INTEGER,
3964                     context-negotiation SEQUENCE {
3965                         presentation-context-id INTEGER,
3966                         transfer-syntax OBJECT IDENTIFIER },
3967                     transfer-syntax OBJECT IDENTIFIER,
3968                     fixed NULL },
3969                 string-value OCTET STRING }
3970         )
3971 
3972         This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification)
3973         choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5.
3974 
3975         Returns: an instance of $(D types.universal.characterstring.CharacterString).
3976 
3977         Throws:
3978         $(UL
3979             $(LI $(D ASN1ValueException) if encoded $(MONO CharacterString) has too few or too many
3980                 elements, or if $(MONO syntaxes) or $(MONO context-negotiation) element has
3981                 too few or too many elements)
3982             $(LI $(D ASN1ValueSizeException) if encoded $(MONO INTEGER) is too large to decode)
3983             $(LI $(D ASN1RecursionException) if using constructed form and the element
3984                 is constructed of too many nested constructed elements)
3985             $(LI $(D ASN1TagClassException) if any nested primitives do not have the
3986                 correct tag class)
3987             $(LI $(D ASN1ConstructionException) if any element has the wrong construction)
3988             $(LI $(D ASN1TagNumberException) if any nested primitives do not have the
3989                 correct tag number)
3990         )
3991     */
3992     override public @property @system
3993     CharacterString characterString() const
3994     {
3995         if (this.construction != ASN1Construction.constructed)
3996             throw new ASN1ConstructionException
3997             (this.construction, "decode a CharacterString");
3998 
3999         const DERElement[] components = this.sequence;
4000         ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID();
4001 
4002         if (components.length != 2u)
4003             throw new ASN1ValueException
4004             (
4005                 "This exception was thrown because you attempted to decode " ~
4006                 "a CharacterString that contained too many or too few elements. " ~
4007                 "A CharacterString should have only two elements: " ~
4008                 "an identification CHOICE, and a data-value OCTET STRING, " ~
4009                 "in that order. " ~
4010                 notWhatYouMeantText ~ forMoreInformationText ~
4011                 debugInformationText ~ reportBugsText
4012             );
4013 
4014         if (components[0].tagClass != ASN1TagClass.contextSpecific)
4015             throw new ASN1TagClassException
4016             (
4017                 [ ASN1TagClass.contextSpecific ],
4018                 components[0].tagClass,
4019                 "decode the first component of a CharacterString"
4020             );
4021 
4022         if (components[1].tagClass != ASN1TagClass.contextSpecific)
4023             throw new ASN1TagClassException
4024             (
4025                 [ ASN1TagClass.contextSpecific ],
4026                 components[1].tagClass,
4027                 "decode the second component of a CharacterString"
4028             );
4029 
4030         /* NOTE:
4031             See page 224 of Dubuisson, item 11:
4032             It sounds like, even if you have an ABSENT constraint applied,
4033             all automatically-tagged items still have the same numbers as
4034             though the constrained component were PRESENT.
4035         */
4036         if (components[0].tagNumber != 0u)
4037             throw new ASN1TagNumberException
4038             ([ 0u ], components[0].tagNumber, "decode the first component of a CharacterString");
4039 
4040         if (components[1].tagNumber != 2u)
4041             throw new ASN1TagNumberException
4042             ([ 2u ], components[1].tagNumber, "decode the second component of a CharacterString");
4043 
4044         ubyte[] bytes = components[0].value.dup;
4045         const DERElement identificationChoice = new DERElement(bytes);
4046         switch (identificationChoice.tagNumber)
4047         {
4048             case (0u): // syntaxes
4049             {
4050                 if (identificationChoice.construction != ASN1Construction.constructed)
4051                     throw new ASN1ConstructionException
4052                     (identificationChoice.construction, "decode the syntaxes component of a CharacterString");
4053 
4054                 const DERElement[] syntaxesComponents = identificationChoice.sequence;
4055 
4056                 if (syntaxesComponents.length != 2u)
4057                     throw new ASN1ValueException
4058                     (
4059                         "This exception was thrown because you attempted to " ~
4060                         "decode a CharacterString whose syntaxes component " ~
4061                         "contained an invalid number of elements. The " ~
4062                         "syntaxes component should contain abstract and transfer " ~
4063                         "syntax OBJECT IDENTIFIERS, in that order. " ~
4064                         notWhatYouMeantText ~ forMoreInformationText ~
4065                         debugInformationText ~ reportBugsText
4066                     );
4067 
4068                 // Class Validation
4069                 if (syntaxesComponents[0].tagClass != ASN1TagClass.contextSpecific)
4070                     throw new ASN1TagClassException
4071                     (
4072                         [ ASN1TagClass.contextSpecific ],
4073                         syntaxesComponents[0].tagClass,
4074                         "decode the first syntaxes component of a CharacterString"
4075                     );
4076 
4077                 if (syntaxesComponents[1].tagClass != ASN1TagClass.contextSpecific)
4078                     throw new ASN1TagClassException
4079                     (
4080                         [ ASN1TagClass.contextSpecific ],
4081                         syntaxesComponents[1].tagClass,
4082                         "decode the second syntaxes component of a CharacterString"
4083                     );
4084 
4085                 // Construction Validation
4086                 if (syntaxesComponents[0].construction != ASN1Construction.primitive)
4087                     throw new ASN1ConstructionException
4088                     (syntaxesComponents[0].construction, "decode the first syntaxes component of a CharacterString");
4089 
4090                 if (syntaxesComponents[1].construction != ASN1Construction.primitive)
4091                     throw new ASN1ConstructionException
4092                     (syntaxesComponents[1].construction, "decode the second syntaxes component of a CharacterString");
4093 
4094                 // Number Validation
4095                 if (syntaxesComponents[0].tagNumber != 0u)
4096                     throw new ASN1TagNumberException
4097                     (
4098                         [ 0u ],
4099                         syntaxesComponents[0].tagNumber,
4100                         "decode the first syntaxes component of a CharacterString"
4101                     );
4102 
4103                 if (syntaxesComponents[1].tagNumber != 1u)
4104                     throw new ASN1TagNumberException
4105                     (
4106                         [ 1u ],
4107                         syntaxesComponents[1].tagNumber,
4108                         "decode the second syntaxes component of a CharacterString"
4109                     );
4110 
4111                 identification.syntaxes  = ASN1Syntaxes(
4112                     syntaxesComponents[0].objectIdentifier,
4113                     syntaxesComponents[1].objectIdentifier
4114                 );
4115 
4116                 break;
4117             }
4118             case (1u): // syntax
4119             {
4120                 identification.syntax = identificationChoice.objectIdentifier;
4121                 break;
4122             }
4123             case (4u): // transfer-syntax
4124             {
4125                 identification.transferSyntax = identificationChoice.objectIdentifier;
4126                 break;
4127             }
4128             case (5u): // fixed
4129             {
4130                 identification.fixed = true;
4131                 break;
4132             }
4133             default:
4134                 throw new ASN1TagNumberException
4135                 (
4136                     [ 0u, 1u, 4u, 5u ],
4137                     identificationChoice.tagNumber,
4138                     "decode a CharacterString identification"
4139                 );
4140         }
4141 
4142         CharacterString cs = CharacterString();
4143         cs.identification = identification;
4144         cs.stringValue = components[1].octetString;
4145         return cs;
4146     }
4147 
4148     /**
4149         Encodes a $(MONO CharacterString), which is a constructed data type, defined
4150         in the $(LINK https://www.itu.int, International Telecommunications Union)'s
4151             $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680).
4152 
4153         The specification defines $(MONO CharacterString) as:
4154 
4155         $(PRE
4156             CHARACTER STRING ::= [UNIVERSAL 29] SEQUENCE {
4157                 identification CHOICE {
4158                     syntaxes SEQUENCE {
4159                         abstract OBJECT IDENTIFIER,
4160                         transfer OBJECT IDENTIFIER },
4161                     syntax OBJECT IDENTIFIER,
4162                     presentation-context-id INTEGER,
4163                     context-negotiation SEQUENCE {
4164                         presentation-context-id INTEGER,
4165                         transfer-syntax OBJECT IDENTIFIER },
4166                     transfer-syntax OBJECT IDENTIFIER,
4167                     fixed NULL },
4168                 string-value OCTET STRING }
4169         )
4170 
4171         This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification)
4172         choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5.
4173     */
4174     override public @property @system
4175     void characterString(in CharacterString value)
4176     out
4177     {
4178         assert(this.value.length > 0u);
4179     }
4180     body
4181     {
4182         scope(success) this.construction = ASN1Construction.constructed;
4183         DERElement identification = new DERElement();
4184         identification.tagClass = ASN1TagClass.contextSpecific;
4185         identification.tagNumber = 0u; // CHOICE is EXPLICIT, even with automatic tagging.
4186 
4187         DERElement identificationChoice = new DERElement();
4188         identificationChoice.tagClass = ASN1TagClass.contextSpecific;
4189         if (!(value.identification.syntaxes.isNull))
4190         {
4191             DERElement abstractSyntax = new DERElement();
4192             abstractSyntax.tagClass = ASN1TagClass.contextSpecific;
4193             abstractSyntax.tagNumber = 0u;
4194             abstractSyntax.objectIdentifier = value.identification.syntaxes.abstractSyntax;
4195 
4196             DERElement transferSyntax = new DERElement();
4197             transferSyntax.tagClass = ASN1TagClass.contextSpecific;
4198             transferSyntax.tagNumber = 1u;
4199             transferSyntax.objectIdentifier = value.identification.syntaxes.transferSyntax;
4200 
4201             identificationChoice.construction = ASN1Construction.constructed;
4202             identificationChoice.tagNumber = 0u;
4203             identificationChoice.sequence = [ abstractSyntax, transferSyntax ];
4204         }
4205         else if (!(value.identification.syntax.isNull))
4206         {
4207             identificationChoice.tagNumber = 1u;
4208             identificationChoice.objectIdentifier = value.identification.syntax;
4209         }
4210         else if (!(value.identification.transferSyntax.isNull))
4211         {
4212             identificationChoice.tagNumber = 4u;
4213             identificationChoice.objectIdentifier = value.identification.transferSyntax;
4214         }
4215         else
4216         {
4217             identificationChoice.tagNumber = 5u;
4218             identificationChoice.value = [];
4219         }
4220 
4221         // This makes identification: [CONTEXT 0][L][CONTEXT #][L][V]
4222         identification.value = cast(ubyte[]) identificationChoice;
4223 
4224         DERElement stringValue = new DERElement();
4225         stringValue.tagClass = ASN1TagClass.contextSpecific;
4226         stringValue.tagNumber = 2u;
4227         stringValue.octetString = value.stringValue;
4228 
4229         this.sequence = [ identification, stringValue ];
4230     }
4231 
4232     /* NOTE:
4233         This unit test had to be moved out of ASN1Element because DER and CER
4234         do not support encoding of context-negotiation in CharacterString.
4235 
4236         This unit test ensures that, if you attempt to create a CharacterString
4237         with context-negotiation as the CHOICE of identification, the
4238         encoded CharacterString's identification defaults to fixed.
4239     */
4240     @system
4241     unittest
4242     {
4243         ASN1ContextNegotiation cn = ASN1ContextNegotiation();
4244         cn.presentationContextID = 27L;
4245         cn.transferSyntax = new OID(1, 3, 6, 4, 1, 256, 39);
4246 
4247         ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID();
4248         id.contextNegotiation = cn;
4249 
4250         CharacterString input = CharacterString();
4251         input.identification = id;
4252         input.stringValue = [ 'H', 'E', 'N', 'L', 'O' ];
4253 
4254         DERElement el = new DERElement();
4255         el.characterString = input;
4256         CharacterString output = el.characterString;
4257         assert(output.identification.fixed == true);
4258         assert(output.stringValue == [ 'H', 'E', 'N', 'L', 'O' ]);
4259     }
4260 
4261     // Inspired by CVE-2017-9023
4262     @system
4263     unittest
4264     {
4265         ubyte[] data = [ // This is valid.
4266             0x1Eu, 0x0Au, // CharacterString, Length 11
4267                 0x80u, 0x02u, // CHOICE
4268                     0x85u, 0x00u, // NULL
4269                 0x82u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u ]; // OCTET STRING
4270 
4271         // Valid values for data[2]: 80
4272         for (ubyte i = 0x81u; i < 0x9Eu; i++)
4273         {
4274             data[2] = i;
4275             size_t x = 0u;
4276             DERElement el = new DERElement(x, data);
4277             assertThrown!ASN1Exception(el.characterString);
4278         }
4279 
4280         // Valid values for data[4]: 80-85
4281         for (ubyte i = 0x86u; i < 0x9Eu; i++)
4282         {
4283             data[4] = i;
4284             size_t x = 0u;
4285             DERElement el = new DERElement(x, data);
4286             assertThrown!ASN1Exception(el.characterString);
4287         }
4288 
4289         // Valid values for data[6]: 82
4290         for (ubyte i = 0x83u; i < 0x9Eu; i++)
4291         {
4292             data[6] = i;
4293             size_t x = 0u;
4294             DERElement el = new DERElement(x, data);
4295             assertThrown!ASN1Exception(el.characterString);
4296         }
4297     }
4298 
4299     /**
4300         Decodes a $(MONO wstring) of UTF-16 characters.
4301 
4302         Throws:
4303         $(UL
4304             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
4305             $(LI $(D ASN1ValueException)
4306                 if the encoded bytes is not evenly divisible by two)
4307         )
4308     */
4309     override public @property @system
4310     wstring basicMultilingualPlaneString() const
4311     {
4312         if (this.construction != ASN1Construction.primitive)
4313             throw new ASN1ConstructionException
4314             (this.construction, "decode a BMPString");
4315 
4316         if (this.value.length == 0u) return ""w;
4317         if (this.value.length % 2u)
4318             throw new ASN1ValueException
4319             (
4320                 "This exception was thrown because you tried to decode " ~
4321                 "a BMPString that contained a number of bytes that " ~
4322                 "is not divisible by two. " ~
4323                 notWhatYouMeantText ~ forMoreInformationText ~
4324                 debugInformationText ~ reportBugsText
4325             );
4326 
4327         version (BigEndian)
4328         {
4329             return cast(wstring) this.value;
4330         }
4331         else version (LittleEndian)
4332         {
4333             wstring ret;
4334             size_t i = 0u;
4335             while (i < this.value.length-1)
4336             {
4337                 ubyte[] character;
4338                 character.length = 2u;
4339                 character[1] = this.value[i++];
4340                 character[0] = this.value[i++];
4341                 ret ~= (*cast(wchar *) character.ptr);
4342             }
4343             return ret;
4344         }
4345         else
4346         {
4347             static assert(0, "Could not determine endianness!");
4348         }
4349     }
4350 
4351     /// Encodes a $(MONO wstring) of UTF-16 characters.
4352     override public @property @system
4353     void basicMultilingualPlaneString(in wstring value)
4354     {
4355         scope(success) this.construction = ASN1Construction.primitive;
4356         version (BigEndian)
4357         {
4358             this.value = cast(ubyte[]) value.dup;
4359         }
4360         else version (LittleEndian)
4361         {
4362             foreach(immutable character; value)
4363             {
4364                 ubyte[] charBytes = cast(ubyte[]) *cast(char[2] *) &character;
4365                 reverse(charBytes);
4366                 this.value ~= charBytes;
4367             }
4368         }
4369         else
4370         {
4371             static assert(0, "Could not determine endianness!");
4372         }
4373     }
4374 
4375     /// Creates an $(MONO END OF CONTENT)
4376     public @safe @nogc nothrow
4377     this()
4378     {
4379         this.tagNumber = 0u;
4380         this.value = [];
4381     }
4382 
4383     /**
4384         Creates a $(D DERElement) from the supplied bytes, inferring that the first
4385         byte is the type tag. The supplied $(D ubyte[]) array is "chomped" by
4386         reference, so the original array will grow shorter as $(D DERElement)s are
4387         generated.
4388 
4389         Throws:
4390             All of the same exceptions as $(D fromBytes())
4391 
4392         Examples:
4393         ---
4394         // Decoding looks like:
4395         DERElement[] result;
4396         while (bytes.length > 0)
4397             result ~= new DERElement(bytes);
4398 
4399         // Encoding looks like:
4400         ubyte[] result;
4401         foreach (dv; dervalues)
4402         {
4403             result ~= cast(ubyte[]) dv;
4404         }
4405         ---
4406     */
4407     public @system
4408     this(ref ubyte[] bytes)
4409     {
4410         immutable size_t bytesRead = this.fromBytes(bytes);
4411         bytes = bytes[bytesRead .. $];
4412     }
4413 
4414     /**
4415         Creates a $(D DERElement) from the supplied bytes, inferring that the first
4416         byte is the type tag. The supplied $(D ubyte[]) array is read, starting
4417         from the index specified by $(D bytesRead), and increments
4418         $(D bytesRead) by the number of bytes read.
4419 
4420         Throws:
4421             All of the same exceptions as $(D fromBytes())
4422 
4423         Examples:
4424         ---
4425         // Decoding looks like:
4426         DERElement[] result;
4427         size_t i = 0u;
4428         while (i < bytes.length)
4429             result ~= new DERElement(i, bytes);
4430 
4431         // Encoding looks like:
4432         ubyte[] result;
4433         foreach (dv; dervalues)
4434         {
4435             result ~= cast(ubyte[]) dv;
4436         }
4437         ---
4438     */
4439     public @system
4440     this(ref size_t bytesRead, in ubyte[] bytes)
4441     {
4442         bytesRead += this.fromBytes(bytes[bytesRead .. $].dup);
4443     }
4444 
4445     /**
4446         Returns: the number of bytes read
4447 
4448         Throws:
4449         $(UL
4450             $(LI $(D ASN1TagPaddingException) if the tag number is "padded" with
4451                 "leading zero bytes" ($(D 0x80u)))
4452             $(LI $(D ASN1TagOverflowException) if the tag number is too large to
4453                 fit into a $(D size_t))
4454             $(LI $(D ASN1LengthUndefinedException) if the reserved length byte of
4455                 $(D 0xFF) is encountered)
4456             $(LI $(D ASN1LengthOverflowException) if the length is too large to fit
4457                 into a $(D size_t))
4458             $(LI $(D ASN1TruncationException) if the tag, length, or value appear to
4459                 be truncated)
4460             $(LI $(D ASN1ConstructionException) if the length is indefinite, but the
4461                 element is marked as being encoded primitively)
4462             $(LI $(D ASN1LengthException) if indefinite-length encoding is encountered)
4463         )
4464     */
4465     public
4466     size_t fromBytes (in ubyte[] bytes)
4467     {
4468         if (bytes.length < 2u)
4469             throw new ASN1TruncationException
4470             (
4471                 2u,
4472                 bytes.length,
4473                 "decode the tag of a Distinguished Encoding Rules (DER) encoded element"
4474             );
4475 
4476         // Index of what we are currently parsing.
4477         size_t cursor = 0u;
4478 
4479         switch (bytes[cursor] & 0b11000000u)
4480         {
4481             case (0b00000000u): this.tagClass = ASN1TagClass.universal; break;
4482             case (0b01000000u): this.tagClass = ASN1TagClass.application; break;
4483             case (0b10000000u): this.tagClass = ASN1TagClass.contextSpecific; break;
4484             case (0b11000000u): this.tagClass = ASN1TagClass.privatelyDefined; break;
4485             default: assert(0, "Impossible tag class appeared!");
4486         }
4487 
4488         this.construction = ((bytes[cursor] & 0b00100000u) ?
4489             ASN1Construction.constructed : ASN1Construction.primitive);
4490 
4491         this.tagNumber = (bytes[cursor] & 0b00011111u);
4492         cursor++;
4493         if (this.tagNumber >= 31u)
4494         {
4495             /* NOTE:
4496                 Section 8.1.2.4.2, point C of the International
4497                 Telecommunications Union's X.690 specification says:
4498 
4499                 "bits 7 to 1 of the first subsequent octet shall not all be zero."
4500 
4501                 in reference to the bytes used to encode the tag number in long
4502                 form, which happens when the least significant five bits of the
4503                 first byte are all set.
4504 
4505                 This essentially means that the long-form tag number must be
4506                 encoded on the fewest possible octets. If the first byte is
4507                 0x80, then it is not encoded on the fewest possible octets.
4508             */
4509             if (bytes[cursor] == 0b10000000u)
4510                 throw new ASN1TagPaddingException
4511                 (
4512                     "This exception was thrown because you attempted to decode " ~
4513                     "a Distinguished Encoding Rules (DER) encoded element whose tag " ~
4514                     "number was encoded in long form in the octets following " ~
4515                     "the first octet of the type tag, and whose tag number " ~
4516                     "was encoded with a 'leading zero' byte, 0x80. When " ~
4517                     "using Distinguished Encoding Rules (DER), the tag number must " ~
4518                     "be encoded on the smallest number of octets possible, " ~
4519                     "which the inclusion of leading zero bytes necessarily " ~
4520                     "contradicts. " ~
4521                     forMoreInformationText ~ debugInformationText ~ reportBugsText
4522                 );
4523 
4524             this.tagNumber = 0u;
4525 
4526             // This loop looks for the end of the encoded tag number.
4527             immutable size_t limit = ((bytes.length-1 >= size_t.sizeof) ? size_t.sizeof : bytes.length-1);
4528             while (cursor < limit)
4529             {
4530                 if (!(bytes[cursor++] & 0x80u)) break;
4531             }
4532 
4533             if (bytes[cursor-1] & 0x80u)
4534             {
4535                 if (limit == bytes.length-1)
4536                 {
4537                     throw new ASN1TruncationException
4538                     (size_t.max, bytes.length, "decode an ASN.1 tag number");
4539                 }
4540                 else
4541                 {
4542                     throw new ASN1TagOverflowException
4543                     (
4544                         "This exception was thrown because you attempted to decode " ~
4545                         "a Distinguished Encoding Rules (DER) encoded element that encoded " ~
4546                         "a tag number that was either too large to decode or " ~
4547                         "terminated prematurely."
4548                     );
4549                 }
4550             }
4551 
4552             for (size_t i = 1; i < cursor; i++)
4553             {
4554                 this.tagNumber <<= 7;
4555                 this.tagNumber |= cast(size_t) (bytes[i] & 0x7Fu);
4556             }
4557         }
4558 
4559         // Length
4560         if ((bytes[cursor] & 0x80u) == 0x80u)
4561         {
4562             immutable ubyte numberOfLengthOctets = (bytes[cursor] & 0x7Fu);
4563             if (numberOfLengthOctets) // Definite Long or Reserved
4564             {
4565                 if (numberOfLengthOctets == 0b01111111u) // Reserved
4566                     throw new ASN1LengthUndefinedException();
4567 
4568                 // Definite Long, if it has made it this far
4569 
4570                 if (numberOfLengthOctets > size_t.sizeof)
4571                     throw new ASN1LengthOverflowException();
4572 
4573                 if (cursor + numberOfLengthOctets >= bytes.length)
4574                     throw new ASN1TruncationException
4575                     (
4576                         numberOfLengthOctets,
4577                         ((bytes.length - 1) - cursor), // FIXME: You can increment the cursor before this.
4578                         "decode the length of a Distinguished Encoding Rules (DER) encoded element"
4579                     );
4580 
4581                 if (bytes[++cursor] == 0x00u)
4582                     throw new ASN1LengthException
4583                     (
4584                         "This exception was thrown because you attempted to " ~
4585                         "decode a Distinguished Encoding Rules (DER) encoded " ~
4586                         "element whose length was encoded in definite long " ~
4587                         "form, and encoded on more octets than necessary, " ~
4588                         "which is prohibited by the specification for " ~
4589                         "Distinguished Encoding Rules (DER). " ~
4590                         forMoreInformationText ~ debugInformationText ~ reportBugsText
4591                     );
4592 
4593                 ubyte[] lengthNumberOctets;
4594                 lengthNumberOctets.length = size_t.sizeof;
4595                 for (ubyte i = numberOfLengthOctets; i > 0u; i--)
4596                 {
4597                     lengthNumberOctets[size_t.sizeof-i] = bytes[cursor+numberOfLengthOctets-i];
4598                 }
4599                 version (LittleEndian) reverse(lengthNumberOctets);
4600                 size_t length = *cast(size_t *) lengthNumberOctets.ptr;
4601 
4602                 if (length <= 127u)
4603                     throw new ASN1LengthException
4604                     (
4605                         "This exception was thrown because you attempted to " ~
4606                         "decode a Distinguished Encoding Rules (DER) encoded " ~
4607                         "element whose length was encoded in definite long " ~
4608                         "form, and encoded on more octets than necessary, " ~
4609                         "which is prohibited by the specification for " ~
4610                         "Distinguished Encoding Rules (DER). Specifically, it " ~
4611                         "was encoded in definite-long form when it was less " ~
4612                         "than or equal to 127, which could have been encoded " ~
4613                         "in definite-short form. " ~
4614                         forMoreInformationText ~ debugInformationText ~ reportBugsText
4615                     );
4616 
4617                 if ((cursor + length) < cursor) // This catches an overflow.
4618                     throw new ASN1LengthException
4619                     (
4620                         "This exception was thrown because you attempted to " ~
4621                         "decode a Disinguished Encoding Rules (DER) encoded element " ~
4622                         "that indicated that it was exceedingly large--so " ~
4623                         "large, in fact, that it cannot be stored on this " ~
4624                         "computer (18 exabytes if you are on a 64-bit system). " ~
4625                         "This may indicate that the data you attempted to " ~
4626                         "decode was either corrupted, malformed, or deliberately " ~
4627                         "crafted to hack you. You would be wise to ensure that " ~
4628                         "you are running the latest stable version of this " ~
4629                         "library. "
4630                     );
4631 
4632                 cursor += (numberOfLengthOctets);
4633 
4634                 if ((cursor + length) > bytes.length)
4635                     throw new ASN1TruncationException
4636                     (
4637                         length,
4638                         (bytes.length - cursor),
4639                         "decode the value of a Distinguished Encoding Rules (DER) encoded element"
4640                     );
4641 
4642                 this.value = bytes[cursor .. cursor+length].dup;
4643                 return (cursor + length);
4644             }
4645             else // Indefinite
4646             {
4647                 throw new ASN1LengthException
4648                 (
4649                     "This exception was thrown because an invalid length tag " ~
4650                     "was encountered. Distinguished Encoding Rules (DER) do not " ~
4651                     "permit indefinite-length encoded data. Are you sure you are " ~
4652                     "using the correct codec?"
4653                 );
4654             }
4655         }
4656         else // Definite Short
4657         {
4658             ubyte length = (bytes[cursor++] & 0x7Fu);
4659 
4660             if ((cursor + length) > bytes.length)
4661                 throw new ASN1TruncationException
4662                 (
4663                     length,
4664                     ((bytes.length - 1) - cursor),
4665                     "decode the value of a Distinguished Encoding Rules (DER) encoded element"
4666                 );
4667 
4668             this.value = bytes[cursor .. cursor+length].dup;
4669             return (cursor + length);
4670         }
4671     }
4672 
4673     /**
4674         This differs from $(D this.value) in that
4675         $(D this.value) only returns the value octets, whereas
4676         $(D this.toBytes) returns the type tag, length tag / octets,
4677         and the value octets, all concatenated.
4678 
4679         This is the exact same as $(D this.opCast!(ubyte[])()).
4680 
4681         Returns: type tag, length tag, and value, all concatenated as a $(D ubyte) array.
4682     */
4683     public @property @system nothrow
4684     ubyte[] toBytes() const
4685     {
4686         ubyte[] tagBytes = [ 0x00u ];
4687         tagBytes[0] |= cast(ubyte) this.tagClass;
4688         tagBytes[0] |= cast(ubyte) this.construction;
4689 
4690         if (this.tagNumber < 31u)
4691         {
4692             tagBytes[0] |= cast(ubyte) this.tagNumber;
4693         }
4694         else
4695         {
4696             /*
4697                 Per section 8.1.2.4 of X.690:
4698                 The last five bits of the first byte being set indicate that
4699                 the tag number is encoded in base-128 on the subsequent octets,
4700                 using the first bit of each subsequent octet to indicate if the
4701                 encoding continues on the next octet, just like how the
4702                 individual numbers of OBJECT IDENTIFIER and RELATIVE OBJECT
4703                 IDENTIFIER are encoded.
4704             */
4705             tagBytes[0] |= cast(ubyte) 0b00011111u;
4706             size_t number = this.tagNumber; // We do not want to modify by reference.
4707             ubyte[] encodedNumber;
4708             while (number != 0u)
4709             {
4710                 ubyte[] numberbytes;
4711                 numberbytes.length = size_t.sizeof+1;
4712                 *cast(size_t *) numberbytes.ptr = number;
4713                 if ((numberbytes[0] & 0x80u) == 0u) numberbytes[0] |= 0x80u;
4714                 encodedNumber = numberbytes[0] ~ encodedNumber;
4715                 number >>= 7u;
4716             }
4717             tagBytes ~= encodedNumber;
4718             tagBytes[$-1] &= 0x7Fu; // Set first bit of last byte to zero.
4719         }
4720 
4721         ubyte[] lengthOctets = [ 0x00u ];
4722         if (this.length < 127u)
4723         {
4724             lengthOctets = [ cast(ubyte) this.length ];
4725         }
4726         else
4727         {
4728             size_t length = cast(size_t) this.value.length;
4729             ubyte[] lengthNumberOctets = cast(ubyte[]) *cast(ubyte[size_t.sizeof] *) &length;
4730             version (LittleEndian) reverse(lengthNumberOctets);
4731             size_t startOfNonPadding = 0u;
4732             for (size_t i = 0u; i < size_t.sizeof; i++)
4733             {
4734                 if (lengthNumberOctets[i] != 0x00u) break;
4735                 startOfNonPadding++;
4736             }
4737             lengthNumberOctets = lengthNumberOctets[startOfNonPadding .. $];
4738             lengthOctets = [ cast(ubyte) (0x80u + lengthNumberOctets.length) ];
4739             lengthOctets ~= lengthNumberOctets;
4740         }
4741 
4742         return (tagBytes ~ lengthOctets ~ this.value);
4743     }
4744 
4745     /**
4746         This differs from $(D this.value) in that
4747         $(D this.value) only returns the value octets, whereas
4748         $(D this.toBytes) returns the type tag, length tag / octets,
4749         and the value octets, all concatenated.
4750 
4751         This is the exact same as $(D this.toBytes()).
4752 
4753         Returns: type tag, length tag, and value, all concatenated as a $(D ubyte) array.
4754     */
4755     public @system nothrow
4756     ubyte[] opCast(T = ubyte[])()
4757     {
4758         return this.toBytes();
4759     }
4760 
4761 }
4762 
4763 // Tests of all types using definite-short encoding.
4764 @system
4765 unittest
4766 {
4767     // Test data
4768     immutable ubyte[] dataEndOfContent = [ 0x00u, 0x00u ];
4769     immutable ubyte[] dataBoolean = [ 0x01u, 0x01u, 0xFFu ];
4770     immutable ubyte[] dataInteger = [ 0x02u, 0x01u, 0x1Bu ];
4771     immutable ubyte[] dataBitString = [ 0x03u, 0x03u, 0x07u, 0xF0u, 0x80u ];
4772     immutable ubyte[] dataOctetString = [ 0x04u, 0x04u, 0xFF, 0x00u, 0x88u, 0x14u ];
4773     immutable ubyte[] dataNull = [ 0x05u, 0x00u ];
4774     immutable ubyte[] dataOID = [ 0x06u, 0x04u, 0x2Bu, 0x06u, 0x04u, 0x01u ];
4775     immutable ubyte[] dataOD = [ 0x07u, 0x05u, 'H', 'N', 'E', 'L', 'O' ];
4776     immutable ubyte[] dataExternal = [
4777         0x28u, 0x0Bu, 0x06u, 0x03u, 0x29u, 0x05u, 0x07u, 0x82u,
4778         0x04u, 0x01u, 0x02u, 0x03u, 0x04u ];
4779     immutable ubyte[] dataReal = [ 0x09u, 0x03u, 0x80u, 0xFBu, 0x05u ]; // 0.15625 (From StackOverflow question)
4780     immutable ubyte[] dataEnum = [ 0x0Au, 0x01u, 0x3Fu ];
4781     immutable ubyte[] dataEmbeddedPDV = [
4782         0x2Bu, 0x0Au, 0x80u, 0x02u, 0x85u, 0x00u, 0x82u, 0x04u,
4783         0x01u, 0x02u, 0x03u, 0x04u ];
4784     immutable ubyte[] dataUTF8 = [ 0x0Cu, 0x05u, 'H', 'E', 'N', 'L', 'O' ];
4785     immutable ubyte[] dataROID = [ 0x0Du, 0x03u, 0x06u, 0x04u, 0x01u ];
4786     // sequence
4787     // set
4788     immutable ubyte[] dataNumeric = [ 0x12u, 0x07u, '8', '6', '7', '5', '3', '0', '9' ];
4789     immutable ubyte[] dataPrintable = [ 0x13u, 0x06u, '8', '6', ' ', 'b', 'f', '8' ];
4790     immutable ubyte[] dataTeletex = [ 0x14u, 0x06u, 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ];
4791     immutable ubyte[] dataVideotex = [ 0x15u, 0x06u, 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ];
4792     immutable ubyte[] dataIA5 = [ 0x16u, 0x08u, 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S' ];
4793     immutable ubyte[] dataUTC = [ 0x17u, 0x0Du, '1', '7', '0', '8', '3', '1', '1', '3', '4', '5', '0', '0', 'Z' ];
4794     immutable ubyte[] dataGT = [ 0x18u, 0x0Fu, '2', '0', '1', '7', '0', '8', '3', '1', '1', '3', '4', '5', '0', '0', 'Z' ];
4795     immutable ubyte[] dataGraphic = [ 0x19u, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ];
4796     immutable ubyte[] dataVisible = [ 0x1Au, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ];
4797     immutable ubyte[] dataGeneral = [ 0x1Bu, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ];
4798     immutable ubyte[] dataUniversal = [
4799         0x1Cu, 0x10u,
4800         0x00u, 0x00u, 0x00u, 0x61u,
4801         0x00u, 0x00u, 0x00u, 0x62u,
4802         0x00u, 0x00u, 0x00u, 0x63u,
4803         0x00u, 0x00u, 0x00u, 0x64u
4804     ]; // Big-endian "abcd"
4805     immutable ubyte[] dataCharacter = [
4806         0x3Du, 0x0Fu, 0x80u, 0x06u, 0x81u, 0x04u, 0x29u, 0x06u,
4807         0x04u, 0x01u, 0x82u, 0x05u, 0x48u, 0x45u, 0x4Eu, 0x4Cu,
4808         0x4Fu ];
4809     immutable ubyte[] dataBMP = [ 0x1Eu, 0x08u, 0x00u, 0x61u, 0x00u, 0x62u, 0x00u, 0x63u, 0x00u, 0x64u ]; // Big-endian "abcd"
4810 
4811     // Combine it all
4812     ubyte[] data =
4813         (dataEndOfContent ~
4814         dataBoolean ~
4815         dataInteger ~
4816         dataBitString ~
4817         dataOctetString ~
4818         dataNull ~
4819         dataOID ~
4820         dataOD ~
4821         dataExternal ~
4822         dataReal ~
4823         dataEnum ~
4824         dataEmbeddedPDV ~
4825         dataUTF8 ~
4826         dataROID ~
4827         dataNumeric ~
4828         dataPrintable ~
4829         dataTeletex ~
4830         dataVideotex ~
4831         dataIA5 ~
4832         dataUTC ~
4833         dataGT ~
4834         dataGraphic ~
4835         dataVisible ~
4836         dataGeneral ~
4837         dataUniversal ~
4838         dataCharacter ~
4839         dataBMP).dup;
4840 
4841     DERElement[] result;
4842 
4843     size_t i = 0u;
4844     while (i < data.length)
4845         result ~= new DERElement(i, data);
4846 
4847     // Pre-processing
4848     External x = result[8].external;
4849     EmbeddedPDV m = result[11].embeddedPresentationDataValue;
4850     CharacterString c = result[25].characterString;
4851 
4852     // Ensure accessors decode the data correctly.
4853     assert(result[1].boolean == true);
4854     assert(result[2].integer!long == 27L);
4855     assert(result[3].bitString == [ true, true, true, true, false, false, false, false, true ]);
4856     assert(result[4].octetString == [ 0xFFu, 0x00u, 0x88u, 0x14u ]);
4857     assert(result[6].objectIdentifier == new OID(OIDNode(0x01u), OIDNode(0x03u), OIDNode(0x06u), OIDNode(0x04u), OIDNode(0x01u)));
4858     assert(result[7].objectDescriptor == result[7].objectDescriptor);
4859     assert((x.identification.syntax == new OID(1u, 1u, 5u, 7u)) && (x.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]));
4860     assert(result[9].realNumber!float == 0.15625);
4861     assert(result[9].realNumber!double == 0.15625);
4862     assert(result[10].enumerated!long == 63L);
4863     assert((m.identification.fixed == true) && (m.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]));
4864     assert(result[12].utf8String == "HENLO");
4865     assert(result[13].relativeObjectIdentifier == [ OIDNode(6), OIDNode(4), OIDNode(1) ]);
4866     assert(result[14].numericString == "8675309");
4867     assert(result[15].printableString ==  "86 bf8");
4868     assert(result[16].teletexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]);
4869     assert(result[17].videotexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]);
4870     assert(result[18].ia5String == "BORTHERS");
4871     assert(result[19].utcTime == DateTime(2017, 8, 31, 13, 45));
4872     assert(result[20].generalizedTime == DateTime(2017, 8, 31, 13, 45));
4873     assert(result[21].graphicString == "PowerThirst");
4874     assert(result[22].visibleString == "PowerThirst");
4875     assert(result[23].generalString == "PowerThirst");
4876     assert(result[24].universalString == "abcd"d);
4877     assert((c.identification.syntax == new OID(1u, 1u, 6u, 4u, 1u)) && (c.stringValue == "HENLO"w));
4878 
4879     result = [];
4880     while (data.length > 0)
4881         result ~= new DERElement(data);
4882 
4883     // Pre-processing
4884     x = result[8].external;
4885     m = result[11].embeddedPresentationDataValue;
4886     c = result[25].characterString;
4887 
4888     // Ensure accessors decode the data correctly.
4889     assert(result[1].boolean == true);
4890     assert(result[2].integer!long == 27L);
4891     assert(result[3].bitString == [ true, true, true, true, false, false, false, false, true ]);
4892     assert(result[4].octetString == [ 0xFFu, 0x00u, 0x88u, 0x14u ]);
4893     assert(result[6].objectIdentifier == new OID(OIDNode(0x01u), OIDNode(0x03u), OIDNode(0x06u), OIDNode(0x04u), OIDNode(0x01u)));
4894     assert(result[7].objectDescriptor == result[7].objectDescriptor);
4895     assert((x.identification.syntax == new OID(1u, 1u, 5u, 7u)) && (x.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]));
4896     assert(result[9].realNumber!float == 0.15625);
4897     assert(result[9].realNumber!double == 0.15625);
4898     assert(result[10].enumerated!long == 63L);
4899     assert((m.identification.fixed == true) && (m.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]));
4900     assert(result[12].utf8String == "HENLO");
4901     assert(result[13].relativeObjectIdentifier == [ OIDNode(6), OIDNode(4), OIDNode(1) ]);
4902     assert(result[14].numericString == "8675309");
4903     assert(result[15].printableString ==  "86 bf8");
4904     assert(result[16].teletexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]);
4905     assert(result[17].videotexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]);
4906     assert(result[18].ia5String == "BORTHERS");
4907     assert(result[19].utcTime == DateTime(2017, 8, 31, 13, 45));
4908     assert(result[20].generalizedTime == DateTime(2017, 8, 31, 13, 45));
4909     assert(result[21].graphicString == "PowerThirst");
4910     assert(result[22].visibleString == "PowerThirst");
4911     assert(result[23].generalString == "PowerThirst");
4912     assert(result[24].universalString == "abcd"d);
4913     assert((c.identification.syntax == new OID(1u, 1u, 6u, 4u, 1u)) && (c.stringValue == "HENLO"w));
4914 }
4915 
4916 // Test of definite-long encoding
4917 @system
4918 unittest
4919 {
4920     ubyte[] data = [ // 192 characters of boomer-posting
4921         0x0Cu, 0x81u, 0xC0u,
4922         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4923         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4924         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4925         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4926         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4927         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4928         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4929         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4930         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4931         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4932         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4933         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n'
4934     ];
4935 
4936     data = (data ~ data ~ data); // Triple the data, to catch any bugs that arise with subsequent values.
4937 
4938     DERElement[] result;
4939     size_t i = 0u;
4940     while (i < data.length)
4941         result ~= new DERElement(i, data);
4942 
4943     assert(result.length == 3);
4944     assert(result[0].utf8String[0 .. 5] == "AMREN");
4945     assert(result[1].utf8String[6 .. 14] == "BORTHERS");
4946     assert(result[2].utf8String[$-2] == '!');
4947 
4948     result = [];
4949     while (data.length > 0)
4950         result ~= new DERElement(data);
4951 
4952     assert(result.length == 3);
4953     assert(result[0].utf8String[0 .. 5] == "AMREN");
4954     assert(result[1].utf8String[6 .. 14] == "BORTHERS");
4955     assert(result[2].utf8String[$-2] == '!');
4956 }
4957 
4958 // Test that indefinite-length encoding throws an exception.
4959 @system
4960 unittest
4961 {
4962     ubyte[] data = [ // 192 characters of boomer-posting
4963         0x0Cu, 0x80u,
4964         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4965         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4966         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4967         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4968         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4969         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4970         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4971         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4972         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4973         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4974         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4975         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
4976         0x00u, 0x00u
4977     ];
4978 
4979     data = (data ~ data ~ data); // Triple the data, to catch any bugs that arise with subsequent values.
4980 
4981     size_t i = 0u;
4982     assertThrown!ASN1LengthException(new DERElement(i, data));
4983     assertThrown!ASN1LengthException(new DERElement(data));
4984 }
4985 
4986 /*
4987     Test of OCTET STRING encoding on 500 bytes (+4 for type and length tags)
4988 
4989     The number 500 was specifically selected for this test because CER
4990     uses 1000 as the threshold after which OCTET STRING must be represented
4991     as a constructed sequence of definite-length-encoded OCTET STRINGS,
4992     followed by an EOC element, but 500 is also big enough to require
4993     the length to be encoded on two octets in definite-long form.
4994 */
4995 @system
4996 unittest
4997 {
4998     ubyte[] test;
4999     test.length = 504u;
5000     test[0] = cast(ubyte) ASN1UniversalType.octetString;
5001     test[1] = 0b10000010u; // Length is encoded on next two octets
5002     test[2] = 0x01u; // Most significant byte of length
5003     test[3] = 0xF4u; // Least significant byte of length
5004     test[4] = 0x0Au; // First byte of the encoded value
5005     test[5 .. $-1] = 0x0Bu;
5006     test[$-1] = 0x0Cu;
5007 
5008     DERElement el;
5009     assertNotThrown!Exception(el = new DERElement(test));
5010     ubyte[] output = el.octetString;
5011     assert(output.length == 500u);
5012     assert(output[0] == 0x0Au);
5013     assert(output[1] == 0x0Bu);
5014     assert(output[$-2] == 0x0Bu);
5015     assert(output[$-1] == 0x0Cu);
5016 }
5017 
5018 // Assert all single-byte encodings do not decode successfully.
5019 @system
5020 unittest
5021 {
5022     for (ubyte i = 0x00u; i < ubyte.max; i++)
5023     {
5024         ubyte[] data = [i];
5025         assertThrown!Exception(new DERElement(data)); // FIXME: Narrow down the exception
5026     }
5027 
5028     size_t index;
5029     for (ubyte i = 0x00u; i < ubyte.max; i++)
5030     {
5031         immutable ubyte[] data = [i];
5032         assertThrown!Exception(new DERElement(index, data));
5033     }
5034 }
5035 
5036 // Test long-form tag number (when # >= 31) with leading zero bytes (0x80)
5037 @system
5038 unittest
5039 {
5040     ubyte[] invalid;
5041     invalid = [ 0b10011111u, 0b10000000u ];
5042     assertThrown!ASN1TagException(new DERElement(invalid));
5043 
5044     invalid = [ 0b10011111u, 0b10000000u, 0b10000000u ];
5045     assertThrown!ASN1TagException(new DERElement(invalid));
5046 
5047     invalid = [ 0b10011111u, 0b10000000u, 0b10000111u ];
5048     assertThrown!ASN1TagException(new DERElement(invalid));
5049 }
5050 
5051 // Test long-form tag numbers do not encode with leading zeroes
5052 @system
5053 unittest
5054 {
5055     ubyte[] invalid;
5056     DERElement element = new DERElement();
5057     element.tagNumber = 73u;
5058     assert((element.toBytes)[1] != 0x80u);
5059 }
5060 
5061 // Test that a value that is a byte too short does not throw a RangeError.
5062 @system
5063 unittest
5064 {
5065     ubyte[] test = [ 0x00u, 0x03u, 0x00u, 0x00u ];
5066     assertThrown!ASN1TruncationException(new DERElement(test));
5067 }
5068 
5069 // Test that a misleading definite-long length byte does not throw a RangeError.
5070 @system
5071 unittest
5072 {
5073     ubyte[] invalid = [ 0b00000000u, 0b10000001u ];
5074     assertThrown!ASN1TruncationException(new DERElement(invalid));
5075 }
5076 
5077 // Test that leading zeroes in definite long length encodings throw exceptions
5078 @system
5079 unittest
5080 {
5081     ubyte[] invalid = [ 0b00000000u, 0b10000010u, 0b00000000u, 0b00000001u ];
5082     assertThrown!ASN1LengthException(new DERElement(invalid));
5083 }
5084 
5085 // Test that a very large value does not cause a segfault or something
5086 @system
5087 unittest
5088 {
5089     size_t biggest = cast(size_t) int.max;
5090     ubyte[] big = [ 0x00u, 0x80u ];
5091     big[1] += cast(ubyte) int.sizeof;
5092     big ~= cast(ubyte[]) *cast(ubyte[int.sizeof] *) &biggest;
5093     big ~= [ 0x01u, 0x02u, 0x03u ]; // Plus some arbitrary data.
5094     assertThrown!ASN1TruncationException(new DERElement(big));
5095 }
5096 
5097 // Test that the largest possible item does not cause a segfault or something
5098 @system
5099 unittest
5100 {
5101     size_t biggest = size_t.max;
5102     ubyte[] big = [ 0x00u, 0x80u ];
5103     big[1] += cast(ubyte) size_t.sizeof;
5104     big ~= cast(ubyte[]) *cast(ubyte[size_t.sizeof] *) &biggest;
5105     big ~= [ 0x01u, 0x02u, 0x03u ]; // Plus some arbitrary data.
5106     assertThrown!ASN1LengthException(new DERElement(big));
5107 }
5108 
5109 // Test that a valueless long-form definite-length element does not throw a RangeError
5110 @system
5111 unittest
5112 {
5113     ubyte[] naughty = [ 0x00u, 0x82, 0x00u, 0x01u ];
5114     size_t bytesRead = 0u;
5115     assertThrown!ASN1LengthException(new DERElement(bytesRead, naughty));
5116 }
5117 
5118 // PyASN1 Comparison Testing
5119 @system
5120 unittest
5121 {
5122     DERElement e = new DERElement();
5123     e.boolean = false;
5124     assert(e.value == [ 0x00u ]);
5125     e.integer = 5;
5126     assert(e.value == [ 0x05u ]);
5127     e.bitString = [
5128         true, false, true, true, false, false, true, true,
5129         true, false, false, false ];
5130     assert(e.value == [ 0x04u, 0xB3u, 0x80u ]);
5131     e.bitString = [
5132         true, false, true, true, false, true, false, false ];
5133     assert(e.value == [ 0x00u, 0xB4u ]);
5134     e.objectIdentifier = new OID(1, 2, 0, 256, 79999, 7);
5135     assert(e.value == [
5136         0x2Au, 0x00u, 0x82u, 0x00u, 0x84u, 0xF0u, 0x7Fu, 0x07u ]);
5137     e.enumerated = 5;
5138     assert(e.value == [ 0x05u ]);
5139     e.enumerated = 90000;
5140     assert(e.value == [ 0x01u, 0x5Fu, 0x90u ]);
5141 }
5142 
5143 // Test that all data types that cannot have value length = 0 throw exceptions.
5144 // See CVE-2015-5726.
5145 @system
5146 unittest
5147 {
5148     DERElement el = new DERElement();
5149     assertThrown!ASN1Exception(el.boolean);
5150     assertThrown!ASN1Exception(el.integer!byte);
5151     assertThrown!ASN1Exception(el.integer!short);
5152     assertThrown!ASN1Exception(el.integer!int);
5153     assertThrown!ASN1Exception(el.integer!long);
5154     assertThrown!ASN1Exception(el.bitString);
5155     assertThrown!ASN1Exception(el.objectIdentifier);
5156     assertThrown!ASN1Exception(el.enumerated!byte);
5157     assertThrown!ASN1Exception(el.enumerated!short);
5158     assertThrown!ASN1Exception(el.enumerated!int);
5159     assertThrown!ASN1Exception(el.enumerated!long);
5160     assertThrown!ASN1Exception(el.generalizedTime);
5161     assertThrown!ASN1Exception(el.utcTime);
5162 }