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