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