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