1 module hunt.security.util.DerInputStream; 2 3 import hunt.security.util.DerIndefLenConverter; 4 import hunt.security.util.DerInputBuffer; 5 import hunt.security.util.DerValue; 6 // import hunt.security.util.ObjectIdentifier; 7 8 import hunt.stream.Common; 9 import hunt.Exceptions; 10 11 import std.container.array; 12 import std.bigint; 13 import std.bitmanip; 14 import std.datetime; 15 import std.conv; 16 17 alias BigInteger = BigInt; 18 19 /** 20 * A DER input stream, used for parsing ASN.1 DER-encoded data such as 21 * that found in X.509 certificates. DER is a subset of BER/1, which has 22 * the advantage that it allows only a single encoding of primitive data. 23 * (High level data such as dates still support many encodings.) That is, 24 * it uses the "Definite" Encoding Rules (DER) not the "Basic" ones (BER). 25 * 26 * <P>Note that, like BER/1, DER streams are streams of explicitly 27 * tagged data values. Accordingly, this programming interface does 28 * not expose any variant of the java.io.InputStream interface, since 29 * that kind of input stream holds untagged data values and using that 30 * I/O model could prevent correct parsing of the DER data. 31 * 32 * <P>At this time, this class supports only a subset of the types of DER 33 * data encodings which are defined. That subset is sufficient for parsing 34 * most X.509 certificates. 35 * 36 * 37 * @author David Brownell 38 * @author Amit Kapoor 39 * @author Hemma Prafullchandra 40 */ 41 42 class DerInputStream { 43 44 /* 45 * This version only supports fully buffered DER. This is easy to 46 * work with, though if large objects are manipulated DER becomes 47 * awkward to deal with. That's where BER is useful, since BER 48 * handles streaming data relatively well. 49 */ 50 DerInputBuffer buffer; 51 52 /** The DER tag of the value; one of the tag_ constants. */ 53 byte tag; 54 55 /** 56 * Create a DER input stream from a data buffer. The buffer is not 57 * copied, it is shared. Accordingly, the buffer should be treated 58 * as read-only. 59 * 60 * @param data the buffer from which to create the string (CONSUMED) 61 */ 62 this(byte[] data) { 63 initilize(data, 0, data.length, true); 64 } 65 66 /** 67 * Create a DER input stream from part of a data buffer with 68 * additional arg to control whether DER checks are enforced. 69 * The buffer is not copied, it is shared. Accordingly, the 70 * buffer should be treated as read-only. 71 * 72 * @param data the buffer from which to create the string (CONSUMED) 73 * @param offset the first index of <em>data</em> which will 74 * be read as DER input in the new stream 75 * @param len how long a chunk of the buffer to use, 76 * starting at "offset" 77 * @param allowBER whether to allow constructed indefinite-length 78 * encoding as well as tolerate leading 0s 79 */ 80 this(byte[] data, int offset, int len, 81 bool allowBER) { 82 initilize(data, offset, len, allowBER); 83 } 84 85 /** 86 * Create a DER input stream from part of a data buffer. 87 * The buffer is not copied, it is shared. Accordingly, the 88 * buffer should be treated as read-only. 89 * 90 * @param data the buffer from which to create the string (CONSUMED) 91 * @param offset the first index of <em>data</em> which will 92 * be read as DER input in the new stream 93 * @param len how long a chunk of the buffer to use, 94 * starting at "offset" 95 */ 96 this(byte[] data, int offset, int len) { 97 initilize(data, offset, len, true); 98 } 99 100 /* 101 * private helper routine 102 */ 103 private void initilize(byte[] data, int offset, size_t len, bool allowBER) { 104 if ((offset+2 > data.length) || (offset+len > data.length)) { 105 throw new IOException("Encoding bytes too short"); 106 } 107 // check for indefinite length encoding 108 if (DerIndefLenConverter.isIndefinite(data[offset+1])) { 109 if (!allowBER) { 110 throw new IOException("Indefinite length BER encoding found"); 111 } else { 112 // byte[] inData = new byte[len]; 113 // System.arraycopy(data, offset, inData, 0, len); 114 byte[] inData = data[offset .. offset+len].dup; 115 116 DerIndefLenConverter derIn = new DerIndefLenConverter(); 117 buffer = new DerInputBuffer(derIn.convert(inData), allowBER); 118 } 119 } else { 120 buffer = new DerInputBuffer(data, offset, len, allowBER); 121 } 122 buffer.mark(int.max); 123 } 124 125 this(DerInputBuffer buf) { 126 buffer = buf; 127 buffer.mark(int.max); 128 } 129 130 /** 131 * Creates a new DER input stream from part of this input stream. 132 * 133 * @param len how long a chunk of the current input stream to use, 134 * starting at the current position. 135 * @param do_skip true if the existing data in the input stream should 136 * be skipped. If this value is false, the next data read 137 * on this stream and the newly created stream will be the 138 * same. 139 */ 140 DerInputStream subStream(int len, bool do_skip) { 141 DerInputBuffer newbuf = buffer.dup; 142 143 newbuf.truncate(len); 144 if (do_skip) { 145 buffer.skip(len); 146 } 147 return new DerInputStream(newbuf); 148 } 149 150 /** 151 * Return what has been written to this DerInputStream 152 * as a byte array. Useful for debugging. 153 */ 154 byte[] toByteArray() { 155 return buffer.toByteArray(); 156 } 157 158 /* 159 * PRIMITIVES -- these are "universal" ASN.1 simple types. 160 * 161 * INTEGER, ENUMERATED, BIT STRING, OCTET STRING, NULL 162 * OBJECT IDENTIFIER, SEQUENCE (OF), SET (OF) 163 * UTF8String, PrintableString, T61String, IA5String, UTCTime, 164 * GeneralizedTime, BMPString. 165 * Note: UniversalString not supported till encoder is available. 166 */ 167 168 /** 169 * Get an integer from the input stream as an integer. 170 * 171 * @return the integer held in this DER input stream. 172 */ 173 int getInteger() { 174 if (buffer.read() != DerValue.tag_Integer) { 175 throw new IOException("DER input, Integer tag error"); 176 } 177 return buffer.getInteger(getLength(buffer)); 178 } 179 180 /** 181 * Get a integer from the input stream as a BigInteger object. 182 * 183 * @return the integer held in this DER input stream. 184 */ 185 BigInteger getBigInteger() { 186 if (buffer.read() != DerValue.tag_Integer) { 187 throw new IOException("DER input, Integer tag error"); 188 } 189 return buffer.getBigInteger(getLength(buffer), false); 190 } 191 192 /** 193 * Returns an ASN.1 INTEGER value as a positive BigInteger. 194 * This is just to deal with implementations that incorrectly encode 195 * some values as negative. 196 * 197 * @return the integer held in this DER value as a BigInteger. 198 */ 199 BigInteger getPositiveBigInteger() { 200 if (buffer.read() != DerValue.tag_Integer) { 201 throw new IOException("DER input, Integer tag error"); 202 } 203 return buffer.getBigInteger(getLength(buffer), true); 204 } 205 206 /** 207 * Get an enumerated from the input stream. 208 * 209 * @return the integer held in this DER input stream. 210 */ 211 int getEnumerated() { 212 if (buffer.read() != DerValue.tag_Enumerated) { 213 throw new IOException("DER input, Enumerated tag error"); 214 } 215 return buffer.getInteger(getLength(buffer)); 216 } 217 218 /** 219 * Get a bit string from the input stream. Padded bits (if any) 220 * will be stripped off before the bit string is returned. 221 */ 222 byte[] getBitString() { 223 if (buffer.read() != DerValue.tag_BitString) 224 throw new IOException("DER input not an bit string"); 225 226 return buffer.getBitString(getLength(buffer)); 227 } 228 229 /** 230 * Get a bit string from the input stream. The bit string need 231 * not be byte-aligned. 232 */ 233 BitArray getUnalignedBitString() { 234 if (buffer.read() != DerValue.tag_BitString) 235 throw new IOException("DER input not a bit string"); 236 237 int length = getLength(buffer) - 1; 238 239 /* 240 * First byte = number of excess bits in the last octet of the 241 * representation. 242 */ 243 int excessBits = buffer.read(); 244 if (excessBits < 0) { 245 throw new IOException("Unused bits of bit string invalid"); 246 } 247 int validBits = length*8 - excessBits; 248 if (validBits < 0) { 249 throw new IOException("Valid bits of bit string invalid"); 250 } 251 252 byte[] repn = new byte[length]; 253 254 if ((length != 0) && (buffer.read(repn) != length)) { 255 throw new IOException("Short read of DER bit string"); 256 } 257 258 // return new BitArray(validBits, repn); 259 implementationMissing(); 260 return BitArray.init; 261 } 262 263 /** 264 * Returns an ASN.1 OCTET STRING from the input stream. 265 */ 266 byte[] getOctetString() { 267 if (buffer.read() != DerValue.tag_OctetString) 268 throw new IOException("DER input not an octet string"); 269 270 int length = getLength(buffer); 271 byte[] retval = new byte[length]; 272 if ((length != 0) && (buffer.read(retval) != length)) 273 throw new IOException("Short read of DER octet string"); 274 275 return retval; 276 } 277 278 /** 279 * Returns the asked number of bytes from the input stream. 280 */ 281 void getBytes(byte[] val) { 282 if ((val.length != 0) && (buffer.read(val) != val.length)) { 283 throw new IOException("Short read of DER octet string"); 284 } 285 } 286 287 /** 288 * Reads an encoded null value from the input stream. 289 */ 290 void getNull() { 291 if (buffer.read() != DerValue.tag_Null || buffer.read() != 0) 292 throw new IOException("getNull, bad data"); 293 } 294 295 /** 296 * Reads an X.200 style Object Identifier from the stream. 297 */ 298 // ObjectIdentifier getOID() { 299 // return new ObjectIdentifier(this); 300 // } 301 302 /** 303 * Return a sequence of encoded entities. ASN.1 sequences are 304 * ordered, and they are often used, like a "struct" in C or C++, 305 * to group data values. They may have optional or context 306 * specific values. 307 * 308 * @param startLen guess about how long the sequence will be 309 * (used to initialize an auto-growing data structure) 310 * @return array of the values in the sequence 311 */ 312 DerValue[] getSequence(int startLen) { 313 tag = cast(byte)buffer.read(); 314 if (tag != DerValue.tag_Sequence) 315 throw new IOException("Sequence tag error"); 316 return readVector(startLen); 317 } 318 319 /** 320 * Return a set of encoded entities. ASN.1 sets are unordered, 321 * though DER may specify an order for some kinds of sets (such 322 * as the attributes in an X.500 relative distinguished name) 323 * to facilitate binary comparisons of encoded values. 324 * 325 * @param startLen guess about how large the set will be 326 * (used to initialize an auto-growing data structure) 327 * @return array of the values in the sequence 328 */ 329 DerValue[] getSet(int startLen) { 330 tag = cast(byte)buffer.read(); 331 if (tag != DerValue.tag_Set) 332 throw new IOException("Set tag error"); 333 return readVector(startLen); 334 } 335 336 /** 337 * Return a set of encoded entities. ASN.1 sets are unordered, 338 * though DER may specify an order for some kinds of sets (such 339 * as the attributes in an X.500 relative distinguished name) 340 * to facilitate binary comparisons of encoded values. 341 * 342 * @param startLen guess about how large the set will be 343 * (used to initialize an auto-growing data structure) 344 * @param implicit if true tag is assumed implicit. 345 * @return array of the values in the sequence 346 */ 347 DerValue[] getSet(int startLen, bool implicit) 348 { 349 tag = cast(byte)buffer.read(); 350 if (!implicit) { 351 if (tag != DerValue.tag_Set) { 352 throw new IOException("Set tag error"); 353 } 354 } 355 return (readVector(startLen)); 356 } 357 358 /* 359 * Read a "vector" of values ... set or sequence have the 360 * same encoding, except for the initial tag, so both use 361 * this same helper routine. 362 */ 363 protected DerValue[] readVector(int startLen) { 364 DerInputStream newstr; 365 366 byte lenByte = cast(byte)buffer.read(); 367 int len = getLength(lenByte, buffer); 368 369 implementationMissing(); 370 return null; 371 372 // if (len == -1) { 373 // // indefinite length encoding found 374 // int readLen = buffer.available(); 375 // int offset = 2; // for tag and length bytes 376 // byte[] indefData = new byte[readLen + offset]; 377 // indefData[0] = tag; 378 // indefData[1] = lenByte; 379 // DataInputStream dis = new DataInputStream(buffer); 380 // dis.readFully(indefData, offset, readLen); 381 // dis.close(); 382 // DerIndefLenConverter derIn = new DerIndefLenConverter(); 383 // buffer = new DerInputBuffer(derIn.convert(indefData), buffer.allowBER); 384 385 // if (tag != buffer.read()) 386 // throw new IOException("Indefinite length encoding" ~ 387 // " not supported"); 388 // len = DerInputStream.getLength(buffer); 389 // } 390 391 // if (len == 0) 392 // // return empty array instead of null, which should be 393 // // used only for missing optionals 394 // return new DerValue[0]; 395 396 // /* 397 // * Create a temporary stream from which to read the data, 398 // * unless it's not really needed. 399 // */ 400 // if (buffer.available() == len) 401 // newstr = this; 402 // else 403 // newstr = subStream(len, true); 404 405 // /* 406 // * Pull values out of the stream. 407 // */ 408 // Array!DerValue vec; // = new Vector<DerValue>(startLen); 409 // DerValue value; 410 411 // do { 412 // value = new DerValue(newstr.buffer, buffer.allowBER); 413 // vec.insertBack(value); 414 // } while (newstr.available() > 0); 415 416 // if (newstr.available() != 0) 417 // throw new IOException("Extra data at end of vector"); 418 419 // /* 420 // * Now stick them into the array we're returning. 421 // */ 422 // int i; 423 // size_t max = vec.length; 424 // DerValue[] retval = new DerValue[max]; 425 426 // for (i = 0; i < max; i++) 427 // retval[i] = vec[i]; 428 429 // return retval; 430 } 431 432 /** 433 * Get a single DER-encoded value from the input stream. 434 * It can often be useful to pull a value from the stream 435 * and defer parsing it. For example, you can pull a nested 436 * sequence out with one call, and only examine its elements 437 * later when you really need to. 438 */ 439 DerValue getDerValue() { 440 return new DerValue(buffer); 441 } 442 443 /** 444 * Read a string that was encoded as a UTF8String DER value. 445 */ 446 string getUTF8String() { 447 return readString(DerValue.tag_UTF8String, "UTF-8", "UTF8"); 448 } 449 450 /** 451 * Read a string that was encoded as a PrintableString DER value. 452 */ 453 string getPrintableString() { 454 return readString(DerValue.tag_PrintableString, "Printable", 455 "ASCII"); 456 } 457 458 /** 459 * Read a string that was encoded as a T61String DER value. 460 */ 461 string getT61String() { 462 /* 463 * Works for common characters between T61 and ASCII. 464 */ 465 return readString(DerValue.tag_T61String, "T61", "ISO-8859-1"); 466 } 467 468 /** 469 * Read a string that was encoded as a IA5tring DER value. 470 */ 471 string getIA5String() { 472 return readString(DerValue.tag_IA5String, "IA5", "ASCII"); 473 } 474 475 /** 476 * Read a string that was encoded as a BMPString DER value. 477 */ 478 string getBMPString() { 479 return readString(DerValue.tag_BMPString, "BMP", 480 "UnicodeBigUnmarked"); 481 } 482 483 /** 484 * Read a string that was encoded as a GeneralString DER value. 485 */ 486 string getGeneralString() { 487 return readString(DerValue.tag_GeneralString, "General", 488 "ASCII"); 489 } 490 491 /** 492 * Private helper routine to read an encoded string from the input 493 * stream. 494 * @param stringTag the tag for the type of string to read 495 * @param stringName a name to display in error messages 496 * @param enc the encoder to use to interpret the data. Should 497 * correspond to the stringTag above. 498 */ 499 private string readString(byte stringTag, string stringName, 500 string enc) { 501 502 if (buffer.read() != stringTag) 503 throw new IOException("DER input not a " ~ 504 stringName ~ " string"); 505 506 int length = getLength(buffer); 507 byte[] retval = new byte[length]; 508 if ((length != 0) && (buffer.read(retval) != length)) 509 throw new IOException("Short read of DER " ~ 510 stringName ~ " string"); 511 512 return cast(string)retval; 513 } 514 515 /** 516 * Get a UTC encoded time value from the input stream. 517 */ 518 // Date getUTCTime() { 519 // if (buffer.read() != DerValue.tag_UtcTime) 520 // throw new IOException("DER input, UTCtime tag invalid "); 521 // return buffer.getUTCTime(getLength(buffer)); 522 // } 523 524 /** 525 * Get a Generalized encoded time value from the input stream. 526 */ 527 // Date getGeneralizedTime() { 528 // if (buffer.read() != DerValue.tag_GeneralizedTime) 529 // throw new IOException("DER input, GeneralizedTime tag invalid "); 530 // return buffer.getGeneralizedTime(getLength(buffer)); 531 // } 532 533 /* 534 * Get a byte from the input stream. 535 */ 536 // package private 537 int getByte() { 538 return (0x00ff & buffer.read()); 539 } 540 541 int peekByte() { 542 return buffer.peek(); 543 } 544 545 // package private 546 int getLength() { 547 return getLength(buffer); 548 } 549 550 /* 551 * Get a length from the input stream, allowing for at most 32 bits of 552 * encoding to be used. (Not the same as getting a tagged integer!) 553 * 554 * @return the length or -1 if indefinite length found. 555 * @exception IOException on parsing error or unsupported lengths. 556 */ 557 static int getLength(InputStream stream) { 558 return getLength(stream.read(), stream); 559 } 560 561 /* 562 * Get a length from the input stream, allowing for at most 32 bits of 563 * encoding to be used. (Not the same as getting a tagged integer!) 564 * 565 * @return the length or -1 if indefinite length found. 566 * @exception IOException on parsing error or unsupported lengths. 567 */ 568 static int getLength(int lenByte, InputStream stream) { 569 int value, tmp; 570 if (lenByte == -1) { 571 throw new IOException("Short read of DER length"); 572 } 573 574 string mdName = "DerInputStream.getLength(): "; 575 tmp = lenByte; 576 if ((tmp & 0x080) == 0x00) { // short form, 1 byte datum 577 value = tmp; 578 } else { // long form or indefinite 579 tmp &= 0x07f; 580 581 /* 582 * NOTE: tmp == 0 indicates indefinite length encoded data. 583 * tmp > 4 indicates more than 4Gb of data. 584 */ 585 if (tmp == 0) 586 return -1; 587 if (tmp < 0 || tmp > 4) 588 throw new IOException(mdName ~ "lengthTag=" ~ tmp.to!string() ~ ", " 589 ~ ((tmp < 0) ? "incorrect DER encoding." : "too big.")); 590 591 value = 0x0ff & stream.read(); 592 tmp--; 593 if (value == 0) { 594 // DER requires length value be encoded in minimum number of bytes 595 throw new IOException(mdName ~ "Redundant length bytes found"); 596 } 597 while (tmp-- > 0) { 598 value <<= 8; 599 value += 0x0ff & stream.read(); 600 } 601 if (value < 0) { 602 throw new IOException(mdName ~ "Invalid length bytes"); 603 } else if (value <= 127) { 604 throw new IOException(mdName ~ "Should use short form for length"); 605 } 606 } 607 return value; 608 } 609 610 /** 611 * Mark the current position in the buffer, so that 612 * a later call to <code>reset</code> will return here. 613 */ 614 void mark(int value) { buffer.mark(value); } 615 616 617 /** 618 * Return to the position of the last <code>mark</code> 619 * call. A mark is implicitly set at the beginning of 620 * the stream when it is created. 621 */ 622 void reset() { buffer.reset(); } 623 624 625 /** 626 * Returns the number of bytes available for reading. 627 * This is most useful for testing whether the stream is 628 * empty. 629 */ 630 int available() { return buffer.available(); } 631 } 632