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