1 module hunt.security.util.ObjectIdentifier; 2 3 import hunt.security.util.DerInputBuffer; 4 import hunt.security.util.DerInputStream; 5 import hunt.security.util.DerOutputStream; 6 import hunt.security.util.DerValue; 7 8 import hunt.Exceptions; 9 import hunt.text.Common; 10 import hunt.collection; 11 import hunt.util.StringBuilder; 12 13 import std.conv; 14 import std.string; 15 import std.bigint; 16 17 import hunt.logging; 18 19 alias BigInteger = BigInt; 20 21 /** 22 * Represent an ISO Object Identifier. 23 * 24 * <P>Object Identifiers are arbitrary length hierarchical identifiers. 25 * The individual components are numbers, and they define paths from the 26 * root of an ISO-managed identifier space. You will sometimes see a 27 * string name used instead of (or in addition to) the numerical id. 28 * These are synonyms for the numerical IDs, but are not widely used 29 * since most sites do not know all the requisite strings, while all 30 * sites can parse the numeric forms. 31 * 32 * <P>So for example, JavaSoft has the sole authority to assign the 33 * meaning to identifiers below the 1.3.6.1.4.1.42.2.17 node in the 34 * hierarchy, and other organizations can easily acquire the ability 35 * to assign such unique identifiers. 36 * 37 * @author David Brownell 38 * @author Amit Kapoor 39 * @author Hemma Prafullchandra 40 */ 41 42 final class ObjectIdentifier 43 { 44 /** 45 * We use the DER value (no tag, no length) as the internal format 46 * @serial 47 */ 48 private byte[] encoding = null; 49 50 private string stringForm; 51 52 /* 53 * IMPORTANT NOTES FOR CODE CHANGES (bug 4811968) IN JDK 1.7.0 54 * =========================================================== 55 * 56 * (Almost) serialization compatibility with old versions: 57 * 58 * serialVersionUID is unchanged. Old field "component" is changed to 59 * type Object so that "poison" (unknown object type for old versions) 60 * can be put inside if there are huge components that cannot be saved 61 * as integers. 62 * 63 * New version use the new filed "encoding" only. 64 * 65 * Below are all 4 cases in a serialization/deserialization process: 66 * 67 * 1. old -> old: Not covered here 68 * 2. old -> new: There's no "encoding" field, new readObject() reads 69 * "components" and "componentLen" instead and inits correctly. 70 * 3. new -> new: "encoding" field exists, new readObject() uses it 71 * (ignoring the other 2 fields) and inits correctly. 72 * 4. new -> old: old readObject() only recognizes "components" and 73 * "componentLen" fields. If no huge components are involved, they 74 * are serialized as legal values and old object can init correctly. 75 * Otherwise, old object cannot recognize the form (component not int[]) 76 * and throw a ClassNotFoundException at deserialization time. 77 * 78 * Therfore, for the first 3 cases, exact compatibility is preserved. In 79 * the 4th case, non-huge OID is still supportable in old versions, while 80 * huge OID is not. 81 */ 82 // private static final long serialVersionUID = 8697030238860181294L; 83 84 /** 85 * Changed to Object 86 * @serial 87 */ 88 private Object components = null; // path from root 89 /** 90 * @serial 91 */ 92 private int componentLen = -1; // how much is used. 93 94 // Is the components field calculated? 95 private bool componentsCalculated = false; 96 97 // private void readObject(ObjectInputStream is) 98 // , ClassNotFoundException { 99 // is.defaultReadObject(); 100 101 // if (encoding is null) { // from an old version 102 // init((int[])components, componentLen); 103 // } 104 // } 105 106 // private void writeObject(ObjectOutputStream os) 107 // { 108 // if (!componentsCalculated) { 109 // int[] comps = toIntArray(); 110 // if (comps !is null) { // every one understands this 111 // components = comps; 112 // componentLen = comps.length; 113 // } else { 114 // components = HugeOidNotSupportedByOldJDK.theOne; 115 // } 116 // componentsCalculated = true; 117 // } 118 // os.defaultWriteObject(); 119 // } 120 121 // static class HugeOidNotSupportedByOldJDK implements Serializable { 122 // private static final long serialVersionUID = 1L; 123 // static HugeOidNotSupportedByOldJDK theOne = new HugeOidNotSupportedByOldJDK(); 124 // } 125 126 /** 127 * Constructs, from a string. This string should be of the form 1.23.56. 128 * Validity check included. 129 */ 130 this (string oid) 131 { 132 int ch = '.'; 133 ptrdiff_t start = 0; 134 ptrdiff_t end = 0; 135 136 int pos = 0; 137 byte[] tmp = new byte[oid.length]; 138 int first = 0, second; 139 int count = 0; 140 141 try { 142 string comp = null; 143 do { 144 size_t length = 0; // length of one section 145 end = oid.indexOf(ch,start); 146 if (end == -1) { 147 comp = oid[start .. $]; 148 length = oid.length - start; 149 } else { 150 comp = oid[start .. end]; 151 length = end - start; 152 } 153 154 if (length > 9) { 155 BigInteger bignum = BigInteger(comp); 156 if (count == 0) { 157 checkFirstComponent(bignum); 158 first = bignum.toInt(); 159 } else { 160 if (count == 1) { 161 checkSecondComponent(first, bignum); 162 bignum = bignum + BigInteger(40*first); 163 } else { 164 checkOtherComponent(count, bignum); 165 } 166 pos += pack7Oid(bignum, tmp, pos); 167 } 168 } else { 169 int num = to!int(comp); 170 if (count == 0) { 171 checkFirstComponent(num); 172 first = num; 173 } else { 174 if (count == 1) { 175 checkSecondComponent(first, num); 176 num += 40 * first; 177 } else { 178 checkOtherComponent(count, num); 179 } 180 pos += pack7Oid(num, tmp, pos); 181 } 182 } 183 start = end + 1; 184 count++; 185 } while (end != -1); 186 187 checkCount(count); 188 encoding = tmp.dup; // new byte[pos]; 189 // System.arraycopy(tmp, 0, encoding, 0, pos); 190 this.stringForm = oid; 191 } catch (IOException ioe) { // already detected by checkXXX 192 throw ioe; 193 } catch (Exception e) { 194 throw new IOException("ObjectIdentifier() -- Invalid format: " 195 ~ e.toString(), e); 196 } 197 } 198 199 /** 200 * Constructor, from an array of integers. 201 * Validity check included. 202 */ 203 this (int[] values ) 204 { 205 checkCount(values.length); 206 checkFirstComponent(values[0]); 207 checkSecondComponent(values[0], values[1]); 208 for (size_t i=2; i<values.length; i++) 209 checkOtherComponent(i, values[i]); 210 initilize(values, cast(int)values.length); 211 } 212 213 /** 214 * Constructor, from an ASN.1 encoded input stream. 215 * Validity check NOT included. 216 * The encoding of the ID in the stream uses "DER", a BER/1 subset. 217 * In this case, that means a triple { typeId, length, data }. 218 * 219 * <P><STRONG>NOTE:</STRONG> When an exception is thrown, the 220 * input stream has not been returned to its "initial" state. 221 * 222 * @param in DER-encoded data holding an object ID 223 * @exception IOException indicates a decoding error 224 */ 225 this (DerInputStream inputStream) 226 { 227 byte type_id; 228 int bufferEnd; 229 230 /* 231 * Object IDs are a "universal" type, and their tag needs only 232 * one byte of encoding. Verify that the tag of this datum 233 * is that of an object ID. 234 * 235 * Then get and check the length of the ID's encoding. We set 236 * up so that we can use inputStream.available() to check for the end of 237 * this value in the data stream. 238 */ 239 type_id = cast(byte) inputStream.getByte (); 240 if (type_id != DerValue.tag_ObjectId) 241 throw new IOException ( 242 "ObjectIdentifier() -- data isn't an object ID" 243 ~ " (tag = " ~ type_id ~ ")" 244 ); 245 246 int len = inputStream.getLength(); 247 if (len > inputStream.available()) { 248 throw new IOException("ObjectIdentifier() -- length exceeds" ~ 249 "data available. Length: " ~ len.to!string() ~ ", Available: " ~ 250 inputStream.available().to!string()); 251 } 252 encoding = new byte[len]; 253 inputStream.getBytes(encoding); 254 check(encoding); 255 } 256 257 /* 258 * Constructor, from the rest of a DER input buffer; 259 * the tag and length have been removed/verified 260 * Validity check NOT included. 261 */ 262 this (DerInputBuffer buf) 263 { 264 DerInputStream inputStream = new DerInputStream(buf); 265 encoding = new byte[inputStream.available()]; 266 inputStream.getBytes(encoding); 267 check(encoding); 268 } 269 270 private void initilize(int[] components, int length) { 271 int pos = 0; 272 byte[] tmp = new byte[length*5+1]; // +1 for empty input 273 274 if (components[1] < int.max - components[0]*40) 275 pos += pack7Oid(components[0]*40+components[1], tmp, pos); 276 else { 277 BigInteger big = BigInteger(components[1]); 278 big = big + BigInteger(components[0]*40); 279 pos += pack7Oid(big, tmp, pos); 280 } 281 282 for (int i=2; i<length; i++) { 283 pos += pack7Oid(components[i], tmp, pos); 284 } 285 encoding = tmp.dup; 286 // encoding = new byte[pos]; 287 // System.arraycopy(tmp, 0, encoding, 0, pos); 288 } 289 290 /** 291 * This method is kept for compatibility reasons. The new implementation 292 * does the check and conversion. All around the JDK, the method is called 293 * in static blocks to initialize pre-defined ObjectIdentifieies. No 294 * obvious performance hurt will be made after this change. 295 * 296 * Old doc: Create a new ObjectIdentifier for internal use. The values are 297 * neither checked nor cloned. 298 */ 299 static ObjectIdentifier newInternal(int[] values) { 300 try { 301 return new ObjectIdentifier(values); 302 } catch (IOException ex) { 303 throw new RuntimeException(ex); 304 // Should not happen, internal calls always uses legal values. 305 } 306 } 307 308 /* 309 * n.b. the only interface is DerOutputStream.putOID() 310 */ 311 void encode (DerOutputStream ot) 312 { 313 ot.write (DerValue.tag_ObjectId, encoding); 314 } 315 316 /** 317 * @deprecated Use equals((Object)oid) 318 */ 319 // @Deprecated 320 // bool equals(ObjectIdentifier other) { 321 // return equals((Object)other); 322 // } 323 324 /** 325 * Compares this identifier with another, for equality. 326 * 327 * @return true iff the names are identical. 328 */ 329 override bool opEquals(Object obj) { 330 if (this is obj) { 331 return true; 332 } 333 334 ObjectIdentifier other = cast(ObjectIdentifier)obj; 335 if(other is null) 336 return false; 337 return encoding == other.encoding; 338 } 339 340 override size_t toHash() @trusted const nothrow { 341 return hashOf(encoding); 342 } 343 344 /** 345 * Private helper method for serialization. To be compatible with old 346 * versions of JDK. 347 * @return components in an int array, if all the components are less than 348 * int.max. Otherwise, null. 349 */ 350 private int[] toIntArray() { 351 size_t length = encoding.length; 352 int[] result = new int[20]; 353 int which = 0; 354 size_t fromPos = 0; 355 for (size_t i = 0; i < length; i++) { 356 if ((encoding[i] & 0x80) == 0) { 357 // one section [fromPos..i] 358 if (i - fromPos + 1 > 4) { 359 implementationMissing(); 360 BigInteger big ; //= BigInteger(pack(encoding, fromPos, i-fromPos+1, 7, 8)); 361 if (fromPos == 0) { 362 result[which++] = 2; 363 BigInteger second = big - BigInteger(80); 364 if (second > BigInteger(int.max)) { 365 return null; 366 } else { 367 result[which++] = second.toInt(); 368 } 369 } else { 370 if (big > BigInteger(int.max)) { 371 return null; 372 } else { 373 result[which++] = big.toInt(); 374 } 375 } 376 } else { 377 int retval = 0; 378 for (size_t j = fromPos; j <= i; j++) { 379 retval <<= 7; 380 byte tmp = encoding[j]; 381 retval |= (tmp & 0x07f); 382 } 383 if (fromPos == 0) { 384 if (retval < 80) { 385 result[which++] = retval / 40; 386 result[which++] = retval % 40; 387 } else { 388 result[which++] = 2; 389 result[which++] = retval - 80; 390 } 391 } else { 392 result[which++] = retval; 393 } 394 } 395 fromPos = i+1; 396 } 397 if (which >= result.length) { 398 result = result[0..which + 10].dup; // Arrays.copyOf(result, which + 10); 399 } 400 } 401 return result[0..which].dup; //Arrays.copyOf(result, which); 402 } 403 404 /** 405 * Returns a string form of the object ID. The format is the 406 * conventional "dot" notation for such IDs, without any 407 * user-friendly descriptive strings, since those strings 408 * will not be understood everywhere. 409 */ 410 override 411 string toString() { 412 string s = stringForm; 413 if (s is null) { 414 size_t length = encoding.length; 415 StringBuilder sb = new StringBuilder(length * 4); 416 417 size_t fromPos = 0; 418 for (size_t i = 0; i < length; i++) { 419 if ((encoding[i] & 0x80) == 0) { 420 // one section [fromPos..i] 421 if (fromPos != 0) { // not the first segment 422 sb.append('.'); 423 } 424 if (i - fromPos + 1 > 4) { // maybe big integer 425 implementationMissing(); 426 BigInteger big ; // = new BigInteger(pack(encoding, fromPos, i-fromPos+1, 7, 8)); 427 if (fromPos == 0) { 428 // first section encoded with more than 4 bytes, 429 // must be 2.something 430 sb.append("2."); 431 sb.append(format("%d", big - BigInteger(80))); 432 } else { 433 sb.append(format("%d", big)); 434 } 435 } else { // small integer 436 int retval = 0; 437 for (size_t j = fromPos; j <= i; j++) { 438 retval <<= 7; 439 byte tmp = encoding[j]; 440 retval |= (tmp & 0x07f); 441 } 442 if (fromPos == 0) { 443 if (retval < 80) { 444 sb.append(retval/40); 445 sb.append('.'); 446 sb.append(retval%40); 447 } else { 448 sb.append("2."); 449 sb.append(retval - 80); 450 } 451 } else { 452 sb.append(retval); 453 } 454 } 455 fromPos = i+1; 456 } 457 } 458 s = sb.toString(); 459 stringForm = s; 460 } 461 return s; 462 } 463 464 /** 465 * Repack all bits from input to output. On the both sides, only a portion 466 * (from the least significant bit) of the 8 bits in a byte is used. This 467 * number is defined as the number of useful bits (NUB) for the array. All the 468 * used bits from the input byte array and repacked into the output in the 469 * exactly same order. The output bits are aligned so that the final bit of 470 * the input (the least significant bit in the last byte), when repacked as 471 * the final bit of the output, is still at the least significant position. 472 * Zeroes will be padded on the left side of the first output byte if 473 * necessary. All unused bits in the output are also zeroed. 474 * 475 * For example: if the input is 01001100 with NUB 8, the output which 476 * has a NUB 6 will look like: 477 * 00000001 00001100 478 * The first 2 bits of the output bytes are unused bits. The other bits 479 * turn out to be 000001 001100. While the 8 bits on the right are from 480 * the input, the left 4 zeroes are padded to fill the 6 bits space. 481 * 482 * @param in the input byte array 483 * @param ioffset start point inside <code>in</code> 484 * @param ilength number of bytes to repack 485 * @param iw NUB for input 486 * @param ow NUB for output 487 * @return the repacked bytes 488 */ 489 private static byte[] pack(byte[] data, size_t ioffset, size_t ilength, int iw, int ow) { 490 assert (iw > 0 && iw <= 8, "input NUB must be between 1 and 8"); 491 assert (ow > 0 && ow <= 8, "output NUB must be between 1 and 8"); 492 493 if (iw == ow) { 494 return data.dup; 495 } 496 497 size_t bits = ilength * iw; // number of all used bits 498 byte[] ot = new byte[(bits+ow-1)/ow]; 499 500 // starting from the 0th bit in the input 501 size_t ipos = 0; 502 503 // the number of padding 0's needed in the output, skip them 504 size_t opos = (bits+ow-1)/ow*ow-bits; 505 506 while(ipos < bits) { 507 size_t count = iw - ipos%iw; // unpacked bits in current input byte 508 if (count > ow - opos%ow) { // free space available in output byte 509 count = ow - opos%ow; // choose the smaller number 510 } 511 // and move them! 512 ot[opos/ow] |= // paste! 513 (((data[ioffset+ipos/iw]+256) // locate the byte (+256 so that it's never negative) 514 >> (iw-ipos%iw-count)) // move to the end of a byte 515 & ((1 << (count))-1)) // zero out all other bits 516 << (ow-opos%ow-count); // move to the output position 517 ipos += count; // advance 518 opos += count; // advance 519 } 520 return ot; 521 } 522 523 /** 524 * Repack from NUB 8 to a NUB 7 OID sub-identifier, remove all 525 * unnecessary 0 headings, set the first bit of all non-tail 526 * output bytes to 1 (as ITU-T Rec. X.690 8.19.2 says), and 527 * paste it into an existing byte array. 528 * @param ot the existing array to be pasted into 529 * @param ooffset the starting position to paste 530 * @return the number of bytes pasted 531 */ 532 private static int pack7Oid(byte[] data, int ioffset, size_t ilength, byte[] ot, int ooffset) { 533 byte[] pack = pack(data, ioffset, ilength, 8, 7); 534 size_t packLenth = pack.length; 535 size_t firstNonZero = packLenth-1; // paste at least one byte 536 for (int i= cast(int)packLenth-2; i>=0; i--) { 537 // tracef("i=%d, len=%d", i, packLenth); 538 if (pack[i] != 0) { 539 firstNonZero = i; 540 } 541 pack[i] |= 0x80; 542 } 543 // System.arraycopy(pack, firstNonZero, ot, ooffset, packLenth-firstNonZero); 544 size_t len = packLenth-firstNonZero; 545 ot[ooffset .. ooffset+len] = pack[firstNonZero .. firstNonZero+len]; 546 return cast(int)(packLenth-firstNonZero); 547 } 548 549 /** 550 * Repack from NUB 7 to NUB 8, remove all unnecessary 0 551 * headings, and paste it into an existing byte array. 552 * @param ot the existing array to be pasted into 553 * @param ooffset the starting position to paste 554 * @return the number of bytes pasted 555 */ 556 private static int pack8(byte[] data, int ioffset, int ilength, byte[] ot, int ooffset) { 557 byte[] pack = pack(data, ioffset, ilength, 7, 8); 558 size_t firstNonZero = pack.length-1; // paste at least one byte 559 for (int i=cast(int)pack.length-2; i>=0; i--) { 560 if (pack[i] != 0) { 561 firstNonZero = i; 562 } 563 } 564 // System.arraycopy(pack, firstNonZero, ot, ooffset, pack.length-firstNonZero); 565 size_t len = pack.length-firstNonZero; 566 ot[ooffset .. ooffset+len] = pack[firstNonZero .. firstNonZero+len]; 567 return cast(int)(pack.length-firstNonZero); 568 } 569 570 /** 571 * Pack the int into a OID sub-identifier DER encoding 572 */ 573 private static int pack7Oid(int input, byte[] ot, int ooffset) { 574 byte[] b = new byte[4]; 575 b[0] = cast(byte)(input >> 24); 576 b[1] = cast(byte)(input >> 16); 577 b[2] = cast(byte)(input >> 8); 578 b[3] = cast(byte)(input); 579 return pack7Oid(b, 0, 4, ot, ooffset); 580 } 581 582 /** 583 * Pack the BigInteger into a OID subidentifier DER encoding 584 */ 585 private static int pack7Oid(BigInteger input, byte[] ot, int ooffset) { 586 implementationMissing(); 587 byte[] b = null; // input.toByteArray(); 588 return pack7Oid(b, 0, b.length, ot, ooffset); 589 } 590 591 /** 592 * Private methods to check validity of OID. They must be -- 593 * 1. at least 2 components 594 * 2. all components must be non-negative 595 * 3. the first must be 0, 1 or 2 596 * 4. if the first is 0 or 1, the second must be <40 597 */ 598 599 /** 600 * Check the DER encoding. Since DER encoding defines that the integer bits 601 * are unsigned, so there's no need to check the MSB. 602 */ 603 private static void check(byte[] encoding) { 604 size_t length = encoding.length; 605 if (length < 1 || // too short 606 (encoding[length - 1] & 0x80) != 0) { // not ended 607 throw new IOException("ObjectIdentifier() -- " ~ 608 "Invalid DER encoding, not ended"); 609 } 610 for (size_t i=0; i<length; i++) { 611 // 0x80 at the beginning of a subidentifier 612 if (encoding[i] == cast(byte)0x80 && 613 (i==0 || (encoding[i-1] & 0x80) == 0)) { 614 throw new IOException("ObjectIdentifier() -- " ~ 615 "Invalid DER encoding, useless extra octet detected"); 616 } 617 } 618 } 619 private static void checkCount(size_t count) { 620 if (count < 2) { 621 throw new IOException("ObjectIdentifier() -- " ~ 622 "Must be at least two oid components "); 623 } 624 } 625 private static void checkFirstComponent(size_t first) { 626 if (first < 0 || first > 2) { 627 throw new IOException("ObjectIdentifier() -- " ~ 628 "First oid component is invalid "); 629 } 630 } 631 private static void checkFirstComponent(BigInteger first) { 632 implementationMissing(); 633 // if (first.signum() == -1 || 634 // first > BigInteger(2)) { 635 // throw new IOException("ObjectIdentifier() -- " ~ 636 // "First oid component is invalid "); 637 // } 638 } 639 private static void checkSecondComponent(size_t first, int second) { 640 if (second < 0 || first != 2 && second > 39) { 641 throw new IOException("ObjectIdentifier() -- " ~ 642 "Second oid component is invalid "); 643 } 644 } 645 private static void checkSecondComponent(size_t first, BigInteger second) { 646 647 implementationMissing(); 648 // if (second.signum() == -1 || 649 // first != 2 && 650 // second > BigInteger(39)) { 651 // throw new IOException("ObjectIdentifier() -- " ~ 652 // "Second oid component is invalid "); 653 // } 654 } 655 private static void checkOtherComponent(size_t i, int num) { 656 if (num < 0) { 657 throw new IOException("ObjectIdentifier() -- " ~ 658 "oid component #" ~ (i+1).to!string() ~ " must be non-negative "); 659 } 660 } 661 private static void checkOtherComponent(size_t i, BigInteger num) { 662 663 implementationMissing(); 664 // if (num.signum() == -1) { 665 // throw new IOException("ObjectIdentifier() -- " ~ 666 // "oid component #" ~ (i+1).to!string() ~ " must be non-negative "); 667 // } 668 } 669 }