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