1 /** 2 Canonical Encoding Rules (CER) is a standard for encoding ASN.1 data. 3 CER is often used for cryptgraphically-signed data, such as X.509 4 certificates, because CER'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, CER 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), Canonical Encoding Rules (CER) is a 10 specification created by the 11 $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union), 12 and specified in $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules) 13 14 Authors: 15 $(UL 16 $(LI $(PERSON Jonathan M. Wilbur, jonathan@wilbur.space, http://jonathan.wilbur.space)) 17 ) 18 Copyright: Copyright (C) Jonathan M. Wilbur 19 License: $(LINK https://mit-license.org/, MIT License) 20 Standards: 21 $(UL 22 $(LI $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1))) 23 $(LI $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules)) 24 ) 25 See_Also: 26 $(UL 27 $(LI $(LINK https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One, The Wikipedia Page on ASN.1)) 28 $(LI $(LINK https://en.wikipedia.org/wiki/X.690, The Wikipedia Page on X.690)) 29 $(LI $(LINK https://www.strozhevsky.com/free_docs/asn1_in_simple_words.pdf, ASN.1 By Simple Words)) 30 $(LI $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF, ASN.1: Communication Between Heterogeneous Systems)) 31 ) 32 */ 33 module codecs.cer; 34 public import codec; 35 public import interfaces : Byteable; 36 public import types.identification; 37 38 /// 39 public alias cerOID = canonicalEncodingRulesObjectIdentifier; 40 /// 41 public alias cerObjectID = canonicalEncodingRulesObjectIdentifier; 42 /// 43 public alias cerObjectIdentifier = canonicalEncodingRulesObjectIdentifier; 44 /** 45 The object identifier assigned to the Canonical Encoding Rules (CER), per the 46 $(LINK https://www.itu.int/en/pages/default.aspx, 47 International Telecommunications Union)'s, 48 $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules) 49 50 $(MONO {joint-iso-itu-t asn1(1) ber-derived(2) canonical-encoding(0)} ) 51 */ 52 public immutable OID canonicalEncodingRulesObjectIdentifier = cast(immutable(OID)) new OID(2, 1, 2, 0); 53 54 /// 55 public alias CERElement = CanonicalEncodingRulesElement; 56 /** 57 The unit of encoding and decoding for Canonical Encoding Rules (CER). 58 59 There are three parts to an element encoded according to the Canonical 60 Encoding Rules (CER): 61 62 $(UL 63 $(LI A Type Tag, which specifies what data type is encoded) 64 $(LI A Length Tag, which specifies how many subsequent bytes encode the data) 65 $(LI The Encoded Value) 66 ) 67 68 They appear in the binary encoding in that order, and as such, the encoding 69 scheme is sometimes described as "TLV," which stands for Type-Length-Value. 70 71 This class provides a properties for getting and setting bit fields of 72 the type tag, but most of it is functionality for encoding data per 73 the specification. 74 75 As an example, this is what encoding a simple INTEGER looks like: 76 77 --- 78 CERElement cv = new CERElement(); 79 cv.tagNumber = 0x02u; // "2" means this is an INTEGER 80 cv.integer = 1433; // Now the data is encoded. 81 transmit(cast(ubyte[]) cv); // transmit() is a made-up function. 82 --- 83 84 And this is what decoding looks like: 85 86 --- 87 ubyte[] data = receive(); // receive() is a made-up function. 88 CERElement cv2 = new CERElement(data); 89 90 long x; 91 if (cv.tagNumber == 0x02u) // it is an INTEGER 92 { 93 x = cv.integer; 94 } 95 // Now x is 1433! 96 --- 97 */ 98 /* FIXME: 99 This class should be "final," but a bug in the DMD compiler produces 100 unlinkable objects if a final class inherits an alias to an internal 101 member of a parent class. 102 103 I have reported this to the D Language Foundation's Bugzilla site on 104 17 October, 2017, and this bug can be viewed here: 105 https://issues.dlang.org/show_bug.cgi?id=17909 106 */ 107 public 108 class CanonicalEncodingRulesElement : ASN1Element!CERElement, Byteable 109 { 110 @system 111 unittest 112 { 113 writeln("Running unit tests for codec: " ~ typeof(this).stringof); 114 } 115 116 /// 117 public ASN1TagClass tagClass; 118 /// 119 public ASN1Construction construction; 120 /// 121 public size_t tagNumber; 122 123 /// The length of the value in octets 124 final public @property @safe nothrow 125 size_t length() const 126 { 127 return this.value.length; 128 } 129 130 /// The octets of the encoded value. 131 public ubyte[] value; 132 133 /** 134 "Decodes" an $(MONO END OF CONTENT), by which I mean: returns nothing, but 135 throws exceptions if the element is not correct. 136 137 Throws: 138 $(UL 139 $(LI $(D ASN1ConstructionException) if the element is marked as "constructed") 140 $(LI $(D ASN1ValueSizeException) if there are any content octets) 141 ) 142 */ 143 override public @property @safe 144 void endOfContent() const 145 { 146 if (this.construction != ASN1Construction.primitive) 147 throw new ASN1ConstructionException 148 (this.construction, "decode an END OF CONTENT"); 149 150 if (this.value.length != 0u) 151 throw new ASN1ValueSizeException 152 (0u, 0u, this.value.length, "decode an END OF CONTENT"); 153 } 154 155 /** 156 Decodes a $(D bool). 157 158 A $(D 0xFF) byte will be interpreted as $(MONO TRUE). A zero byte 159 ($(D 0x00)) will be interpreted as $(MONO FALSE). Any other value, 160 or any length other than 1 byte will throw an exception. 161 162 Throws: 163 $(UL 164 $(LI $(D ASN1ConstructionException) 165 if the encoded value is not primitively-constructed) 166 $(LI $(D ASN1ValueSizeException) 167 if the encoded value is not exactly 1 byte in size) 168 $(LI $(D ASN1ValueException) 169 if the encoded value is not either 0xFF or 0x00) 170 ) 171 */ 172 override public @property @safe 173 bool boolean() const 174 { 175 if (this.construction != ASN1Construction.primitive) 176 throw new ASN1ConstructionException 177 (this.construction, "decode a BOOLEAN"); 178 179 if (this.value.length != 1u) 180 throw new ASN1ValueSizeException 181 (1u, 1u, this.value.length, "decode a BOOLEAN"); 182 183 if (this.value[0] == 0xFFu) 184 { 185 return true; 186 } 187 else if (this.value[0] == 0x00u) 188 { 189 return false; 190 } 191 else 192 { 193 throw new ASN1ValueException 194 ( 195 "This exception was thrown because you attempted to decode a BOOLEAN " ~ 196 "that was encoded on a byte that was not 0xFF or 0x00 using the CER " ~ 197 "codec. Any encoding of a boolean other than 0xFF (true) or 0x00 " ~ 198 "(false) is restricted by the CER codec. " ~ notWhatYouMeantText ~ 199 forMoreInformationText ~ debugInformationText ~ reportBugsText 200 ); 201 } 202 } 203 204 /// Encodes a $(D bool) 205 override public @property @safe nothrow 206 void boolean(in bool value) 207 out 208 { 209 assert(this.value.length > 0u); 210 } 211 body 212 { 213 scope(success) this.construction = ASN1Construction.primitive; 214 this.value = [(value ? 0xFFu : 0x00u)]; 215 } 216 217 @safe 218 unittest 219 { 220 CERElement cv = new CERElement(); 221 cv.value = [ 0xFFu ]; 222 assert(cv.boolean == true); 223 cv.value = [ 0x00u ]; 224 assert(cv.boolean == false); 225 cv.value = [ 0x01u, 0x00u ]; 226 assertThrown!ASN1ValueSizeException(cv.boolean); 227 cv.value = []; 228 assertThrown!ASN1ValueSizeException(cv.boolean); 229 cv.value = [ 0x01u ]; 230 assertThrown!ASN1ValueException(cv.boolean); 231 } 232 233 /** 234 Decodes a signed integer. 235 236 Throws: 237 $(UL 238 $(LI $(D ASN1ConstructionException) 239 if the encoded value is not primitively-constructed) 240 $(LI $(D ASN1ValueSizeException) 241 if the value is too big to decode to a signed integral type, 242 or if the value is zero bytes) 243 $(LI $(D ASN1ValuePaddingException) 244 if there are padding bytes) 245 ) 246 */ 247 public @property @system 248 T integer(T)() const 249 if (isIntegral!T && isSigned!T) 250 { 251 if (this.construction != ASN1Construction.primitive) 252 throw new ASN1ConstructionException 253 (this.construction, "decode an INTEGER"); 254 255 if (this.value.length == 1u) 256 return cast(T) cast(byte) this.value[0]; 257 258 if (this.value.length == 0u || this.value.length > T.sizeof) 259 throw new ASN1ValueSizeException 260 (1u, long.sizeof, this.value.length, "decode an INTEGER"); 261 262 if 263 ( 264 (this.value[0] == 0x00u && (!(this.value[1] & 0x80u))) || // Unnecessary positive leading bytes 265 (this.value[0] == 0xFFu && (this.value[1] & 0x80u)) // Unnecessary negative leading bytes 266 ) 267 throw new ASN1ValuePaddingException 268 ( 269 "This exception was thrown because you attempted to decode " ~ 270 "an INTEGER that was encoded on more than the minimum " ~ 271 "necessary bytes. " ~ 272 notWhatYouMeantText ~ forMoreInformationText ~ 273 debugInformationText ~ reportBugsText 274 ); 275 276 /* NOTE: 277 Because the INTEGER is stored in two's complement form, you 278 can't just apppend 0x00u to the big end of it until it is as long 279 as T in bytes, then cast to T. Instead, you have to first determine 280 if the encoded integer is negative or positive. If it is negative, 281 then you actually want to append 0xFFu to the big end until it is 282 as big as T, so you get the two's complement form of whatever T 283 you choose. 284 285 The line immediately below this determines whether the padding byte 286 should be 0xFF or 0x00 based on the most significant bit of the 287 most significant byte (which, since CER encodes big-endian, will 288 always be the first byte). If set (1), the number is negative, and 289 hence, the padding byte should be 0xFF. If not, it is positive, 290 and the padding byte should be 0x00. 291 */ 292 immutable ubyte paddingByte = ((this.value[0] & 0x80u) ? 0xFFu : 0x00u); 293 ubyte[] value = this.value.dup; // Duplication is necessary to prevent modifying the source bytes 294 while (value.length < T.sizeof) 295 value = (paddingByte ~ value); 296 version (LittleEndian) reverse(value); 297 version (unittest) assert(value.length == T.sizeof); 298 return *cast(T *) value.ptr; 299 } 300 301 /// Encodes a signed integral type 302 public @property @system nothrow 303 void integer(T)(in T value) 304 if (isIntegral!T && isSigned!T) 305 out 306 { 307 assert(this.value.length > 0u); 308 } 309 body 310 { 311 scope(success) this.construction = ASN1Construction.primitive; 312 if (value <= byte.max && value >= byte.min) 313 { 314 this.value = [ cast(ubyte) cast(byte) value ]; 315 return; 316 } 317 318 ubyte[] ub; 319 ub.length = T.sizeof; 320 *cast(T *)&ub[0] = value; 321 version (LittleEndian) reverse(ub); 322 323 /* 324 An INTEGER must be encoded on the fewest number of bytes than can 325 encode it. The loops below identify how many bytes can be 326 truncated from the start of the INTEGER, with one loop for positive 327 and another loop for negative numbers. 328 329 From X.690, Section 8.3.2: 330 331 If the contents octets of an integer value encoding consist of more 332 than one octet, then the bits of the first octet and bit 8 of the 333 second octet: 334 a) shall not all be ones; and 335 b) shall not all be zero. 336 NOTE – These rules ensure that an integer value is always 337 encoded in the smallest possible number of octets. 338 */ 339 size_t startOfNonPadding = 0u; 340 if (T.sizeof > 1u) 341 { 342 if (value >= 0) 343 { 344 for (size_t i = 0u; i < ub.length-1; i++) 345 { 346 if (ub[i] != 0x00u) break; 347 if (!(ub[i+1] & 0x80u)) startOfNonPadding++; 348 } 349 } 350 else 351 { 352 for (size_t i = 0u; i < ub.length-1; i++) 353 { 354 if (ub[i] != 0xFFu) break; 355 if (ub[i+1] & 0x80u) startOfNonPadding++; 356 } 357 } 358 } 359 360 this.value = ub[startOfNonPadding .. $]; 361 } 362 363 // Ensure that INTEGER 0 gets encoded on a single null byte. 364 @system 365 unittest 366 { 367 CERElement el = new CERElement(); 368 369 el.integer!byte = cast(byte) 0x00; 370 assert(el.value == [ 0x00u ]); 371 372 el.integer!short = cast(short) 0x0000; 373 assert(el.value == [ 0x00u ]); 374 375 el.integer!int = cast(int) 0; 376 assert(el.value == [ 0x00u ]); 377 378 el.integer!long = cast(long) 0; 379 assert(el.value == [ 0x00u ]); 380 381 el.value = []; 382 assertThrown!ASN1ValueSizeException(el.integer!byte); 383 assertThrown!ASN1ValueSizeException(el.integer!short); 384 assertThrown!ASN1ValueSizeException(el.integer!int); 385 assertThrown!ASN1ValueSizeException(el.integer!long); 386 } 387 388 // Test encoding -0 for the sake of CVE-2016-2108 389 @system 390 unittest 391 { 392 CERElement el = new CERElement(); 393 394 el.integer!byte = -0; 395 assertNotThrown!RangeError(el.integer!byte); 396 assertNotThrown!ASN1Exception(el.integer!byte); 397 398 el.integer!short = -0; 399 assertNotThrown!RangeError(el.integer!short); 400 assertNotThrown!ASN1Exception(el.integer!short); 401 402 el.integer!int = -0; 403 assertNotThrown!RangeError(el.integer!int); 404 assertNotThrown!ASN1Exception(el.integer!int); 405 406 el.integer!long = -0; 407 assertNotThrown!RangeError(el.integer!long); 408 assertNotThrown!ASN1Exception(el.integer!long); 409 } 410 411 /** 412 Decodes an array of $(D bool)s representing a string of bits. 413 414 Returns: an array of $(D bool)s, where each $(D bool) represents a bit 415 in the encoded bit string 416 417 Throws: 418 $(UL 419 $(LI $(D ASN1ValueSizeException) 420 if the any primitive contains 0 bytes) 421 $(LI $(D ASN1ValueException) 422 if the first byte has a value greater 423 than seven, or if the first byte indicates the presence of 424 padding bits when no subsequent bytes exist, or if any primitive 425 but the last in a constructed BIT STRING uses padding bits, or 426 if any of the padding bits are set) 427 $(LI $(D ASN1RecursionException) 428 if using constructed form and the element 429 is constructed of too many nested constructed elements) 430 $(LI $(D ASN1TagClassException) 431 if any nested primitives do not share the 432 same tag class as their outer constructed element) 433 $(LI $(D ASN1TagNumberException) 434 if any nested primitives do not share the 435 same tag number as their outer constructed element) 436 ) 437 */ 438 override public @property @system 439 bool[] bitString() const 440 in 441 { 442 // If the blank constructor ever stops producing an EOC, 443 // this method must change. 444 CERElement test = new CERElement(); 445 assert(test.tagNumber == 0x00u); 446 assert(test.length == 0u); 447 } 448 body 449 { 450 if (this.construction == ASN1Construction.primitive) 451 { 452 if (this.value.length == 0u || this.value.length > 1000u) 453 throw new ASN1ValueSizeException 454 (1u, 1000u, this.value.length, "decode a primitively-encoded BIT STRING"); 455 456 if (this.value[0] > 0x07u) 457 throw new ASN1ValueException 458 ( 459 "In Canonical Encoding Rules (CER), the first byte of the encoded " ~ 460 "binary value (after the type and length bytes, of course) " ~ 461 "is used to indicate how many unused bits there are at the " ~ 462 "end of the BIT STRING. Since everything is encoded in bytes " ~ 463 "in Canonical Encoding Rules (CER), but a BIT STRING may not " ~ 464 "necessarily encode a number of bits, divisible by eight " ~ 465 "there may be bits at the end of the BIT STRING that will " ~ 466 "need to be identified as padding instead of meaningful data." ~ 467 "Since a byte is eight bits, the largest number that the " ~ 468 "first byte should encode is 7, since, if you have eight " ~ 469 "unused bits or more, you may as well truncate an entire " ~ 470 "byte from the encoded data. This exception was thrown because " ~ 471 "you attempted to decode a BIT STRING whose first byte " ~ 472 "had a value greater than seven. The value was: " ~ 473 text(this.value[0]) ~ ". " ~ notWhatYouMeantText ~ 474 forMoreInformationText ~ debugInformationText ~ reportBugsText 475 ); 476 477 if (this.value[0] > 0x00u && this.value.length <= 1u) 478 throw new ASN1ValueException 479 ( 480 "This exception was thrown because you attempted to decode a " ~ 481 "BIT STRING that had a misleading first byte, which indicated " ~ 482 "that there were more than zero padding bits, but there were " ~ 483 "no subsequent octets supplied, which contain the octet-" ~ 484 "aligned bits and padding. This may have been a mistake on " ~ 485 "the part of the encoder, but this looks really suspicious: " ~ 486 "it is likely that an attempt was made to hack your systems " ~ 487 "by inducing an out-of-bounds read from an array. " ~ 488 notWhatYouMeantText ~ forMoreInformationText ~ 489 debugInformationText ~ reportBugsText 490 ); 491 492 bool[] ret; 493 for (size_t i = 1; i < this.value.length; i++) 494 { 495 ret ~= [ 496 (this.value[i] & 0b10000000u ? true : false), 497 (this.value[i] & 0b01000000u ? true : false), 498 (this.value[i] & 0b00100000u ? true : false), 499 (this.value[i] & 0b00010000u ? true : false), 500 (this.value[i] & 0b00001000u ? true : false), 501 (this.value[i] & 0b00000100u ? true : false), 502 (this.value[i] & 0b00000010u ? true : false), 503 (this.value[i] & 0b00000001u ? true : false) 504 ]; 505 } 506 507 foreach (immutable bit; ret[$-this.value[0] .. $]) 508 { 509 if (bit == true) 510 throw new ASN1ValueException 511 ( 512 "This exception was thrown because you attempted to decode " ~ 513 "a BIT STRING whose padding bits were not entirely zeroes. " ~ 514 "If you were using the Basic Encoding Rules (BER), this " ~ 515 "would not be a problem, but under Canonical Encoding " ~ 516 "Rules (CER), padding the BIT STRING with anything other " ~ 517 "zeroes is forbidden. " ~ 518 notWhatYouMeantText ~ forMoreInformationText ~ 519 debugInformationText ~ reportBugsText 520 ); 521 } 522 523 ret.length -= this.value[0]; 524 return ret; 525 } 526 else 527 { 528 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 529 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a BIT STRING"); 530 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 531 532 CERElement[] substrings = this.sequence; 533 if (substrings.length < 2u) 534 throw new ASN1ValueException 535 ( 536 "This exception was thrown because you attempted to decode " ~ 537 "a constructed BIT STRING that was constituted by fewer " ~ 538 "than two nested elements. When using the Canonical Encoding " ~ 539 "Rules (CER), a legitimate constructed BIT STRING must " ~ 540 "contain at least two nested elements, and each but the " ~ 541 "last must contain exactly 1000 content octets. The last " ~ 542 "nested element may have between 1 and 1000 content " ~ 543 "octets inclusively. This element should have been encoded " ~ 544 "primitively using definite-length encoding." 545 ); 546 547 foreach (substring; substrings[0 .. $-1]) 548 { 549 if (substring.length != 1000u) 550 throw new ASN1ValueSizeException 551 (1000u, 1000u, substring.length, "decode a substring of a constructed BIT STRING"); 552 553 if 554 ( 555 substring.construction == ASN1Construction.primitive && 556 substring.length > 0u && 557 substring.value[0] != 0x00u 558 ) 559 throw new ASN1ValueException 560 ( 561 "This exception was thrown because you attempted to " ~ 562 "decode a constructed BIT STRING that contained a " ~ 563 "substring whose first byte indicated a non-zero " ~ 564 "number of padding bits, despite not being the " ~ 565 "last substring of the constructed BIT STRING. " ~ 566 "Only the last substring may have padding bits. " 567 ); 568 } 569 570 if (substrings[$-1].length == 0u) 571 throw new ASN1ValueSizeException 572 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed OCTET STRING"); 573 574 Appender!(bool[]) appendy = appender!(bool[])(); 575 foreach (substring; substrings) 576 { 577 if (substring.tagClass != this.tagClass) 578 throw new ASN1TagClassException 579 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed BIT STRING"); 580 581 if (substring.construction != ASN1Construction.primitive) 582 throw new ASN1ConstructionException 583 (substring.construction, "decode a substring of a constructed OCTET STRING"); 584 585 if (substring.tagNumber != this.tagNumber) 586 throw new ASN1TagNumberException 587 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed BIT STRING"); 588 589 appendy.put(substring.bitString); 590 } 591 return appendy.data; 592 } 593 } 594 595 /// Encodes an array of $(D bool)s representing a string of bits. 596 override public @property @system 597 void bitString(in bool[] value) 598 out 599 { 600 assert(this.value.length > 0u); 601 } 602 body 603 { 604 ubyte[] ub; 605 ub.length = ((value.length / 8u) + (value.length % 8u ? 1u : 0u)); 606 607 for (size_t i = 0u; i < value.length; i++) 608 { 609 if (value[i] == false) continue; 610 ub[(i/8u)] |= (0b10000000u >> (i % 8u)); 611 } 612 613 if (ub.length <= 1000u) 614 { 615 this.value = [ cast(ubyte) (8u - (value.length % 8u)) ] ~ ub; 616 if (this.value[0] == 0x08u) this.value[0] = 0x00u; 617 } 618 else 619 { 620 CERElement[] primitives; 621 size_t i = 0u; 622 while (i+999u < value.length) 623 { 624 CERElement x = new CERElement(); 625 x.tagNumber = this.tagNumber; 626 x.construction = ASN1Construction.primitive; 627 x.value = [ cast(ubyte) 0u ] ~ ub[i .. i+999u]; 628 primitives ~= x; 629 i += 999u; 630 } 631 632 CERElement y = new CERElement(); 633 y.tagNumber = this.tagNumber; 634 y.construction = ASN1Construction.primitive; 635 y.value = ([ cast(ubyte) 0u ] ~ ub[i .. $]); 636 primitives ~= y; 637 638 CERElement z = new CERElement(); 639 primitives ~= z; 640 641 this.sequence = primitives; 642 this.construction = ASN1Construction.constructed; 643 } 644 } 645 646 @system 647 unittest 648 { 649 bool[] data; 650 data.length = 289u; 651 for (size_t i = 0u; i < data.length; i++) 652 { 653 data[i] = cast(bool) (i % 3); 654 } 655 CERElement el = new CERElement(); 656 el.bitString = data; 657 assert(el.bitString == data); 658 } 659 660 @system 661 unittest 662 { 663 void test(size_t length) 664 { 665 bool[] data; 666 data.length = length; 667 for (size_t i = 0u; i < data.length; i++) 668 { 669 data[i] = cast(bool) (i % 3); 670 } 671 CERElement el = new CERElement(); 672 el.bitString = data; 673 assert(el.bitString == data); 674 } 675 test(0u); 676 test(1u); 677 test(8u); 678 test(127u); 679 test(128u); 680 test(129u); 681 test(192u); 682 test(999u); 683 test(1000u); 684 test(1001u); 685 test(2017u); 686 } 687 688 // Ensure that 1s in the padding get PUNISHED with an exception 689 @system 690 unittest 691 { 692 CERElement el = new CERElement(); 693 el.value = [ 0x07u, 0b11000000u ]; 694 assertThrown!ASN1ValueException(el.bitString); 695 696 el.value = [ 0x01u, 0b11111111u ]; 697 assertThrown!ASN1ValueException(el.bitString); 698 699 el.value = [ 0x00u, 0b11111111u ]; 700 assertNotThrown!ASN1ValueException(el.bitString); 701 } 702 703 // Test a BIT STRING with a deceptive first byte. 704 @system 705 unittest 706 { 707 CERElement el = new CERElement(); 708 el.value = [ 0x01u ]; 709 assertThrown!ASN1ValueException(el.bitString); 710 } 711 712 /** 713 Decodes an $(MONO OCTET STRING) into an unsigned byte array. 714 715 Throws: 716 $(UL 717 $(LI $(D ASN1RecursionException) 718 if using constructed form and the element 719 is constructed of too many nested constructed elements) 720 $(LI $(D ASN1TagClassException) 721 if any nested primitives do not share the 722 same tag class as their outer constructed element) 723 $(LI $(D ASN1TagNumberException) 724 if any nested primitives do not share the 725 same tag number as their outer constructed element) 726 ) 727 */ 728 override public @property @system 729 ubyte[] octetString() const 730 { 731 if (this.construction == ASN1Construction.primitive) 732 { 733 if (this.value.length > 1000u) 734 throw new ASN1ValueSizeException 735 (0u, 1000u, this.value.length, "decode a primitively-encoded OCTET STRING"); 736 737 return this.value.dup; 738 } 739 else 740 { 741 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 742 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse an OCTET STRING"); 743 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 744 745 CERElement[] substrings = this.sequence; 746 if (substrings.length < 2u) 747 throw new ASN1ValueException 748 ( 749 "This exception was thrown because you attempted to decode " ~ 750 "a constructed OCTET STRING that was constituted by fewer " ~ 751 "than two nested elements. When using the Canonical Encoding " ~ 752 "Rules (CER), a legitimate constructed OCTET STRING must " ~ 753 "contain at least two nested elements, and each but the " ~ 754 "last must contain exactly 1000 content octets. The last " ~ 755 "nested element may have between 1 and 1000 content " ~ 756 "octets inclusively. This element should have been encoded " ~ 757 "primitively using definite-length encoding." 758 ); 759 760 foreach (substring; substrings[0 .. $-1]) 761 { 762 if (substring.length != 1000u) 763 throw new ASN1ValueSizeException 764 (1000u, 1000u, substring.length, "decode a substring of a constructed OCTET STRING"); 765 } 766 767 if (substrings[$-1].length == 0u) 768 throw new ASN1ValueSizeException 769 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed OCTET STRING"); 770 771 Appender!(ubyte[]) appendy = appender!(ubyte[])(); 772 foreach (substring; substrings) 773 { 774 if (substring.tagClass != this.tagClass) 775 throw new ASN1TagClassException 776 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed OCTET STRING"); 777 778 if (substring.construction != ASN1Construction.primitive) 779 throw new ASN1ConstructionException 780 (substring.construction, "decode a substring of a constructed OCTET STRING"); 781 782 if (substring.tagNumber != this.tagNumber) 783 throw new ASN1TagNumberException 784 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed OCTET STRING"); 785 786 appendy.put(substring.octetString); 787 } 788 return appendy.data; 789 } 790 } 791 792 /// Encodes an $(MONO OCTET STRING) from an unsigned byte ($(D ubyte)) array. 793 override public @property @system 794 void octetString(in ubyte[] value) 795 in 796 { 797 // If the blank constructor ever stops producing an EOC, 798 // this method must change. 799 CERElement test = new CERElement(); 800 assert(test.tagNumber == 0x00u); 801 assert(test.length == 0u); 802 } 803 body 804 { 805 if (value.length <= 1000u) 806 { 807 this.value = value.dup; 808 } 809 else 810 { 811 CERElement[] primitives; 812 size_t i = 0u; 813 while (i+1000u < value.length) 814 { 815 CERElement x = new CERElement(); 816 x.tagNumber = this.tagNumber; 817 x.construction = ASN1Construction.primitive; 818 x.value = value[i .. i+1000u].dup; 819 primitives ~= x; 820 i += 1000u; 821 } 822 823 CERElement y = new CERElement(); 824 y.tagNumber = this.tagNumber; 825 y.construction = ASN1Construction.primitive; 826 y.value = value[i .. $].dup; 827 primitives ~= y; 828 829 this.sequence = primitives; 830 this.construction = ASN1Construction.constructed; 831 } 832 } 833 834 @system 835 unittest 836 { 837 void test(size_t length) 838 { 839 ubyte[] data; 840 data.length = length; 841 for (size_t i = 0u; i < data.length; i++) 842 { 843 data[i] = (i % 9u); 844 } 845 CERElement el = new CERElement(); 846 el.octetString = data; 847 assert(el.octetString == data); 848 } 849 test(0u); 850 test(1u); 851 test(8u); 852 test(127u); 853 test(128u); 854 test(129u); 855 test(192u); 856 test(999u); 857 test(1000u); 858 test(1001u); 859 test(2017u); 860 } 861 862 /** 863 "Decodes" a $(D null), by which I mean: returns nothing, but 864 throws exceptions if the element is not correct. 865 866 Note: 867 I had to name this method $(D nill), because $(D null) is a keyword in D. 868 869 Throws: 870 $(UL 871 $(LI $(D ASN1ConstructionException) if the element is marked as "constructed") 872 $(LI $(D ASN1ValueSizeException) if there are any content octets) 873 ) 874 */ 875 override public @property @safe 876 void nill() const 877 { 878 if (this.construction != ASN1Construction.primitive) 879 throw new ASN1ConstructionException 880 (this.construction, "decode a NULL"); 881 882 if (this.value.length != 0u) 883 throw new ASN1ValueSizeException 884 (0u, 0u, this.value.length, "decode a NULL"); 885 } 886 887 /** 888 Decodes an $(MONO OBJECT IDENTIFIER). 889 See $(MONO source/types/universal/objectidentifier.d) for information about 890 the $(D ObjectIdentifier) class (aliased as $(D OID)). 891 892 Throws: 893 $(UL 894 $(LI $(D ASN1ConstructionException) if the element is marked as "constructed") 895 $(LI $(D ASN1ValueSizeException) if there are no value bytes) 896 $(LI $(D ASN1ValuePaddingException) if a single OID number is encoded with 897 "leading zero bytes" ($(D 0x80u))) 898 $(LI $(D ASN1ValueOverflowException) if a single OID number is too big to 899 decode to a $(D size_t)) 900 $(LI $(D ASN1TruncationException) if a single OID number is "cut off") 901 ) 902 903 Standards: 904 $(UL 905 $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660)) 906 ) 907 */ 908 override public @property @system 909 OID objectIdentifier() const 910 out (value) 911 { 912 assert(value.length >= 2u); 913 } 914 body 915 { 916 if (this.construction != ASN1Construction.primitive) 917 throw new ASN1ConstructionException 918 (this.construction, "decode an OBJECT IDENTIFIER"); 919 920 if (this.value.length == 0u) 921 throw new ASN1ValueSizeException 922 (1u, size_t.max, 0u, "decode an OBJECT IDENTIFIER"); 923 924 if (this.value.length >= 2u) 925 { 926 // Skip the first, because it is fine if it is 0x80 927 // Skip the last because it will be checked next 928 foreach (immutable octet; this.value[1 .. $-1]) 929 { 930 if (octet == 0x80u) 931 throw new ASN1ValuePaddingException 932 ( 933 "This exception was thrown because you attempted to decode " ~ 934 "an OBJECT IDENTIFIER that contained a number that was " ~ 935 "encoded on more than the minimum necessary octets. This " ~ 936 "is indicated by an occurrence of the octet 0x80, which " ~ 937 "is the encoded equivalent of a leading zero. " ~ 938 notWhatYouMeantText ~ forMoreInformationText ~ 939 debugInformationText ~ reportBugsText 940 ); 941 } 942 943 if ((this.value[$-1] & 0x80u) == 0x80u) 944 throw new ASN1TruncationException 945 (size_t.max, this.value.length, "decode an OBJECT IDENTIFIER"); 946 } 947 948 size_t[] numbers; 949 if (this.value[0] >= 0x50u) 950 { 951 numbers = [ 2u, (this.value[0] - 0x50u) ]; 952 } 953 else if (this.value[0] >= 0x28u) 954 { 955 numbers = [ 1u, (this.value[0] - 0x28u) ]; 956 } 957 else 958 { 959 numbers = [ 0u, this.value[0] ]; 960 } 961 962 // Breaks bytes into groups, where each group encodes one OID component. 963 ubyte[][] byteGroups; 964 size_t lastTerminator = 1; 965 for (size_t i = 1; i < this.length; i++) 966 { 967 if (!(this.value[i] & 0x80u)) 968 { 969 byteGroups ~= cast(ubyte[]) this.value[lastTerminator .. i+1]; 970 lastTerminator = i+1; 971 } 972 } 973 974 // Converts each group of bytes to a number. 975 foreach (const byteGroup; byteGroups) 976 { 977 if (byteGroup.length > size_t.sizeof) 978 throw new ASN1ValueOverflowException 979 ( 980 "This exception was thrown because you attempted to decode " ~ 981 "a OBJECT IDENTIFIER that encoded a number on more than " ~ 982 "size_t.sizeof bytes. " ~ 983 notWhatYouMeantText ~ forMoreInformationText ~ 984 debugInformationText ~ reportBugsText 985 ); 986 987 numbers ~= 0u; 988 for (size_t i = 0u; i < byteGroup.length; i++) 989 { 990 numbers[$-1] <<= 7; 991 numbers[$-1] |= cast(size_t) (byteGroup[i] & 0x7Fu); 992 } 993 } 994 995 // Constructs the array of OIDNodes from the array of numbers. 996 OIDNode[] nodes; 997 foreach (number; numbers) 998 { 999 nodes ~= OIDNode(number); 1000 } 1001 1002 return new OID(nodes); 1003 } 1004 1005 /** 1006 Encodes an $(MONO OBJECT IDENTIFIER). 1007 See $(MONO source/types/universal/objectidentifier.d) for information about 1008 the $(D ObjectIdentifier) class (aliased as $(OID)). 1009 1010 Standards: 1011 $(UL 1012 $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660)) 1013 ) 1014 */ 1015 override public @property @system 1016 void objectIdentifier(in OID value) 1017 in 1018 { 1019 assert(value.length >= 2u); 1020 assert(value.numericArray[0] <= 2u); 1021 if (value.numericArray[0] == 2u) 1022 assert(value.numericArray[1] <= 175u); 1023 else 1024 assert(value.numericArray[1] <= 39u); 1025 } 1026 out 1027 { 1028 assert(this.value.length > 0u); 1029 } 1030 body 1031 { 1032 scope(success) this.construction = ASN1Construction.primitive; 1033 size_t[] numbers = value.numericArray(); 1034 this.value = [ cast(ubyte) (numbers[0] * 40u + numbers[1]) ]; 1035 if (numbers.length > 2u) 1036 { 1037 foreach (number; numbers[2 .. $]) 1038 { 1039 if (number < 128u) 1040 { 1041 this.value ~= cast(ubyte) number; 1042 continue; 1043 } 1044 1045 ubyte[] encodedOIDNode; 1046 while (number != 0u) 1047 { 1048 ubyte[] numberBytes; 1049 numberBytes.length = size_t.sizeof; 1050 *cast(size_t *) numberBytes.ptr = number; 1051 if ((numberBytes[0] & 0x80u) == 0u) numberBytes[0] |= 0x80u; 1052 encodedOIDNode = numberBytes[0] ~ encodedOIDNode; 1053 number >>= 7u; 1054 } 1055 1056 encodedOIDNode[$-1] &= 0x7Fu; 1057 this.value ~= encodedOIDNode; 1058 } 1059 } 1060 } 1061 1062 @system 1063 unittest 1064 { 1065 CERElement element = new CERElement(); 1066 1067 // All values of octet[0] should pass. 1068 for (ubyte i = 0x00u; i < 0xFFu; i++) 1069 { 1070 element.value = [ i ]; 1071 assertNotThrown!Exception(element.objectIdentifier); 1072 } 1073 1074 // All values of octet[0] should pass. 1075 for (ubyte i = 0x00u; i < 0xFFu; i++) 1076 { 1077 element.value = [ i, 0x14u ]; 1078 assertNotThrown!Exception(element.objectIdentifier); 1079 } 1080 } 1081 1082 @system 1083 unittest 1084 { 1085 CERElement element = new CERElement(); 1086 1087 // Tests for the "leading zero byte," 0x80 1088 element.value = [ 0x29u, 0x80u, 0x14u ]; 1089 assertThrown!ASN1ValuePaddingException(element.objectIdentifier); 1090 element.value = [ 0x29u, 0x80u, 0x80u ]; 1091 assertThrown!ASN1ValuePaddingException(element.objectIdentifier); 1092 element.value = [ 0x80u, 0x80u, 0x80u ]; 1093 assertThrown!ASN1ValuePaddingException(element.objectIdentifier); 1094 1095 // Test for non-terminating components 1096 element.value = [ 0x29u, 0x81u ]; 1097 assertThrown!ASN1TruncationException(element.objectIdentifier); 1098 element.value = [ 0x29u, 0x80u ]; 1099 assertThrown!ASN1TruncationException(element.objectIdentifier); 1100 element.value = [ 0x29u, 0x14u, 0x81u ]; 1101 assertThrown!ASN1TruncationException(element.objectIdentifier); 1102 element.value = [ 0x29u, 0x14u, 0x80u ]; 1103 assertThrown!ASN1TruncationException(element.objectIdentifier); 1104 1105 // This one should not fail. 0x80u is valid for the first octet. 1106 element.value = [ 0x80u, 0x14u, 0x14u ]; 1107 assertNotThrown!ASN1ValuePaddingException(element.objectIdentifier); 1108 } 1109 1110 /** 1111 Decodes an $(D ObjectDescriptor), which is a string consisting of only 1112 graphical characters. In fact, $(D ObjectDescriptor) is actually implicitly 1113 just a $(MONO GraphicString)! The formal specification for an $(D ObjectDescriptor) 1114 is: 1115 1116 $(MONO ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString) 1117 1118 $(MONO GraphicString) is just a string containing only characters between 1119 and including $(D 0x20) and $(D 0x7E), therefore ObjectDescriptor is just 1120 $(D 0x20) and $(D 0x7E). 1121 1122 Throws: 1123 $(UL 1124 $(LI $(D ASN1ValueCharactersException) 1125 if the encoded value contains any character outside of 1126 $(D 0x20) to $(D 0x7E), which means any control characters or $(MONO DELETE)) 1127 $(LI $(D ASN1RecursionException) 1128 if using constructed form and the element 1129 is constructed of too many nested constructed elements) 1130 $(LI $(D ASN1TagClassException) 1131 if any nested primitives do not share the 1132 same tag class as their outer constructed element) 1133 $(LI $(D ASN1TagNumberException) 1134 if any nested primitives do not share the 1135 same tag number as their outer constructed element) 1136 ) 1137 1138 Citations: 1139 $(UL 1140 $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).” 1141 $(I ASN.1: Communication between Heterogeneous Systems), 1142 Morgan Kaufmann, 2001, pp. 175-178.) 1143 $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022)) 1144 $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022)) 1145 ) 1146 */ 1147 override public @property @system 1148 string objectDescriptor() const 1149 { 1150 if (this.construction == ASN1Construction.primitive) 1151 { 1152 if (this.value.length > 1000u) 1153 throw new ASN1ValueSizeException 1154 (0u, 1000u, this.value.length, "decode a primitively-encoded ObjectDescriptor"); 1155 1156 foreach (immutable character; this.value) 1157 { 1158 if ((!character.isGraphical) && (character != ' ')) 1159 throw new ASN1ValueCharactersException 1160 ("all characters within the range 0x20 to 0x7E", character, "ObjectDescriptor"); 1161 } 1162 return cast(string) this.value; 1163 } 1164 else 1165 { 1166 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 1167 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse an ObjectDescriptor"); 1168 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 1169 1170 CERElement[] substrings = this.sequence; 1171 if (substrings.length < 2u) 1172 throw new ASN1ValueException 1173 ( 1174 "This exception was thrown because you attempted to decode " ~ 1175 "a constructed ObjectDescriptor that was constituted by fewer " ~ 1176 "than two nested elements. When using the Canonical Encoding " ~ 1177 "Rules (CER), a legitimate constructed ObjectDescriptor must " ~ 1178 "contain at least two nested elements, and each but the " ~ 1179 "last must contain exactly 1000 content octets. The last " ~ 1180 "nested element may have between 1 and 1000 content " ~ 1181 "octets inclusively. This element should have been encoded " ~ 1182 "primitively using definite-length encoding." 1183 ); 1184 1185 foreach (substring; substrings[0 .. $-1]) 1186 { 1187 if (substring.length != 1000u) 1188 throw new ASN1ValueSizeException 1189 (1000u, 1000u, substring.length, "decode a substring of a constructed ObjectDescriptor"); 1190 } 1191 1192 if (substrings[$-1].length == 0u) 1193 throw new ASN1ValueSizeException 1194 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed ObjectDescriptor"); 1195 1196 Appender!string appendy = appender!string(); 1197 foreach (substring; substrings) 1198 { 1199 if (substring.tagClass != this.tagClass) 1200 throw new ASN1TagClassException 1201 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed ObjectDescriptor"); 1202 1203 if (substring.construction != ASN1Construction.primitive) 1204 throw new ASN1ConstructionException 1205 (substring.construction, "decode a substring of a constructed ObjectDescriptor"); 1206 1207 if (substring.tagNumber != this.tagNumber) 1208 throw new ASN1TagNumberException 1209 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed ObjectDescriptor"); 1210 1211 appendy.put(substring.octetString); 1212 } 1213 return appendy.data; 1214 } 1215 } 1216 1217 /** 1218 Encodes an $(D ObjectDescriptor), which is a string consisting of only 1219 graphical characters. In fact, $(D ObjectDescriptor) is actually implicitly 1220 just a $(MONO GraphicString)! The formal specification for an $(D ObjectDescriptor) 1221 is: 1222 1223 $(MONO ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString) 1224 1225 $(MONO GraphicString) is just a string containing only characters between 1226 and including $(D 0x20) and $(D 0x7E), therefore ObjectDescriptor is just 1227 $(D 0x20) and $(D 0x7E). 1228 1229 Throws: 1230 $(UL 1231 $(LI $(D ASN1ValueCharactersException) 1232 if the string value contains any character outside of 1233 $(D 0x20) to $(D 0x7E), which means any control characters or $(MONO DELETE)) 1234 ) 1235 1236 Citations: 1237 $(UL 1238 $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).” 1239 $(I ASN.1: Communication between Heterogeneous Systems), 1240 Morgan Kaufmann, 2001, pp. 175-178.) 1241 $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022)) 1242 $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022)) 1243 ) 1244 */ 1245 override public @property @system 1246 void objectDescriptor(in string value) 1247 { 1248 foreach (immutable character; value) 1249 { 1250 if ((!character.isGraphical) && (character != ' ')) 1251 throw new ASN1ValueCharactersException 1252 ("all characters within the range 0x20 to 0x7E", character, "ObjectDescriptor"); 1253 } 1254 1255 if (value.length <= 1000u) 1256 { 1257 this.value = cast(ubyte[]) value; 1258 } 1259 else 1260 { 1261 CERElement[] primitives; 1262 size_t i = 0u; 1263 while (i+1000u < value.length) 1264 { 1265 CERElement x = new CERElement(); 1266 x.tagNumber = this.tagNumber; 1267 x.construction = ASN1Construction.primitive; 1268 x.value = cast(ubyte[]) value[i .. i+1000u]; 1269 primitives ~= x; 1270 i += 1000u; 1271 } 1272 1273 CERElement y = new CERElement(); 1274 y.tagNumber = this.tagNumber; 1275 y.construction = ASN1Construction.primitive; 1276 y.value = cast(ubyte[]) value[i .. $]; 1277 primitives ~= y; 1278 1279 this.sequence = primitives; 1280 this.construction = ASN1Construction.constructed; 1281 } 1282 } 1283 1284 @system 1285 unittest 1286 { 1287 void test(size_t length) 1288 { 1289 char[] data; 1290 data.length = length; 1291 for (size_t i = 0u; i < data.length; i++) 1292 { 1293 data[i] = ((i % 0x20u) + 0x41u); 1294 } 1295 CERElement el = new CERElement(); 1296 el.objectDescriptor = cast(string) data; 1297 assert(el.objectDescriptor == cast(string) data); 1298 } 1299 test(0u); 1300 test(1u); 1301 test(8u); 1302 test(127u); 1303 test(128u); 1304 test(129u); 1305 test(192u); 1306 test(999u); 1307 test(1000u); 1308 test(1001u); 1309 test(2017u); 1310 } 1311 1312 /** 1313 Decodes an $(MONO EXTERNAL). 1314 1315 According to the 1316 $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s 1317 $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1)), 1318 the abstract definition for an $(MONO EXTERNAL), after removing the comments in the 1319 specification, is as follows: 1320 1321 $(PRE 1322 EXTERNAL ::= [UNIVERSAL 8] SEQUENCE { 1323 identification CHOICE { 1324 syntaxes SEQUENCE { 1325 abstract OBJECT IDENTIFIER, 1326 transfer OBJECT IDENTIFIER }, 1327 syntax OBJECT IDENTIFIER, 1328 presentation-context-id INTEGER, 1329 context-negotiation SEQUENCE { 1330 presentation-context-id INTEGER, 1331 transfer-syntax OBJECT IDENTIFIER }, 1332 transfer-syntax OBJECT IDENTIFIER, 1333 fixed NULL }, 1334 data-value-descriptor ObjectDescriptor OPTIONAL, 1335 data-value OCTET STRING } 1336 ( WITH COMPONENTS { 1337 ... , 1338 identification ( WITH COMPONENTS { 1339 ... , 1340 syntaxes ABSENT, 1341 transfer-syntax ABSENT, 1342 fixed ABSENT } ) } ) 1343 ) 1344 1345 Note that the abstract syntax resembles that of $(MONO EmbeddedPDV) and 1346 $(MONO CharacterString), except with a $(MONO WITH COMPONENTS) constraint that removes some 1347 of our choices of $(MONO identification). 1348 As can be seen on page 303 of Olivier Dubuisson's 1349 $(I $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF, 1350 ASN.1: Communication Between Heterogeneous Systems)), 1351 after applying the $(MONO WITH COMPONENTS) constraint, our reduced syntax becomes: 1352 1353 $(PRE 1354 EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE { 1355 identification CHOICE { 1356 syntax OBJECT IDENTIFIER, 1357 presentation-context-id INTEGER, 1358 context-negotiation SEQUENCE { 1359 presentation-context-id INTEGER, 1360 transfer-syntax OBJECT IDENTIFIER } }, 1361 data-value-descriptor ObjectDescriptor OPTIONAL, 1362 data-value OCTET STRING } 1363 ) 1364 1365 But, according to the 1366 $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s 1367 $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules), 1368 section 8.18, when encoded using Basic Encoding Rules (BER), is encoded as 1369 follows, for compatibility reasons: 1370 1371 $(PRE 1372 EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE { 1373 direct-reference OBJECT IDENTIFIER OPTIONAL, 1374 indirect-reference INTEGER OPTIONAL, 1375 data-value-descriptor ObjectDescriptor OPTIONAL, 1376 encoding CHOICE { 1377 single-ASN1-type [0] ANY, 1378 octet-aligned [1] IMPLICIT OCTET STRING, 1379 arbitrary [2] IMPLICIT BIT STRING } } 1380 ) 1381 1382 The definition above is the pre-1994 definition of $(MONO EXTERNAL). The $(MONO syntax) 1383 field of the post-1994 definition maps to the $(MONO direct-reference) field of 1384 the pre-1994 definition. The $(MONO presentation-context-id) field of the post-1994 1385 definition maps to the $(MONO indirect-reference) field of the pre-1994 definition. 1386 If $(MONO context-negotiation) is used, per the abstract syntax, then the 1387 $(MONO presentation-context-id) field of the $(MONO context-negotiation) $(MONO SEQUENCE) in the 1388 post-1994 definition maps to the $(MONO indirect-reference) field of the pre-1994 1389 definition, and the $(MONO transfer-syntax) field of the $(MONO context-negotiation) 1390 $(MONO SEQUENCE) maps to the $(MONO direct-reference) field of the pre-1994 definition. 1391 1392 The following additional constraints are applied to the abstract syntax 1393 when using Canonical Encoding Rules or Distinguished Encoding Rules, 1394 which are also defined in the 1395 $(LINK https://www.itu.int/en/pages/default.aspx, 1396 International Telecommunications Union)'s 1397 $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules): 1398 1399 $(PRE 1400 EXTERNAL ( WITH COMPONENTS { 1401 ... , 1402 identification ( WITH COMPONENTS { 1403 ... , 1404 presentation-context-id ABSENT, 1405 context-negotiation ABSENT } ) } ) 1406 ) 1407 1408 The stated purpose of the constraints shown above is to restrict the use of 1409 the $(MONO presentation-context-id), either by itself or within the 1410 $(MONO context-negotiation), which makes the following the effective abstract 1411 syntax of $(MONO EXTERNAL) when using Canonical Encoding Rules or 1412 Distinguished Encoding Rules: 1413 1414 $(PRE 1415 EXTERNAL ::= [UNIVERSAL 8] SEQUENCE { 1416 identification CHOICE { 1417 syntaxes SEQUENCE { 1418 abstract OBJECT IDENTIFIER, 1419 transfer OBJECT IDENTIFIER }, 1420 syntax OBJECT IDENTIFIER, 1421 presentation-context-id INTEGER, 1422 context-negotiation SEQUENCE { 1423 presentation-context-id INTEGER, 1424 transfer-syntax OBJECT IDENTIFIER }, 1425 transfer-syntax OBJECT IDENTIFIER, 1426 fixed NULL }, 1427 data-value-descriptor ObjectDescriptor OPTIONAL, 1428 data-value OCTET STRING } 1429 ( WITH COMPONENTS { 1430 ... , 1431 identification ( WITH COMPONENTS { 1432 ... , 1433 syntaxes ABSENT, 1434 presentation-context-id ABSENT, 1435 context-negotiation ABSENT, 1436 transfer-syntax ABSENT, 1437 fixed ABSENT } ) } ) 1438 ) 1439 1440 With the constraints applied, the abstract syntax for $(MONO EXTERNAL)s encoded 1441 using Canonical Encoding Rules or Distinguished Encoding Rules becomes: 1442 1443 $(PRE 1444 EXTERNAL ::= [UNIVERSAL 8] SEQUENCE { 1445 identification CHOICE { 1446 syntax OBJECT IDENTIFIER }, 1447 data-value-descriptor ObjectDescriptor OPTIONAL, 1448 data-value OCTET STRING } 1449 ) 1450 1451 Upon removing the $(MONO CHOICE) tag (since you have no choice but to use syntax 1452 at this point), the encoding definition when using 1453 Canonical Encoding Rules or Distinguished Encoding Rules: 1454 1455 $(PRE 1456 EXTERNAL ::= [UNIVERSAL 8] SEQUENCE { 1457 syntax OBJECT IDENTIFIER, 1458 data-value-descriptor ObjectDescriptor OPTIONAL, 1459 data-value OCTET STRING } 1460 ) 1461 1462 Returns: an instance of $(D types.universal.external.External) 1463 1464 Throws: 1465 $(UL 1466 $(LI $(D ASN1ValueException) 1467 if the SEQUENCE does not contain two to four elements) 1468 $(LI $(D ASN1RecursionException) 1469 if using constructed form and the element 1470 is constructed of too many nested constructed elements) 1471 $(LI $(D ASN1TagClassException) 1472 if any nested primitives do not have the correct tag class) 1473 $(LI $(D ASN1ConstructionException) 1474 if any element has the wrong construction) 1475 $(LI $(D ASN1TagNumberException) 1476 if any nested primitives do not have the correct tag number) 1477 $(LI $(D ASN1ValueCharactersException) 1478 if a data-value-descriptor is supplied with invalid characters) 1479 ) 1480 */ 1481 deprecated override public @property @system 1482 External external() const 1483 { 1484 if (this.construction != ASN1Construction.constructed) 1485 throw new ASN1ConstructionException 1486 (this.construction, "decode an EXTERNAL"); 1487 1488 const CERElement[] components = this.sequence; 1489 External ext = External(); 1490 ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID(); 1491 1492 if (components.length < 2u || components.length > 3u) 1493 throw new ASN1ValueException 1494 ( 1495 "This exception was thrown because you attempted to decode " ~ 1496 "an EXTERNAL that contained too many or too few elements. " ~ 1497 "An EXTERNAL should have either two and three elements: " ~ 1498 "a direct-reference (syntax), an optional " ~ 1499 "data-value-descriptor, and an encoding (data-value). " ~ 1500 notWhatYouMeantText ~ forMoreInformationText ~ 1501 debugInformationText ~ reportBugsText 1502 ); 1503 1504 // Every component except the last must be universal class 1505 foreach (const component; components[0 .. $-1]) 1506 { 1507 if (component.tagClass != ASN1TagClass.universal) 1508 throw new ASN1TagClassException 1509 ([ ASN1TagClass.universal ], component.tagClass, "decode all but the last component of an EXTERNAL"); 1510 } 1511 1512 // The last tag must be context-specific class 1513 if (components[$-1].tagClass != ASN1TagClass.contextSpecific) 1514 throw new ASN1TagClassException 1515 ([ ASN1TagClass.contextSpecific ], components[$-1].tagClass, "decode the last component of an EXTERNAL"); 1516 1517 // The first component should always be primitive 1518 if (components[0].construction != ASN1Construction.primitive) 1519 throw new ASN1ConstructionException 1520 (components[0].construction, "decode the first component of an EXTERNAL"); 1521 1522 if (components[0].tagNumber != ASN1UniversalType.objectIdentifier) 1523 throw new ASN1TagNumberException 1524 ([ 2u, 6u ], components[0].tagNumber, "decode the first component of an EXTERNAL"); 1525 1526 identification.directReference = components[0].objectIdentifier; 1527 if (components.length == 3u) 1528 { 1529 if (components[1].tagNumber != ASN1UniversalType.objectDescriptor) 1530 throw new ASN1TagNumberException 1531 ( 1532 [ ASN1UniversalType.objectDescriptor ], 1533 components[1].tagNumber, 1534 "decode the second of three subcomponents of an EXTERNAL" 1535 ); 1536 1537 ext.dataValueDescriptor = components[1].objectDescriptor; 1538 } 1539 1540 switch (components[$-1].tagNumber) 1541 { 1542 case (0u): // single-ASN1-value 1543 { 1544 ext.encoding = ASN1ExternalEncodingChoice.singleASN1Type; 1545 break; 1546 } 1547 case (1u): // octet-aligned 1548 { 1549 ext.encoding = ASN1ExternalEncodingChoice.octetAligned; 1550 break; 1551 } 1552 case (2u): // arbitrary 1553 { 1554 ext.encoding = ASN1ExternalEncodingChoice.arbitrary; 1555 break; 1556 } 1557 default: 1558 throw new ASN1TagNumberException 1559 ([ 0u, 1u, 2u ], components[$-1].tagNumber, "decode an EXTERNAL identification"); 1560 } 1561 1562 ext.dataValue = components[$-1].value.dup; 1563 ext.identification = identification; 1564 return ext; 1565 } 1566 1567 /** 1568 Encodes an $(MONO EXTERNAL). 1569 1570 According to the 1571 $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s 1572 $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1)), 1573 the abstract definition for an $(MONO EXTERNAL), after removing the comments in the 1574 specification, is as follows: 1575 1576 $(PRE 1577 EXTERNAL ::= [UNIVERSAL 8] SEQUENCE { 1578 identification CHOICE { 1579 syntaxes SEQUENCE { 1580 abstract OBJECT IDENTIFIER, 1581 transfer OBJECT IDENTIFIER }, 1582 syntax OBJECT IDENTIFIER, 1583 presentation-context-id INTEGER, 1584 context-negotiation SEQUENCE { 1585 presentation-context-id INTEGER, 1586 transfer-syntax OBJECT IDENTIFIER }, 1587 transfer-syntax OBJECT IDENTIFIER, 1588 fixed NULL }, 1589 data-value-descriptor ObjectDescriptor OPTIONAL, 1590 data-value OCTET STRING } 1591 ( WITH COMPONENTS { 1592 ... , 1593 identification ( WITH COMPONENTS { 1594 ... , 1595 syntaxes ABSENT, 1596 transfer-syntax ABSENT, 1597 fixed ABSENT } ) } ) 1598 ) 1599 1600 Note that the abstract syntax resembles that of $(MONO EmbeddedPDV) and 1601 $(MONO CharacterString), except with a $(MONO WITH COMPONENTS) constraint that removes some 1602 of our choices of $(MONO identification). 1603 As can be seen on page 303 of Olivier Dubuisson's 1604 $(I $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF, 1605 ASN.1: Communication Between Heterogeneous Systems)), 1606 after applying the $(MONO WITH COMPONENTS) constraint, our reduced syntax becomes: 1607 1608 $(PRE 1609 EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE { 1610 identification CHOICE { 1611 syntax OBJECT IDENTIFIER, 1612 presentation-context-id INTEGER, 1613 context-negotiation SEQUENCE { 1614 presentation-context-id INTEGER, 1615 transfer-syntax OBJECT IDENTIFIER } }, 1616 data-value-descriptor ObjectDescriptor OPTIONAL, 1617 data-value OCTET STRING } 1618 ) 1619 1620 But, according to the 1621 $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s 1622 $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules), 1623 section 8.18, when encoded using Basic Encoding Rules (BER), is encoded as 1624 follows, for compatibility reasons: 1625 1626 $(PRE 1627 EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE { 1628 direct-reference OBJECT IDENTIFIER OPTIONAL, 1629 indirect-reference INTEGER OPTIONAL, 1630 data-value-descriptor ObjectDescriptor OPTIONAL, 1631 encoding CHOICE { 1632 single-ASN1-type [0] ANY, 1633 octet-aligned [1] IMPLICIT OCTET STRING, 1634 arbitrary [2] IMPLICIT BIT STRING } } 1635 ) 1636 1637 The definition above is the pre-1994 definition of $(MONO EXTERNAL). The $(MONO syntax) 1638 field of the post-1994 definition maps to the $(MONO direct-reference) field of 1639 the pre-1994 definition. The $(MONO presentation-context-id) field of the post-1994 1640 definition maps to the $(MONO indirect-reference) field of the pre-1994 definition. 1641 If $(MONO context-negotiation) is used, per the abstract syntax, then the 1642 $(MONO presentation-context-id) field of the $(MONO context-negotiation) $(MONO SEQUENCE) in the 1643 post-1994 definition maps to the $(MONO indirect-reference) field of the pre-1994 1644 definition, and the $(MONO transfer-syntax) field of the $(MONO context-negotiation) 1645 $(MONO SEQUENCE) maps to the $(MONO direct-reference) field of the pre-1994 definition. 1646 1647 The following additional constraints are applied to the abstract syntax 1648 when using Canonical Encoding Rules or Distinguished Encoding Rules, 1649 which are also defined in the 1650 $(LINK https://www.itu.int/en/pages/default.aspx, 1651 International Telecommunications Union)'s 1652 $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules): 1653 1654 $(PRE 1655 EXTERNAL ( WITH COMPONENTS { 1656 ... , 1657 identification ( WITH COMPONENTS { 1658 ... , 1659 presentation-context-id ABSENT, 1660 context-negotiation ABSENT } ) } ) 1661 ) 1662 1663 The stated purpose of the constraints shown above is to restrict the use of 1664 the $(MONO presentation-context-id), either by itself or within the 1665 $(MONO context-negotiation), which makes the following the effective abstract 1666 syntax of $(MONO EXTERNAL) when using Canonical Encoding Rules or 1667 Distinguished Encoding Rules: 1668 1669 $(PRE 1670 EXTERNAL ::= [UNIVERSAL 8] SEQUENCE { 1671 identification CHOICE { 1672 syntaxes SEQUENCE { 1673 abstract OBJECT IDENTIFIER, 1674 transfer OBJECT IDENTIFIER }, 1675 syntax OBJECT IDENTIFIER, 1676 presentation-context-id INTEGER, 1677 context-negotiation SEQUENCE { 1678 presentation-context-id INTEGER, 1679 transfer-syntax OBJECT IDENTIFIER }, 1680 transfer-syntax OBJECT IDENTIFIER, 1681 fixed NULL }, 1682 data-value-descriptor ObjectDescriptor OPTIONAL, 1683 data-value OCTET STRING } 1684 ( WITH COMPONENTS { 1685 ... , 1686 identification ( WITH COMPONENTS { 1687 ... , 1688 syntaxes ABSENT, 1689 presentation-context-id ABSENT, 1690 context-negotiation ABSENT, 1691 transfer-syntax ABSENT, 1692 fixed ABSENT } ) } ) 1693 ) 1694 1695 With the constraints applied, the abstract syntax for $(MONO EXTERNAL)s encoded 1696 using Canonical Encoding Rules or Distinguished Encoding Rules becomes: 1697 1698 $(PRE 1699 EXTERNAL ::= [UNIVERSAL 8] SEQUENCE { 1700 identification CHOICE { 1701 syntax OBJECT IDENTIFIER }, 1702 data-value-descriptor ObjectDescriptor OPTIONAL, 1703 data-value OCTET STRING } 1704 ) 1705 1706 Upon removing the $(MONO CHOICE) tag (since you have no choice but to use syntax 1707 at this point), the encoding definition when using 1708 Canonical Encoding Rules or Distinguished Encoding Rules: 1709 1710 $(PRE 1711 EXTERNAL ::= [UNIVERSAL 8] SEQUENCE { 1712 syntax OBJECT IDENTIFIER, 1713 data-value-descriptor ObjectDescriptor OPTIONAL, 1714 data-value OCTET STRING } 1715 ) 1716 1717 Returns: an instance of $(D types.universal.external.External) 1718 1719 Throws: 1720 $(UL 1721 $(LI $(D ASN1ValueException) 1722 if something other than $(D syntax) is used for $(MONO identification)) 1723 $(LI $(D ASN1ValueCharactersException) 1724 if a data-value-descriptor is supplied with invalid characters) 1725 ) 1726 */ 1727 deprecated override public @property @system 1728 void external(in External value) 1729 out 1730 { 1731 assert(this.value.length > 0u); 1732 } 1733 body 1734 { 1735 scope(success) this.construction = ASN1Construction.constructed; 1736 CERElement[] components = []; 1737 1738 if (!(value.identification.syntax.isNull)) 1739 { 1740 CERElement directReference = new CERElement(); 1741 directReference.tagNumber = ASN1UniversalType.objectIdentifier; 1742 directReference.objectIdentifier = value.identification.directReference; 1743 components ~= directReference; 1744 } 1745 else // it must be the presentationContextID / indirectReference INTEGER 1746 throw new ASN1ValueException 1747 ( 1748 "This exception was thrown because you attempted to encode an " ~ 1749 "EXTERNAL that used something other than syntax as the CHOICE " ~ 1750 "of identification, which is not permitted when using " ~ 1751 "Canonical Encoding Rules (CER). " ~ 1752 notWhatYouMeantText ~ forMoreInformationText ~ 1753 debugInformationText ~ reportBugsText 1754 ); 1755 1756 CERElement dataValueDescriptor = new CERElement(); 1757 dataValueDescriptor.tagNumber = ASN1UniversalType.objectDescriptor; 1758 dataValueDescriptor.objectDescriptor = value.dataValueDescriptor; 1759 components ~= dataValueDescriptor; 1760 1761 CERElement dataValue = new CERElement(); 1762 dataValue.tagClass = ASN1TagClass.contextSpecific; 1763 dataValue.tagNumber = value.encoding; 1764 dataValue.value = value.dataValue.dup; 1765 1766 components ~= dataValue; 1767 this.sequence = components; 1768 } 1769 1770 /* 1771 Since a CER-encoded EXTERNAL can only use the syntax field for 1772 the CHOICE of identification, this unit test ensures that an 1773 exception if thrown if an alternative identification is supplied. 1774 */ 1775 @system 1776 unittest 1777 { 1778 ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID(); 1779 id.presentationContextID = 27L; 1780 1781 External input = External(); 1782 input.identification = id; 1783 input.dataValueDescriptor = "external"; 1784 input.dataValue = [ 0x01u, 0x02u, 0x03u, 0x04u ]; 1785 1786 CERElement el = new CERElement(); 1787 assertThrown!ASN1ValueException(el.external = input); 1788 } 1789 1790 // Inspired by CVE-2017-9023 1791 @system 1792 unittest 1793 { 1794 ubyte[] external = [ // This is valid 1795 0x08u, 0x09u, // EXTERNAL, Length 9 1796 0x02u, 0x01u, 0x1Bu, // INTEGER 27 1797 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4 1798 ]; 1799 1800 // Valid values for octet[2]: 02 06 1801 for (ubyte i = 0x07u; i < 0x1Eu; i++) 1802 { 1803 external[2] = i; 1804 size_t x = 0u; 1805 CERElement el = new CERElement(x, external); 1806 assertThrown!ASN1Exception(el.external); 1807 } 1808 1809 // Valid values for octet[5]: 80 - 82 (Anything else is an invalid value) 1810 for (ubyte i = 0x82u; i < 0x9Eu; i++) 1811 { 1812 external[5] = i; 1813 size_t x = 0u; 1814 CERElement el = new CERElement(x, external); 1815 assertThrown!ASN1Exception(el.external); 1816 } 1817 } 1818 1819 // Assert that duplicate elements throw exceptions 1820 @system 1821 unittest 1822 { 1823 ubyte[] external; 1824 1825 external = [ // This is invalid 1826 0x08u, 0x0Cu, // EXTERNAL, Length 12 1827 0x02u, 0x01u, 0x1Bu, // INTEGER 27 1828 0x02u, 0x01u, 0x1Bu, // INTEGER 27 1829 0x81, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4 1830 ]; 1831 assertThrown!ASN1Exception((new CERElement(external)).external); 1832 1833 external = [ // This is invalid 1834 0x08u, 0x0Eu, // EXTERNAL, Length 14 1835 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3 1836 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3 1837 0x81, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4 1838 ]; 1839 assertThrown!ASN1Exception((new CERElement(external)).external); 1840 1841 external = [ // This is invalid 1842 0x08u, 0x12u, // EXTERNAL, Length 18 1843 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3 1844 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE" 1845 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE" 1846 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4 1847 ]; 1848 assertThrown!ASN1Exception((new CERElement(external)).external); 1849 1850 external = [ // This is invalid 1851 0x08u, 0x14u, // EXTERNAL, Length 20 1852 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3 1853 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE" 1854 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u, // OCTET STRING 1,2,3,4 1855 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4 1856 ]; 1857 assertThrown!ASN1Exception((new CERElement(external)).external); 1858 } 1859 1860 /** 1861 Decodes a floating-point type. 1862 1863 Note that this method assumes that your machine uses 1864 $(LINK http://ieeexplore.ieee.org/document/4610935/, IEEE 754-2008) 1865 floating point format. 1866 1867 Throws: 1868 $(UL 1869 $(LI $(D ASN1ConstructionException) if the element is marked as "constructed") 1870 $(LI $(D ASN1TruncationException) if the value appears to be "cut off") 1871 $(LI $(D ConvException) if character-encoding cannot be converted to 1872 the selected floating-point type, T) 1873 $(LI $(D ConvOverflowException) if the character-encoding encodes a 1874 number that is too big for the selected floating-point 1875 type to express) 1876 $(LI $(D ASN1ValueSizeException) if the binary-encoding contains fewer 1877 bytes than the information byte purports, or if the 1878 binary-encoded mantissa is too big to be expressed by an 1879 unsigned long integer) 1880 $(LI $(D ASN1ValueException) if a complicated-form exponent or a 1881 non-zero-byte mantissa encodes a zero or even number, of if a base-10 1882 (character-encoded) REAL is has something wrong that is not 1883 covered by $(D ASN1ValueCharactersException) or 1884 $(D ASN1ValuePaddingException)) 1885 $(LI $(D ASN1ValueUndefinedException) if both bits indicating the base in the 1886 information byte of a binary-encoded $(MONO REAL)'s information byte 1887 are set, which would indicate an invalid base, or if a special 1888 value has been indicated that is not defined by the specification) 1889 $(LI $(D ASN1ValuePaddingException) if a base-10 (character-encoded) 1890 REAL is encoded with leading zeroes, or trailing zeroes on the 1891 mantissa, or if the mantissa is encoded on more than the minimal 1892 necessary octets) 1893 $(LI $(D ASN1ValueCharactersException) if a base-10 (character-encoded) 1894 REAL is encoded with characters that are not numeric, a decimal, E, 1895 or plus or minus) 1896 ) 1897 1898 Citations: 1899 $(UL 1900 $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).” 1901 $(I ASN.1: Communication between Heterogeneous Systems), 1902 Morgan Kaufmann, 2001, pp. 400-402.) 1903 $(LI $(LINK https://www.iso.org/standard/12285.html, ISO 6093)) 1904 ) 1905 */ 1906 public @property @system 1907 T realNumber(T)() const 1908 if (isFloatingPoint!T) 1909 { 1910 if (this.construction != ASN1Construction.primitive) 1911 throw new ASN1ConstructionException 1912 (this.construction, "decode a REAL"); 1913 1914 if (this.value.length == 0u) return cast(T) 0.0; 1915 switch (this.value[0] & 0b11000000u) 1916 { 1917 case (0b01000000u): 1918 { 1919 if (this.value[0] == ASN1SpecialRealValue.notANumber) return T.nan; 1920 if (this.value[0] == ASN1SpecialRealValue.minusZero) return -0.0; 1921 if (this.value[0] == ASN1SpecialRealValue.plusInfinity) return T.infinity; 1922 if (this.value[0] == ASN1SpecialRealValue.minusInfinity) return -T.infinity; 1923 throw new ASN1ValueUndefinedException 1924 ( 1925 "This exception was thrown because you attempted to decode " ~ 1926 "a REAL whose information byte indicated a special value " ~ 1927 "not recognized by the specification. The only special " ~ 1928 "values recognized by the specification are PLUS-INFINITY, " ~ 1929 "MINUS-INFINITY, NOT-A-NUMBER, and minus zero, identified " ~ 1930 "by information bytes of 0x40, 0x41 0x42, 0x43 respectively. " ~ 1931 notWhatYouMeantText ~ forMoreInformationText ~ 1932 debugInformationText ~ reportBugsText 1933 ); 1934 } 1935 case (0b00000000u): // Character Encoding 1936 { 1937 /* NOTE: 1938 Specification X.690 lays out very strict standards for the 1939 Canonical Encoding Rules (CER) and Distinguished Encoding 1940 Rules (DER) base-10-encoded REAL. 1941 1942 The character encoding form must be NR3, from ISO 6093, but 1943 with even more restrictions applied. 1944 1945 It must be encoded like so: 1946 * No whitespace whatsoever 1947 * No leading zeroes under any circumstance. 1948 * No trailing zeroes under any circumstance. 1949 * No plus sign unless exponent is 0. 1950 1951 A valid encoding looks like this: 22.E-5 1952 */ 1953 import std.conv : to; 1954 import std.string : indexOf; 1955 1956 immutable string invalidNR3RealMessage = 1957 "This exception was thrown because you attempted to decode " ~ 1958 "a base-10 encoded REAL that was encoded with improper " ~ 1959 "format. When using Canonical Encoding Rules (CER) or " ~ 1960 "Distinguished Encoding Rules (DER), the base-10 encoded " ~ 1961 "REAL must be encoded in the NR3 format specified in " ~ 1962 "ISO 6093. Further, there may be no whitespace, no leading " ~ 1963 "zeroes, no trailing zeroes on the mantissa, before or " ~ 1964 "after the decimal point, and no plus sign should ever " ~ 1965 "appear, unless the exponent is 0, in which case, the " ~ 1966 "exponent should read '+0'. Further, there must be a " ~ 1967 "decimal point, immediately followed by a capital 'E'." ~ 1968 "Your problem, in this case, was that your encoded value "; 1969 1970 // Smallest possible is '#.E#'. Decimal is necessary. 1971 if (this.value.length < 5u) 1972 throw new ASN1ValueSizeException 1973 (5u, size_t.max, this.value.length, "decode a base-10 encoded REAL"); 1974 1975 if (this.value[0] != 0b00000011u) 1976 throw new ASN1ValueException 1977 (invalidNR3RealMessage ~ "was not NR3 format at all."); 1978 1979 string valueString = cast(string) this.value[1 .. $]; 1980 1981 foreach (character; valueString) 1982 { 1983 import std.ascii : isWhite; 1984 if 1985 ( 1986 character.isWhite || 1987 character == ',' || 1988 character == '_' 1989 ) 1990 throw new ASN1ValueCharactersException 1991 ("1234567890+-.E", character, "decode a base-10 encoded REAL"); 1992 } 1993 1994 if 1995 ( 1996 valueString[0] == '0' || 1997 (valueString[0] == '-' && valueString[1] == '0') 1998 ) 1999 throw new ASN1ValuePaddingException 2000 (invalidNR3RealMessage ~ "contained a leading zero."); 2001 2002 ptrdiff_t indexOfDecimalPoint = valueString.indexOf("."); 2003 if (indexOfDecimalPoint == -1) 2004 throw new ASN1ValueException 2005 (invalidNR3RealMessage ~ "contained no decimal point."); 2006 2007 if (valueString[indexOfDecimalPoint+1] != 'E') 2008 throw new ASN1ValueException 2009 (invalidNR3RealMessage ~ "contained no 'E'."); 2010 2011 if (valueString[indexOfDecimalPoint-1] == '0') 2012 throw new ASN1ValuePaddingException 2013 (invalidNR3RealMessage ~ "contained a trailing zero on the mantissa."); 2014 2015 if (valueString[$-2 .. $] != "+0" && canFind(valueString, '+')) 2016 throw new ASN1ValueException 2017 (invalidNR3RealMessage ~ "contained an illegitimate plus sign."); 2018 2019 if (canFind(valueString, "E0") || canFind(valueString, "E-0")) 2020 throw new ASN1ValuePaddingException 2021 (invalidNR3RealMessage ~ "contained a leading zero on the exponent."); 2022 2023 return to!(T)(valueString); 2024 } 2025 case 0b10000000u, 0b11000000u: // Binary Encoding 2026 { 2027 ulong mantissa; 2028 short exponent; 2029 ubyte scale; 2030 ubyte base; 2031 size_t startOfMantissa; 2032 2033 switch (this.value[0] & 0b00000011u) 2034 { 2035 case 0b00000000u: // Exponent on the following octet 2036 { 2037 if (this.value.length < 3u) 2038 throw new ASN1TruncationException 2039 (3u, this.value.length, "decode a REAL exponent"); 2040 2041 exponent = cast(short) cast(byte) this.value[1]; 2042 startOfMantissa = 2u; 2043 break; 2044 } 2045 case 0b00000001u: // Exponent on the following two octets 2046 { 2047 if (this.value.length < 4u) 2048 throw new ASN1TruncationException 2049 (4u, this.value.length, "decode a REAL exponent"); 2050 2051 ubyte[] exponentBytes = this.value[1 .. 3].dup; 2052 version (LittleEndian) exponentBytes = [ exponentBytes[1], exponentBytes[0] ]; 2053 exponent = *cast(short *) exponentBytes.ptr; 2054 2055 if (exponent <= byte.max && exponent >= byte.min) 2056 throw new ASN1ValuePaddingException 2057 ( 2058 "This exception was thrown because you attempted " ~ 2059 "to decode a binary-encoded REAL whose exponent " ~ 2060 "was encoded on more bytes than necessary. This " ~ 2061 "would not be a problem if you were using the " ~ 2062 "Basic Encoding Rules (BER), but the Canonical " ~ 2063 "Encoding Rules (CER) and Distinguished Encoding " ~ 2064 "Rules (DER) require that the exponent be " ~ 2065 "encoded on the fewest possible bytes. " ~ 2066 notWhatYouMeantText ~ forMoreInformationText ~ 2067 debugInformationText ~ reportBugsText 2068 ); 2069 2070 startOfMantissa = 3u; 2071 break; 2072 } 2073 case 0b00000010u: // Exponent on the following three octets 2074 case 0b00000011u: // Complicated 2075 { 2076 throw new ASN1ValueOverflowException 2077 ( 2078 "This exception was thrown because, according to " ~ 2079 "section 11.3.1 of specification X.690, a REAL's " ~ 2080 "exponent must be encoded on the fewest possible " ~ 2081 "octets, but you attempted to decode one that was " ~ 2082 "either too big to fit in an IEEE 754 floating " ~ 2083 "point type, or would have had unnecessary leading " ~ 2084 "bytes if it could. " 2085 ); 2086 } 2087 default: assert(0, "Impossible binary exponent encoding on REAL type"); 2088 } 2089 2090 if (this.value.length - startOfMantissa > ulong.sizeof) 2091 throw new ASN1ValueOverflowException 2092 ( 2093 "This exception was thrown because you attempted to " ~ 2094 "decode a REAL whose mantissa was encoded on too many " ~ 2095 "bytes to decode to the largest unsigned integral data " ~ 2096 "type. " 2097 ); 2098 2099 ubyte[] mantissaBytes = this.value[startOfMantissa .. $].dup; 2100 2101 if (mantissaBytes[0] == 0x00u) 2102 throw new ASN1ValuePaddingException 2103 ( 2104 "This exception was thrown because you attempted to decode " ~ 2105 "a REAL mantissa that was encoded on more than the minimum " ~ 2106 "necessary bytes. " ~ 2107 notWhatYouMeantText ~ forMoreInformationText ~ 2108 debugInformationText ~ reportBugsText 2109 ); 2110 2111 while (mantissaBytes.length < ulong.sizeof) 2112 mantissaBytes = (0x00u ~ mantissaBytes); 2113 version (LittleEndian) reverse(mantissaBytes); 2114 version (unittest) assert(mantissaBytes.length == ulong.sizeof); 2115 mantissa = *cast(ulong *) mantissaBytes.ptr; 2116 2117 if (mantissa == 0u) 2118 throw new ASN1ValueException 2119 ( 2120 "This exception was thrown because you attempted to " ~ 2121 "decode a REAL that was encoded on more than zero " ~ 2122 "bytes, but whose mantissa encoded a zero. This " ~ 2123 "is prohibited by specification X.690. If the " ~ 2124 "abstract value encoded is a real number of zero, " ~ 2125 "the REAL must be encoded upon zero bytes. " ~ 2126 notWhatYouMeantText ~ forMoreInformationText ~ 2127 debugInformationText ~ reportBugsText 2128 ); 2129 2130 switch (this.value[0] & 0b00110000u) 2131 { 2132 case (0b00000000u): 2133 { 2134 /* NOTE: 2135 Section 11.3.1 of X.690 states that, for Canonical Encoding Rules 2136 (CER) and Distinguished Encoding Rules (DER), the mantissa must be 2137 zero or odd. 2138 */ 2139 if (!(mantissa & 1u)) 2140 throw new ASN1ValueException 2141 ( 2142 "This exception was thrown because you attempted to " ~ 2143 "decode a base-2 encoded REAL whose mantissa was " ~ 2144 "not zero or odd. Both Canonical Encoding Rules (CER) " ~ 2145 "and Distinguished Encoding Rules (DER) require that " ~ 2146 "a base-2 encoded REAL's mantissa be shifted so that " ~ 2147 "it is either zero or odd. " 2148 ); 2149 2150 base = 0x02u; 2151 break; 2152 } 2153 case (0b00010000u): base = 0x08u; break; 2154 case (0b00100000u): base = 0x10u; break; 2155 default: 2156 throw new ASN1ValueUndefinedException 2157 ( 2158 "This exception was thrown because you attempted to " ~ 2159 "decode a REAL that had both base bits in the " ~ 2160 "information block set, the meaning of which is " ~ 2161 "not specified. " ~ 2162 notWhatYouMeantText ~ forMoreInformationText ~ 2163 debugInformationText ~ reportBugsText 2164 ); 2165 } 2166 2167 if (this.value[0] & 0b00001100u) 2168 throw new ASN1ValueException 2169 ( 2170 "This exception was thrown because you attempted to " ~ 2171 "decode a REAL whose scale was not zero. This would " ~ 2172 "not be a problem if you were using the Basic " ~ 2173 "Encoding Rules (BER), but specification X.690 " ~ 2174 "says that, when using the Canonical Encoding Rules " ~ 2175 "(CER) or Distinguished Encoding Rules (DER), the " ~ 2176 "scale must be zero. " ~ 2177 notWhatYouMeantText ~ forMoreInformationText ~ 2178 debugInformationText ~ reportBugsText 2179 ); 2180 2181 /* 2182 For some reason that I have yet to discover, you must 2183 cast the exponent to T. If you do not, specifically 2184 any usage of realNumber!T() outside of this library will 2185 produce a "floating point exception 8" message and 2186 crash. For some reason, all of the tests pass within 2187 this library without doing this. 2188 */ 2189 return ( 2190 ((this.value[0] & 0b01000000u) ? -1.0 : 1.0) * 2191 cast(T) mantissa * 2192 2^^scale * 2193 (cast(T) base)^^(cast(T) exponent) // base must be cast 2194 ); 2195 } 2196 default: assert(0, "Impossible information byte value appeared!"); 2197 } 2198 } 2199 2200 /** 2201 Encodes a floating-point type, using base-2 binary encoding. 2202 2203 Note that this method assumes that your machine uses 2204 $(LINK http://ieeexplore.ieee.org/document/4610935/, IEEE 754-2008) 2205 floating point format. 2206 2207 Citations: 2208 $(UL 2209 $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).” 2210 $(I ASN.1: Communication between Heterogeneous Systems), 2211 Morgan Kaufmann, 2001, pp. 400-402.) 2212 $(LI $(LINK https://www.iso.org/standard/12285.html, ISO 6093)) 2213 ) 2214 */ 2215 public @property @system nothrow 2216 void realNumber(T)(in T value) 2217 if (isFloatingPoint!T) 2218 { 2219 scope(success) this.construction = ASN1Construction.primitive; 2220 /* NOTE: 2221 You must use isIdentical() to compare FP types to negative zero, 2222 because the basic == operator does not distinguish between zero 2223 and negative zero. 2224 2225 isNaN() must be used to compare NaNs, because comparison using == 2226 does not work for that at all. 2227 2228 Also, this cannot go in a switch statement, because FP types 2229 cannot be the switch value. 2230 */ 2231 if (isIdentical(value, 0.0)) 2232 { 2233 this.value = []; 2234 return; 2235 } 2236 else if (isIdentical(value, -0.0)) 2237 { 2238 this.value = [ ASN1SpecialRealValue.minusZero ]; 2239 return; 2240 } 2241 if (value.isNaN) 2242 { 2243 this.value = [ ASN1SpecialRealValue.notANumber ]; 2244 return; 2245 } 2246 else if (value == T.infinity) 2247 { 2248 this.value = [ ASN1SpecialRealValue.plusInfinity ]; 2249 return; 2250 } 2251 else if (value == -T.infinity) 2252 { 2253 this.value = [ ASN1SpecialRealValue.minusInfinity ]; 2254 return; 2255 } 2256 2257 real realValue = cast(real) value; 2258 bool positive = true; 2259 ulong mantissa; 2260 short exponent; 2261 2262 /* 2263 Per the IEEE specifications, the exponent of a floating-point 2264 type is stored with a bias, meaning that the exponent counts 2265 up from a negative number, the reaches zero at the bias. We 2266 subtract the bias from the raw binary exponent to get the 2267 actual exponent encoded in the IEEE floating-point number. 2268 In the case of an x86 80-bit extended-precision floating-point 2269 type, the bias is 16383. In the case of double-precision, it is 2270 1023. For single-precision, it is 127. 2271 2272 We then subtract the number of bits in the fraction from the 2273 exponent, which is equivalent to having had multiplied the 2274 fraction enough to have made it an integer represented by the 2275 same sequence of bits. 2276 */ 2277 ubyte[] realBytes; 2278 realBytes.length = real.sizeof; 2279 *cast(real *)&realBytes[0] = realValue; 2280 2281 version (BigEndian) 2282 { 2283 static if (real.sizeof > 10u) realBytes = realBytes[real.sizeof-10 .. $]; 2284 positive = ((realBytes[0] & 0x80u) ? false : true); 2285 } 2286 else version (LittleEndian) 2287 { 2288 static if (real.sizeof > 10u) realBytes.length = 10u; 2289 positive = ((realBytes[$-1] & 0x80u) ? false : true); 2290 } 2291 else assert(0, "Could not determine endianness"); 2292 2293 static if (real.mant_dig == 64) // x86 Extended Precision 2294 { 2295 version (BigEndian) 2296 { 2297 exponent = (((*cast(short *) &realBytes[0]) & 0x7FFF) - 16383 - 63); // 16383 is the bias 2298 mantissa = *cast(ulong *) &realBytes[2]; 2299 } 2300 else version (LittleEndian) 2301 { 2302 exponent = (((*cast(short *) &realBytes[8]) & 0x7FFF) - 16383 - 63); // 16383 is the bias 2303 mantissa = *cast(ulong *) &realBytes[0]; 2304 } 2305 else assert(0, "Could not determine endianness"); 2306 } 2307 else if (T.mant_dig == 53) // Double Precision 2308 { 2309 /* 2310 The IEEE 754 double-precision floating point type only stores 2311 the fractional part of the mantissa, because there is an 2312 implicit 1 prior to the fractional part. To retrieve the actual 2313 mantissa encoded, we flip the bit that comes just before the 2314 most significant bit of the fractional part of the number. 2315 */ 2316 version (BigEndian) 2317 { 2318 exponent = (((*cast(short *) &realBytes[0]) & 0x7FFF) - 1023 - 53); // 1023 is the bias 2319 mantissa = (((*cast(ulong *) &realBytes[2]) & 0x000FFFFFFFFFFFFFu) | 0x0010000000000000u); 2320 } 2321 else version (LittleEndian) 2322 { 2323 exponent = (((*cast(short *) &realBytes[8]) & 0x7FFF) - 1023 - 53); // 1023 is the bias 2324 mantissa = (((*cast(ulong *) &realBytes[0]) & 0x000FFFFFFFFFFFFFu) | 0x0010000000000000u); 2325 } 2326 else assert(0, "Could not determine endianness"); 2327 } 2328 else if (T.mant_dig == 24) // Single Precision 2329 { 2330 /* 2331 The IEEE 754 single-precision floating point type only stores 2332 the fractional part of the mantissa, because there is an 2333 implicit 1 prior to the fractional part. To retrieve the actual 2334 mantissa encoded, we flip the bit that comes just before the 2335 most significant bit of the fractional part of the number. 2336 */ 2337 version (BigEndian) 2338 { 2339 exponent = ((((*cast(short *) &realBytes[0]) & 0x7F80) >> 7) - 127 - 23); // 127 is the bias 2340 mantissa = cast(ulong) (((*cast(uint *) &realBytes[2]) & 0x007FFFFFu) | 0x00800000u); 2341 } 2342 else version (LittleEndian) 2343 { 2344 exponent = ((((*cast(short *) &realBytes[8]) & 0x7F80) >> 7) - 127 - 23); // 127 is the bias 2345 mantissa = cast(ulong) (((*cast(uint *) &realBytes[0]) & 0x007FFFFFu) | 0x00800000u); 2346 } 2347 else assert(0, "Could not determine endianness"); 2348 } 2349 else assert(0, "Unrecognized real floating-point format."); 2350 2351 /* NOTE: 2352 Section 11.3.1 of X.690 states that, for Canonical Encoding Rules 2353 (CER) and Distinguished Encoding Rules (DER), the mantissa must be 2354 zero or odd. 2355 */ 2356 if (mantissa != 0u) 2357 { 2358 while (!(mantissa & 1u)) 2359 { 2360 mantissa >>= 1; 2361 exponent++; 2362 } 2363 version(unittest) assert(mantissa & 1u); 2364 } 2365 2366 ubyte[] exponentBytes; 2367 exponentBytes.length = short.sizeof; 2368 *cast(short *)exponentBytes.ptr = exponent; 2369 version (LittleEndian) exponentBytes = [ exponentBytes[1], exponentBytes[0] ]; // Manual reversal (optimization) 2370 if 2371 ( 2372 (exponentBytes[0] == 0x00u && (!(exponentBytes[1] & 0x80u))) || // Unnecessary positive leading bytes 2373 (exponentBytes[0] == 0xFFu && (exponentBytes[1] & 0x80u)) // Unnecessary negative leading bytes 2374 ) 2375 exponentBytes = exponentBytes[1 .. 2]; 2376 2377 ubyte[] mantissaBytes; 2378 mantissaBytes.length = ulong.sizeof; 2379 *cast(ulong *)mantissaBytes.ptr = cast(ulong) mantissa; 2380 version (LittleEndian) reverse(mantissaBytes); 2381 2382 size_t startOfNonPadding = 0u; 2383 for (size_t i = 0u; i < mantissaBytes.length-1; i++) 2384 { 2385 if (mantissaBytes[i] != 0x00u) break; 2386 startOfNonPadding++; 2387 } 2388 mantissaBytes = mantissaBytes[startOfNonPadding .. $]; 2389 2390 ubyte infoByte = 2391 0x80u | // First bit gets set for base2, base8, or base16 encoding 2392 (positive ? 0x00u : 0x40u) | // 1 = negative, 0 = positive 2393 // Scale = 0 2394 cast(ubyte) (exponentBytes.length == 1u ? 2395 ASN1RealExponentEncoding.followingOctet : 2396 ASN1RealExponentEncoding.following2Octets); 2397 2398 this.value = (infoByte ~ exponentBytes ~ mantissaBytes); 2399 } 2400 2401 @system 2402 unittest 2403 { 2404 CERElement el = new CERElement(); 2405 2406 // float 2407 el.realNumber!float = cast(float) float.nan; 2408 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2409 el.realNumber!double = cast(double) float.nan; 2410 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2411 el.realNumber!real = cast(real) float.nan; 2412 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2413 2414 el.realNumber!float = cast(float) 0.0; 2415 assert(el.value == []); 2416 el.realNumber!double = cast(float) 0.0; 2417 assert(el.value == []); 2418 el.realNumber!real = cast(float) 0.0; 2419 assert(el.value == []); 2420 2421 el.realNumber!float = cast(float) -0.0; 2422 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2423 el.realNumber!double = cast(float) -0.0; 2424 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2425 el.realNumber!real = cast(float) -0.0; 2426 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2427 2428 el.realNumber!float = cast(float) float.infinity; 2429 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2430 el.realNumber!double = cast(double) float.infinity; 2431 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2432 el.realNumber!real = cast(real) float.infinity; 2433 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2434 2435 el.realNumber!float = cast(float) -float.infinity; 2436 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2437 el.realNumber!double = cast(double) -float.infinity; 2438 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2439 el.realNumber!real = cast(real) -float.infinity; 2440 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2441 2442 // double 2443 el.realNumber!float = cast(float) double.nan; 2444 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2445 el.realNumber!double = cast(double) double.nan; 2446 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2447 el.realNumber!real = cast(real) double.nan; 2448 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2449 2450 el.realNumber!float = cast(double) 0.0; 2451 assert(el.value == []); 2452 el.realNumber!double = cast(double) 0.0; 2453 assert(el.value == []); 2454 el.realNumber!real = cast(double) 0.0; 2455 assert(el.value == []); 2456 2457 el.realNumber!float = cast(double) -0.0; 2458 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2459 el.realNumber!double = cast(double) -0.0; 2460 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2461 el.realNumber!real = cast(double) -0.0; 2462 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2463 2464 el.realNumber!float = cast(float) double.infinity; 2465 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2466 el.realNumber!double = cast(double) double.infinity; 2467 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2468 el.realNumber!real = cast(real) double.infinity; 2469 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2470 2471 el.realNumber!float = cast(float) -double.infinity; 2472 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2473 el.realNumber!double = cast(double) -double.infinity; 2474 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2475 el.realNumber!real = cast(real) -double.infinity; 2476 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2477 2478 // real 2479 el.realNumber!float = cast(float) real.nan; 2480 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2481 el.realNumber!double = cast(double) real.nan; 2482 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2483 el.realNumber!real = cast(real) real.nan; 2484 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]); 2485 2486 el.realNumber!float = cast(real) 0.0; 2487 assert(el.value == []); 2488 el.realNumber!double = cast(real) 0.0; 2489 assert(el.value == []); 2490 el.realNumber!real = cast(real) 0.0; 2491 assert(el.value == []); 2492 2493 el.realNumber!float = cast(real) -0.0; 2494 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2495 el.realNumber!double = cast(real) -0.0; 2496 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2497 el.realNumber!real = cast(real) -0.0; 2498 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]); 2499 2500 el.realNumber!float = cast(float) real.infinity; 2501 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2502 el.realNumber!double = cast(double) real.infinity; 2503 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2504 el.realNumber!real = cast(real) real.infinity; 2505 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]); 2506 2507 el.realNumber!float = cast(float) -real.infinity; 2508 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2509 el.realNumber!double = cast(double) -real.infinity; 2510 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2511 el.realNumber!real = cast(real) -real.infinity; 2512 assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]); 2513 } 2514 2515 // Positive Testing Base-10 (Character-Encoded) REALs 2516 @system 2517 unittest 2518 { 2519 immutable string[] tests = [ 2520 "1.E1", 2521 "2.E10", 2522 "4.E100", 2523 "1.E-1", 2524 "2.E-10", 2525 "4.E-100", 2526 "-1.E1", 2527 "-2.E10", 2528 "-4.E100", 2529 "-1.E-1", 2530 "-2.E-10", 2531 "-4.E-100", 2532 "19.E1", 2533 "29.E10", 2534 "49.E100", 2535 "19.E-1", 2536 "29.E-10", 2537 "49.E-100", 2538 "-19.E1", 2539 "-29.E10", 2540 "-49.E100", 2541 "-19.E-1", 2542 "-29.E-10", 2543 "-49.E-100", 2544 "33.E+0" 2545 ]; 2546 2547 CERElement el = new CERElement(); 2548 2549 foreach (test; tests) 2550 { 2551 el.value = [ 0b00000011u ]; 2552 el.value ~= cast(ubyte[]) test; 2553 assertNotThrown!ASN1ValueException(el.realNumber!float); 2554 assertNotThrown!ASN1ValueException(el.realNumber!double); 2555 assertNotThrown!ASN1ValueException(el.realNumber!real); 2556 } 2557 } 2558 2559 // Negative Testing Base-10 (Character-Encoded) REALs 2560 @system 2561 unittest 2562 { 2563 immutable string[] tests = [ 2564 " 1.E1", // Leading whitespace 2565 "1.E1 ", // Trailing whitespace 2566 "1 .E1", // Internal whitespace 2567 "1. E1", // Internal whitespace 2568 "1.E 1", // Internal whitespace 2569 "+1.E1", // Leading plus sign 2570 "01.E1", // Leading zero 2571 "10.E1", // Trailing zero 2572 "1.0E1", // Fractional zero 2573 "1.E+1", // Leading plus sign 2574 "1.E01", // Leading zero 2575 "1E100", // No decimal point 2576 "1.1", // No 'E' 2577 "" // Empty string 2578 ]; 2579 2580 CERElement el = new CERElement(); 2581 2582 foreach (test; tests) 2583 { 2584 el.value = [ 0b00000011u ]; 2585 el.value ~= cast(ubyte[]) test; 2586 assertThrown!ASN1ValueException(el.realNumber!float); 2587 assertThrown!ASN1ValueException(el.realNumber!double); 2588 assertThrown!ASN1ValueException(el.realNumber!real); 2589 } 2590 } 2591 2592 /** 2593 Decodes a signed integer, which represents a selection from an 2594 $(MONO ENUMERATION) of choices. 2595 2596 Throws: 2597 $(UL 2598 $(LI $(D ASN1ConstructionException) 2599 if the encoded value is not primitively-constructed) 2600 $(LI $(D ASN1ValueSizeException) 2601 if the value is too big to decode to a signed integral type, 2602 or if the value is zero bytes) 2603 $(LI $(D ASN1ValuePaddingException) 2604 if there are padding bytes) 2605 ) 2606 */ 2607 public @property @system 2608 T enumerated(T)() const 2609 if (isIntegral!T && isSigned!T) 2610 { 2611 if (this.construction != ASN1Construction.primitive) 2612 throw new ASN1ConstructionException 2613 (this.construction, "decode an ENUMERATED"); 2614 2615 if (this.value.length == 1u) 2616 return cast(T) cast(byte) this.value[0]; 2617 2618 if (this.value.length == 0u || this.value.length > T.sizeof) 2619 throw new ASN1ValueSizeException 2620 (1u, long.sizeof, this.value.length, "decode an ENUMERATED"); 2621 2622 if 2623 ( 2624 (this.value[0] == 0x00u && (!(this.value[1] & 0x80u))) || // Unnecessary positive leading bytes 2625 (this.value[0] == 0xFFu && (this.value[1] & 0x80u)) // Unnecessary negative leading bytes 2626 ) 2627 throw new ASN1ValueException 2628 ( 2629 "This exception was thrown because you attempted to decode " ~ 2630 "an ENUMERATED that was encoded on more than the minimum " ~ 2631 "necessary bytes. " ~ 2632 notWhatYouMeantText ~ forMoreInformationText ~ 2633 debugInformationText ~ reportBugsText 2634 ); 2635 2636 /* NOTE: 2637 Because the ENUMERATED is stored in two's complement form, you 2638 can't just apppend 0x00u to the big end of it until it is as long 2639 as T in bytes, then cast to T. Instead, you have to first determine 2640 if the encoded integer is negative or positive. If it is negative, 2641 then you actually want to append 0xFFu to the big end until it is 2642 as big as T, so you get the two's complement form of whatever T 2643 you choose. 2644 2645 The line immediately below this determines whether the padding byte 2646 should be 0xFF or 0x00 based on the most significant bit of the 2647 most significant byte (which, since CER encodes big-endian, will 2648 always be the first byte). If set (1), the number is negative, and 2649 hence, the padding byte should be 0xFF. If not, it is positive, 2650 and the padding byte should be 0x00. 2651 */ 2652 immutable ubyte paddingByte = ((this.value[0] & 0x80u) ? 0xFFu : 0x00u); 2653 ubyte[] value = this.value.dup; // Duplication is necessary to prevent modifying the source bytes 2654 while (value.length < T.sizeof) 2655 value = (paddingByte ~ value); 2656 version (LittleEndian) reverse(value); 2657 version (unittest) assert(value.length == T.sizeof); 2658 return *cast(T *) value.ptr; 2659 } 2660 2661 /// Encodes an $(MONO ENUMERATED) type from an integer. 2662 public @property @system nothrow 2663 void enumerated(T)(in T value) 2664 out 2665 { 2666 assert(this.value.length > 0u); 2667 } 2668 body 2669 { 2670 scope(success) this.construction = ASN1Construction.primitive; 2671 if (value <= byte.max && value >= byte.min) 2672 { 2673 this.value = [ cast(ubyte) cast(byte) value ]; 2674 return; 2675 } 2676 2677 ubyte[] ub; 2678 ub.length = T.sizeof; 2679 *cast(T *)&ub[0] = value; 2680 version (LittleEndian) reverse(ub); 2681 2682 /* 2683 An ENUMERATED must be encoded on the fewest number of bytes than can 2684 encode it. The loops below identify how many bytes can be 2685 truncated from the start of the ENUMERATED, with one loop for positive 2686 and another loop for negative numbers. ENUMERATED is encoded in the 2687 same exact way that INTEGER is encoded. 2688 2689 From X.690, Section 8.3.2: 2690 2691 If the contents octets of an integer value encoding consist of more 2692 than one octet, then the bits of the first octet and bit 8 of the 2693 second octet: 2694 a) shall not all be ones; and 2695 b) shall not all be zero. 2696 NOTE – These rules ensure that an integer value is always 2697 encoded in the smallest possible number of octets. 2698 */ 2699 size_t startOfNonPadding = 0u; 2700 if (T.sizeof > 1u) 2701 { 2702 if (value >= 0) 2703 { 2704 for (size_t i = 0u; i < ub.length-1; i++) 2705 { 2706 if (ub[i] != 0x00u) break; 2707 if (!(ub[i+1] & 0x80u)) startOfNonPadding++; 2708 } 2709 } 2710 else 2711 { 2712 for (size_t i = 0u; i < ub.length-1; i++) 2713 { 2714 if (ub[i] != 0xFFu) break; 2715 if (ub[i+1] & 0x80u) startOfNonPadding++; 2716 } 2717 } 2718 } 2719 2720 this.value = ub[startOfNonPadding .. $]; 2721 } 2722 2723 // Ensure that ENUMERATED 0 gets encoded on a single null byte. 2724 @system 2725 unittest 2726 { 2727 CERElement el = new CERElement(); 2728 2729 el.enumerated!byte = cast(byte) 0x00; 2730 assert(el.value == [ 0x00u ]); 2731 2732 el.enumerated!short = cast(short) 0x0000; 2733 assert(el.value == [ 0x00u ]); 2734 2735 el.enumerated!int = cast(int) 0; 2736 assert(el.value == [ 0x00u ]); 2737 2738 el.enumerated!long = cast(long) 0; 2739 assert(el.value == [ 0x00u ]); 2740 2741 el.value = []; 2742 assertThrown!ASN1ValueSizeException(el.enumerated!byte); 2743 assertThrown!ASN1ValueSizeException(el.enumerated!short); 2744 assertThrown!ASN1ValueSizeException(el.enumerated!int); 2745 assertThrown!ASN1ValueSizeException(el.enumerated!long); 2746 } 2747 2748 // Test encoding -0 for the sake of CVE-2016-2108 2749 @system 2750 unittest 2751 { 2752 CERElement el = new CERElement(); 2753 2754 el.enumerated!byte = -0; 2755 assertNotThrown!RangeError(el.enumerated!byte); 2756 assertNotThrown!ASN1Exception(el.enumerated!byte); 2757 2758 el.enumerated!short = -0; 2759 assertNotThrown!RangeError(el.enumerated!short); 2760 assertNotThrown!ASN1Exception(el.enumerated!short); 2761 2762 el.enumerated!int = -0; 2763 assertNotThrown!RangeError(el.enumerated!int); 2764 assertNotThrown!ASN1Exception(el.enumerated!int); 2765 2766 el.enumerated!long = -0; 2767 assertNotThrown!RangeError(el.enumerated!long); 2768 assertNotThrown!ASN1Exception(el.enumerated!long); 2769 } 2770 2771 /** 2772 Decodes an $(MONO EmbeddedPDV), which is a constructed data type, defined in 2773 the $(LINK https://www.itu.int, International Telecommunications Union)'s 2774 $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680). 2775 2776 The specification defines $(MONO EmbeddedPDV) as: 2777 2778 $(PRE 2779 EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE { 2780 identification CHOICE { 2781 syntaxes SEQUENCE { 2782 abstract OBJECT IDENTIFIER, 2783 transfer OBJECT IDENTIFIER }, 2784 syntax OBJECT IDENTIFIER, 2785 presentation-context-id INTEGER, 2786 context-negotiation SEQUENCE { 2787 presentation-context-id INTEGER, 2788 transfer-syntax OBJECT IDENTIFIER }, 2789 transfer-syntax OBJECT IDENTIFIER, 2790 fixed NULL }, 2791 data-value-descriptor ObjectDescriptor OPTIONAL, 2792 data-value OCTET STRING } 2793 (WITH COMPONENTS { ... , data-value-descriptor ABSENT }) 2794 ) 2795 2796 This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification) 2797 choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5. 2798 2799 The following additional constraints are applied to the abstract syntax 2800 when using Canonical Encoding Rules or Distinguished Encoding Rules, 2801 which are also defined in the 2802 $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s 2803 $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules): 2804 2805 $(PRE 2806 EmbeddedPDV ( WITH COMPONENTS { 2807 ... , 2808 identification ( WITH COMPONENTS { 2809 ... , 2810 presentation-context-id ABSENT, 2811 context-negotiation ABSENT } ) } ) 2812 ) 2813 2814 The stated purpose of the constraints shown above is to restrict the use of 2815 the $(MONO presentation-context-id), either by itself or within the 2816 context-negotiation, which makes the following the effective abstract 2817 syntax of $(MONO EmbeddedPDV) when using Canonical Encoding Rules or 2818 Distinguished Encoding Rules: 2819 2820 $(PRE 2821 EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE { 2822 identification CHOICE { 2823 syntaxes SEQUENCE { 2824 abstract OBJECT IDENTIFIER, 2825 transfer OBJECT IDENTIFIER }, 2826 syntax OBJECT IDENTIFIER, 2827 presentation-context-id INTEGER, 2828 context-negotiation SEQUENCE { 2829 presentation-context-id INTEGER, 2830 transfer-syntax OBJECT IDENTIFIER }, 2831 transfer-syntax OBJECT IDENTIFIER, 2832 fixed NULL }, 2833 data-value-descriptor ObjectDescriptor OPTIONAL, 2834 data-value OCTET STRING } 2835 ( WITH COMPONENTS { 2836 ... , 2837 identification ( WITH COMPONENTS { 2838 ... , 2839 presentation-context-id ABSENT, 2840 context-negotiation ABSENT } ) } ) 2841 ) 2842 2843 With the constraints applied, the abstract syntax for $(MONO EmbeddedPDV)s encoded 2844 using Canonical Encoding Rules or Distinguished Encoding Rules becomes: 2845 2846 $(PRE 2847 EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE { 2848 identification CHOICE { 2849 syntaxes SEQUENCE { 2850 abstract OBJECT IDENTIFIER, 2851 transfer OBJECT IDENTIFIER }, 2852 syntax OBJECT IDENTIFIER, 2853 transfer-syntax OBJECT IDENTIFIER, 2854 fixed NULL }, 2855 data-value-descriptor ObjectDescriptor OPTIONAL, 2856 data-value OCTET STRING } 2857 ) 2858 2859 Returns: an instance of $(D types.universal.embeddedpdv.EmbeddedPDV) 2860 2861 Throws: 2862 $(UL 2863 $(LI $(D ASN1ValueException) if encoded EmbeddedPDV has too few or too many 2864 elements, or if syntaxes or context-negotiation element has 2865 too few or too many elements) 2866 $(LI $(D ASN1ValueSizeException) if encoded INTEGER is too large to decode) 2867 $(LI $(D ASN1RecursionException) if using constructed form and the element 2868 is constructed of too many nested constructed elements) 2869 $(LI $(D ASN1TagClassException) if any nested primitives do not have the 2870 correct tag class) 2871 $(LI $(D ASN1ConstructionException) if any element has the wrong construction) 2872 $(LI $(D ASN1TagNumberException) if any nested primitives do not have the 2873 correct tag number) 2874 ) 2875 */ 2876 override public @property @system 2877 EmbeddedPDV embeddedPresentationDataValue() const 2878 { 2879 if (this.construction != ASN1Construction.constructed) 2880 throw new ASN1ConstructionException 2881 (this.construction, "decode an EmbeddedPDV"); 2882 2883 const CERElement[] components = this.sequence; 2884 ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID(); 2885 2886 if (components.length != 2u) 2887 throw new ASN1ValueException 2888 ( 2889 "This exception was thrown because you attempted to decode " ~ 2890 "an EmbeddedPDV that contained too many or too few elements. " ~ 2891 "An EmbeddedPDV should have only two elements: " ~ 2892 "an identification CHOICE, and a data-value OCTET STRING, " ~ 2893 "in that order. " ~ 2894 notWhatYouMeantText ~ forMoreInformationText ~ 2895 debugInformationText ~ reportBugsText 2896 ); 2897 2898 if (components[0].tagClass != ASN1TagClass.contextSpecific) 2899 throw new ASN1TagClassException 2900 ( 2901 [ ASN1TagClass.contextSpecific ], 2902 components[0].tagClass, 2903 "decode the first component of an EmbeddedPDV" 2904 ); 2905 2906 if (components[1].tagClass != ASN1TagClass.contextSpecific) 2907 throw new ASN1TagClassException 2908 ( 2909 [ ASN1TagClass.contextSpecific ], 2910 components[1].tagClass, 2911 "decode the second component of an EmbeddedPDV" 2912 ); 2913 2914 /* NOTE: 2915 See page 224 of Dubuisson, item 11: 2916 It sounds like, even if you have an ABSENT constraint applied, 2917 all automatically-tagged items still have the same numbers as 2918 though the constrained component were PRESENT. 2919 */ 2920 if (components[0].tagNumber != 0u) 2921 throw new ASN1TagNumberException 2922 ([ 0u ], components[0].tagNumber, "decode the first component of an EmbeddedPDV"); 2923 2924 if (components[1].tagNumber != 2u) 2925 throw new ASN1TagNumberException 2926 ([ 2u ], components[1].tagNumber, "decode the second component of an EmbeddedPDV"); 2927 2928 2929 ubyte[] bytes = components[0].value.dup; 2930 const CERElement identificationChoice = new CERElement(bytes); 2931 switch (identificationChoice.tagNumber) 2932 { 2933 case (0u): // syntaxes 2934 { 2935 if (identificationChoice.construction != ASN1Construction.constructed) 2936 throw new ASN1ConstructionException 2937 (identificationChoice.construction, "decode the syntaxes component of an EmbeddedPDV"); 2938 2939 const CERElement[] syntaxesComponents = identificationChoice.sequence; 2940 2941 if (syntaxesComponents.length != 2u) 2942 throw new ASN1ValueException 2943 ( 2944 "This exception was thrown because you attempted to " ~ 2945 "decode an EmbeddedPDV whose syntaxes component " ~ 2946 "contained an invalid number of elements. The " ~ 2947 "syntaxes component should contain abstract and transfer " ~ 2948 "syntax OBJECT IDENTIFIERS, in that order. " ~ 2949 notWhatYouMeantText ~ forMoreInformationText ~ 2950 debugInformationText ~ reportBugsText 2951 ); 2952 2953 // Class Validation 2954 if (syntaxesComponents[0].tagClass != ASN1TagClass.contextSpecific) 2955 throw new ASN1TagClassException 2956 ( 2957 [ ASN1TagClass.contextSpecific ], 2958 syntaxesComponents[0].tagClass, 2959 "decode the first syntaxes component of an EmbeddedPDV" 2960 ); 2961 2962 if (syntaxesComponents[1].tagClass != ASN1TagClass.contextSpecific) 2963 throw new ASN1TagClassException 2964 ( 2965 [ ASN1TagClass.contextSpecific ], 2966 syntaxesComponents[1].tagClass, 2967 "decode the second syntaxes component of an EmbeddedPDV" 2968 ); 2969 2970 // Construction Validation 2971 if (syntaxesComponents[0].construction != ASN1Construction.primitive) 2972 throw new ASN1ConstructionException 2973 (syntaxesComponents[0].construction, "decode the first syntaxes component of an EmbeddedPDV"); 2974 2975 if (syntaxesComponents[1].construction != ASN1Construction.primitive) 2976 throw new ASN1ConstructionException 2977 (syntaxesComponents[1].construction, "decode the second syntaxes component of an EmbeddedPDV"); 2978 2979 // Number Validation 2980 if (syntaxesComponents[0].tagNumber != 0u) 2981 throw new ASN1TagNumberException 2982 ( 2983 [ 0u ], 2984 syntaxesComponents[0].tagNumber, 2985 "decode the first syntaxes component of an EmbeddedPDV" 2986 ); 2987 2988 if (syntaxesComponents[1].tagNumber != 1u) 2989 throw new ASN1TagNumberException 2990 ( 2991 [ 1u ], 2992 syntaxesComponents[1].tagNumber, 2993 "decode the second syntaxes component of an EmbeddedPDV" 2994 ); 2995 2996 identification.syntaxes = ASN1Syntaxes( 2997 syntaxesComponents[0].objectIdentifier, 2998 syntaxesComponents[1].objectIdentifier 2999 ); 3000 3001 break; 3002 } 3003 case (1u): // syntax 3004 { 3005 identification.syntax = identificationChoice.objectIdentifier; 3006 break; 3007 } 3008 case (4u): // transfer-syntax 3009 { 3010 identification.transferSyntax = identificationChoice.objectIdentifier; 3011 break; 3012 } 3013 case (5u): // fixed 3014 { 3015 identification.fixed = true; 3016 break; 3017 } 3018 default: 3019 throw new ASN1TagNumberException 3020 ([ 0u, 1u, 4u, 5u ], identificationChoice.tagNumber, "decode an EmbeddedPDV identification"); 3021 } 3022 3023 EmbeddedPDV pdv = EmbeddedPDV(); 3024 pdv.identification = identification; 3025 pdv.dataValue = components[1].octetString; 3026 return pdv; 3027 } 3028 3029 /** 3030 Encodes an $(MONO EmbeddedPDV), which is a constructed data type, defined in 3031 the $(LINK https://www.itu.int, International Telecommunications Union)'s 3032 $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680). 3033 3034 The specification defines $(MONO EmbeddedPDV) as: 3035 3036 $(PRE 3037 EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE { 3038 identification CHOICE { 3039 syntaxes SEQUENCE { 3040 abstract OBJECT IDENTIFIER, 3041 transfer OBJECT IDENTIFIER }, 3042 syntax OBJECT IDENTIFIER, 3043 presentation-context-id INTEGER, 3044 context-negotiation SEQUENCE { 3045 presentation-context-id INTEGER, 3046 transfer-syntax OBJECT IDENTIFIER }, 3047 transfer-syntax OBJECT IDENTIFIER, 3048 fixed NULL }, 3049 data-value-descriptor ObjectDescriptor OPTIONAL, 3050 data-value OCTET STRING } 3051 (WITH COMPONENTS { ... , data-value-descriptor ABSENT }) 3052 ) 3053 3054 This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification) 3055 choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5. 3056 3057 The following additional constraints are applied to the abstract syntax 3058 when using Canonical Encoding Rules or Distinguished Encoding Rules, 3059 which are also defined in the 3060 $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s 3061 $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules): 3062 3063 $(PRE 3064 EmbeddedPDV ( WITH COMPONENTS { 3065 ... , 3066 identification ( WITH COMPONENTS { 3067 ... , 3068 presentation-context-id ABSENT, 3069 context-negotiation ABSENT } ) } ) 3070 ) 3071 3072 The stated purpose of the constraints shown above is to restrict the use of 3073 the $(MONO presentation-context-id), either by itself or within the 3074 context-negotiation, which makes the following the effective abstract 3075 syntax of $(MONO EmbeddedPDV) when using Canonical Encoding Rules or 3076 Distinguished Encoding Rules: 3077 3078 $(PRE 3079 EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE { 3080 identification CHOICE { 3081 syntaxes SEQUENCE { 3082 abstract OBJECT IDENTIFIER, 3083 transfer OBJECT IDENTIFIER }, 3084 syntax OBJECT IDENTIFIER, 3085 presentation-context-id INTEGER, 3086 context-negotiation SEQUENCE { 3087 presentation-context-id INTEGER, 3088 transfer-syntax OBJECT IDENTIFIER }, 3089 transfer-syntax OBJECT IDENTIFIER, 3090 fixed NULL }, 3091 data-value-descriptor ObjectDescriptor OPTIONAL, 3092 data-value OCTET STRING } 3093 ( WITH COMPONENTS { 3094 ... , 3095 identification ( WITH COMPONENTS { 3096 ... , 3097 presentation-context-id ABSENT, 3098 context-negotiation ABSENT } ) } ) 3099 ) 3100 3101 With the constraints applied, the abstract syntax for $(MONO EmbeddedPDV)s encoded 3102 using Canonical Encoding Rules or Distinguished Encoding Rules becomes: 3103 3104 $(PRE 3105 EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE { 3106 identification CHOICE { 3107 syntaxes SEQUENCE { 3108 abstract OBJECT IDENTIFIER, 3109 transfer OBJECT IDENTIFIER }, 3110 syntax OBJECT IDENTIFIER, 3111 transfer-syntax OBJECT IDENTIFIER, 3112 fixed NULL }, 3113 data-value-descriptor ObjectDescriptor OPTIONAL, 3114 data-value OCTET STRING } 3115 ) 3116 3117 If the supplied $(MONO identification) for the EmbeddedPDV is a 3118 $(MONO presentation-context-id) or a $(MONO context-negotiation), 3119 no exception will be thrown; the $(MONO identification) will be set to 3120 $(MONO fixed) silently. 3121 3122 Throws: 3123 $(UL 3124 $(LI $(D ASN1ValueException) if encoded ObjectDescriptor contains invalid characters) 3125 ) 3126 */ 3127 override public @property @system 3128 void embeddedPresentationDataValue(in EmbeddedPDV value) 3129 out 3130 { 3131 assert(this.value.length > 0u); 3132 } 3133 body 3134 { 3135 scope(success) this.construction = ASN1Construction.constructed; 3136 CERElement identification = new CERElement(); 3137 identification.tagClass = ASN1TagClass.contextSpecific; 3138 identification.tagNumber = 0u; // CHOICE is EXPLICIT, even with automatic tagging. 3139 3140 CERElement identificationChoice = new CERElement(); 3141 identificationChoice.tagClass = ASN1TagClass.contextSpecific; 3142 if (!(value.identification.syntaxes.isNull)) 3143 { 3144 CERElement abstractSyntax = new CERElement(); 3145 abstractSyntax.tagClass = ASN1TagClass.contextSpecific; 3146 abstractSyntax.tagNumber = 0u; 3147 abstractSyntax.objectIdentifier = value.identification.syntaxes.abstractSyntax; 3148 3149 CERElement transferSyntax = new CERElement(); 3150 transferSyntax.tagClass = ASN1TagClass.contextSpecific; 3151 transferSyntax.tagNumber = 1u; 3152 transferSyntax.objectIdentifier = value.identification.syntaxes.transferSyntax; 3153 3154 identificationChoice.construction = ASN1Construction.constructed; 3155 identificationChoice.tagNumber = 0u; 3156 identificationChoice.sequence = [ abstractSyntax, transferSyntax ]; 3157 } 3158 else if (!(value.identification.syntax.isNull)) 3159 { 3160 identificationChoice.tagNumber = 1u; 3161 identificationChoice.objectIdentifier = value.identification.syntax; 3162 } 3163 else if (!(value.identification.transferSyntax.isNull)) 3164 { 3165 identificationChoice.tagNumber = 4u; 3166 identificationChoice.objectIdentifier = value.identification.transferSyntax; 3167 } 3168 else 3169 { 3170 identificationChoice.tagNumber = 5u; 3171 identificationChoice.value = []; 3172 } 3173 3174 // This makes identification: [CONTEXT 0][L][CONTEXT #][L][V] 3175 identification.value = cast(ubyte[]) identificationChoice; 3176 3177 CERElement dataValue = new CERElement(); 3178 dataValue.tagClass = ASN1TagClass.contextSpecific; 3179 dataValue.tagNumber = 2u; 3180 dataValue.octetString = value.dataValue; 3181 3182 this.sequence = [ identification, dataValue ]; 3183 } 3184 3185 /* NOTE: 3186 This unit test had to be moved out of ASN1Element because CER and CER 3187 do not support encoding of presentation-context-id in EmbeddedPDV. 3188 3189 This unit test ensures that, if you attempt to create an EmbeddedPDV 3190 with presentation-context-id as the CHOICE of identification, the 3191 encoded EmbeddedPDV's identification defaults to fixed. 3192 */ 3193 @system 3194 unittest 3195 { 3196 ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID(); 3197 id.presentationContextID = 27L; 3198 3199 EmbeddedPDV input = EmbeddedPDV(); 3200 input.identification = id; 3201 input.dataValue = [ 0x01u, 0x02u, 0x03u, 0x04u ]; 3202 3203 CERElement el = new CERElement(); 3204 el.tagNumber = 0x08u; 3205 el.embeddedPDV = input; 3206 EmbeddedPDV output = el.embeddedPDV; 3207 assert(output.identification.fixed == true); 3208 assert(output.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]); 3209 } 3210 3211 /* NOTE: 3212 This unit test had to be moved out of ASN1Element because CER and CER 3213 do not support encoding of context-negotiation in EmbeddedPDV. 3214 3215 This unit test ensures that, if you attempt to create an EmbeddedPDV 3216 with context-negotiation as the CHOICE of identification, the 3217 encoded EmbeddedPDV's identification defaults to fixed. 3218 */ 3219 @system 3220 unittest 3221 { 3222 ASN1ContextNegotiation cn = ASN1ContextNegotiation(); 3223 cn.presentationContextID = 27L; 3224 cn.transferSyntax = new OID(1, 3, 6, 4, 1, 256, 39); 3225 3226 ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID(); 3227 id.contextNegotiation = cn; 3228 3229 EmbeddedPDV input = EmbeddedPDV(); 3230 input.identification = id; 3231 input.dataValue = [ 0x13u, 0x15u, 0x17u, 0x19u ]; 3232 3233 CERElement el = new CERElement(); 3234 el.embeddedPDV = input; 3235 EmbeddedPDV output = el.embeddedPDV; 3236 assert(output.identification.fixed == true); 3237 assert(output.dataValue == [ 0x13u, 0x15u, 0x17u, 0x19u ]); 3238 } 3239 3240 // Inspired by CVE-2017-9023 3241 @system 3242 unittest 3243 { 3244 ubyte[] data = [ // This is valid. 3245 0x0Bu, 0x0Au, // EmbeddedPDV, Length 11 3246 0x80u, 0x02u, // CHOICE 3247 0x85u, 0x00u, // NULL 3248 0x82u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u ]; // OCTET STRING 3249 3250 // Valid values for data[2]: 80 3251 for (ubyte i = 0x81u; i < 0x9Eu; i++) 3252 { 3253 data[2] = i; 3254 size_t x = 0u; 3255 CERElement el = new CERElement(x, data); 3256 assertThrown!ASN1Exception(el.embeddedPDV); 3257 } 3258 3259 // Valid values for data[4]: 80-85 3260 for (ubyte i = 0x86u; i < 0x9Eu; i++) 3261 { 3262 data[4] = i; 3263 size_t x = 0u; 3264 CERElement el = new CERElement(x, data); 3265 assertThrown!ASN1Exception(el.embeddedPDV); 3266 } 3267 3268 // Valid values for data[6]: 82 3269 for (ubyte i = 0x83u; i < 0x9Eu; i++) 3270 { 3271 data[6] = i; 3272 size_t x = 0u; 3273 CERElement el = new CERElement(x, data); 3274 assertThrown!ASN1Exception(el.embeddedPDV); 3275 } 3276 } 3277 3278 /** 3279 Decodes the value to UTF-8 characters. 3280 3281 Throws: 3282 $(UL 3283 $(LI $(D UTF8Exception) 3284 if the encoded value does not decode to UTF-8) 3285 $(LI $(D ASN1RecursionException) 3286 if using constructed form and the element 3287 is constructed of too many nested constructed elements) 3288 $(LI $(D ASN1TagClassException) 3289 if any nested primitives do not share the 3290 same tag class as their outer constructed element) 3291 $(LI $(D ASN1TagNumberException) 3292 if any nested primitives do not share the 3293 same tag number as their outer constructed element) 3294 ) 3295 */ 3296 override public @property @system 3297 string unicodeTransformationFormat8String() const 3298 { 3299 if (this.construction == ASN1Construction.primitive) 3300 { 3301 if (this.value.length > 1000u) 3302 throw new ASN1ValueSizeException 3303 (0u, 1000u, this.value.length, "decode a primitively-encoded UTF8String"); 3304 3305 return cast(string) this.value; 3306 } 3307 else 3308 { 3309 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 3310 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a UTF8String"); 3311 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 3312 3313 CERElement[] substrings = this.sequence; 3314 if (substrings.length < 2u) 3315 throw new ASN1ValueException 3316 ( 3317 "This exception was thrown because you attempted to decode " ~ 3318 "a constructed UTF8String that was constituted by fewer " ~ 3319 "than two nested elements. When using the Canonical Encoding " ~ 3320 "Rules (CER), a legitimate constructed UTF8String must " ~ 3321 "contain at least two nested elements, and each but the " ~ 3322 "last must contain exactly 1000 content octets. The last " ~ 3323 "nested element may have between 1 and 1000 content " ~ 3324 "octets inclusively. This element should have been encoded " ~ 3325 "primitively using definite-length encoding." 3326 ); 3327 3328 foreach (substring; substrings[0 .. $-1]) 3329 { 3330 if (substring.length != 1000u) 3331 throw new ASN1ValueSizeException 3332 (1000u, 1000u, substring.length, "decode a substring of a constructed UTF8String"); 3333 } 3334 3335 if (substrings[$-1].length == 0u) 3336 throw new ASN1ValueSizeException 3337 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed UTF8String"); 3338 3339 Appender!string appendy = appender!string(); 3340 foreach (substring; substrings) 3341 { 3342 if (substring.tagClass != this.tagClass) 3343 throw new ASN1TagClassException 3344 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed UTF8String"); 3345 3346 if (substring.construction != ASN1Construction.primitive) 3347 throw new ASN1ConstructionException 3348 (substring.construction, "decode a substring of a constructed UTF8String"); 3349 3350 if (substring.tagNumber != this.tagNumber) 3351 throw new ASN1TagNumberException 3352 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed UTF8String"); 3353 3354 appendy.put(substring.utf8String); 3355 } 3356 return appendy.data; 3357 } 3358 } 3359 3360 /// Encodes a UTF-8 string to bytes. 3361 override public @property @system 3362 void unicodeTransformationFormat8String(in string value) 3363 { 3364 if (value.length <= 1000u) 3365 { 3366 this.value = cast(ubyte[]) value.dup; 3367 } 3368 else 3369 { 3370 CERElement[] primitives; 3371 size_t i = 0u; 3372 while (i+1000u < value.length) 3373 { 3374 CERElement x = new CERElement(); 3375 x.tagNumber = this.tagNumber; 3376 x.construction = ASN1Construction.primitive; 3377 x.value = cast(ubyte[]) value[i .. i+1000u]; 3378 primitives ~= x; 3379 i += 1000u; 3380 } 3381 3382 CERElement y = new CERElement(); 3383 y.tagNumber = this.tagNumber; 3384 y.construction = ASN1Construction.primitive; 3385 y.value = cast(ubyte[]) value[i .. $]; 3386 primitives ~= y; 3387 3388 this.sequence = primitives; 3389 this.construction = ASN1Construction.constructed; 3390 } 3391 } 3392 3393 @system 3394 unittest 3395 { 3396 void test(size_t length) 3397 { 3398 char[] data; 3399 data.length = length; 3400 for (size_t i = 0u; i < data.length; i++) 3401 { 3402 data[i] = cast(char) ((i % 0x10) + 0x41); 3403 } 3404 CERElement el = new CERElement(); 3405 el.utf8String = cast(string) data; 3406 assert(el.utf8String == cast(string) data); 3407 } 3408 test(0u); 3409 test(1u); 3410 test(8u); 3411 test(127u); 3412 test(128u); 3413 test(129u); 3414 test(192u); 3415 test(999u); 3416 test(1000u); 3417 test(1001u); 3418 test(2017u); 3419 } 3420 3421 /** 3422 Decodes a $(MONO RELATIVE OBJECT IDENTIFIER). 3423 3424 Throws: 3425 $(UL 3426 $(LI $(D ASN1ConstructionException) if the element is marked as "constructed") 3427 $(LI $(D ASN1ValuePaddingException) if a single OID number is encoded with 3428 "leading zero bytes" ($(D 0x80u))) 3429 $(LI $(D ASN1ValueOverflowException) if a single OID number is too big to 3430 decode to a $(D size_t)) 3431 $(LI $(D ASN1TruncationException) if a single OID number is "cut off") 3432 ) 3433 3434 Standards: 3435 $(UL 3436 $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660)) 3437 ) 3438 */ 3439 override public @property @system 3440 OIDNode[] relativeObjectIdentifier() const 3441 { 3442 if (this.construction != ASN1Construction.primitive) 3443 throw new ASN1ConstructionException 3444 (this.construction, "decode an RELATIVE OID"); 3445 3446 if (this.value.length == 0u) return []; 3447 foreach (immutable octet; this.value) 3448 { 3449 if (octet == 0x80u) 3450 throw new ASN1ValuePaddingException 3451 ( 3452 "This exception was thrown because you attempted to decode " ~ 3453 "a RELATIVE OID that contained a number that was " ~ 3454 "encoded on more than the minimum necessary octets. This " ~ 3455 "is indicated by an occurrence of the octet 0x80, which " ~ 3456 "is the encoded equivalent of a leading zero. " ~ 3457 notWhatYouMeantText ~ forMoreInformationText ~ 3458 debugInformationText ~ reportBugsText 3459 ); 3460 } 3461 3462 if (this.value[$-1] > 0x80u) 3463 throw new ASN1TruncationException 3464 (size_t.max, this.value.length, "decode a RELATIVE OID"); 3465 3466 // Breaks bytes into groups, where each group encodes one OID component. 3467 ubyte[][] byteGroups; 3468 size_t lastTerminator = 0u; 3469 for (size_t i = 0u; i < this.length; i++) 3470 { 3471 if (!(this.value[i] & 0x80u)) 3472 { 3473 byteGroups ~= cast(ubyte[]) this.value[lastTerminator .. i+1]; 3474 lastTerminator = i+1; 3475 } 3476 } 3477 3478 // Converts each group of bytes to a number. 3479 size_t[] numbers; 3480 foreach (const byteGroup; byteGroups) 3481 { 3482 if (byteGroup.length > size_t.sizeof) 3483 throw new ASN1ValueOverflowException 3484 ( 3485 "This exception was thrown because you attempted to decode " ~ 3486 "a RELATIVE OID that encoded a number on more than " ~ 3487 "size_t.sizeof bytes. " ~ 3488 notWhatYouMeantText ~ forMoreInformationText ~ 3489 debugInformationText ~ reportBugsText 3490 ); 3491 3492 numbers ~= 0u; 3493 for (size_t i = 0u; i < byteGroup.length; i++) 3494 { 3495 numbers[$-1] <<= 7; 3496 numbers[$-1] |= cast(size_t) (byteGroup[i] & 0x7Fu); 3497 } 3498 } 3499 3500 // Constructs the array of OIDNodes from the array of numbers. 3501 OIDNode[] nodes; 3502 foreach (number; numbers) 3503 { 3504 nodes ~= OIDNode(number); 3505 } 3506 3507 return nodes; 3508 } 3509 3510 /** 3511 Encodes a $(MONO RELATIVE OBJECT IDENTIFIER). 3512 3513 Standards: 3514 $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660) 3515 */ 3516 override public @property @system nothrow 3517 void relativeObjectIdentifier(in OIDNode[] value) 3518 { 3519 scope(success) this.construction = ASN1Construction.primitive; 3520 foreach (node; value) 3521 { 3522 size_t number = node.number; 3523 if (number < 128u) 3524 { 3525 this.value ~= cast(ubyte) number; 3526 continue; 3527 } 3528 3529 ubyte[] encodedOIDNode; 3530 while (number != 0u) 3531 { 3532 ubyte[] numberBytes; 3533 numberBytes.length = size_t.sizeof; 3534 *cast(size_t *) numberBytes.ptr = number; 3535 if ((numberBytes[0] & 0x80u) == 0u) numberBytes[0] |= 0x80u; 3536 encodedOIDNode = numberBytes[0] ~ encodedOIDNode; 3537 number >>= 7u; 3538 } 3539 3540 encodedOIDNode[$-1] &= 0x7Fu; 3541 this.value ~= encodedOIDNode; 3542 } 3543 } 3544 3545 @system 3546 unittest 3547 { 3548 CERElement element = new CERElement(); 3549 3550 // All values of octet[0] should pass. 3551 for (ubyte i = 0x00u; i < 0x80u; i++) 3552 { 3553 element.value = [ i ]; 3554 assertNotThrown!Exception(element.roid); 3555 } 3556 3557 // All values of octet[0] should pass. 3558 for (ubyte i = 0x81u; i < 0xFFu; i++) 3559 { 3560 element.value = [ i, 0x14u ]; 3561 assertNotThrown!Exception(element.roid); 3562 } 3563 } 3564 3565 @system 3566 unittest 3567 { 3568 CERElement element = new CERElement(); 3569 3570 // Tests for the "leading zero byte," 0x80 3571 element.value = [ 0x29u, 0x80u, 0x14u ]; 3572 assertThrown!ASN1ValuePaddingException(element.roid); 3573 element.value = [ 0x29u, 0x80u, 0x80u ]; 3574 assertThrown!ASN1ValuePaddingException(element.roid); 3575 element.value = [ 0x80u, 0x80u, 0x80u ]; 3576 assertThrown!ASN1ValuePaddingException(element.roid); 3577 element.value = [ 0x29u, 0x14u, 0x80u ]; 3578 assertThrown!ASN1ValuePaddingException(element.roid); 3579 element.value = [ 0x29u, 0x80u ]; 3580 assertThrown!ASN1ValuePaddingException(element.roid); 3581 3582 // Test for non-terminating components 3583 element.value = [ 0x29u, 0x81u ]; 3584 assertThrown!ASN1TruncationException(element.roid); 3585 element.value = [ 0x29u, 0x14u, 0x81u ]; 3586 assertThrown!ASN1TruncationException(element.roid); 3587 } 3588 3589 /** 3590 Decodes a sequence of elements 3591 3592 Throws: 3593 $(UL 3594 $(LI $(D ASN1ConstructionException) if the element is marked as "primitive") 3595 $(LI And all of the exceptions thrown by the constructor) 3596 ) 3597 */ 3598 override public @property @system 3599 CERElement[] sequence() const 3600 { 3601 if (this.construction != ASN1Construction.constructed) 3602 throw new ASN1ConstructionException 3603 (this.construction, "decode a SEQUENCE"); 3604 3605 ubyte[] data = this.value.dup; 3606 CERElement[] result; 3607 while (data.length > 0u) 3608 result ~= new CERElement(data); 3609 return result; 3610 } 3611 3612 /// Encodes a sequence of elements 3613 override public @property @system 3614 void sequence(in CERElement[] value) 3615 { 3616 scope(success) this.construction = ASN1Construction.constructed; 3617 ubyte[] result; 3618 foreach (cv; value) 3619 { 3620 result ~= cv.toBytes; 3621 } 3622 this.value = result; 3623 } 3624 3625 /** 3626 Decodes a set of elements 3627 3628 Throws: 3629 $(UL 3630 $(LI $(D ASN1ConstructionException) if the element is marked as "primitive") 3631 $(LI And all of the exceptions thrown by the constructor) 3632 ) 3633 */ 3634 override public @property @system 3635 CERElement[] set() const 3636 { 3637 if (this.construction != ASN1Construction.constructed) 3638 throw new ASN1ConstructionException 3639 (this.construction, "decode a SET"); 3640 3641 ubyte[] data = this.value.dup; 3642 CERElement[] result; 3643 while (data.length > 0u) 3644 result ~= new CERElement(data); 3645 return result; 3646 } 3647 3648 /// Encodes a set of elements 3649 override public @property @system 3650 void set(in CERElement[] value) 3651 { 3652 scope(success) this.construction = ASN1Construction.constructed; 3653 ubyte[] result; 3654 foreach (cv; value) 3655 { 3656 result ~= cv.toBytes; 3657 } 3658 this.value = result; 3659 } 3660 3661 /** 3662 Decodes a string, where the characters of the string are limited to 3663 0 - 9 and $(MONO SPACE). 3664 3665 Throws: 3666 $(UL 3667 $(LI $(D ASN1ValueCharactersException) if any character other than 0-9 or space is encoded.) 3668 $(LI $(D ASN1RecursionException) 3669 if using constructed form and the element 3670 is constructed of too many nested constructed elements) 3671 $(LI $(D ASN1TagClassException) 3672 if any nested primitives do not share the 3673 same tag class as their outer constructed element) 3674 $(LI $(D ASN1TagNumberException) 3675 if any nested primitives do not share the 3676 same tag number as their outer constructed element) 3677 ) 3678 */ 3679 override public @property @system 3680 string numericString() const 3681 { 3682 if (this.construction == ASN1Construction.primitive) 3683 { 3684 if (this.value.length > 1000u) 3685 throw new ASN1ValueSizeException 3686 (0u, 1000u, this.value.length, "decode a primitively-encoded NumericString"); 3687 3688 foreach (immutable character; this.value) 3689 { 3690 if (!canFind(numericStringCharacters, character)) 3691 throw new ASN1ValueCharactersException 3692 ("1234567890 ", character, "NumericString"); 3693 } 3694 return cast(string) this.value; 3695 } 3696 else 3697 { 3698 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 3699 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a NumericString"); 3700 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 3701 3702 CERElement[] substrings = this.sequence; 3703 if (substrings.length < 2u) 3704 throw new ASN1ValueException 3705 ( 3706 "This exception was thrown because you attempted to decode " ~ 3707 "a constructed NumericString that was constituted by fewer " ~ 3708 "than two nested elements. When using the Canonical Encoding " ~ 3709 "Rules (CER), a legitimate constructed NumericString must " ~ 3710 "contain at least two nested elements, and each but the " ~ 3711 "last must contain exactly 1000 content octets. The last " ~ 3712 "nested element may have between 1 and 1000 content " ~ 3713 "octets inclusively. This element should have been encoded " ~ 3714 "primitively using definite-length encoding." 3715 ); 3716 3717 foreach (substring; substrings[0 .. $-1]) 3718 { 3719 if (substring.length != 1000u) 3720 throw new ASN1ValueSizeException 3721 (1000u, 1000u, substring.length, "decode a substring of a constructed NumericString"); 3722 } 3723 3724 if (substrings[$-1].length == 0u) 3725 throw new ASN1ValueSizeException 3726 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed NumericString"); 3727 3728 Appender!string appendy = appender!string(); 3729 foreach (substring; substrings) 3730 { 3731 if (substring.tagClass != this.tagClass) 3732 throw new ASN1TagClassException 3733 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed NumericString"); 3734 3735 if (substring.construction != ASN1Construction.primitive) 3736 throw new ASN1ConstructionException 3737 (substring.construction, "decode a substring of a constructed NumericString"); 3738 3739 if (substring.tagNumber != this.tagNumber) 3740 throw new ASN1TagNumberException 3741 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed NumericString"); 3742 3743 appendy.put(substring.numericString); 3744 } 3745 return appendy.data; 3746 } 3747 } 3748 3749 /** 3750 Encodes a string, where the characters of the string are limited to 3751 0 - 9 and space. 3752 3753 Throws: 3754 $(UL 3755 $(LI $(D ASN1ValueCharactersException) if any character other than 0-9 or space is supplied.) 3756 ) 3757 */ 3758 override public @property @system 3759 void numericString(in string value) 3760 { 3761 foreach (immutable character; value) 3762 { 3763 if (!canFind(numericStringCharacters, character)) 3764 throw new ASN1ValueCharactersException 3765 ("1234567890 ", character, "NumericString"); 3766 } 3767 3768 if (value.length <= 1000u) 3769 { 3770 this.value = cast(ubyte[]) value.dup; 3771 } 3772 else 3773 { 3774 CERElement[] primitives; 3775 size_t i = 0u; 3776 while (i+1000u < value.length) 3777 { 3778 CERElement x = new CERElement(); 3779 x.tagNumber = this.tagNumber; 3780 x.construction = ASN1Construction.primitive; 3781 x.value = cast(ubyte[]) value[i .. i+1000u]; 3782 primitives ~= x; 3783 i += 1000u; 3784 } 3785 3786 CERElement y = new CERElement(); 3787 y.tagNumber = this.tagNumber; 3788 y.construction = ASN1Construction.primitive; 3789 y.value = cast(ubyte[]) value[i .. $]; 3790 primitives ~= y; 3791 3792 this.sequence = primitives; 3793 this.construction = ASN1Construction.constructed; 3794 } 3795 } 3796 3797 @system 3798 unittest 3799 { 3800 void test(size_t length) 3801 { 3802 char[] data; 3803 data.length = length; 3804 for (size_t i = 0u; i < data.length; i++) 3805 { 3806 data[i] = cast(char) ((i % 0x09) + 0x30); 3807 } 3808 CERElement el = new CERElement(); 3809 el.numericString = cast(string) data; 3810 assert(el.numericString == cast(string) data); 3811 } 3812 test(0u); 3813 test(1u); 3814 test(8u); 3815 test(127u); 3816 test(128u); 3817 test(129u); 3818 test(192u); 3819 test(999u); 3820 test(1000u); 3821 test(1001u); 3822 test(2017u); 3823 } 3824 3825 /** 3826 Decodes a string that will only contain characters a-z, A-Z, 0-9, 3827 space, apostrophe, parentheses, comma, minus, plus, period, 3828 forward slash, colon, equals, and question mark. 3829 3830 Throws: 3831 $(UL 3832 $(LI $(D ASN1ValueCharactersException) 3833 if any character other than a-z, A-Z, 3834 0-9, space, apostrophe, parentheses, comma, minus, plus, 3835 period, forward slash, colon, equals, or question mark are 3836 encoded) 3837 $(LI $(D ASN1RecursionException) 3838 if using constructed form and the element 3839 is constructed of too many nested constructed elements) 3840 $(LI $(D ASN1TagClassException) 3841 if any nested primitives do not share the 3842 same tag class as their outer constructed element) 3843 $(LI $(D ASN1TagNumberException) 3844 if any nested primitives do not share the 3845 same tag number as their outer constructed element) 3846 ) 3847 */ 3848 override public @property @system 3849 string printableString() const 3850 { 3851 if (this.construction == ASN1Construction.primitive) 3852 { 3853 if (this.value.length > 1000u) 3854 throw new ASN1ValueSizeException 3855 (0u, 1000u, this.value.length, "decode a primitively-encoded PrintableString"); 3856 3857 foreach (immutable character; this.value) 3858 { 3859 if (!canFind(printableStringCharacters, character)) 3860 throw new ASN1ValueCharactersException 3861 (printableStringCharacters, character, "PrintableString"); 3862 } 3863 return cast(string) this.value; 3864 } 3865 else 3866 { 3867 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 3868 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a PrintableString"); 3869 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 3870 3871 CERElement[] substrings = this.sequence; 3872 if (substrings.length < 2u) 3873 throw new ASN1ValueException 3874 ( 3875 "This exception was thrown because you attempted to decode " ~ 3876 "a constructed PrintableString that was constituted by fewer " ~ 3877 "than two nested elements. When using the Canonical Encoding " ~ 3878 "Rules (CER), a legitimate constructed PrintableString must " ~ 3879 "contain at least two nested elements, and each but the " ~ 3880 "last must contain exactly 1000 content octets. The last " ~ 3881 "nested element may have between 1 and 1000 content " ~ 3882 "octets inclusively. This element should have been encoded " ~ 3883 "primitively using definite-length encoding." 3884 ); 3885 3886 foreach (substring; substrings[0 .. $-1]) 3887 { 3888 if (substring.length != 1000u) 3889 throw new ASN1ValueSizeException 3890 (1000u, 1000u, substring.length, "decode a substring of a constructed PrintableString"); 3891 } 3892 3893 if (substrings[$-1].length == 0u) 3894 throw new ASN1ValueSizeException 3895 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed PrintableString"); 3896 3897 Appender!string appendy = appender!string(); 3898 foreach (substring; substrings) 3899 { 3900 if (substring.tagClass != this.tagClass) 3901 throw new ASN1TagClassException 3902 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed PrintableString"); 3903 3904 if (substring.construction != ASN1Construction.primitive) 3905 throw new ASN1ConstructionException 3906 (substring.construction, "decode a substring of a constructed PrintableString"); 3907 3908 if (substring.tagNumber != this.tagNumber) 3909 throw new ASN1TagNumberException 3910 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed PrintableString"); 3911 3912 appendy.put(substring.printableString); 3913 } 3914 return appendy.data; 3915 } 3916 } 3917 3918 /** 3919 Encodes a string that may only contain characters a-z, A-Z, 0-9, 3920 space, apostrophe, parentheses, comma, minus, plus, period, 3921 forward slash, colon, equals, and question mark. 3922 3923 Throws: 3924 $(LI $(D ASN1ValueCharactersException) if any character other than a-z, A-Z, 3925 0-9, space, apostrophe, parentheses, comma, minus, plus, 3926 period, forward slash, colon, equals, or question mark are 3927 supplied) 3928 */ 3929 override public @property @system 3930 void printableString(in string value) 3931 { 3932 foreach (immutable character; value) 3933 { 3934 if (!canFind(printableStringCharacters, character)) 3935 throw new ASN1ValueCharactersException 3936 (printableStringCharacters, character, "PrintableString"); 3937 } 3938 3939 if (value.length <= 1000u) 3940 { 3941 this.value = cast(ubyte[]) value.dup; 3942 } 3943 else 3944 { 3945 CERElement[] primitives; 3946 size_t i = 0u; 3947 while (i+1000u < value.length) 3948 { 3949 CERElement x = new CERElement(); 3950 x.tagNumber = this.tagNumber; 3951 x.construction = ASN1Construction.primitive; 3952 x.value = cast(ubyte[]) value[i .. i+1000u]; 3953 primitives ~= x; 3954 i += 1000u; 3955 } 3956 3957 CERElement y = new CERElement(); 3958 y.tagNumber = this.tagNumber; 3959 y.construction = ASN1Construction.primitive; 3960 y.value = cast(ubyte[]) value[i .. $]; 3961 primitives ~= y; 3962 3963 this.sequence = primitives; 3964 this.construction = ASN1Construction.constructed; 3965 } 3966 } 3967 3968 @system 3969 unittest 3970 { 3971 void test(size_t length) 3972 { 3973 char[] data; 3974 data.length = length; 3975 for (size_t i = 0u; i < data.length; i++) 3976 { 3977 data[i] = cast(char) ((i % 0x10) + 0x41); 3978 } 3979 CERElement el = new CERElement(); 3980 el.printableString = cast(string) data; 3981 assert(el.printableString == cast(string) data); 3982 } 3983 test(0u); 3984 test(1u); 3985 test(8u); 3986 test(127u); 3987 test(128u); 3988 test(129u); 3989 test(192u); 3990 test(999u); 3991 test(1000u); 3992 test(1001u); 3993 test(2017u); 3994 } 3995 3996 /** 3997 Literally just returns the value bytes. 3998 3999 Returns: an unsigned byte array, where each byte is a T.61 character. 4000 4001 Throws: 4002 $(UL 4003 $(LI $(D ASN1RecursionException) 4004 if using constructed form and the element 4005 is constructed of too many nested constructed elements) 4006 $(LI $(D ASN1TagClassException) 4007 if any nested primitives do not share the 4008 same tag class as their outer constructed element) 4009 $(LI $(D ASN1TagNumberException) 4010 if any nested primitives do not share the 4011 same tag number as their outer constructed element) 4012 ) 4013 */ 4014 override public @property @system 4015 ubyte[] teletexString() const 4016 { 4017 if (this.construction == ASN1Construction.primitive) 4018 { 4019 if (this.value.length > 1000u) 4020 throw new ASN1ValueSizeException 4021 (0u, 1000u, this.value.length, "decode a primitively-encoded TeletexString"); 4022 4023 return this.value.dup; 4024 } 4025 else 4026 { 4027 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 4028 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a TeletexString"); 4029 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 4030 4031 CERElement[] substrings = this.sequence; 4032 if (substrings.length < 2u) 4033 throw new ASN1ValueException 4034 ( 4035 "This exception was thrown because you attempted to decode " ~ 4036 "a constructed TeletexString that was constituted by fewer " ~ 4037 "than two nested elements. When using the Canonical Encoding " ~ 4038 "Rules (CER), a legitimate constructed TeletexString must " ~ 4039 "contain at least two nested elements, and each but the " ~ 4040 "last must contain exactly 1000 content octets. The last " ~ 4041 "nested element may have between 1 and 1000 content " ~ 4042 "octets inclusively. This element should have been encoded " ~ 4043 "primitively using definite-length encoding." 4044 ); 4045 4046 foreach (substring; substrings[0 .. $-1]) 4047 { 4048 if (substring.length != 1000u) 4049 throw new ASN1ValueSizeException 4050 (1000u, 1000u, substring.length, "decode a substring of a constructed TeletexString"); 4051 } 4052 4053 if (substrings[$-1].length == 0u) 4054 throw new ASN1ValueSizeException 4055 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed TeletexString"); 4056 4057 Appender!(ubyte[]) appendy = appender!(ubyte[])(); 4058 foreach (substring; substrings) 4059 { 4060 if (substring.tagClass != this.tagClass) 4061 throw new ASN1TagClassException 4062 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed TeletexString"); 4063 4064 if (substring.construction != ASN1Construction.primitive) 4065 throw new ASN1ConstructionException 4066 (substring.construction, "decode a substring of a constructed TeletexString"); 4067 4068 if (substring.tagNumber != this.tagNumber) 4069 throw new ASN1TagNumberException 4070 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed TeletexString"); 4071 4072 appendy.put(substring.teletexString); 4073 } 4074 return appendy.data; 4075 } 4076 } 4077 4078 /// Literally just sets the value bytes. 4079 override public @property @system 4080 void teletexString(in ubyte[] value) 4081 { 4082 if (value.length <= 1000u) 4083 { 4084 this.value = value.dup; 4085 } 4086 else 4087 { 4088 CERElement[] primitives; 4089 size_t i = 0u; 4090 while (i+1000u < value.length) 4091 { 4092 CERElement x = new CERElement(); 4093 x.tagNumber = this.tagNumber; 4094 x.construction = ASN1Construction.primitive; 4095 x.value = value[i .. i+1000u].dup; 4096 primitives ~= x; 4097 i += 1000u; 4098 } 4099 4100 CERElement y = new CERElement(); 4101 y.tagNumber = this.tagNumber; 4102 y.construction = ASN1Construction.primitive; 4103 y.value = value[i .. $].dup; 4104 primitives ~= y; 4105 4106 this.sequence = primitives; 4107 this.construction = ASN1Construction.constructed; 4108 } 4109 } 4110 4111 @system 4112 unittest 4113 { 4114 void test(size_t length) 4115 { 4116 ubyte[] data; 4117 data.length = length; 4118 for (size_t i = 0u; i < data.length; i++) 4119 { 4120 data[i] = (i % 9u); 4121 } 4122 CERElement el = new CERElement(); 4123 el.teletexString = data; 4124 assert(el.teletexString == data); 4125 } 4126 test(0u); 4127 test(1u); 4128 test(8u); 4129 test(127u); 4130 test(128u); 4131 test(129u); 4132 test(192u); 4133 test(999u); 4134 test(1000u); 4135 test(1001u); 4136 test(2017u); 4137 } 4138 4139 /** 4140 Literally just returns the value bytes. 4141 4142 Returns: an unsigned byte array, where each byte is a Videotex character. 4143 4144 Throws: 4145 $(UL 4146 $(LI $(D ASN1RecursionException) 4147 if using constructed form and the element 4148 is constructed of too many nested constructed elements) 4149 $(LI $(D ASN1TagClassException) 4150 if any nested primitives do not share the 4151 same tag class as their outer constructed element) 4152 $(LI $(D ASN1TagNumberException) 4153 if any nested primitives do not share the 4154 same tag number as their outer constructed element) 4155 ) 4156 */ 4157 override public @property @system 4158 ubyte[] videotexString() const 4159 { 4160 if (this.construction == ASN1Construction.primitive) 4161 { 4162 if (this.value.length > 1000u) 4163 throw new ASN1ValueSizeException 4164 (0u, 1000u, this.value.length, "decode a primitively-encoded VideotexString"); 4165 4166 return this.value.dup; 4167 } 4168 else 4169 { 4170 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 4171 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a VideotexString"); 4172 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 4173 4174 CERElement[] substrings = this.sequence; 4175 if (substrings.length < 2u) 4176 throw new ASN1ValueException 4177 ( 4178 "This exception was thrown because you attempted to decode " ~ 4179 "a constructed VideotexString that was constituted by fewer " ~ 4180 "than two nested elements. When using the Canonical Encoding " ~ 4181 "Rules (CER), a legitimate constructed VideotexString must " ~ 4182 "contain at least two nested elements, and each but the " ~ 4183 "last must contain exactly 1000 content octets. The last " ~ 4184 "nested element may have between 1 and 1000 content " ~ 4185 "octets inclusively. This element should have been encoded " ~ 4186 "primitively using definite-length encoding." 4187 ); 4188 4189 foreach (substring; substrings[0 .. $-1]) 4190 { 4191 if (substring.length != 1000u) 4192 throw new ASN1ValueSizeException 4193 (1000u, 1000u, substring.length, "decode a substring of a constructed VideotexString"); 4194 } 4195 4196 if (substrings[$-1].length == 0u) 4197 throw new ASN1ValueSizeException 4198 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed VideotexString"); 4199 4200 Appender!(ubyte[]) appendy = appender!(ubyte[])(); 4201 foreach (substring; substrings) 4202 { 4203 if (substring.tagClass != this.tagClass) 4204 throw new ASN1TagClassException 4205 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed VideotexString"); 4206 4207 if (substring.construction != ASN1Construction.primitive) 4208 throw new ASN1ConstructionException 4209 (substring.construction, "decode a substring of a constructed VideotexString"); 4210 4211 if (substring.tagNumber != this.tagNumber) 4212 throw new ASN1TagNumberException 4213 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed VideotexString"); 4214 4215 appendy.put(substring.videotexString); 4216 } 4217 return appendy.data; 4218 } 4219 } 4220 4221 /// Literally just sets the value bytes. 4222 override public @property @system 4223 void videotexString(in ubyte[] value) 4224 { 4225 if (value.length <= 1000u) 4226 { 4227 this.value = value.dup; 4228 } 4229 else 4230 { 4231 CERElement[] primitives; 4232 size_t i = 0u; 4233 while (i+1000u < value.length) 4234 { 4235 CERElement x = new CERElement(); 4236 x.tagNumber = this.tagNumber; 4237 x.construction = ASN1Construction.primitive; 4238 x.value = value[i .. i+1000u].dup; 4239 primitives ~= x; 4240 i += 1000u; 4241 } 4242 4243 CERElement y = new CERElement(); 4244 y.tagNumber = this.tagNumber; 4245 y.construction = ASN1Construction.primitive; 4246 y.value = value[i .. $].dup; 4247 primitives ~= y; 4248 4249 this.sequence = primitives; 4250 this.construction = ASN1Construction.constructed; 4251 } 4252 } 4253 4254 @system 4255 unittest 4256 { 4257 void test(size_t length) 4258 { 4259 ubyte[] data; 4260 data.length = length; 4261 for (size_t i = 0u; i < data.length; i++) 4262 { 4263 data[i] = (i % 9u); 4264 } 4265 CERElement el = new CERElement(); 4266 el.videotexString = data; 4267 assert(el.videotexString == data); 4268 } 4269 test(0u); 4270 test(1u); 4271 test(8u); 4272 test(127u); 4273 test(128u); 4274 test(129u); 4275 test(192u); 4276 test(999u); 4277 test(1000u); 4278 test(1001u); 4279 test(2017u); 4280 } 4281 4282 /** 4283 Decodes a string that only contains ASCII characters. 4284 4285 $(MONO IA5String) differs from ASCII ever so slightly: IA5 is international, 4286 leaving 10 characters up to be locale-specific: 4287 4288 $(TABLE 4289 $(TR $(TH Byte) $(TH ASCII Character)) 4290 $(TR $(TD 0x40) $(TD @)) 4291 $(TR $(TD 0x5B) $(TD [)) 4292 $(TR $(TD 0x5C) $(TD \)) 4293 $(TR $(TD 0x5D) $(TD ])) 4294 $(TR $(TD 0x5E) $(TD ^)) 4295 $(TR $(TD 0x60) $(TD `)) 4296 $(TR $(TD 0x7B) $(TD {)) 4297 $(TR $(TD 0x7C) $(TD /)) 4298 $(TR $(TD 0x7D) $(TD })) 4299 $(TR $(TD 0x7E) $(TD ~)) 4300 ) 4301 4302 Throws: 4303 $(UL 4304 $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII) 4305 $(LI $(D ASN1RecursionException) 4306 if using constructed form and the element 4307 is constructed of too many nested constructed elements) 4308 $(LI $(D ASN1TagClassException) 4309 if any nested primitives do not share the 4310 same tag class as their outer constructed element) 4311 $(LI $(D ASN1TagNumberException) 4312 if any nested primitives do not share the 4313 same tag number as their outer constructed element) 4314 ) 4315 */ 4316 override public @property @system 4317 string internationalAlphabetNumber5String() const 4318 { 4319 if (this.construction == ASN1Construction.primitive) 4320 { 4321 if (this.value.length > 1000u) 4322 throw new ASN1ValueSizeException 4323 (0u, 1000u, this.value.length, "decode a primitively-encoded IA5String"); 4324 4325 foreach (immutable character; this.value) 4326 { 4327 if (!character.isASCII) 4328 throw new ASN1ValueCharactersException 4329 ("all ASCII characters", character, "IA5String"); 4330 } 4331 return cast(string) this.value; 4332 } 4333 else 4334 { 4335 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 4336 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a IA5String"); 4337 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 4338 4339 CERElement[] substrings = this.sequence; 4340 if (substrings.length < 2u) 4341 throw new ASN1ValueException 4342 ( 4343 "This exception was thrown because you attempted to decode " ~ 4344 "a constructed IA5String that was constituted by fewer " ~ 4345 "than two nested elements. When using the Canonical Encoding " ~ 4346 "Rules (CER), a legitimate constructed IA5String must " ~ 4347 "contain at least two nested elements, and each but the " ~ 4348 "last must contain exactly 1000 content octets. The last " ~ 4349 "nested element may have between 1 and 1000 content " ~ 4350 "octets inclusively. This element should have been encoded " ~ 4351 "primitively using definite-length encoding." 4352 ); 4353 4354 foreach (substring; substrings[0 .. $-1]) 4355 { 4356 if (substring.length != 1000u) 4357 throw new ASN1ValueSizeException 4358 (1000u, 1000u, substring.length, "decode a substring of a constructed IA5String"); 4359 } 4360 4361 if (substrings[$-1].length == 0u) 4362 throw new ASN1ValueSizeException 4363 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed IA5String"); 4364 4365 Appender!string appendy = appender!string(); 4366 foreach (substring; substrings) 4367 { 4368 if (substring.tagClass != this.tagClass) 4369 throw new ASN1TagClassException 4370 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed IA5String"); 4371 4372 if (substring.construction != ASN1Construction.primitive) 4373 throw new ASN1ConstructionException 4374 (substring.construction, "decode a substring of a constructed IA5String"); 4375 4376 if (substring.tagNumber != this.tagNumber) 4377 throw new ASN1TagNumberException 4378 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed IA5String"); 4379 4380 appendy.put(substring.ia5String); 4381 } 4382 return appendy.data; 4383 } 4384 } 4385 4386 /** 4387 Encodes a string that may only contain ASCII characters. 4388 4389 $(MONO IA5String) differs from ASCII ever so slightly: IA5 is international, 4390 leaving 10 characters up to be locale-specific: 4391 4392 $(TABLE 4393 $(TR $(TH Byte) $(TH ASCII Character)) 4394 $(TR $(TD 0x40) $(TD @)) 4395 $(TR $(TD 0x5B) $(TD [)) 4396 $(TR $(TD 0x5C) $(TD \)) 4397 $(TR $(TD 0x5D) $(TD ])) 4398 $(TR $(TD 0x5E) $(TD ^)) 4399 $(TR $(TD 0x60) $(TD `)) 4400 $(TR $(TD 0x7B) $(TD {)) 4401 $(TR $(TD 0x7C) $(TD /)) 4402 $(TR $(TD 0x7D) $(TD })) 4403 $(TR $(TD 0x7E) $(TD ~)) 4404 ) 4405 4406 Throws: 4407 $(UL 4408 $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII) 4409 ) 4410 */ 4411 override public @property @system 4412 void internationalAlphabetNumber5String(in string value) 4413 { 4414 foreach (immutable character; value) 4415 { 4416 if (!character.isASCII) 4417 throw new ASN1ValueCharactersException 4418 ("all ASCII characters", character, "IA5String"); 4419 } 4420 4421 if (value.length <= 1000u) 4422 { 4423 this.value = cast(ubyte[]) value.dup; 4424 } 4425 else 4426 { 4427 CERElement[] primitives; 4428 size_t i = 0u; 4429 while (i+1000u < value.length) 4430 { 4431 CERElement x = new CERElement(); 4432 x.tagNumber = this.tagNumber; 4433 x.construction = ASN1Construction.primitive; 4434 x.value = cast(ubyte[]) value[i .. i+1000u]; 4435 primitives ~= x; 4436 i += 1000u; 4437 } 4438 4439 CERElement y = new CERElement(); 4440 y.tagNumber = this.tagNumber; 4441 y.construction = ASN1Construction.primitive; 4442 y.value = cast(ubyte[]) value[i .. $]; 4443 primitives ~= y; 4444 4445 this.sequence = primitives; 4446 this.construction = ASN1Construction.constructed; 4447 } 4448 } 4449 4450 @system 4451 unittest 4452 { 4453 void test(size_t length) 4454 { 4455 char[] data; 4456 data.length = length; 4457 for (size_t i = 0u; i < data.length; i++) 4458 { 4459 data[i] = cast(char) ((i % 0x40) + 0x30); 4460 } 4461 CERElement el = new CERElement(); 4462 el.ia5String = cast(string) data; 4463 assert(el.ia5String == cast(string) data); 4464 } 4465 test(0u); 4466 test(1u); 4467 test(8u); 4468 test(127u); 4469 test(128u); 4470 test(129u); 4471 test(192u); 4472 test(999u); 4473 test(1000u); 4474 test(1001u); 4475 test(2017u); 4476 } 4477 4478 /** 4479 Decodes a $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime). 4480 The value is just the ASCII character representation of the UTC-formatted timestamp. 4481 4482 An UTC Timestamp looks like: 4483 $(UL 4484 $(LI $(MONO 9912312359Z)) 4485 $(LI $(MONO 991231235959+0200)) 4486 ) 4487 4488 If the first digit of the two-digit year is 7, 6, 5, 4, 3, 2, 1, or 0, 4489 meaning that the date refers to the first 80 years of the century, this 4490 assumes we are talking about the 21st century and prepend '20' when 4491 creating the ISO Date String. Otherwise, it assumes we are talking 4492 about the 20th century, and prepend '19' when creating the string. 4493 4494 See_Also: 4495 $(UL 4496 $(LI $(LINK https://www.obj-sys.com/asn1tutorial/node15.html, UTCTime)) 4497 $(LI $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime)) 4498 ) 4499 4500 Throws: 4501 $(UL 4502 $(LI $(D ASN1ValueException) if the encoded value does not end with a 'Z') 4503 $(LI $(D ASN1ValueCharactersException) if any character is not valid in a $(MONO Visiblestring)) 4504 $(LI $(D DateTimeException) if the encoded string cannot be decoded to a DateTime) 4505 $(LI $(D ASN1RecursionException) 4506 if using constructed form and the element 4507 is constructed of too many nested constructed elements) 4508 $(LI $(D ASN1TagClassException) 4509 if any nested primitives do not share the 4510 same tag class as their outer constructed element) 4511 $(LI $(D ASN1TagNumberException) 4512 if any nested primitives do not share the 4513 same tag number as their outer constructed element) 4514 ) 4515 */ 4516 override public @property @system 4517 DateTime coordinatedUniversalTime() const 4518 { 4519 if (this.construction != ASN1Construction.primitive) 4520 throw new ASN1ConstructionException(this.construction, "decode a UTCTime"); 4521 4522 // Mandated in X.690, section 11.8.2 4523 if (this.value.length != 13u) // YYMMDDhhmmssZ 4524 throw new ASN1ValueSizeException(13u, 13u, this.value.length, "decode a UTCTime"); 4525 4526 // Mandated in X.690, section 11.8.1 4527 if (this.value[$-1] != 'Z') 4528 throw new ASN1ValueException 4529 ( 4530 "This exception was thrown because you attempted to decode " ~ 4531 "a UTCTime whose encoding did not terminate with a 'Z', as " ~ 4532 "is expected by the Canonical Encoding Rules (CER) and " ~ 4533 "Distinguished Encoding Rules (DER). " ~ 4534 notWhatYouMeantText ~ forMoreInformationText ~ 4535 debugInformationText ~ reportBugsText 4536 ); 4537 4538 /** NOTE: 4539 .fromISOString() MUST be called from SysTime, not DateTime. There 4540 is a subtle difference in how .fromISOString() works in both SysTime 4541 and DateTime: SysTime's accepts the "Z" at the end (indicating that 4542 the time is in GMT). 4543 4544 If you use DateTime.fromISOString, you will get a DateTimeException 4545 whose cryptic message reads "Invalid ISO String: " followed, 4546 strangely, by only the last six characters of the string. 4547 */ 4548 immutable string dt = (((this.value[0] <= '7') ? "20" : "19") ~ cast(string) this.value); 4549 return cast(DateTime) SysTime.fromISOString(dt[0 .. 8].idup ~ "T" ~ dt[8 .. $].idup); 4550 } 4551 4552 /** 4553 Encodes a DateTime. The value is just the ASCII character representation of 4554 the UTC-formatted timestamp. 4555 4556 An UTC Timestamp looks like: 4557 $(UL 4558 $(LI $(MONO 9912312359Z)) 4559 $(LI $(MONO 991231235959+0200)) 4560 ) 4561 4562 See_Also: 4563 $(LINK https://www.obj-sys.com/asn1tutorial/node15.html, UTCTime) 4564 */ 4565 override public @property @system 4566 void coordinatedUniversalTime(in DateTime value) 4567 out 4568 { 4569 // YYMMDDhhmmssZ is the only acceptable format for CER 4570 assert(this.value.length == 13u); 4571 assert(this.value[$-1] == 'Z'); 4572 } 4573 body 4574 { 4575 scope(success) this.construction = ASN1Construction.primitive; 4576 immutable SysTime st = SysTime(value, UTC()); 4577 this.value = cast(ubyte[]) ((st.toUTC()).toISOString()[2 .. $].replace("T", "")); 4578 } 4579 4580 @system 4581 unittest 4582 { 4583 CERElement el = new CERElement(); 4584 el.utcTime = DateTime(2017, 10, 3); 4585 assert(el.visibleString == "171003000000Z"); 4586 } 4587 4588 /** 4589 Decodes a DateTime. The value is just the ASCII character representation of 4590 the $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601)-formatted timestamp. 4591 4592 An ISO-8601 Timestamp looks like: 4593 $(UL 4594 $(LI $(MONO 19851106210627.3)) 4595 $(LI $(MONO 19851106210627.3Z)) 4596 $(LI $(MONO 19851106210627.3-0500)) 4597 ) 4598 4599 Throws: 4600 $(UL 4601 $(LI $(D ASN1ValueException) if the encoded value did not end with a 'Z' 4602 or contained a misplaced or unnecessary decimal point) 4603 $(LI $(D ASN1ValueCharactersException) if any character is not valid in a $(MONO Visiblestring)) 4604 $(LI $(D DateTimeException) if the encoded string cannot be decoded to a DateTime) 4605 $(LI $(D ASN1ValuePaddingException) if the seconds fraction contains trailing zeroes) 4606 $(LI $(D ASN1RecursionException) 4607 if using constructed form and the element 4608 is constructed of too many nested constructed elements) 4609 $(LI $(D ASN1TagClassException) 4610 if any nested primitives do not share the 4611 same tag class as their outer constructed element) 4612 $(LI $(D ASN1TagNumberException) 4613 if any nested primitives do not share the 4614 same tag number as their outer constructed element) 4615 ) 4616 4617 Standards: 4618 $(UL 4619 $(LI $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601)) 4620 ) 4621 */ 4622 override public @property @system 4623 DateTime generalizedTime() const 4624 { 4625 if (this.construction != ASN1Construction.primitive) 4626 throw new ASN1ConstructionException(this.construction, "decode a GeneralizedTime"); 4627 4628 if (this.value.length < 15u || this.value.length > 1000u) 4629 throw new ASN1ValueSizeException(15u, 1000u, this.value.length, "decode a GeneralizedTime"); 4630 4631 // Inferred, because YYYYMMDDhhmmss.Z could not be valid. 4632 if (this.value.length == 16u) 4633 throw new ASN1ValueException 4634 ( 4635 "This exception was thrown because you attempted to decode a " ~ 4636 "GeneralizedTime that was encoded on too few bytes to be " ~ 4637 "correct. A valid GeneralizedTime cannot be 16 bytes. " ~ 4638 notWhatYouMeantText ~ forMoreInformationText ~ 4639 debugInformationText ~ reportBugsText 4640 ); 4641 4642 // Mandated in X.690, section 11.7.1 4643 if (this.value[$-1] != 'Z') 4644 throw new ASN1ValueException 4645 ( 4646 "This exception was thrown because you attempted to decode " ~ 4647 "a GeneralizedTime whose encoding did not terminate with a 'Z', " ~ 4648 "as is expected by the Canonical Encoding Rules (CER) and " ~ 4649 "Distinguished Encoding Rules (DER). " ~ 4650 notWhatYouMeantText ~ forMoreInformationText ~ 4651 debugInformationText ~ reportBugsText 4652 ); 4653 4654 ptrdiff_t indexOfDecimalPoint = (cast(string) this.value).indexOf('.'); 4655 if (indexOfDecimalPoint != -1) 4656 { 4657 if (this.value[$-2] == '0') 4658 throw new ASN1ValuePaddingException 4659 ( 4660 "This exception was thrown because you attempted to decode " ~ 4661 "a GeneralizedTime that contained trailing zeroes in the " ~ 4662 "fraction-of-seconds part. " ~ 4663 notWhatYouMeantText ~ forMoreInformationText ~ 4664 debugInformationText ~ reportBugsText 4665 ); 4666 4667 if (indexOfDecimalPoint != 14) 4668 throw new ASN1ValueException 4669 ( 4670 "This exception was thrown because you attempted to decode " ~ 4671 "a GeneralizedTime whose decimal point was misplaced. " ~ 4672 notWhatYouMeantText ~ forMoreInformationText ~ 4673 debugInformationText ~ reportBugsText 4674 ); 4675 } 4676 else 4677 { 4678 if ((cast(string) this.value).indexOf(',') != -1) 4679 throw new ASN1ValueCharactersException 4680 ("1234567890Z.", ',', "decode a GeneralizedTime"); 4681 } 4682 4683 /** NOTE: 4684 .fromISOString() MUST be called from SysTime, not DateTime. There 4685 is a subtle difference in how .fromISOString() works in both SysTime 4686 and DateTime: SysTime's accepts the "Z" at the end (indicating that 4687 the time is in GMT). 4688 4689 If you use DateTime.fromISOString, you will get a DateTimeException 4690 whose cryptic message reads "Invalid ISO String: " followed, 4691 strangely, by only the last six characters of the string. 4692 */ 4693 immutable string dt = cast(string) this.value; 4694 return cast(DateTime) SysTime.fromISOString(dt[0 .. 8].idup ~ "T" ~ dt[8 .. $].idup); 4695 } 4696 4697 /** 4698 Encodes a DateTime. 4699 4700 The value is just the ASCII character representation of 4701 the $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, 4702 ISO 8601)-formatted timestamp. 4703 4704 An ISO-8601 Timestamp looks like: 4705 $(UL 4706 $(LI $(MONO 19851106210627.3)) 4707 $(LI $(MONO 19851106210627.3Z)) 4708 $(LI $(MONO 19851106210627.3-0500)) 4709 ) 4710 4711 Standards: 4712 $(UL 4713 $(LI $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601)) 4714 ) 4715 */ 4716 override public @property @system 4717 void generalizedTime(in DateTime value) 4718 out 4719 { 4720 assert(this.value.length > 0u); 4721 assert((cast(string) this.value).indexOf("T") == -1); 4722 } 4723 body 4724 { 4725 scope(success) this.construction = ASN1Construction.primitive; 4726 immutable SysTime st = SysTime(value, UTC()); 4727 this.value = cast(ubyte[]) ((st.toUTC()).toISOString().replace("T", "")); 4728 } 4729 4730 // Positive tests 4731 @system 4732 unittest 4733 { 4734 CERElement el = new CERElement(); 4735 4736 el.generalizedTime = DateTime(2017, 10, 3); 4737 assert(el.visibleString == "20171003000000Z"); 4738 4739 el.visibleString = "20171003000000.003Z"; 4740 assertNotThrown!Exception(el.generalizedTime); 4741 4742 el.visibleString = "20171003000000.0000003Z"; 4743 assertNotThrown!Exception(el.generalizedTime); 4744 } 4745 4746 // Negative tests 4747 @system 4748 unittest 4749 { 4750 CERElement el = new CERElement(); 4751 4752 el.visibleString = "20171003000000.Z"; 4753 assertThrown!ASN1Exception(el.generalizedTime); 4754 4755 el.visibleString = "20171003000000.0Z"; 4756 assertThrown!ASN1Exception(el.generalizedTime); 4757 4758 el.visibleString = "20171003000000.000000Z"; 4759 assertThrown!ASN1Exception(el.generalizedTime); 4760 4761 el.visibleString = "0171003000000Z"; 4762 assertThrown!ASN1Exception(el.generalizedTime); 4763 4764 el.visibleString = "2017100300000Z"; // Missing 1 trailing zero. 4765 assertThrown!ASN1Exception(el.generalizedTime); 4766 } 4767 4768 /** 4769 Decodes an ASCII string that contains only characters between and 4770 including $(D 0x20) and $(D 0x75). Deprecated, according to page 182 of the 4771 Dubuisson book. 4772 4773 Throws: 4774 $(UL 4775 $(LI $(D ASN1ValueCharactersException) if any non-graphical character (including space) is encoded) 4776 $(LI $(D ASN1RecursionException) 4777 if using constructed form and the element 4778 is constructed of too many nested constructed elements) 4779 $(LI $(D ASN1TagClassException) 4780 if any nested primitives do not share the 4781 same tag class as their outer constructed element) 4782 $(LI $(D ASN1TagNumberException) 4783 if any nested primitives do not share the 4784 same tag number as their outer constructed element) 4785 ) 4786 4787 Citations: 4788 $(UL 4789 $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).” 4790 $(I ASN.1: Communication between Heterogeneous Systems), 4791 Morgan Kaufmann, 2001, pp. 175-178.) 4792 $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022)) 4793 $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022)) 4794 ) 4795 */ 4796 override public @property @system 4797 string graphicString() const 4798 { 4799 if (this.construction == ASN1Construction.primitive) 4800 { 4801 if (this.value.length > 1000u) 4802 throw new ASN1ValueSizeException 4803 (0u, 1000u, this.value.length, "decode a primitively-encoded GraphicString"); 4804 4805 foreach (immutable character; this.value) 4806 { 4807 if (!character.isGraphical && character != ' ') 4808 throw new ASN1ValueCharactersException 4809 ("all characters within the range 0x20 to 0x7E", character, "GraphicString"); 4810 } 4811 return cast(string) this.value; 4812 } 4813 else 4814 { 4815 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 4816 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a GraphicString"); 4817 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 4818 4819 CERElement[] substrings = this.sequence; 4820 if (substrings.length < 2u) 4821 throw new ASN1ValueException 4822 ( 4823 "This exception was thrown because you attempted to decode " ~ 4824 "a constructed GraphicString that was constituted by fewer " ~ 4825 "than two nested elements. When using the Canonical Encoding " ~ 4826 "Rules (CER), a legitimate constructed GraphicString must " ~ 4827 "contain at least two nested elements, and each but the " ~ 4828 "last must contain exactly 1000 content octets. The last " ~ 4829 "nested element may have between 1 and 1000 content " ~ 4830 "octets inclusively. This element should have been encoded " ~ 4831 "primitively using definite-length encoding." 4832 ); 4833 4834 foreach (substring; substrings[0 .. $-1]) 4835 { 4836 if (substring.length != 1000u) 4837 throw new ASN1ValueSizeException 4838 (1000u, 1000u, substring.length, "decode a substring of a constructed GraphicString"); 4839 } 4840 4841 if (substrings[$-1].length == 0u) 4842 throw new ASN1ValueSizeException 4843 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed GraphicString"); 4844 4845 Appender!string appendy = appender!string(); 4846 foreach (substring; substrings) 4847 { 4848 if (substring.tagClass != this.tagClass) 4849 throw new ASN1TagClassException 4850 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed GraphicString"); 4851 4852 if (substring.construction != ASN1Construction.primitive) 4853 throw new ASN1ConstructionException 4854 (substring.construction, "decode a substring of a constructed GraphicString"); 4855 4856 if (substring.tagNumber != this.tagNumber) 4857 throw new ASN1TagNumberException 4858 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed GraphicString"); 4859 4860 appendy.put(substring.graphicString); 4861 } 4862 return appendy.data; 4863 } 4864 } 4865 4866 /** 4867 Encodes an ASCII string that may contain only characters between and 4868 including $(D 0x20) and $(D 0x75). Deprecated, according to page 182 4869 of the Dubuisson book. 4870 4871 Throws: 4872 $(UL 4873 $(LI $(D ASN1ValueCharactersException) if any non-graphical character (including space) is supplied) 4874 ) 4875 4876 Citations: 4877 $(UL 4878 $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).” 4879 $(I ASN.1: Communication between Heterogeneous Systems), 4880 Morgan Kaufmann, 2001, pp. 175-178.) 4881 $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022)) 4882 $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022)) 4883 ) 4884 */ 4885 override public @property @system 4886 void graphicString(in string value) 4887 { 4888 foreach (immutable character; value) 4889 { 4890 if (!character.isGraphical && character != ' ') 4891 throw new ASN1ValueCharactersException 4892 ("all characters within the range 0x20 to 0x7E", character, "GraphicString"); 4893 } 4894 4895 if (value.length <= 1000u) 4896 { 4897 this.value = cast(ubyte[]) value.dup; 4898 } 4899 else 4900 { 4901 CERElement[] primitives; 4902 size_t i = 0u; 4903 while (i+1000u < value.length) 4904 { 4905 CERElement x = new CERElement(); 4906 x.tagNumber = this.tagNumber; 4907 x.construction = ASN1Construction.primitive; 4908 x.value = cast(ubyte[]) value[i .. i+1000u]; 4909 primitives ~= x; 4910 i += 1000u; 4911 } 4912 4913 CERElement y = new CERElement(); 4914 y.tagNumber = this.tagNumber; 4915 y.construction = ASN1Construction.primitive; 4916 y.value = cast(ubyte[]) value[i .. $]; 4917 primitives ~= y; 4918 4919 this.sequence = primitives; 4920 this.construction = ASN1Construction.constructed; 4921 } 4922 } 4923 4924 @system 4925 unittest 4926 { 4927 void test(size_t length) 4928 { 4929 char[] data; 4930 data.length = length; 4931 for (size_t i = 0u; i < data.length; i++) 4932 { 4933 data[i] = cast(char) ((i % 0x40) + 0x20); 4934 } 4935 CERElement el = new CERElement(); 4936 el.graphicString = cast(string) data; 4937 assert(el.graphicString == cast(string) data); 4938 } 4939 test(0u); 4940 test(1u); 4941 test(8u); 4942 test(127u); 4943 test(128u); 4944 test(129u); 4945 test(192u); 4946 test(999u); 4947 test(1000u); 4948 test(1001u); 4949 test(2017u); 4950 } 4951 4952 /** 4953 Decodes a string that only contains characters between and including 4954 $(D 0x20) and $(D 0x7E). (Honestly, I don't know how this differs from 4955 $(MONO GraphicalString).) 4956 4957 Throws: 4958 $(UL 4959 $(LI $(D ASN1ValueCharactersException) 4960 if any non-graphical character (including space) is encoded) 4961 $(LI $(D ASN1RecursionException) 4962 if using constructed form and the element 4963 is constructed of too many nested constructed elements) 4964 $(LI $(D ASN1TagClassException) 4965 if any nested primitives do not share the 4966 same tag class as their outer constructed element) 4967 $(LI $(D ASN1TagNumberException) 4968 if any nested primitives do not share the 4969 same tag number as their outer constructed element) 4970 ) 4971 */ 4972 override public @property @system 4973 string visibleString() const 4974 { 4975 if (this.construction == ASN1Construction.primitive) 4976 { 4977 if (this.value.length > 1000u) 4978 throw new ASN1ValueSizeException 4979 (0u, 1000u, this.value.length, "decode a primitively-encoded VisibleString"); 4980 4981 foreach (immutable character; this.value) 4982 { 4983 if (!character.isGraphical && character != ' ') 4984 throw new ASN1ValueCharactersException 4985 ("all characters within the range 0x20 to 0x7E", character, "VisibleString"); 4986 } 4987 return cast(string) this.value; 4988 } 4989 else 4990 { 4991 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 4992 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a VisibleString"); 4993 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 4994 4995 CERElement[] substrings = this.sequence; 4996 if (substrings.length < 2u) 4997 throw new ASN1ValueException 4998 ( 4999 "This exception was thrown because you attempted to decode " ~ 5000 "a constructed VisibleString that was constituted by fewer " ~ 5001 "than two nested elements. When using the Canonical Encoding " ~ 5002 "Rules (CER), a legitimate constructed VisibleString must " ~ 5003 "contain at least two nested elements, and each but the " ~ 5004 "last must contain exactly 1000 content octets. The last " ~ 5005 "nested element may have between 1 and 1000 content " ~ 5006 "octets inclusively. This element should have been encoded " ~ 5007 "primitively using definite-length encoding." 5008 ); 5009 5010 foreach (substring; substrings[0 .. $-1]) 5011 { 5012 if (substring.length != 1000u) 5013 throw new ASN1ValueSizeException 5014 (1000u, 1000u, substring.length, "decode a substring of a constructed VisibleString"); 5015 } 5016 5017 if (substrings[$-1].length == 0u) 5018 throw new ASN1ValueSizeException 5019 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed VisibleString"); 5020 5021 Appender!string appendy = appender!string(); 5022 foreach (substring; substrings) 5023 { 5024 if (substring.tagClass != this.tagClass) 5025 throw new ASN1TagClassException 5026 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed VisibleString"); 5027 5028 if (substring.construction != ASN1Construction.primitive) 5029 throw new ASN1ConstructionException 5030 (substring.construction, "decode a substring of a constructed VisibleString"); 5031 5032 if (substring.tagNumber != this.tagNumber) 5033 throw new ASN1TagNumberException 5034 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed VisibleString"); 5035 5036 appendy.put(substring.visibleString); 5037 } 5038 return appendy.data; 5039 } 5040 } 5041 5042 /** 5043 Encodes a string that only contains characters between and including 5044 $(D 0x20) and $(D 0x7E). (Honestly, I don't know how this differs from 5045 $(MONO GraphicalString).) 5046 5047 Throws: 5048 $(UL 5049 $(LI $(D ASN1ValueCharactersException) 5050 if any non-graphical character (including space) is supplied.) 5051 ) 5052 */ 5053 override public @property @system 5054 void visibleString(in string value) 5055 { 5056 foreach (immutable character; value) 5057 { 5058 if (!character.isGraphical && character != ' ') 5059 throw new ASN1ValueCharactersException 5060 ("all characters within the range 0x20 to 0x7E", character, "VisibleString"); 5061 } 5062 5063 if (value.length <= 1000u) 5064 { 5065 this.value = cast(ubyte[]) value.dup; 5066 } 5067 else 5068 { 5069 CERElement[] primitives; 5070 size_t i = 0u; 5071 while (i+1000u < value.length) 5072 { 5073 CERElement x = new CERElement(); 5074 x.tagNumber = this.tagNumber; 5075 x.construction = ASN1Construction.primitive; 5076 x.value = cast(ubyte[]) value[i .. i+1000u]; 5077 primitives ~= x; 5078 i += 1000u; 5079 } 5080 5081 CERElement y = new CERElement(); 5082 y.tagNumber = this.tagNumber; 5083 y.construction = ASN1Construction.primitive; 5084 y.value = cast(ubyte[]) value[i .. $]; 5085 primitives ~= y; 5086 5087 this.sequence = primitives; 5088 this.construction = ASN1Construction.constructed; 5089 } 5090 } 5091 5092 @system 5093 unittest 5094 { 5095 void test(size_t length) 5096 { 5097 char[] data; 5098 data.length = length; 5099 for (size_t i = 0u; i < data.length; i++) 5100 { 5101 data[i] = cast(char) ((i % 0x40) + 0x20); 5102 } 5103 CERElement el = new CERElement(); 5104 el.visibleString = cast(string) data; 5105 assert(el.visibleString == cast(string) data); 5106 } 5107 test(0u); 5108 test(1u); 5109 test(8u); 5110 test(127u); 5111 test(128u); 5112 test(129u); 5113 test(192u); 5114 test(999u); 5115 test(1000u); 5116 test(1001u); 5117 test(2017u); 5118 } 5119 5120 /** 5121 Decodes a string containing only ASCII characters. Deprecated, according 5122 to page 182 of the Dubuisson book. 5123 5124 Throws: 5125 $(UL 5126 $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII) 5127 $(LI $(D ASN1RecursionException) 5128 if using constructed form and the element 5129 is constructed of too many nested constructed elements) 5130 $(LI $(D ASN1TagClassException) 5131 if any nested primitives do not share the 5132 same tag class as their outer constructed element) 5133 $(LI $(D ASN1TagNumberException) 5134 if any nested primitives do not share the 5135 same tag number as their outer constructed element) 5136 ) 5137 5138 Citations: 5139 $(UL 5140 $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).” 5141 $(I ASN.1: Communication between Heterogeneous Systems), 5142 Morgan Kaufmann, 2001, p. 182.) 5143 $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022)) 5144 $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022)) 5145 ) 5146 */ 5147 override public @property @system 5148 string generalString() const 5149 { 5150 if (this.construction == ASN1Construction.primitive) 5151 { 5152 if (this.value.length > 1000u) 5153 throw new ASN1ValueSizeException 5154 (0u, 1000u, this.value.length, "decode a primitively-encoded GeneralString"); 5155 5156 foreach (immutable character; this.value) 5157 { 5158 if (!character.isASCII) 5159 throw new ASN1ValueCharactersException 5160 ("all ASCII characters", character, "GeneralString"); 5161 } 5162 return cast(string) this.value; 5163 } 5164 else 5165 { 5166 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 5167 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a GeneralString"); 5168 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 5169 5170 CERElement[] substrings = this.sequence; 5171 if (substrings.length < 2u) 5172 throw new ASN1ValueException 5173 ( 5174 "This exception was thrown because you attempted to decode " ~ 5175 "a constructed GeneralString that was constituted by fewer " ~ 5176 "than two nested elements. When using the Canonical Encoding " ~ 5177 "Rules (CER), a legitimate constructed GeneralString must " ~ 5178 "contain at least two nested elements, and each but the " ~ 5179 "last must contain exactly 1000 content octets. The last " ~ 5180 "nested element may have between 1 and 1000 content " ~ 5181 "octets inclusively. This element should have been encoded " ~ 5182 "primitively using definite-length encoding." 5183 ); 5184 5185 foreach (substring; substrings[0 .. $-1]) 5186 { 5187 if (substring.length != 1000u) 5188 throw new ASN1ValueSizeException 5189 (1000u, 1000u, substring.length, "decode a substring of a constructed GeneralString"); 5190 } 5191 5192 if (substrings[$-1].length == 0u) 5193 throw new ASN1ValueSizeException 5194 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed GeneralString"); 5195 5196 Appender!string appendy = appender!string(); 5197 foreach (substring; substrings) 5198 { 5199 if (substring.tagClass != this.tagClass) 5200 throw new ASN1TagClassException 5201 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed GeneralString"); 5202 5203 if (substring.construction != ASN1Construction.primitive) 5204 throw new ASN1ConstructionException 5205 (substring.construction, "decode a substring of a constructed GeneralString"); 5206 5207 if (substring.tagNumber != this.tagNumber) 5208 throw new ASN1TagNumberException 5209 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed GeneralString"); 5210 5211 appendy.put(substring.generalString); 5212 } 5213 return appendy.data; 5214 } 5215 } 5216 5217 /** 5218 Encodes a string containing only ASCII characters. Deprecated, 5219 according to page 182 of the Dubuisson book. 5220 5221 Throws: 5222 $(UL 5223 $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII) 5224 ) 5225 5226 Citations: 5227 $(UL 5228 $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).” 5229 $(I ASN.1: Communication between Heterogeneous Systems), 5230 Morgan Kaufmann, 2001, p. 182.) 5231 ) 5232 */ 5233 override public @property @system 5234 void generalString(in string value) 5235 { 5236 foreach (immutable character; value) 5237 { 5238 if (!character.isASCII) 5239 throw new ASN1ValueCharactersException 5240 ("all ASCII characters", character, "GeneralString"); 5241 } 5242 5243 if (value.length <= 1000u) 5244 { 5245 this.value = cast(ubyte[]) value.dup; 5246 } 5247 else 5248 { 5249 CERElement[] primitives; 5250 size_t i = 0u; 5251 while (i+1000u < value.length) 5252 { 5253 CERElement x = new CERElement(); 5254 x.tagNumber = this.tagNumber; 5255 x.construction = ASN1Construction.primitive; 5256 x.value = cast(ubyte[]) value[i .. i+1000u]; 5257 primitives ~= x; 5258 i += 1000u; 5259 } 5260 5261 CERElement y = new CERElement(); 5262 y.tagNumber = this.tagNumber; 5263 y.construction = ASN1Construction.primitive; 5264 y.value = cast(ubyte[]) value[i .. $]; 5265 primitives ~= y; 5266 5267 this.sequence = primitives; 5268 this.construction = ASN1Construction.constructed; 5269 } 5270 } 5271 5272 @system 5273 unittest 5274 { 5275 void test(size_t length) 5276 { 5277 char[] data; 5278 data.length = length; 5279 for (size_t i = 0u; i < data.length; i++) 5280 { 5281 data[i] = cast(char) ((i % 0x40) + 0x20); 5282 } 5283 CERElement el = new CERElement(); 5284 el.generalString = cast(string) data; 5285 assert(el.generalString == cast(string) data); 5286 } 5287 test(0u); 5288 test(1u); 5289 test(8u); 5290 test(127u); 5291 test(128u); 5292 test(129u); 5293 test(192u); 5294 test(999u); 5295 test(1000u); 5296 test(1001u); 5297 test(2017u); 5298 } 5299 5300 /** 5301 Decodes a $(MONO dstring) of UTF-32 characters. 5302 5303 Throws: 5304 $(UL 5305 $(LI $(D ASN1ValueException) 5306 if the encoded bytes is not evenly divisible by four) 5307 $(LI $(D ASN1RecursionException) 5308 if using constructed form and the element 5309 is constructed of too many nested constructed elements) 5310 $(LI $(D ASN1TagClassException) 5311 if any nested primitives do not share the 5312 same tag class as their outer constructed element) 5313 $(LI $(D ASN1TagNumberException) 5314 if any nested primitives do not share the 5315 same tag number as their outer constructed element) 5316 ) 5317 */ 5318 override public @property @system 5319 dstring universalString() const 5320 { 5321 if (this.construction == ASN1Construction.primitive) 5322 { 5323 if (this.value.length > 1000u) 5324 throw new ASN1ValueSizeException 5325 (0u, 1000u, this.value.length, "decode a primitively-encoded UniversalString"); 5326 5327 if (this.value.length == 0u) return ""d; 5328 if (this.value.length % 4u) 5329 throw new ASN1ValueException 5330 ( 5331 "This exception was thrown because you tried to decode " ~ 5332 "a UniversalString that contained a number of bytes that " ~ 5333 "is not divisible by four. " ~ 5334 notWhatYouMeantText ~ forMoreInformationText ~ 5335 debugInformationText ~ reportBugsText 5336 ); 5337 5338 version (BigEndian) 5339 { 5340 return cast(dstring) this.value; 5341 } 5342 else version (LittleEndian) 5343 { 5344 dstring ret; 5345 size_t i = 0u; 5346 while (i < this.value.length-3u) 5347 { 5348 ubyte[] character; 5349 character.length = 4u; 5350 character[3] = this.value[i++]; 5351 character[2] = this.value[i++]; 5352 character[1] = this.value[i++]; 5353 character[0] = this.value[i++]; 5354 ret ~= (*cast(dchar *) character.ptr); 5355 } 5356 return ret; 5357 } 5358 else 5359 { 5360 static assert(0, "Could not determine endianness!"); 5361 } 5362 } 5363 else 5364 { 5365 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 5366 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a UniversalString"); 5367 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 5368 5369 CERElement[] substrings = this.sequence; 5370 if (substrings.length < 2u) 5371 throw new ASN1ValueException 5372 ( 5373 "This exception was thrown because you attempted to decode " ~ 5374 "a constructed UniversalString that was constituted by fewer " ~ 5375 "than two nested elements. When using the Canonical Encoding " ~ 5376 "Rules (CER), a legitimate constructed UniversalString must " ~ 5377 "contain at least two nested elements, and each but the " ~ 5378 "last must contain exactly 1000 content octets. The last " ~ 5379 "nested element may have between 1 and 1000 content " ~ 5380 "octets inclusively. This element should have been encoded " ~ 5381 "primitively using definite-length encoding." 5382 ); 5383 5384 foreach (substring; substrings[0 .. $-1]) 5385 { 5386 if (substring.length != 1000u) 5387 throw new ASN1ValueSizeException 5388 (1000u, 1000u, substring.length, "decode a substring of a constructed UniversalString"); 5389 } 5390 5391 if (substrings[$-1].length == 0u) 5392 throw new ASN1ValueSizeException 5393 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed UniversalString"); 5394 5395 Appender!dstring appendy = appender!dstring(); 5396 foreach (substring; substrings) 5397 { 5398 if (substring.tagClass != this.tagClass) 5399 throw new ASN1TagClassException 5400 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed UniversalString"); 5401 5402 if (substring.construction != ASN1Construction.primitive) 5403 throw new ASN1ConstructionException 5404 (substring.construction, "decode a substring of a constructed UniversalString"); 5405 5406 if (substring.tagNumber != this.tagNumber) 5407 throw new ASN1TagNumberException 5408 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed UniversalString"); 5409 5410 appendy.put(substring.universalString); 5411 } 5412 return appendy.data; 5413 } 5414 } 5415 5416 /// Encodes a $(MONO dstring) of UTF-32 characters. 5417 override public @property @system 5418 void universalString(in dstring value) 5419 { 5420 if (value.length <= 250u) 5421 { 5422 version (BigEndian) 5423 { 5424 this.value = cast(ubyte[]) value.dup; 5425 } 5426 else version (LittleEndian) 5427 { 5428 foreach (immutable character; value) 5429 { 5430 ubyte[] charBytes = cast(ubyte[]) *cast(char[4] *) &character; 5431 reverse(charBytes); 5432 this.value ~= charBytes; 5433 } 5434 } 5435 else 5436 { 5437 static assert(0, "Could not determine endianness!"); 5438 } 5439 } 5440 else 5441 { 5442 CERElement[] primitives; 5443 size_t i = 0u; 5444 while (i+250u < value.length) 5445 { 5446 CERElement x = new CERElement(); 5447 x.tagNumber = this.tagNumber; 5448 x.construction = ASN1Construction.primitive; 5449 version (BigEndian) 5450 { 5451 x.value = cast(ubyte[]) value[i .. i+250u]; 5452 } 5453 else version (LittleEndian) 5454 { 5455 foreach (immutable character; value[i .. i+250u]) 5456 { 5457 ubyte[] charBytes = cast(ubyte[]) *cast(char[4] *) &character; 5458 reverse(charBytes); 5459 x.value ~= charBytes; 5460 } 5461 } 5462 else 5463 { 5464 static assert(0, "Could not determine endianness!"); 5465 } 5466 primitives ~= x; 5467 i += 250u; 5468 } 5469 5470 CERElement y = new CERElement(); 5471 y.tagNumber = this.tagNumber; 5472 y.construction = ASN1Construction.primitive; 5473 version (BigEndian) 5474 { 5475 y.value = cast(ubyte[]) value[i .. $]; 5476 } 5477 else version (LittleEndian) 5478 { 5479 foreach (immutable character; value[i .. $]) 5480 { 5481 ubyte[] charBytes = cast(ubyte[]) *cast(char[4] *) &character; 5482 reverse(charBytes); 5483 y.value ~= charBytes; 5484 } 5485 } 5486 else 5487 { 5488 static assert(0, "Could not determine endianness!"); 5489 } 5490 primitives ~= y; 5491 5492 this.sequence = primitives; 5493 this.construction = ASN1Construction.constructed; 5494 } 5495 } 5496 5497 @system 5498 unittest 5499 { 5500 void test(size_t length) 5501 { 5502 dchar[] data; 5503 data.length = length; 5504 for (size_t i = 0u; i < data.length; i++) 5505 { 5506 data[i] = cast(dchar) ((i % 0x60u) + 0x20u); 5507 } 5508 CERElement el = new CERElement(); 5509 el.universalString = cast(dstring) data; 5510 assert(el.universalString == cast(dstring) data); 5511 } 5512 test(0u); 5513 test(1u); 5514 test(8u); 5515 test(127u); 5516 test(128u); 5517 test(129u); 5518 test(192u); 5519 test(999u); 5520 test(1000u); 5521 test(1001u); 5522 test(2017u); 5523 } 5524 5525 /** 5526 Decodes a $(MONO CharacterString), which is a constructed data type, defined 5527 in the $(LINK https://www.itu.int, International Telecommunications Union)'s 5528 $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680). 5529 5530 The specification defines $(MONO CharacterString) as: 5531 5532 $(PRE 5533 CHARACTER STRING ::= [UNIVERSAL 29] SEQUENCE { 5534 identification CHOICE { 5535 syntaxes SEQUENCE { 5536 abstract OBJECT IDENTIFIER, 5537 transfer OBJECT IDENTIFIER }, 5538 syntax OBJECT IDENTIFIER, 5539 presentation-context-id INTEGER, 5540 context-negotiation SEQUENCE { 5541 presentation-context-id INTEGER, 5542 transfer-syntax OBJECT IDENTIFIER }, 5543 transfer-syntax OBJECT IDENTIFIER, 5544 fixed NULL }, 5545 string-value OCTET STRING } 5546 ) 5547 5548 This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification) 5549 choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5. 5550 5551 Returns: an instance of $(D types.universal.characterstring.CharacterString). 5552 5553 Throws: 5554 $(UL 5555 $(LI $(D ASN1ValueException) if encoded $(MONO CharacterString) has too few or too many 5556 elements, or if $(MONO syntaxes) or $(MONO context-negotiation) element has 5557 too few or too many elements) 5558 $(LI $(D ASN1ValueSizeException) if encoded $(MONO INTEGER) is too large to decode) 5559 $(LI $(D ASN1RecursionException) if using constructed form and the element 5560 is constructed of too many nested constructed elements) 5561 $(LI $(D ASN1TagClassException) if any nested primitives do not have the 5562 correct tag class) 5563 $(LI $(D ASN1ConstructionException) if any element has the wrong construction) 5564 $(LI $(D ASN1TagNumberException) if any nested primitives do not have the 5565 correct tag number) 5566 ) 5567 */ 5568 override public @property @system 5569 CharacterString characterString() const 5570 { 5571 if (this.construction != ASN1Construction.constructed) 5572 throw new ASN1ConstructionException 5573 (this.construction, "decode an INTEGER"); 5574 5575 const CERElement[] components = this.sequence; 5576 ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID(); 5577 5578 if (components.length != 2u) 5579 throw new ASN1ValueException 5580 ( 5581 "This exception was thrown because you attempted to decode " ~ 5582 "a CharacterString that contained too many or too few elements. " ~ 5583 "A CharacterString should have only two elements: " ~ 5584 "an identification CHOICE, and a data-value OCTET STRING, " ~ 5585 "in that order. " ~ 5586 notWhatYouMeantText ~ forMoreInformationText ~ 5587 debugInformationText ~ reportBugsText 5588 ); 5589 5590 if (components[0].tagClass != ASN1TagClass.contextSpecific) 5591 throw new ASN1TagClassException 5592 ( 5593 [ ASN1TagClass.contextSpecific ], 5594 components[0].tagClass, 5595 "decode the first component of a CharacterString" 5596 ); 5597 5598 if (components[1].tagClass != ASN1TagClass.contextSpecific) 5599 throw new ASN1TagClassException 5600 ( 5601 [ ASN1TagClass.contextSpecific ], 5602 components[1].tagClass, 5603 "decode the second component of a CharacterString" 5604 ); 5605 5606 /* NOTE: 5607 See page 224 of Dubuisson, item 11: 5608 It sounds like, even if you have an ABSENT constraint applied, 5609 all automatically-tagged items still have the same numbers as 5610 though the constrained component were PRESENT. 5611 */ 5612 if (components[0].tagNumber != 0u) 5613 throw new ASN1TagNumberException 5614 ([ 0u ], components[0].tagNumber, "decode the first component of a CharacterString"); 5615 5616 if (components[1].tagNumber != 2u) 5617 throw new ASN1TagNumberException 5618 ([ 2u ], components[1].tagNumber, "decode the second component of a CharacterString"); 5619 5620 ubyte[] bytes = components[0].value.dup; 5621 const CERElement identificationChoice = new CERElement(bytes); 5622 switch (identificationChoice.tagNumber) 5623 { 5624 case (0u): // syntaxes 5625 { 5626 if (identificationChoice.construction != ASN1Construction.constructed) 5627 throw new ASN1ConstructionException 5628 (identificationChoice.construction, "decode the syntaxes component of a CharacterString"); 5629 5630 const CERElement[] syntaxesComponents = identificationChoice.sequence; 5631 5632 if (syntaxesComponents.length != 2u) 5633 throw new ASN1ValueException 5634 ( 5635 "This exception was thrown because you attempted to " ~ 5636 "decode a CharacterString whose syntaxes component " ~ 5637 "contained an invalid number of elements. The " ~ 5638 "syntaxes component should contain abstract and transfer " ~ 5639 "syntax OBJECT IDENTIFIERS, in that order. " ~ 5640 notWhatYouMeantText ~ forMoreInformationText ~ 5641 debugInformationText ~ reportBugsText 5642 ); 5643 5644 // Class Validation 5645 if (syntaxesComponents[0].tagClass != ASN1TagClass.contextSpecific) 5646 throw new ASN1TagClassException 5647 ( 5648 [ ASN1TagClass.contextSpecific ], 5649 syntaxesComponents[0].tagClass, 5650 "decode the first syntaxes component of a CharacterString" 5651 ); 5652 5653 if (syntaxesComponents[1].tagClass != ASN1TagClass.contextSpecific) 5654 throw new ASN1TagClassException 5655 ( 5656 [ ASN1TagClass.contextSpecific ], 5657 syntaxesComponents[1].tagClass, 5658 "decode the second syntaxes component of a CharacterString" 5659 ); 5660 5661 // Construction Validation 5662 if (syntaxesComponents[0].construction != ASN1Construction.primitive) 5663 throw new ASN1ConstructionException 5664 (syntaxesComponents[0].construction, "decode the first syntaxes component of a CharacterString"); 5665 5666 if (syntaxesComponents[1].construction != ASN1Construction.primitive) 5667 throw new ASN1ConstructionException 5668 (syntaxesComponents[1].construction, "decode the second syntaxes component of a CharacterString"); 5669 5670 // Number Validation 5671 if (syntaxesComponents[0].tagNumber != 0u) 5672 throw new ASN1TagNumberException 5673 ( 5674 [ 0u ], 5675 syntaxesComponents[0].tagNumber, 5676 "decode the first syntaxes component of a CharacterString" 5677 ); 5678 5679 if (syntaxesComponents[1].tagNumber != 1u) 5680 throw new ASN1TagNumberException 5681 ( 5682 [ 1u ], 5683 syntaxesComponents[1].tagNumber, 5684 "decode the second syntaxes component of a CharacterString" 5685 ); 5686 5687 identification.syntaxes = ASN1Syntaxes( 5688 syntaxesComponents[0].objectIdentifier, 5689 syntaxesComponents[1].objectIdentifier 5690 ); 5691 5692 break; 5693 } 5694 case (1u): // syntax 5695 { 5696 identification.syntax = identificationChoice.objectIdentifier; 5697 break; 5698 } 5699 case (4u): // transfer-syntax 5700 { 5701 identification.transferSyntax = identificationChoice.objectIdentifier; 5702 break; 5703 } 5704 case (5u): // fixed 5705 { 5706 identification.fixed = true; 5707 break; 5708 } 5709 default: 5710 throw new ASN1TagNumberException 5711 ( 5712 [ 0u, 1u, 4u, 5u ], 5713 identificationChoice.tagNumber, 5714 "decode a CharacterString identification" 5715 ); 5716 } 5717 5718 CharacterString cs = CharacterString(); 5719 cs.identification = identification; 5720 cs.stringValue = components[1].octetString; 5721 return cs; 5722 } 5723 5724 /** 5725 Encodes a $(MONO CharacterString), which is a constructed data type, defined 5726 in the $(LINK https://www.itu.int, International Telecommunications Union)'s 5727 $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680). 5728 5729 The specification defines $(MONO CharacterString) as: 5730 5731 $(PRE 5732 CHARACTER STRING ::= [UNIVERSAL 29] SEQUENCE { 5733 identification CHOICE { 5734 syntaxes SEQUENCE { 5735 abstract OBJECT IDENTIFIER, 5736 transfer OBJECT IDENTIFIER }, 5737 syntax OBJECT IDENTIFIER, 5738 presentation-context-id INTEGER, 5739 context-negotiation SEQUENCE { 5740 presentation-context-id INTEGER, 5741 transfer-syntax OBJECT IDENTIFIER }, 5742 transfer-syntax OBJECT IDENTIFIER, 5743 fixed NULL }, 5744 string-value OCTET STRING } 5745 ) 5746 5747 This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification) 5748 choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5. 5749 */ 5750 override public @property @system 5751 void characterString(in CharacterString value) 5752 out 5753 { 5754 assert(this.value.length > 0u); 5755 } 5756 body 5757 { 5758 scope(success) this.construction = ASN1Construction.constructed; 5759 CERElement identification = new CERElement(); 5760 identification.tagClass = ASN1TagClass.contextSpecific; 5761 identification.tagNumber = 0u; // CHOICE is EXPLICIT, even with automatic tagging. 5762 5763 CERElement identificationChoice = new CERElement(); 5764 identificationChoice.tagClass = ASN1TagClass.contextSpecific; 5765 if (!(value.identification.syntaxes.isNull)) 5766 { 5767 CERElement abstractSyntax = new CERElement(); 5768 abstractSyntax.tagClass = ASN1TagClass.contextSpecific; 5769 abstractSyntax.tagNumber = 0u; 5770 abstractSyntax.objectIdentifier = value.identification.syntaxes.abstractSyntax; 5771 5772 CERElement transferSyntax = new CERElement(); 5773 transferSyntax.tagClass = ASN1TagClass.contextSpecific; 5774 transferSyntax.tagNumber = 1u; 5775 transferSyntax.objectIdentifier = value.identification.syntaxes.transferSyntax; 5776 5777 identificationChoice.construction = ASN1Construction.constructed; 5778 identificationChoice.tagNumber = 0u; 5779 identificationChoice.sequence = [ abstractSyntax, transferSyntax ]; 5780 } 5781 else if (!(value.identification.syntax.isNull)) 5782 { 5783 identificationChoice.tagNumber = 1u; 5784 identificationChoice.objectIdentifier = value.identification.syntax; 5785 } 5786 else if (!(value.identification.transferSyntax.isNull)) 5787 { 5788 identificationChoice.tagNumber = 4u; 5789 identificationChoice.objectIdentifier = value.identification.transferSyntax; 5790 } 5791 else 5792 { 5793 identificationChoice.tagNumber = 5u; 5794 identificationChoice.value = []; 5795 } 5796 5797 // This makes identification: [CONTEXT 0][L][CONTEXT #][L][V] 5798 identification.value = cast(ubyte[]) identificationChoice; 5799 5800 CERElement stringValue = new CERElement(); 5801 stringValue.tagClass = ASN1TagClass.contextSpecific; 5802 stringValue.tagNumber = 2u; 5803 stringValue.octetString = value.stringValue; 5804 5805 this.sequence = [ identification, stringValue ]; 5806 } 5807 5808 /* NOTE: 5809 This unit test had to be moved out of ASN1Element because CER and CER 5810 do not support encoding of context-negotiation in CharacterString. 5811 5812 This unit test ensures that, if you attempt to create a CharacterString 5813 with context-negotiation as the CHOICE of identification, the 5814 encoded CharacterString's identification defaults to fixed. 5815 */ 5816 @system 5817 unittest 5818 { 5819 ASN1ContextNegotiation cn = ASN1ContextNegotiation(); 5820 cn.presentationContextID = 27L; 5821 cn.transferSyntax = new OID(1, 3, 6, 4, 1, 256, 39); 5822 5823 ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID(); 5824 id.contextNegotiation = cn; 5825 5826 CharacterString input = CharacterString(); 5827 input.identification = id; 5828 input.stringValue = [ 'H', 'E', 'N', 'L', 'O' ]; 5829 5830 CERElement el = new CERElement(); 5831 el.characterString = input; 5832 CharacterString output = el.characterString; 5833 assert(output.identification.fixed == true); 5834 assert(output.stringValue == [ 'H', 'E', 'N', 'L', 'O' ]); 5835 } 5836 5837 // Inspired by CVE-2017-9023 5838 @system 5839 unittest 5840 { 5841 ubyte[] data = [ // This is valid. 5842 0x1Eu, 0x0Au, // CharacterString, Length 11 5843 0x80u, 0x02u, // CHOICE 5844 0x85u, 0x00u, // NULL 5845 0x82u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u ]; // OCTET STRING 5846 5847 // Valid values for data[2]: 80 5848 for (ubyte i = 0x81u; i < 0x9Eu; i++) 5849 { 5850 data[2] = i; 5851 size_t x = 0u; 5852 CERElement el = new CERElement(x, data); 5853 assertThrown!ASN1Exception(el.characterString); 5854 } 5855 5856 // Valid values for data[4]: 80-85 5857 for (ubyte i = 0x86u; i < 0x9Eu; i++) 5858 { 5859 data[4] = i; 5860 size_t x = 0u; 5861 CERElement el = new CERElement(x, data); 5862 assertThrown!ASN1Exception(el.characterString); 5863 } 5864 5865 // Valid values for data[6]: 82 5866 for (ubyte i = 0x83u; i < 0x9Eu; i++) 5867 { 5868 data[6] = i; 5869 size_t x = 0u; 5870 CERElement el = new CERElement(x, data); 5871 assertThrown!ASN1Exception(el.characterString); 5872 } 5873 } 5874 5875 /** 5876 Decodes a $(MONO wstring) of UTF-16 characters. 5877 5878 Throws: 5879 $(UL 5880 $(LI $(D ASN1ValueException) 5881 if the encoded bytes is not evenly divisible by two) 5882 $(LI $(D ASN1RecursionException) 5883 if using constructed form and the element 5884 is constructed of too many nested constructed elements) 5885 $(LI $(D ASN1TagClassException) 5886 if any nested primitives do not share the 5887 same tag class as their outer constructed element) 5888 $(LI $(D ASN1TagNumberException) 5889 if any nested primitives do not share the 5890 same tag number as their outer constructed element) 5891 ) 5892 */ 5893 override public @property @system 5894 wstring basicMultilingualPlaneString() const 5895 { 5896 if (this.construction == ASN1Construction.primitive) 5897 { 5898 if (this.value.length > 1000u) 5899 throw new ASN1ValueSizeException 5900 (0u, 1000u, this.value.length, "decode a primitively-encoded BMPString"); 5901 5902 if (this.value.length == 0u) return ""w; 5903 if (this.value.length % 2u) 5904 throw new ASN1ValueException 5905 ( 5906 "This exception was thrown because you tried to decode " ~ 5907 "a BMPString that contained a number of bytes that " ~ 5908 "is not divisible by two. " ~ 5909 notWhatYouMeantText ~ forMoreInformationText ~ 5910 debugInformationText ~ reportBugsText 5911 ); 5912 5913 version (BigEndian) 5914 { 5915 return cast(wstring) this.value; 5916 } 5917 else version (LittleEndian) 5918 { 5919 wstring ret; 5920 size_t i = 0u; 5921 while (i < this.value.length-1u) 5922 { 5923 ubyte[] character; 5924 character.length = 2u; 5925 character[1] = this.value[i++]; 5926 character[0] = this.value[i++]; 5927 ret ~= (*cast(wchar *) character.ptr); 5928 } 5929 return ret; 5930 } 5931 else 5932 { 5933 static assert(0, "Could not determine endianness!"); 5934 } 5935 } 5936 else 5937 { 5938 if (this.valueRecursionCount++ == this.nestingRecursionLimit) 5939 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a BMPString"); 5940 scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion. 5941 5942 CERElement[] substrings = this.sequence; 5943 if (substrings.length < 2u) 5944 throw new ASN1ValueException 5945 ( 5946 "This exception was thrown because you attempted to decode " ~ 5947 "a constructed BMPString that was constituted by fewer " ~ 5948 "than two nested elements. When using the Canonical Encoding " ~ 5949 "Rules (CER), a legitimate constructed BMPString must " ~ 5950 "contain at least two nested elements, and each but the " ~ 5951 "last must contain exactly 1000 content octets. The last " ~ 5952 "nested element may have between 1 and 1000 content " ~ 5953 "octets inclusively. This element should have been encoded " ~ 5954 "primitively using definite-length encoding." 5955 ); 5956 5957 foreach (substring; substrings[0 .. $-1]) 5958 { 5959 if (substring.length != 1000u) 5960 throw new ASN1ValueSizeException 5961 (1000u, 1000u, substring.length, "decode a substring of a constructed BMPString"); 5962 } 5963 5964 if (substrings[$-1].length == 0u) 5965 throw new ASN1ValueSizeException 5966 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed BMPString"); 5967 5968 Appender!wstring appendy = appender!wstring(); 5969 foreach (substring; substrings) 5970 { 5971 if (substring.tagClass != this.tagClass) 5972 throw new ASN1TagClassException 5973 ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed BMPString"); 5974 5975 if (substring.construction != ASN1Construction.primitive) 5976 throw new ASN1ConstructionException 5977 (substring.construction, "decode a substring of a constructed BMPString"); 5978 5979 if (substring.tagNumber != this.tagNumber) 5980 throw new ASN1TagNumberException 5981 ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed BMPString"); 5982 5983 appendy.put(substring.bmpString); 5984 } 5985 return appendy.data; 5986 } 5987 } 5988 5989 /// Encodes a $(MONO wstring) of UTF-16 characters. 5990 override public @property @system 5991 void basicMultilingualPlaneString(in wstring value) 5992 { 5993 if (value.length <= 500u) 5994 { 5995 version (BigEndian) 5996 { 5997 this.value = cast(ubyte[]) value.dup; 5998 } 5999 else version (LittleEndian) 6000 { 6001 foreach (immutable character; value) 6002 { 6003 ubyte[] charBytes = cast(ubyte[]) *cast(char[2] *) &character; 6004 reverse(charBytes); 6005 this.value ~= charBytes; 6006 } 6007 } 6008 else 6009 { 6010 static assert(0, "Could not determine endianness!"); 6011 } 6012 } 6013 else 6014 { 6015 CERElement[] primitives; 6016 size_t i = 0u; 6017 while (i+500u < value.length) 6018 { 6019 CERElement x = new CERElement(); 6020 x.tagNumber = this.tagNumber; 6021 x.construction = ASN1Construction.primitive; 6022 version (BigEndian) 6023 { 6024 x.value = cast(ubyte[]) value[i .. i+500u]; 6025 } 6026 else version (LittleEndian) 6027 { 6028 foreach (immutable character; value[i .. i+500u]) 6029 { 6030 ubyte[] charBytes = cast(ubyte[]) *cast(char[2] *) &character; 6031 reverse(charBytes); 6032 x.value ~= charBytes; 6033 } 6034 } 6035 else 6036 { 6037 static assert(0, "Could not determine endianness!"); 6038 } 6039 primitives ~= x; 6040 i += 500u; 6041 } 6042 6043 CERElement y = new CERElement(); 6044 y.tagNumber = this.tagNumber; 6045 y.construction = ASN1Construction.primitive; 6046 version (BigEndian) 6047 { 6048 y.value = cast(ubyte[]) value[i .. $]; 6049 } 6050 else version (LittleEndian) 6051 { 6052 foreach (immutable character; value[i .. $]) 6053 { 6054 ubyte[] charBytes = cast(ubyte[]) *cast(char[2] *) &character; 6055 reverse(charBytes); 6056 y.value ~= charBytes; 6057 } 6058 } 6059 else 6060 { 6061 static assert(0, "Could not determine endianness!"); 6062 } 6063 primitives ~= y; 6064 6065 this.sequence = primitives; 6066 this.construction = ASN1Construction.constructed; 6067 } 6068 } 6069 6070 @system 6071 unittest 6072 { 6073 void test(size_t length) 6074 { 6075 wchar[] data; 6076 data.length = length; 6077 for (size_t i = 0u; i < data.length; i++) 6078 { 6079 data[i] = cast(wchar) ((i % 0x60u) + 0x20u); 6080 } 6081 CERElement el = new CERElement(); 6082 el.bmpString = cast(wstring) data; 6083 assert(el.bmpString == cast(wstring) data); 6084 } 6085 test(0u); 6086 test(1u); 6087 test(8u); 6088 test(127u); 6089 test(128u); 6090 test(129u); 6091 test(192u); 6092 test(999u); 6093 test(1000u); 6094 test(1001u); 6095 test(2017u); 6096 } 6097 6098 /// Creates an $(MONO END OF CONTENT) 6099 public @safe @nogc nothrow 6100 this() 6101 { 6102 this.tagNumber = 0u; 6103 this.value = []; 6104 } 6105 6106 /** 6107 Creates a $(D CERElement) from the supplied bytes, inferring that the first 6108 byte is the type tag. The supplied $(D ubyte[]) array is "chomped" by 6109 reference, so the original array will grow shorter as $(D CERElement)s are 6110 generated. 6111 6112 Throws: 6113 All of the same exceptions as $(D fromBytes()) 6114 6115 Examples: 6116 --- 6117 // Decoding looks like: 6118 CERElement[] result; 6119 while (bytes.length > 0) 6120 result ~= new CERElement(bytes); 6121 6122 // Encoding looks like: 6123 ubyte[] result; 6124 foreach (cv; cervalues) 6125 { 6126 result ~= cast(ubyte[]) cv; 6127 } 6128 --- 6129 */ 6130 public @system 6131 this(ref ubyte[] bytes) 6132 { 6133 size_t bytesRead = this.fromBytes(bytes); 6134 bytes = bytes[bytesRead .. $]; 6135 } 6136 6137 /** 6138 Creates a $(D CERElement) from the supplied bytes, inferring that the first 6139 byte is the type tag. The supplied $(D ubyte[]) array is read, starting 6140 from the index specified by $(D bytesRead), and increments 6141 $(D bytesRead) by the number of bytes read. 6142 6143 Throws: 6144 All of the same exceptions as $(D fromBytes()) 6145 6146 Examples: 6147 --- 6148 // Decoding looks like: 6149 CERElement[] result; 6150 size_t i = 0u; 6151 while (i < bytes.length) 6152 result ~= new CERElement(i, bytes); 6153 6154 // Encoding looks like: 6155 ubyte[] result; 6156 foreach (cv; cervalues) 6157 { 6158 result ~= cast(ubyte[]) cv; 6159 } 6160 --- 6161 */ 6162 public @system 6163 this(ref size_t bytesRead, in ubyte[] bytes) 6164 { 6165 bytesRead += this.fromBytes(bytes[bytesRead .. $].dup); 6166 } 6167 6168 /** 6169 Returns: the number of bytes read 6170 6171 Throws: 6172 $(UL 6173 $(LI $(D ASN1TagPaddingException) if the tag number is "padded" with 6174 "leading zero bytes" ($(D 0x80u))) 6175 $(LI $(D ASN1TagOverflowException) if the tag number is too large to 6176 fit into a $(D size_t)) 6177 $(LI $(D ASN1LengthUndefinedException) if the reserved length byte of 6178 $(D 0xFF) is encountered) 6179 $(LI $(D ASN1LengthOverflowException) if the length is too large to fit 6180 into a $(D size_t)) 6181 $(LI $(D ASN1TruncationException) if the tag, length, or value appear to 6182 be truncated) 6183 $(LI $(D ASN1ConstructionException) if the length is indefinite, but the 6184 element is marked as being encoded primitively) 6185 $(LI $(D ASN1RecursionException) if, when trying to determine the end of 6186 an indefinite-length encoded element, the parser has to recurse 6187 too deep) 6188 ) 6189 */ 6190 public 6191 size_t fromBytes (in ubyte[] bytes) 6192 { 6193 if (bytes.length < 2u) 6194 throw new ASN1TruncationException 6195 ( 6196 2u, 6197 bytes.length, 6198 "decode the tag of a Canonical Encoding Rules (CER) encoded element" 6199 ); 6200 6201 // Index of what we are currently parsing. 6202 size_t cursor = 0u; 6203 6204 switch (bytes[cursor] & 0b11000000u) 6205 { 6206 case (0b00000000u): this.tagClass = ASN1TagClass.universal; break; 6207 case (0b01000000u): this.tagClass = ASN1TagClass.application; break; 6208 case (0b10000000u): this.tagClass = ASN1TagClass.contextSpecific; break; 6209 case (0b11000000u): this.tagClass = ASN1TagClass.privatelyDefined; break; 6210 default: assert(0, "Impossible tag class appeared!"); 6211 } 6212 6213 this.construction = ((bytes[cursor] & 0b00100000u) ? 6214 ASN1Construction.constructed : ASN1Construction.primitive); 6215 6216 this.tagNumber = (bytes[cursor] & 0b00011111u); 6217 cursor++; 6218 if (this.tagNumber >= 31u) 6219 { 6220 /* NOTE: 6221 Section 8.1.2.4.2, point C of the International 6222 Telecommunications Union's X.690 specification says: 6223 6224 "bits 7 to 1 of the first subsequent octet shall not all be zero." 6225 6226 in reference to the bytes used to encode the tag number in long 6227 form, which happens when the least significant five bits of the 6228 first byte are all set. 6229 6230 This essentially means that the long-form tag number must be 6231 encoded on the fewest possible octets. If the first byte is 6232 0x80, then it is not encoded on the fewest possible octets. 6233 */ 6234 if (bytes[cursor] == 0b10000000u) 6235 throw new ASN1TagPaddingException 6236 ( 6237 "This exception was thrown because you attempted to decode " ~ 6238 "a Canonical Encoding Rules (CER) encoded element whose tag " ~ 6239 "number was encoded in long form in the octets following " ~ 6240 "the first octet of the type tag, and whose tag number " ~ 6241 "was encoded with a 'leading zero' byte, 0x80. When " ~ 6242 "using Canonical Encoding Rules (CER), the tag number must " ~ 6243 "be encoded on the smallest number of octets possible, " ~ 6244 "which the inclusion of leading zero bytes necessarily " ~ 6245 "contradicts. " ~ 6246 forMoreInformationText ~ debugInformationText ~ reportBugsText 6247 ); 6248 6249 this.tagNumber = 0u; 6250 6251 // This loop looks for the end of the encoded tag number. 6252 immutable size_t limit = ((bytes.length-1 >= size_t.sizeof) ? size_t.sizeof : bytes.length-1); 6253 while (cursor < limit) 6254 { 6255 if (!(bytes[cursor++] & 0x80u)) break; 6256 } 6257 6258 if (bytes[cursor-1] & 0x80u) 6259 { 6260 if (limit == bytes.length-1) 6261 { 6262 throw new ASN1TruncationException 6263 (size_t.max, bytes.length, "decode an ASN.1 tag number"); 6264 } 6265 else 6266 { 6267 throw new ASN1TagOverflowException 6268 ( 6269 "This exception was thrown because you attempted to decode " ~ 6270 "a Canonical Encoding Rules (CER) encoded element that encoded " ~ 6271 "a tag number that was either too large to decode or " ~ 6272 "terminated prematurely." 6273 ); 6274 } 6275 } 6276 6277 for (size_t i = 1; i < cursor; i++) 6278 { 6279 this.tagNumber <<= 7; 6280 this.tagNumber |= cast(size_t) (bytes[i] & 0x7Fu); 6281 } 6282 } 6283 6284 // Length 6285 if ((bytes[cursor] & 0x80u) == 0x80u) 6286 { 6287 immutable ubyte numberOfLengthOctets = (bytes[cursor] & 0x7Fu); 6288 if (numberOfLengthOctets) // Definite Long or Reserved 6289 { 6290 if (numberOfLengthOctets == 0b01111111u) // Reserved 6291 throw new ASN1LengthUndefinedException(); 6292 6293 // Definite Long, if it has made it this far 6294 6295 if (this.construction == ASN1Construction.constructed) 6296 throw new ASN1ConstructionException 6297 (this.construction, "decode a long definite-length Canonical Encoding Rules (CER) encoded element"); 6298 6299 if (numberOfLengthOctets > size_t.sizeof) 6300 throw new ASN1LengthOverflowException(); 6301 6302 if (cursor + numberOfLengthOctets >= bytes.length) 6303 throw new ASN1TruncationException 6304 ( 6305 numberOfLengthOctets, 6306 ((bytes.length - 1) - cursor), 6307 "decode the length of a Basic Encoding Rules (BER) encoded element" 6308 ); 6309 6310 if (bytes[++cursor] == 0x00u) 6311 throw new ASN1LengthException 6312 ( 6313 "This exception was thrown because you attempted to " ~ 6314 "decode a Canonical Encoding Rules (CER) encoded " ~ 6315 "element whose length was encoded in definite long " ~ 6316 "form, and encoded on more octets than necessary, " ~ 6317 "which is prohibited by the specification for " ~ 6318 "Canonical Encoding Rules (CER). " ~ 6319 forMoreInformationText ~ debugInformationText ~ reportBugsText 6320 ); 6321 6322 ubyte[] lengthNumberOctets; 6323 lengthNumberOctets.length = size_t.sizeof; 6324 for (ubyte i = numberOfLengthOctets; i > 0u; i--) 6325 { 6326 lengthNumberOctets[size_t.sizeof-i] = bytes[cursor+numberOfLengthOctets-i]; 6327 } 6328 version (LittleEndian) reverse(lengthNumberOctets); 6329 size_t length = *cast(size_t *) lengthNumberOctets.ptr; 6330 6331 if (length <= 127u) 6332 throw new ASN1LengthException 6333 ( 6334 "This exception was thrown because you attempted to " ~ 6335 "decode a Canonical Encoding Rules (CER) encoded " ~ 6336 "element whose length was encoded in definite long " ~ 6337 "form, and encoded on more octets than necessary, " ~ 6338 "which is prohibited by the specification for " ~ 6339 "Canonical Encoding Rules (CER). Specifically, it " ~ 6340 "was encoded in definite-long form when it was less " ~ 6341 "than or equal to 127, which could have been encoded " ~ 6342 "in definite-short form. " ~ 6343 forMoreInformationText ~ debugInformationText ~ reportBugsText 6344 ); 6345 6346 if ((cursor + length) < cursor) // This catches an overflow. 6347 throw new ASN1LengthException 6348 ( 6349 "This exception was thrown because you attempted to " ~ 6350 "decode a Canonical Encoding Rules (CER) encoded element " ~ 6351 "that indicated that it was exceedingly large--so " ~ 6352 "large, in fact, that it cannot be stored on this " ~ 6353 "computer (18 exabytes if you are on a 64-bit system). " ~ 6354 "This may indicate that the data you attempted to " ~ 6355 "decode was either corrupted, malformed, or deliberately " ~ 6356 "crafted to hack you. You would be wise to ensure that " ~ 6357 "you are running the latest stable version of this " ~ 6358 "library. " 6359 ); 6360 6361 cursor += (numberOfLengthOctets); 6362 6363 if ((cursor + length) > bytes.length) 6364 throw new ASN1TruncationException 6365 ( 6366 length, 6367 (bytes.length - cursor), 6368 "decode the value of a Canonical Encoding Rules (CER) encoded element" 6369 ); 6370 6371 this.value = bytes[cursor .. cursor+length].dup; 6372 return (cursor + length); 6373 } 6374 else // Indefinite 6375 { 6376 if (this.construction != ASN1Construction.constructed) 6377 throw new ASN1ConstructionException 6378 (this.construction, "decode an indefinite-length element"); 6379 6380 if (++(this.lengthRecursionCount) > this.nestingRecursionLimit) 6381 { 6382 this.lengthRecursionCount = 0u; 6383 throw new ASN1RecursionException 6384 ( 6385 this.nestingRecursionLimit, 6386 "decode a Canonical Encoding Rules (CER) encoded element " ~ 6387 "whose value was encoded with indefinite length form " ~ 6388 "and constructed recursively from deeply nested elements" 6389 ); 6390 } 6391 6392 immutable size_t startOfValue = ++cursor; 6393 size_t sentinel = cursor; // Used to track the length of the nested elements. 6394 while (sentinel < bytes.length) 6395 { 6396 CERElement child = new CERElement(sentinel, bytes); 6397 if 6398 ( 6399 child.tagClass == ASN1TagClass.universal && 6400 child.construction == ASN1Construction.primitive && 6401 child.tagNumber == ASN1UniversalType.endOfContent && 6402 child.length == 0u 6403 ) 6404 break; 6405 } 6406 6407 if (sentinel == bytes.length && (bytes[sentinel-1] != 0x00u || bytes[sentinel-2] != 0x00u)) 6408 throw new ASN1TruncationException 6409 ( 6410 length, 6411 (length + 2u), 6412 "find the END OF CONTENT octets (two consecutive null " ~ 6413 "bytes) of an indefinite-length encoded " ~ 6414 "Canonical Encoding Rules (CER) element" 6415 ); 6416 6417 this.lengthRecursionCount--; 6418 this.value = bytes[startOfValue .. sentinel-2u].dup; 6419 return (sentinel); 6420 } 6421 } 6422 else // Definite Short 6423 { 6424 if (this.construction == ASN1Construction.constructed) 6425 throw new ASN1ConstructionException 6426 (this.construction, "decode a short definite-length Canonical Encoding Rules (CER) encoded element"); 6427 6428 ubyte length = (bytes[cursor++] & 0x7Fu); 6429 6430 if ((cursor + length) > bytes.length) 6431 throw new ASN1TruncationException 6432 ( 6433 length, 6434 ((bytes.length - 1) - cursor), 6435 "decode the value of a Canonical Encoding Rules (CER) encoded element" 6436 ); 6437 6438 this.value = bytes[cursor .. cursor+length].dup; 6439 return (cursor + length); 6440 } 6441 } 6442 6443 /** 6444 This differs from $(D this.value) in that 6445 $(D this.value) only returns the value octets, whereas 6446 $(D this.toBytes) returns the type tag, length tag / octets, 6447 and the value octets, all concatenated. 6448 6449 This is the exact same as $(D this.opCast!(ubyte[])()). 6450 6451 Returns: type tag, length tag, and value, all concatenated as a $(D ubyte) array. 6452 */ 6453 public @property @system nothrow 6454 ubyte[] toBytes() const 6455 { 6456 ubyte[] tagBytes = [ 0x00u ]; 6457 tagBytes[0] |= cast(ubyte) this.tagClass; 6458 tagBytes[0] |= cast(ubyte) this.construction; 6459 6460 if (this.tagNumber < 31u) 6461 { 6462 tagBytes[0] |= cast(ubyte) this.tagNumber; 6463 } 6464 else 6465 { 6466 /* 6467 Per section 8.1.2.4 of X.690: 6468 The last five bits of the first byte being set indicate that 6469 the tag number is encoded in base-128 on the subsequent octets, 6470 using the first bit of each subsequent octet to indicate if the 6471 encoding continues on the next octet, just like how the 6472 individual numbers of OBJECT IDENTIFIER and RELATIVE OBJECT 6473 IDENTIFIER are encoded. 6474 */ 6475 tagBytes[0] |= cast(ubyte) 0b00011111u; 6476 size_t number = this.tagNumber; // We do not want to modify by reference. 6477 ubyte[] encodedNumber; 6478 while (number != 0u) 6479 { 6480 ubyte[] numberbytes; 6481 numberbytes.length = size_t.sizeof+1; 6482 *cast(size_t *) numberbytes.ptr = number; 6483 if ((numberbytes[0] & 0x80u) == 0u) numberbytes[0] |= 0x80u; 6484 encodedNumber = numberbytes[0] ~ encodedNumber; 6485 number >>= 7u; 6486 } 6487 tagBytes ~= encodedNumber; 6488 tagBytes[$-1] &= 0x7Fu; // Set first bit of last byte to zero. 6489 } 6490 6491 ubyte[] lengthOctets = [ 0x00u ]; 6492 switch (this.construction) 6493 { 6494 case (ASN1Construction.primitive): 6495 { 6496 if (this.length < 127u) 6497 { 6498 lengthOctets = [ cast(ubyte) this.length ]; 6499 } 6500 else 6501 { 6502 size_t length = cast(size_t) this.value.length; 6503 ubyte[] lengthNumberOctets = cast(ubyte[]) *cast(ubyte[size_t.sizeof] *) &length; 6504 version (LittleEndian) reverse(lengthNumberOctets); 6505 size_t startOfNonPadding = 0u; 6506 for (size_t i = 0u; i < size_t.sizeof; i++) 6507 { 6508 if (lengthNumberOctets[i] != 0x00u) break; 6509 startOfNonPadding++; 6510 } 6511 lengthNumberOctets = lengthNumberOctets[startOfNonPadding .. $]; 6512 lengthOctets = [ cast(ubyte) (0x80u + lengthNumberOctets.length) ]; 6513 lengthOctets ~= lengthNumberOctets; 6514 } 6515 break; 6516 } 6517 case (ASN1Construction.constructed): 6518 { 6519 lengthOctets = [ 0x80u ]; 6520 break; 6521 } 6522 default: 6523 { 6524 assert(0, "Invalid ASN1Construction encountered!"); 6525 } 6526 } 6527 return ( 6528 tagBytes ~ 6529 lengthOctets ~ 6530 this.value ~ 6531 (this.construction == ASN1Construction.constructed ? cast(ubyte[]) [ 0x00u, 0x00u ] : cast(ubyte[]) []) 6532 ); 6533 } 6534 6535 /** 6536 This differs from $(D this.value) in that 6537 $(D this.value) only returns the value octets, whereas 6538 $(D this.toBytes) returns the type tag, length tag / octets, 6539 and the value octets, all concatenated. 6540 6541 This is the exact same as $(D this.toBytes()). 6542 6543 Returns: type tag, length tag, and value, all concatenated as a $(D ubyte) array. 6544 */ 6545 public @system nothrow 6546 ubyte[] opCast(T = ubyte[])() 6547 { 6548 return this.toBytes(); 6549 } 6550 6551 } 6552 6553 // Tests of all types using definite-short encoding. 6554 @system 6555 unittest 6556 { 6557 // Test data 6558 immutable ubyte[] dataEndOfContent = [ 0x00u, 0x00u ]; 6559 immutable ubyte[] dataBoolean = [ 0x01u, 0x01u, 0xFFu ]; 6560 immutable ubyte[] dataInteger = [ 0x02u, 0x01u, 0x1Bu ]; 6561 immutable ubyte[] dataBitString = [ 0x03u, 0x03u, 0x07u, 0xF0u, 0x80u ]; 6562 immutable ubyte[] dataOctetString = [ 0x04u, 0x04u, 0xFF, 0x00u, 0x88u, 0x14u ]; 6563 immutable ubyte[] dataNull = [ 0x05u, 0x00u ]; 6564 immutable ubyte[] dataOID = [ 0x06u, 0x04u, 0x2Bu, 0x06u, 0x04u, 0x01u ]; 6565 immutable ubyte[] dataOD = [ 0x07u, 0x05u, 'H', 'N', 'E', 'L', 'O' ]; 6566 immutable ubyte[] dataExternal = [ 6567 0x28u, 0x80u, 0x06u, 0x03u, 0x29u, 0x05u, 0x07u, 0x82u, 6568 0x04u, 0x01u, 0x02u, 0x03u, 0x04u, 0x00u, 0x00u ]; 6569 immutable ubyte[] dataReal = [ 0x09u, 0x03u, 0x80u, 0xFBu, 0x05u ]; // 0.15625 (From StackOverflow question) 6570 immutable ubyte[] dataEnum = [ 0x0Au, 0x01u, 0x3Fu ]; 6571 immutable ubyte[] dataEmbeddedPDV = [ 6572 0x2Bu, 0x80u, 0x80u, 0x02u, 0x85u, 0x00u, 0x82u, 0x04u, 6573 0x01u, 0x02u, 0x03u, 0x04u, 0x00u, 0x00u ]; 6574 immutable ubyte[] dataUTF8 = [ 0x0Cu, 0x05u, 'H', 'E', 'N', 'L', 'O' ]; 6575 immutable ubyte[] dataROID = [ 0x0Du, 0x03u, 0x06u, 0x04u, 0x01u ]; 6576 // sequence 6577 // set 6578 immutable ubyte[] dataNumeric = [ 0x12u, 0x07u, '8', '6', '7', '5', '3', '0', '9' ]; 6579 immutable ubyte[] dataPrintable = [ 0x13u, 0x06u, '8', '6', ' ', 'b', 'f', '8' ]; 6580 immutable ubyte[] dataTeletex = [ 0x14u, 0x06u, 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]; 6581 immutable ubyte[] dataVideotex = [ 0x15u, 0x06u, 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]; 6582 immutable ubyte[] dataIA5 = [ 0x16u, 0x08u, 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S' ]; 6583 immutable ubyte[] dataUTC = [ 0x17u, 0x0Du, '1', '7', '0', '8', '3', '1', '1', '3', '4', '5', '0', '0', 'Z' ]; 6584 immutable ubyte[] dataGT = [ 0x18u, 0x0Fu, '2', '0', '1', '7', '0', '8', '3', '1', '1', '3', '4', '5', '0', '0', 'Z' ]; 6585 immutable ubyte[] dataGraphic = [ 0x19u, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ]; 6586 immutable ubyte[] dataVisible = [ 0x1Au, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ]; 6587 immutable ubyte[] dataGeneral = [ 0x1Bu, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ]; 6588 immutable ubyte[] dataUniversal = [ 6589 0x1Cu, 0x10u, 6590 0x00u, 0x00u, 0x00u, 0x61u, 6591 0x00u, 0x00u, 0x00u, 0x62u, 6592 0x00u, 0x00u, 0x00u, 0x63u, 6593 0x00u, 0x00u, 0x00u, 0x64u 6594 ]; // Big-endian "abcd" 6595 immutable ubyte[] dataCharacter = [ 6596 0x3Du, 0x80u, 0x80u, 0x06u, 0x81u, 0x04u, 0x29u, 0x06u, 6597 0x04u, 0x01u, 0x82u, 0x05u, 0x48u, 0x45u, 0x4Eu, 0x4Cu, 6598 0x4Fu, 0x00u, 0x00u ]; 6599 immutable ubyte[] dataBMP = [ 0x1Eu, 0x08u, 0x00u, 0x61u, 0x00u, 0x62u, 0x00u, 0x63u, 0x00u, 0x64u ]; // Big-endian "abcd" 6600 6601 // Combine it all 6602 ubyte[] data = 6603 (dataEndOfContent ~ 6604 dataBoolean ~ 6605 dataInteger ~ 6606 dataBitString ~ 6607 dataOctetString ~ 6608 dataNull ~ 6609 dataOID ~ 6610 dataOD ~ 6611 dataExternal ~ 6612 dataReal ~ 6613 dataEnum ~ 6614 dataEmbeddedPDV ~ 6615 dataUTF8 ~ 6616 dataROID ~ 6617 dataNumeric ~ 6618 dataPrintable ~ 6619 dataTeletex ~ 6620 dataVideotex ~ 6621 dataIA5 ~ 6622 dataUTC ~ 6623 dataGT ~ 6624 dataGraphic ~ 6625 dataVisible ~ 6626 dataGeneral ~ 6627 dataUniversal ~ 6628 dataCharacter ~ 6629 dataBMP).dup; 6630 6631 CERElement[] result; 6632 6633 size_t i = 0u; 6634 while (i < data.length) 6635 result ~= new CERElement(i, data); 6636 6637 // Pre-processing 6638 External x = result[8].external; 6639 EmbeddedPDV m = result[11].embeddedPresentationDataValue; 6640 CharacterString c = result[25].characterString; 6641 6642 // Ensure accessors decode the data correctly. 6643 assert(result[1].boolean == true); 6644 assert(result[2].integer!long == 27L); 6645 assert(result[3].bitString == [ true, true, true, true, false, false, false, false, true ]); 6646 assert(result[4].octetString == [ 0xFFu, 0x00u, 0x88u, 0x14u ]); 6647 assert(result[6].objectIdentifier == new OID(OIDNode(0x01u), OIDNode(0x03u), OIDNode(0x06u), OIDNode(0x04u), OIDNode(0x01u))); 6648 assert(result[7].objectDescriptor == result[7].objectDescriptor); 6649 assert((x.identification.syntax == new OID(1u, 1u, 5u, 7u)) && (x.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ])); 6650 assert(result[9].realNumber!float == 0.15625); 6651 assert(result[9].realNumber!double == 0.15625); 6652 assert(result[10].enumerated!long == 63L); 6653 assert((m.identification.fixed == true) && (m.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ])); 6654 assert(result[12].utf8String == "HENLO"); 6655 assert(result[13].relativeObjectIdentifier == [ OIDNode(6), OIDNode(4), OIDNode(1) ]); 6656 assert(result[14].numericString == "8675309"); 6657 assert(result[15].printableString == "86 bf8"); 6658 assert(result[16].teletexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]); 6659 assert(result[17].videotexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]); 6660 assert(result[18].ia5String == "BORTHERS"); 6661 assert(result[19].utcTime == DateTime(2017, 8, 31, 13, 45)); 6662 assert(result[20].generalizedTime == DateTime(2017, 8, 31, 13, 45)); 6663 assert(result[21].graphicString == "PowerThirst"); 6664 assert(result[22].visibleString == "PowerThirst"); 6665 assert(result[23].generalString == "PowerThirst"); 6666 assert(result[24].universalString == "abcd"d); 6667 assert((c.identification.syntax == new OID(1u, 1u, 6u, 4u, 1u)) && (c.stringValue == "HENLO"w)); 6668 6669 result = []; 6670 while (data.length > 0) 6671 result ~= new CERElement(data); 6672 6673 // Pre-processing 6674 x = result[8].external; 6675 m = result[11].embeddedPresentationDataValue; 6676 c = result[25].characterString; 6677 6678 // Ensure accessors decode the data correctly. 6679 assert(result[1].boolean == true); 6680 assert(result[2].integer!long == 27L); 6681 assert(result[3].bitString == [ true, true, true, true, false, false, false, false, true ]); 6682 assert(result[4].octetString == [ 0xFFu, 0x00u, 0x88u, 0x14u ]); 6683 assert(result[6].objectIdentifier == new OID(OIDNode(0x01u), OIDNode(0x03u), OIDNode(0x06u), OIDNode(0x04u), OIDNode(0x01u))); 6684 assert(result[7].objectDescriptor == result[7].objectDescriptor); 6685 assert((x.identification.syntax == new OID(1u, 1u, 5u, 7u)) && (x.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ])); 6686 assert(result[9].realNumber!float == 0.15625); 6687 assert(result[9].realNumber!double == 0.15625); 6688 assert(result[10].enumerated!long == 63L); 6689 assert((m.identification.fixed == true) && (m.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ])); 6690 assert(result[12].utf8String == "HENLO"); 6691 assert(result[13].relativeObjectIdentifier == [ OIDNode(6), OIDNode(4), OIDNode(1) ]); 6692 assert(result[14].numericString == "8675309"); 6693 assert(result[15].printableString == "86 bf8"); 6694 assert(result[16].teletexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]); 6695 assert(result[17].videotexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]); 6696 assert(result[18].ia5String == "BORTHERS"); 6697 assert(result[19].utcTime == DateTime(2017, 8, 31, 13, 45)); 6698 assert(result[20].generalizedTime == DateTime(2017, 8, 31, 13, 45)); 6699 assert(result[21].graphicString == "PowerThirst"); 6700 assert(result[22].visibleString == "PowerThirst"); 6701 assert(result[23].generalString == "PowerThirst"); 6702 assert(result[24].universalString == "abcd"d); 6703 assert((c.identification.syntax == new OID(1u, 1u, 6u, 4u, 1u)) && (c.stringValue == "HENLO"w)); 6704 } 6705 6706 // Test of definite-long encoding 6707 @system 6708 unittest 6709 { 6710 ubyte[] data = [ // 192 characters of boomer-posting 6711 0x0Cu, 0x81u, 0xC0u, 6712 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6713 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6714 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6715 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6716 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6717 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6718 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6719 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6720 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6721 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6722 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6723 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n' 6724 ]; 6725 6726 data = (data ~ data ~ data); // Triple the data, to catch any bugs that arise with subsequent values. 6727 6728 CERElement[] result; 6729 size_t i = 0u; 6730 while (i < data.length) 6731 result ~= new CERElement(i, data); 6732 6733 assert(result.length == 3); 6734 assert(result[0].utf8String[0 .. 5] == "AMREN"); 6735 assert(result[1].utf8String[6 .. 14] == "BORTHERS"); 6736 assert(result[2].utf8String[$-2] == '!'); 6737 6738 result = []; 6739 while (data.length > 0) 6740 result ~= new CERElement(data); 6741 6742 assert(result.length == 3); 6743 assert(result[0].utf8String[0 .. 5] == "AMREN"); 6744 assert(result[1].utf8String[6 .. 14] == "BORTHERS"); 6745 assert(result[2].utf8String[$-2] == '!'); 6746 } 6747 6748 // Test of indefinite-length encoding 6749 // @system 6750 // unittest 6751 // { 6752 // ubyte[] data = [ // 192 characters of boomer-posting 6753 // 0x2Cu, 0x80u, 6754 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6755 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6756 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6757 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6758 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6759 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6760 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6761 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6762 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6763 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6764 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6765 // 0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n', 6766 // 0x00u, 0x00u 6767 // ]; 6768 6769 // data = (data ~ data ~ data); // Triple the data, to catch any bugs that arise with subsequent values. 6770 6771 // CERElement[] result; 6772 // size_t i = 0u; 6773 // while (i < data.length) 6774 // result ~= new CERElement(i, data); 6775 6776 // assert(result.length == 3); 6777 // assert(result[0].length == 216u); 6778 // assert(result[0].utf8String[2 .. 7] == "AMREN"); 6779 // assert(result[1].utf8String[8 .. 16] == "BORTHERS"); 6780 // assert(result[2].utf8String[$-2] == '!'); 6781 6782 // result = []; 6783 // while (data.length > 0) 6784 // result ~= new CERElement(data); 6785 6786 // assert(result.length == 3); 6787 // assert(result[0].utf8String[2 .. 7] == "AMREN"); 6788 // assert(result[1].utf8String[8 .. 16] == "BORTHERS"); 6789 // assert(result[2].utf8String[$-2] == '!'); 6790 // } 6791 6792 // Test deeply (but not too deeply) nested indefinite-length elements 6793 @system 6794 unittest 6795 { 6796 ubyte[] data = [ 6797 0x2Cu, 0x80u, 6798 0x2Cu, 0x80u, 6799 0x0Cu, 0x02u, 'H', 'I', 6800 0x00u, 0x00u, 6801 0x00u, 0x00u ]; 6802 assertNotThrown!ASN1Exception(new CERElement(data)); 6803 } 6804 6805 // Try to induce infinite recursion for an indefinite-length element 6806 @system 6807 unittest 6808 { 6809 ubyte[] invalid = []; 6810 for (size_t i = 0u; i < CERElement.nestingRecursionLimit+1; i++) 6811 { 6812 invalid ~= [ 0x2Cu, 0x80u ]; 6813 } 6814 assertThrown!ASN1RecursionException(new CERElement(invalid)); 6815 } 6816 6817 // Try to crash everything with a short indefinite-length element 6818 @system 6819 unittest 6820 { 6821 ubyte[] invalid; 6822 invalid = [ 0x2Cu, 0x80u, 0x01u ]; 6823 assertThrown!ASN1Exception(new CERElement(invalid)); 6824 invalid = [ 0x2Cu, 0x80u, 0x00u ]; 6825 assertThrown!ASN1Exception(new CERElement(invalid)); 6826 } 6827 6828 // Test an embedded value with two adjacent null octets 6829 @system 6830 unittest 6831 { 6832 ubyte[] data = [ 6833 0x24u, 0x80u, 6834 0x04u, 0x04u, 0x00u, 0x00u, 0x00u, 0x00u, // These should not indicate the end. 6835 0x00u, 0x00u ]; // These should. 6836 assert((new CERElement(data)).value == [ 0x04u, 0x04u, 0x00u, 0x00u, 0x00u, 0x00u ]); 6837 } 6838 6839 /* 6840 Test of OCTET STRING encoding on 500 bytes (+4 for type and length tags) 6841 6842 The number 500 was specifically selected for this test because CER 6843 uses 1000 as the threshold after which OCTET STRING must be represented 6844 as a constructed sequence of definite-length-encoded OCTET STRINGS, 6845 followed by an EOC element, but 500 is also big enough to require 6846 the length to be encoded on two octets in definite-long form. 6847 */ 6848 @system 6849 unittest 6850 { 6851 ubyte[] test; 6852 test.length = 504u; 6853 test[0] = cast(ubyte) ASN1UniversalType.octetString; 6854 test[1] = 0b10000010u; // Length is encoded on next two octets 6855 test[2] = 0x01u; // Most significant byte of length 6856 test[3] = 0xF4u; // Least significant byte of length 6857 test[4] = 0x0Au; // First byte of the encoded value 6858 test[5 .. $-1] = 0x0Bu; 6859 test[$-1] = 0x0Cu; 6860 6861 CERElement el; 6862 assertNotThrown!Exception(el = new CERElement(test)); 6863 ubyte[] output = el.octetString; 6864 assert(output.length == 500u); 6865 assert(output[0] == 0x0Au); 6866 assert(output[1] == 0x0Bu); 6867 assert(output[$-2] == 0x0Bu); 6868 assert(output[$-1] == 0x0Cu); 6869 } 6870 6871 // Assert all single-byte encodings do not decode successfully. 6872 @system 6873 unittest 6874 { 6875 for (ubyte i = 0x00u; i < ubyte.max; i++) 6876 { 6877 ubyte[] data = [i]; 6878 assertThrown!Exception(new CERElement(data)); 6879 } 6880 6881 size_t index; 6882 for (ubyte i = 0x00u; i < ubyte.max; i++) 6883 { 6884 immutable ubyte[] data = [i]; 6885 assertThrown!Exception(new CERElement(index, data)); 6886 } 6887 } 6888 6889 // Test long-form tag number (when # >= 31) with leading zero bytes (0x80) 6890 @system 6891 unittest 6892 { 6893 ubyte[] invalid; 6894 invalid = [ 0b10011111u, 0b10000000u ]; 6895 assertThrown!ASN1TagException(new CERElement(invalid)); 6896 6897 invalid = [ 0b10011111u, 0b10000000u, 0b10000000u ]; 6898 assertThrown!ASN1TagException(new CERElement(invalid)); 6899 6900 invalid = [ 0b10011111u, 0b10000000u, 0b10000111u ]; 6901 assertThrown!ASN1TagException(new CERElement(invalid)); 6902 } 6903 6904 // Test long-form tag numbers do not encode with leading zeroes 6905 @system 6906 unittest 6907 { 6908 ubyte[] invalid; 6909 CERElement element = new CERElement(); 6910 element.tagNumber = 73u; 6911 assert((element.toBytes)[1] != 0x80u); 6912 } 6913 6914 // Test that a value that is a byte too short does not throw a RangeError. 6915 @system 6916 unittest 6917 { 6918 ubyte[] test = [ 0x00u, 0x03u, 0x00u, 0x00u ]; 6919 assertThrown!ASN1TruncationException(new CERElement(test)); 6920 } 6921 6922 // Test that a misleading definite-long length byte does not throw a RangeError. 6923 @system 6924 unittest 6925 { 6926 ubyte[] invalid = [ 0b00000000u, 0b10000001u ]; 6927 assertThrown!ASN1TruncationException(new CERElement(invalid)); 6928 } 6929 6930 // Test that leading zeroes in definite long length encodings throw exceptions 6931 @system 6932 unittest 6933 { 6934 ubyte[] invalid = [ 0b00000000u, 0b10000010u, 0b00000000u, 0b00000001u ]; 6935 assertThrown!ASN1LengthException(new CERElement(invalid)); 6936 } 6937 6938 // Test that a very large value does not cause a segfault or something 6939 @system 6940 unittest 6941 { 6942 size_t biggest = cast(size_t) int.max; 6943 ubyte[] big = [ 0x00u, 0x80u ]; 6944 big[1] += cast(ubyte) int.sizeof; 6945 big ~= cast(ubyte[]) *cast(ubyte[int.sizeof] *) &biggest; 6946 big ~= [ 0x01u, 0x02u, 0x03u ]; // Plus some arbitrary data. 6947 assertThrown!ASN1TruncationException(new CERElement(big)); 6948 } 6949 6950 // Test that the largest possible item does not cause a segfault or something 6951 @system 6952 unittest 6953 { 6954 size_t biggest = size_t.max; 6955 ubyte[] big = [ 0x00u, 0x80u ]; 6956 big[1] += cast(ubyte) size_t.sizeof; 6957 big ~= cast(ubyte[]) *cast(ubyte[size_t.sizeof] *) &biggest; 6958 big ~= [ 0x01u, 0x02u, 0x03u ]; // Plus some arbitrary data. 6959 assertThrown!ASN1LengthException(new CERElement(big)); 6960 } 6961 6962 // Test that a short indefinite-length element does not throw a RangeError 6963 @system 6964 unittest 6965 { 6966 ubyte[] naughty = [ 0x3F, 0x00u, 0x80, 0x00u ]; 6967 size_t bytesRead = 0u; 6968 assertThrown!ASN1TruncationException(new CERElement(bytesRead, naughty)); 6969 } 6970 6971 // Test that a short indefinite-length element does not throw a RangeError 6972 @system 6973 unittest 6974 { 6975 ubyte[] naughty = [ 0x3F, 0x00u, 0x80, 0x00u, 0x00u ]; 6976 size_t bytesRead = 0u; 6977 assertNotThrown!ASN1TruncationException(new CERElement(bytesRead, naughty)); 6978 } 6979 6980 // Test that a valueless long-form definite-length element does not throw a RangeError 6981 @system 6982 unittest 6983 { 6984 ubyte[] naughty = [ 0x00u, 0x82, 0x00u, 0x01u ]; 6985 size_t bytesRead = 0u; 6986 assertThrown!ASN1LengthException(new CERElement(bytesRead, naughty)); 6987 } 6988 6989 // PyASN1 Comparison Testing 6990 @system 6991 unittest 6992 { 6993 CERElement e = new CERElement(); 6994 e.boolean = false; 6995 assert(e.value == [ 0x00u ]); 6996 e.integer = 5; 6997 assert(e.value == [ 0x05u ]); 6998 e.bitString = [ 6999 true, false, true, true, false, false, true, true, 7000 true, false, false, false ]; 7001 assert(e.value == [ 0x04u, 0xB3u, 0x80u ]); 7002 e.bitString = [ 7003 true, false, true, true, false, true, false, false ]; 7004 assert(e.value == [ 0x00u, 0xB4u ]); 7005 e.objectIdentifier = new OID(1, 2, 0, 256, 79999, 7); 7006 assert(e.value == [ 7007 0x2Au, 0x00u, 0x82u, 0x00u, 0x84u, 0xF0u, 0x7Fu, 0x07u ]); 7008 e.enumerated = 5; 7009 assert(e.value == [ 0x05u ]); 7010 e.enumerated = 90000; 7011 assert(e.value == [ 0x01u, 0x5Fu, 0x90u ]); 7012 } 7013 7014 // Test that all data types that cannot have value length = 0 throw exceptions. 7015 // See CVE-2015-5726. 7016 @system 7017 unittest 7018 { 7019 CERElement el = new CERElement(); 7020 assertThrown!ASN1Exception(el.boolean); 7021 assertThrown!ASN1Exception(el.integer!byte); 7022 assertThrown!ASN1Exception(el.integer!short); 7023 assertThrown!ASN1Exception(el.integer!int); 7024 assertThrown!ASN1Exception(el.integer!long); 7025 assertThrown!ASN1Exception(el.bitString); 7026 assertThrown!ASN1Exception(el.objectIdentifier); 7027 assertThrown!ASN1Exception(el.enumerated!byte); 7028 assertThrown!ASN1Exception(el.enumerated!short); 7029 assertThrown!ASN1Exception(el.enumerated!int); 7030 assertThrown!ASN1Exception(el.enumerated!long); 7031 assertThrown!ASN1Exception(el.generalizedTime); 7032 assertThrown!ASN1Exception(el.utcTime); 7033 }