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 }