|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.util; |
|
|
|
import java.io.ByteArrayInputStream; |
|
import java.io.IOException; |
|
import java.math.BigInteger; |
|
import java.util.Date; |
|
import sun.util.calendar.CalendarDate; |
|
import sun.util.calendar.CalendarSystem; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
class DerInputBuffer extends ByteArrayInputStream implements Cloneable { |
|
|
|
boolean allowBER = true; |
|
|
|
|
|
DerInputBuffer(byte[] buf) { |
|
this(buf, true); |
|
} |
|
|
|
DerInputBuffer(byte[] buf, boolean allowBER) { |
|
super(buf); |
|
this.allowBER = allowBER; |
|
} |
|
|
|
DerInputBuffer(byte[] buf, int offset, int len, boolean allowBER) { |
|
super(buf, offset, len); |
|
this.allowBER = allowBER; |
|
} |
|
|
|
DerInputBuffer dup() { |
|
try { |
|
DerInputBuffer retval = (DerInputBuffer)clone(); |
|
retval.mark(Integer.MAX_VALUE); |
|
return retval; |
|
} catch (CloneNotSupportedException e) { |
|
throw new IllegalArgumentException(e.toString()); |
|
} |
|
} |
|
|
|
byte[] toByteArray() { |
|
int len = available(); |
|
if (len <= 0) |
|
return null; |
|
byte[] retval = new byte[len]; |
|
|
|
System.arraycopy(buf, pos, retval, 0, len); |
|
return retval; |
|
} |
|
|
|
int peek() throws IOException { |
|
if (pos >= count) |
|
throw new IOException("out of data"); |
|
else |
|
return buf[pos]; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean equals(Object other) { |
|
if (other instanceof DerInputBuffer) |
|
return equals((DerInputBuffer)other); |
|
else |
|
return false; |
|
} |
|
|
|
boolean equals(DerInputBuffer other) { |
|
if (this == other) |
|
return true; |
|
|
|
int max = this.available(); |
|
if (other.available() != max) |
|
return false; |
|
for (int i = 0; i < max; i++) { |
|
if (this.buf[this.pos + i] != other.buf[other.pos + i]) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int hashCode() { |
|
int retval = 0; |
|
|
|
int len = available(); |
|
int p = pos; |
|
|
|
for (int i = 0; i < len; i++) |
|
retval += buf[p + i] * i; |
|
return retval; |
|
} |
|
|
|
void truncate(int len) throws IOException { |
|
if (len > available()) |
|
throw new IOException("insufficient data"); |
|
count = pos + len; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
BigInteger getBigInteger(int len, boolean makePositive) throws IOException { |
|
if (len > available()) |
|
throw new IOException("short read of integer"); |
|
|
|
if (len == 0) { |
|
throw new IOException("Invalid encoding: zero length Int value"); |
|
} |
|
|
|
byte[] bytes = new byte[len]; |
|
|
|
System.arraycopy(buf, pos, bytes, 0, len); |
|
skip(len); |
|
|
|
|
|
if (!allowBER && (len >= 2 && (bytes[0] == 0) && (bytes[1] >= 0))) { |
|
throw new IOException("Invalid encoding: redundant leading 0s"); |
|
} |
|
|
|
if (makePositive) { |
|
return new BigInteger(1, bytes); |
|
} else { |
|
return new BigInteger(bytes); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getInteger(int len) throws IOException { |
|
|
|
BigInteger result = getBigInteger(len, false); |
|
if (result.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0) { |
|
throw new IOException("Integer below minimum valid value"); |
|
} |
|
if (result.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) { |
|
throw new IOException("Integer exceeds maximum valid value"); |
|
} |
|
return result.intValue(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] getBitString(int len) throws IOException { |
|
if (len > available()) |
|
throw new IOException("short read of bit string"); |
|
|
|
if (len == 0) { |
|
throw new IOException("Invalid encoding: zero length bit string"); |
|
} |
|
|
|
int numOfPadBits = buf[pos]; |
|
if ((numOfPadBits < 0) || (numOfPadBits > 7)) { |
|
throw new IOException("Invalid number of padding bits"); |
|
} |
|
|
|
byte[] retval = new byte[len - 1]; |
|
System.arraycopy(buf, pos + 1, retval, 0, len - 1); |
|
if (numOfPadBits != 0) { |
|
|
|
retval[len - 2] &= (0xff << numOfPadBits); |
|
} |
|
skip(len); |
|
return retval; |
|
} |
|
|
|
|
|
|
|
*/ |
|
byte[] getBitString() throws IOException { |
|
return getBitString(available()); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
BitArray getUnalignedBitString() throws IOException { |
|
if (pos >= count) |
|
return null; |
|
|
|
|
|
|
|
*/ |
|
int len = available(); |
|
int unusedBits = buf[pos] & 0xff; |
|
if (unusedBits > 7 ) { |
|
throw new IOException("Invalid value for unused bits: " + unusedBits); |
|
} |
|
byte[] bits = new byte[len - 1]; |
|
|
|
int length = (bits.length == 0) ? 0 : bits.length * 8 - unusedBits; |
|
|
|
System.arraycopy(buf, pos + 1, bits, 0, len - 1); |
|
|
|
BitArray bitArray = new BitArray(length, bits); |
|
pos = count; |
|
return bitArray; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date getUTCTime(int len) throws IOException { |
|
if (len > available()) |
|
throw new IOException("short read of DER UTC Time"); |
|
|
|
if (len < 11 || len > 17) |
|
throw new IOException("DER UTC Time length error"); |
|
|
|
return getTime(len, false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date getGeneralizedTime(int len) throws IOException { |
|
if (len > available()) |
|
throw new IOException("short read of DER Generalized Time"); |
|
|
|
if (len < 13) |
|
throw new IOException("DER Generalized Time length error"); |
|
|
|
return getTime(len, true); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Date getTime(int len, boolean generalized) throws IOException { |
|
|
|
/* |
|
* UTC time encoded as ASCII chars: |
|
* YYMMDDhhmmZ |
|
* YYMMDDhhmmssZ |
|
* YYMMDDhhmm+hhmm |
|
* YYMMDDhhmm-hhmm |
|
* YYMMDDhhmmss+hhmm |
|
* YYMMDDhhmmss-hhmm |
|
* UTC Time is broken in storing only two digits of year. |
|
* If YY < 50, we assume 20YY; |
|
* if YY >= 50, we assume 19YY, as per RFC 5280. |
|
* |
|
* Generalized time has a four-digit year and allows any |
|
* precision specified in ISO 8601. However, for our purposes, |
|
* we will only allow the same format as UTC time, except that |
|
* fractional seconds (millisecond precision) are supported. |
|
*/ |
|
|
|
int year, month, day, hour, minute, second, millis; |
|
String type = null; |
|
|
|
if (generalized) { |
|
type = "Generalized"; |
|
year = 1000 * toDigit(buf[pos++], type); |
|
year += 100 * toDigit(buf[pos++], type); |
|
year += 10 * toDigit(buf[pos++], type); |
|
year += toDigit(buf[pos++], type); |
|
len -= 2; |
|
} else { |
|
type = "UTC"; |
|
year = 10 * toDigit(buf[pos++], type); |
|
year += toDigit(buf[pos++], type); |
|
|
|
if (year < 50) |
|
year += 2000; |
|
else |
|
year += 1900; |
|
} |
|
|
|
month = 10 * toDigit(buf[pos++], type); |
|
month += toDigit(buf[pos++], type); |
|
|
|
day = 10 * toDigit(buf[pos++], type); |
|
day += toDigit(buf[pos++], type); |
|
|
|
hour = 10 * toDigit(buf[pos++], type); |
|
hour += toDigit(buf[pos++], type); |
|
|
|
minute = 10 * toDigit(buf[pos++], type); |
|
minute += toDigit(buf[pos++], type); |
|
|
|
len -= 10; |
|
|
|
/* |
|
* We allow for non-encoded seconds, even though the |
|
* IETF-PKIX specification says that the seconds should |
|
* always be encoded even if it is zero. |
|
*/ |
|
|
|
millis = 0; |
|
if (len > 2) { |
|
second = 10 * toDigit(buf[pos++], type); |
|
second += toDigit(buf[pos++], type); |
|
len -= 2; |
|
|
|
if (generalized && (buf[pos] == '.' || buf[pos] == ',')) { |
|
len --; |
|
if (len == 0) { |
|
throw new IOException("Parse " + type + |
|
" time, empty fractional part"); |
|
} |
|
pos++; |
|
int precision = 0; |
|
while (buf[pos] != 'Z' && |
|
buf[pos] != '+' && |
|
buf[pos] != '-') { |
|
// Validate all digits in the fractional part but |
|
|
|
int thisDigit = toDigit(buf[pos], type); |
|
precision++; |
|
len--; |
|
if (len == 0) { |
|
throw new IOException("Parse " + type + |
|
" time, invalid fractional part"); |
|
} |
|
pos++; |
|
switch (precision) { |
|
case 1: |
|
millis += 100 * thisDigit; |
|
break; |
|
case 2: |
|
millis += 10 * thisDigit; |
|
break; |
|
case 3: |
|
millis += thisDigit; |
|
break; |
|
} |
|
} |
|
if (precision == 0) { |
|
throw new IOException("Parse " + type + |
|
" time, empty fractional part"); |
|
} |
|
} |
|
} else |
|
second = 0; |
|
|
|
if (month == 0 || day == 0 |
|
|| month > 12 || day > 31 |
|
|| hour >= 24 || minute >= 60 || second >= 60) |
|
throw new IOException("Parse " + type + " time, invalid format"); |
|
|
|
|
|
|
|
|
|
*/ |
|
CalendarSystem gcal = CalendarSystem.getGregorianCalendar(); |
|
CalendarDate date = gcal.newCalendarDate(null); |
|
date.setDate(year, month, day); |
|
date.setTimeOfDay(hour, minute, second, millis); |
|
long time = gcal.getTime(date); |
|
|
|
|
|
|
|
*/ |
|
if (! (len == 1 || len == 5)) |
|
throw new IOException("Parse " + type + " time, invalid offset"); |
|
|
|
int hr, min; |
|
|
|
switch (buf[pos++]) { |
|
case '+': |
|
if (len != 5) { |
|
throw new IOException("Parse " + type + " time, invalid offset"); |
|
} |
|
hr = 10 * toDigit(buf[pos++], type); |
|
hr += toDigit(buf[pos++], type); |
|
min = 10 * toDigit(buf[pos++], type); |
|
min += toDigit(buf[pos++], type); |
|
|
|
if (hr >= 24 || min >= 60) |
|
throw new IOException("Parse " + type + " time, +hhmm"); |
|
|
|
time -= ((hr * 60) + min) * 60 * 1000; |
|
break; |
|
|
|
case '-': |
|
if (len != 5) { |
|
throw new IOException("Parse " + type + " time, invalid offset"); |
|
} |
|
hr = 10 * toDigit(buf[pos++], type); |
|
hr += toDigit(buf[pos++], type); |
|
min = 10 * toDigit(buf[pos++], type); |
|
min += toDigit(buf[pos++], type); |
|
|
|
if (hr >= 24 || min >= 60) |
|
throw new IOException("Parse " + type + " time, -hhmm"); |
|
|
|
time += ((hr * 60) + min) * 60 * 1000; |
|
break; |
|
|
|
case 'Z': |
|
if (len != 1) { |
|
throw new IOException("Parse " + type + " time, invalid format"); |
|
} |
|
break; |
|
|
|
default: |
|
throw new IOException("Parse " + type + " time, garbage offset"); |
|
} |
|
return new Date(time); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static int toDigit(byte b, String type) throws IOException { |
|
if (b < '0' || b > '9') { |
|
throw new IOException("Parse " + type + " time, invalid format"); |
|
} |
|
return b - '0'; |
|
} |
|
} |