1 module hunt.security.util.DerInputBuffer;
2 
3 import hunt.stream.ByteArrayInputStream;
4 import hunt.stream.Common;
5 
6 import hunt.Char;
7 import hunt.Exceptions;
8 
9 import std.bigint;
10 import std.bitmanip;
11 import std.conv;
12 
13 alias BigInteger = BigInt;
14 
15 /**
16  * DER input buffer ... this is the main abstraction in the DER library
17  * which actively works with the "untyped byte stream" abstraction.  It
18  * does so with impunity, since it's not intended to be exposed to
19  * anyone who could violate the "typed value stream" DER model and hence
20  * corrupt the input stream of DER values.
21  *
22  * @author David Brownell
23  */
24 class DerInputBuffer : ByteArrayInputStream {
25 
26     bool allowBER = true;
27 
28     // used by sun/security/util/DerInputBuffer/DerInputBufferEqualsHashCode.java
29     this(byte[] buf) {
30         this(buf, true);
31     }
32 
33     this(byte[] buf, bool allowBER) {
34         super(buf);
35         this.allowBER = allowBER;
36     }
37 
38     this(byte[] buf, int offset, size_t len, bool allowBER) {
39         super(buf, offset, len);
40         this.allowBER = allowBER;
41     }
42 
43     DerInputBuffer dup() {
44         try {
45             DerInputBuffer retval = this; // (DerInputBuffer)clone();
46             // retval.mark(int.max);
47             implementationMissing();
48             return retval;
49         } catch (Exception e) {
50             throw new IllegalArgumentException(e.toString());
51         }
52     }
53 
54     byte[] toByteArray() {
55         int     len = available();
56         if (len <= 0)
57             return null;
58         byte[] retval = buf[pos .. pos+len].dup;
59         // byte[]  retval = new byte[len];
60 
61         // System.arraycopy(buf, pos, retval, 0, len);
62 
63         return retval;
64     }
65 
66     int peek() {
67         if (pos >= count)
68             throw new IOException("out of data");
69         else
70             return buf[pos];
71     }
72 
73     /**
74      * Compares this DerInputBuffer for equality with the specified
75      * object.
76      */
77     override bool opEquals(Object other) {
78         DerInputBuffer buffer = cast(DerInputBuffer)other;
79         if (buffer !is null)
80             return equals(buffer);
81         else
82             return false;
83     }
84 
85     bool equals(DerInputBuffer other) {
86         if (this is other)
87             return true;
88 
89         int max = this.available();
90         if (other.available() != max)
91             return false;
92         for (int i = 0; i < max; i++) {
93             if (this.buf[this.pos + i] != other.buf[other.pos + i]) {
94                 return false;
95             }
96         }
97         return true;
98     }
99 
100     /**
101      * Returns a hashcode for this DerInputBuffer.
102      *
103      * @return a hashcode for this DerInputBuffer.
104      */
105     override size_t toHash() @trusted nothrow {
106         size_t retval = 0;
107 
108         int len = available();
109         int p = pos;
110 
111         for (int i = 0; i < len; i++)
112             retval += buf[p + i] * i;
113         return retval;
114     }
115 
116     void truncate(int len) {
117         if (len > available())
118             throw new IOException("insufficient data");
119         count = pos + len;
120     }
121 
122     /**
123      * Returns the integer which takes up the specified number
124      * of bytes in this buffer as a BigInteger.
125      * @param len the number of bytes to use.
126      * @param makePositive whether to always return a positive value,
127      *   irrespective of actual encoding
128      * @return the integer as a BigInteger.
129      */
130     BigInteger getBigInteger(int len, bool makePositive) {
131         if (len > available())
132             throw new IOException("short read of integer");
133 
134         if (len == 0) {
135             throw new IOException("Invalid encoding: zero length Int value");
136         }
137         byte[] bytes = buf[pos .. pos+len].dup;
138 
139         // byte[] bytes = new byte[len];
140 
141         // System.arraycopy(buf, pos, bytes, 0, len);
142         skip(len);
143 
144         // BER allows leading 0s but DER does not
145         if (!allowBER && (len >= 2 && (bytes[0] == 0) && (bytes[1] >= 0))) {
146             throw new IOException("Invalid encoding: redundant leading 0s");
147         }
148 
149         // if (makePositive) {
150         //     return new BigInteger(1, bytes);
151         // } else {
152         //     return new BigInteger(bytes);
153         // }
154         implementationMissing();
155         return BigInteger(0);
156     }
157 
158     /**
159      * Returns the integer which takes up the specified number
160      * of bytes in this buffer.
161      * @throws IOException if the result is not within the valid
162      * range for integer, i.e. between int.min and
163      * int.max.
164      * @param len the number of bytes to use.
165      * @return the integer.
166      */
167     int getInteger(int len) {
168 
169         BigInteger result = getBigInteger(len, false);
170         if (result < int.min) {
171             throw new IOException("Integer below minimum valid value");
172         }
173         if (result > int.max) {
174             throw new IOException("Integer exceeds maximum valid value");
175         }
176         return result.toInt();
177     }
178 
179     /**
180      * Returns the bit string which takes up the specified
181      * number of bytes in this buffer.
182      */
183     byte[] getBitString(int len) {
184         if (len > available())
185             throw new IOException("short read of bit string");
186 
187         if (len == 0) {
188             throw new IOException("Invalid encoding: zero length bit string");
189         }
190 
191         int numOfPadBits = buf[pos];
192         if ((numOfPadBits < 0) || (numOfPadBits > 7)) {
193             throw new IOException("Invalid number of padding bits");
194         }
195         // minus the first byte which indicates the number of padding bits
196         // byte[] retval = new byte[len - 1];
197         // System.arraycopy(buf, pos + 1, retval, 0, len - 1);
198         byte[] retval = buf[pos+1 .. pos+len].dup;
199         if (numOfPadBits != 0) {
200             // get rid of the padding bits
201             retval[len - 2] &= (0xff << numOfPadBits);
202         }
203         skip(len);
204         return retval;
205     }
206 
207     /**
208      * Returns the bit string which takes up the rest of this buffer.
209      */
210     byte[] getBitString() {
211         return getBitString(available());
212     }
213 
214     /**
215      * Returns the bit string which takes up the rest of this buffer.
216      * The bit string need not be byte-aligned.
217      */
218     BitArray getUnalignedBitString() {
219         if (pos >= count)
220             return BitArray.init;
221         // /*
222         //  * Just copy the data into an aligned, padded octet buffer,
223         //  * and consume the rest of the buffer.
224         //  */
225         // int len = available();
226         // int unusedBits = buf[pos] & 0xff;
227         // if (unusedBits > 7 ) {
228         //     throw new IOException("Invalid value for unused bits: " ~ unusedBits.to!string());
229         // }
230         // byte[] bits = new byte[len - 1];
231         // // number of valid bits
232         // int length = (bits.length == 0) ? 0 : bits.length * 8 - unusedBits;
233 
234         // // System.arraycopy(buf, pos + 1, bits, 0, len - 1);
235         // bits[0 .. $] = buf[pos + 1 .. pos + len].dup;
236 
237         // BitArray bitArray = new BitArray(length, bits);
238         // pos = count;
239         // return bitArray;
240 
241         implementationMissing();
242 
243         return BitArray.init;
244     }
245 
246     /**
247      * Returns the UTC Time value that takes up the specified number
248      * of bytes in this buffer.
249      * @param len the number of bytes to use
250      */
251     // Date getUTCTime(int len) {
252     //     if (len > available())
253     //         throw new IOException("short read of DER UTC Time");
254 
255     //     if (len < 11 || len > 17)
256     //         throw new IOException("DER UTC Time length error");
257 
258     //     return getTime(len, false);
259     // }
260 
261     /**
262      * Returns the Generalized Time value that takes up the specified
263      * number of bytes in this buffer.
264      * @param len the number of bytes to use
265      */
266     // Date getGeneralizedTime(int len) {
267     //     if (len > available())
268     //         throw new IOException("short read of DER Generalized Time");
269 
270     //     if (len < 13 || len > 23)
271     //         throw new IOException("DER Generalized Time length error");
272 
273     //     return getTime(len, true);
274 
275     // }
276 
277     /**
278      * Private helper routine to extract time from the der value.
279      * @param len the number of bytes to use
280      * @param generalized true if Generalized Time is to be read, false
281      * if UTC Time is to be read.
282      */
283     // private Date getTime(int len, bool generalized) {
284 
285     //     /*
286     //      * UTC time encoded as ASCII chars:
287     //      *       YYMMDDhhmmZ
288     //      *       YYMMDDhhmmssZ
289     //      *       YYMMDDhhmm+hhmm
290     //      *       YYMMDDhhmm-hhmm
291     //      *       YYMMDDhhmmss+hhmm
292     //      *       YYMMDDhhmmss-hhmm
293     //      * UTC Time is broken in storing only two digits of year.
294     //      * If YY < 50, we assume 20YY;
295     //      * if YY >= 50, we assume 19YY, as per RFC 3280.
296     //      *
297     //      * Generalized time has a four-digit year and allows any
298     //      * precision specified in ISO 8601. However, for our purposes,
299     //      * we will only allow the same format as UTC time, except that
300     //      * fractional seconds (millisecond precision) are supported.
301     //      */
302 
303     //     int year, month, day, hour, minute, second, millis;
304     //     string type = null;
305 
306     //     if (generalized) {
307     //         type = "Generalized";
308     //         year = 1000 * Character.digit(cast(char)buf[pos++], 10);
309     //         year += 100 * Character.digit(cast(char)buf[pos++], 10);
310     //         year += 10 * Character.digit(cast(char)buf[pos++], 10);
311     //         year += Character.digit(cast(char)buf[pos++], 10);
312     //         len -= 2; // For the two extra YY
313     //     } else {
314     //         type = "UTC";
315     //         year = 10 * Character.digit(cast(char)buf[pos++], 10);
316     //         year += Character.digit(cast(char)buf[pos++], 10);
317 
318     //         if (year < 50)              // origin 2000
319     //             year += 2000;
320     //         else
321     //             year += 1900;   // origin 1900
322     //     }
323 
324     //     month = 10 * Character.digit(cast(char)buf[pos++], 10);
325     //     month += Character.digit(cast(char)buf[pos++], 10);
326 
327     //     day = 10 * Character.digit(cast(char)buf[pos++], 10);
328     //     day += Character.digit(cast(char)buf[pos++], 10);
329 
330     //     hour = 10 * Character.digit(cast(char)buf[pos++], 10);
331     //     hour += Character.digit(cast(char)buf[pos++], 10);
332 
333     //     minute = 10 * Character.digit(cast(char)buf[pos++], 10);
334     //     minute += Character.digit(cast(char)buf[pos++], 10);
335 
336     //     len -= 10; // YYMMDDhhmm
337 
338     //     /*
339     //      * We allow for non-encoded seconds, even though the
340     //      * IETF-PKIX specification says that the seconds should
341     //      * always be encoded even if it is zero.
342     //      */
343 
344     //     millis = 0;
345     //     if (len > 2 && len < 12) {
346     //         second = 10 * Character.digit(cast(char)buf[pos++], 10);
347     //         second += Character.digit(cast(char)buf[pos++], 10);
348     //         len -= 2;
349     //         // handle fractional seconds (if present)
350     //         if (buf[pos] == '.' || buf[pos] == ',') {
351     //             len --;
352     //             pos++;
353     //             // handle upto milisecond precision only
354     //             int precision = 0;
355     //             int peek = pos;
356     //             while (buf[peek] != 'Z' &&
357     //                    buf[peek] != '+' &&
358     //                    buf[peek] != '-') {
359     //                 peek++;
360     //                 precision++;
361     //             }
362     //             switch (precision) {
363     //             case 3:
364     //                 millis += 100 * Character.digit(cast(char)buf[pos++], 10);
365     //                 millis += 10 * Character.digit(cast(char)buf[pos++], 10);
366     //                 millis += Character.digit(cast(char)buf[pos++], 10);
367     //                 break;
368     //             case 2:
369     //                 millis += 100 * Character.digit(cast(char)buf[pos++], 10);
370     //                 millis += 10 * Character.digit(cast(char)buf[pos++], 10);
371     //                 break;
372     //             case 1:
373     //                 millis += 100 * Character.digit(cast(char)buf[pos++], 10);
374     //                 break;
375     //             default:
376     //                     throw new IOException("Parse " ~ type +
377     //                         " time, unsupported precision for seconds value");
378     //             }
379     //             len -= precision;
380     //         }
381     //     } else
382     //         second = 0;
383 
384     //     if (month == 0 || day == 0
385     //         || month > 12 || day > 31
386     //         || hour >= 24 || minute >= 60 || second >= 60)
387     //         throw new IOException("Parse " ~ type ~ " time, invalid format");
388 
389     //     /*
390     //      * Generalized time can theoretically allow any precision,
391     //      * but we're not supporting that.
392     //      */
393     //     CalendarSystem gcal = CalendarSystem.getGregorianCalendar();
394     //     CalendarDate date = gcal.newCalendarDate(null); // no time zone
395     //     date.setDate(year, month, day);
396     //     date.setTimeOfDay(hour, minute, second, millis);
397     //     long time = gcal.getTime(date);
398 
399     //     /*
400     //      * Finally, "Z" or "+hhmm" or "-hhmm" ... offsets change hhmm
401     //      */
402     //     if (! (len == 1 || len == 5))
403     //         throw new IOException("Parse " ~ type ~ " time, invalid offset");
404 
405     //     int hr, min;
406 
407     //     switch (buf[pos++]) {
408     //     case '+':
409     //         hr = 10 * Character.digit(cast(char)buf[pos++], 10);
410     //         hr += Character.digit(cast(char)buf[pos++], 10);
411     //         min = 10 * Character.digit(cast(char)buf[pos++], 10);
412     //         min += Character.digit(cast(char)buf[pos++], 10);
413 
414     //         if (hr >= 24 || min >= 60)
415     //             throw new IOException("Parse " ~ type ~ " time, +hhmm");
416 
417     //         time -= ((hr * 60) + min) * 60 * 1000;
418     //         break;
419 
420     //     case '-':
421     //         hr = 10 * Character.digit(cast(char)buf[pos++], 10);
422     //         hr += Character.digit(cast(char)buf[pos++], 10);
423     //         min = 10 * Character.digit(cast(char)buf[pos++], 10);
424     //         min += Character.digit(cast(char)buf[pos++], 10);
425 
426     //         if (hr >= 24 || min >= 60)
427     //             throw new IOException("Parse " ~ type ~ " time, -hhmm");
428 
429     //         time += ((hr * 60) + min) * 60 * 1000;
430     //         break;
431 
432     //     case 'Z':
433     //         break;
434 
435     //     default:
436     //         throw new IOException("Parse " ~ type ~ " time, garbage offset");
437     //     }
438     //     return new Date(time);
439     // }
440 }