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