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