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 }