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