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