|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.util; |
|
|
|
import sun.nio.cs.UTF_32BE; |
|
import sun.util.calendar.CalendarDate; |
|
import sun.util.calendar.CalendarSystem; |
|
|
|
import java.io.*; |
|
import java.math.BigInteger; |
|
import java.nio.charset.Charset; |
|
import java.util.*; |
|
|
|
import static java.nio.charset.StandardCharsets.*; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public class DerValue { |
|
|
|
|
|
public static final byte TAG_UNIVERSAL = (byte)0x000; |
|
public static final byte TAG_APPLICATION = (byte)0x040; |
|
public static final byte TAG_CONTEXT = (byte)0x080; |
|
public static final byte TAG_PRIVATE = (byte)0x0c0; |
|
|
|
/* |
|
* The type starts at the first byte of the encoding, and |
|
* is one of these tag_* values. That may be all the type |
|
* data that is needed. |
|
*/ |
|
|
|
/* |
|
* These tags are the "universal" tags ... they mean the same |
|
* in all contexts. (Mask with 0x1f -- five bits.) |
|
*/ |
|
|
|
|
|
public static final byte tag_Boolean = 0x01; |
|
|
|
|
|
public static final byte tag_Integer = 0x02; |
|
|
|
|
|
public static final byte tag_BitString = 0x03; |
|
|
|
|
|
public static final byte tag_OctetString = 0x04; |
|
|
|
|
|
public static final byte tag_Null = 0x05; |
|
|
|
|
|
public static final byte tag_ObjectId = 0x06; |
|
|
|
|
|
public static final byte tag_Enumerated = 0x0A; |
|
|
|
|
|
public static final byte tag_UTF8String = 0x0C; |
|
|
|
|
|
public static final byte tag_PrintableString = 0x13; |
|
|
|
|
|
public static final byte tag_T61String = 0x14; |
|
|
|
|
|
public static final byte tag_IA5String = 0x16; |
|
|
|
|
|
public static final byte tag_UtcTime = 0x17; |
|
|
|
|
|
public static final byte tag_GeneralizedTime = 0x18; |
|
|
|
|
|
public static final byte tag_GeneralString = 0x1B; |
|
|
|
|
|
public static final byte tag_UniversalString = 0x1C; |
|
|
|
|
|
public static final byte tag_BMPString = 0x1E; |
|
|
|
// CONSTRUCTED seq/set |
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte tag_Sequence = 0x30; |
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte tag_SequenceOf = 0x30; |
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte tag_Set = 0x31; |
|
|
|
|
|
|
|
|
|
*/ |
|
public static final byte tag_SetOf = 0x31; |
|
|
|
// This class is mostly immutable except that: |
|
// |
|
// 1. resetTag() modifies the tag |
|
// 2. the data field is mutable |
|
// |
|
// For compatibility, data, getData() and resetTag() are preserved. |
|
// A modern caller should call withTag() or data() instead. |
|
// |
|
// Also, some constructors have not cloned buffer, so the data could |
|
// be modified externally. |
|
|
|
public byte tag; |
|
final byte[] buffer; |
|
private final int start; |
|
final int end; |
|
private final boolean allowBER; |
|
|
|
|
|
public final DerInputStream data; |
|
|
|
/* |
|
* These values are the high order bits for the other kinds of tags. |
|
*/ |
|
|
|
|
|
|
|
*/ |
|
public boolean isUniversal() { return ((tag & 0x0c0) == 0x000); } |
|
|
|
|
|
|
|
*/ |
|
public boolean isApplication() { return ((tag & 0x0c0) == 0x040); } |
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isContextSpecific() { return ((tag & 0x0c0) == 0x080); } |
|
|
|
|
|
|
|
*/ |
|
public boolean isContextSpecific(byte cntxtTag) { |
|
if (!isContextSpecific()) { |
|
return false; |
|
} |
|
return ((tag & 0x01f) == cntxtTag); |
|
} |
|
|
|
boolean isPrivate() { return ((tag & 0x0c0) == 0x0c0); } |
|
|
|
|
|
public boolean isConstructed() { return ((tag & 0x020) == 0x020); } |
|
|
|
|
|
|
|
*/ |
|
public boolean isConstructed(byte constructedTag) { |
|
if (!isConstructed()) { |
|
return false; |
|
} |
|
return ((tag & 0x01f) == constructedTag); |
|
} |
|
|
|
|
|
|
|
*/ |
|
DerValue(byte tag, byte[] buffer, int start, int end, boolean allowBER) { |
|
if ((tag & 0x1f) == 0x1f) { |
|
throw new IllegalArgumentException("Tag number over 30 is not supported"); |
|
} |
|
this.tag = tag; |
|
this.buffer = buffer; |
|
this.start = start; |
|
this.end = end; |
|
this.allowBER = allowBER; |
|
this.data = data(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public DerValue(String value) { |
|
this(isPrintableString(value) ? tag_PrintableString : tag_UTF8String, |
|
value); |
|
} |
|
|
|
private static boolean isPrintableString(String value) { |
|
for (int i = 0; i < value.length(); i++) { |
|
if (!isPrintableStringChar(value.charAt(i))) { |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(byte stringTag, String value) { |
|
this(stringTag, string2bytes(stringTag, value), false); |
|
} |
|
|
|
private static byte[] string2bytes(byte stringTag, String value) { |
|
Charset charset = switch (stringTag) { |
|
case tag_PrintableString, tag_IA5String, tag_GeneralString -> US_ASCII; |
|
case tag_T61String -> ISO_8859_1; |
|
case tag_BMPString -> UTF_16BE; |
|
case tag_UTF8String -> UTF_8; |
|
case tag_UniversalString -> Charset.forName("UTF_32BE"); |
|
default -> throw new IllegalArgumentException("Unsupported DER string type"); |
|
}; |
|
return value.getBytes(charset); |
|
} |
|
|
|
DerValue(byte tag, byte[] buffer, boolean allowBER) { |
|
this(tag, buffer, 0, buffer.length, allowBER); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(byte tag, byte[] buffer) { |
|
this(tag, buffer.clone(), true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static DerValue wrap(byte tag, DerOutputStream out) { |
|
return new DerValue(tag, out.buf(), 0, out.size(), false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(byte[] encoding) throws IOException { |
|
this(encoding.clone(), 0, encoding.length, true, false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
DerValue(byte[] buf, int offset, int len, boolean allowBER, boolean allowMore) |
|
throws IOException { |
|
|
|
if (len < 2) { |
|
throw new IOException("Too short"); |
|
} |
|
int pos = offset; |
|
tag = buf[pos++]; |
|
if ((tag & 0x1f) == 0x1f) { |
|
throw new IOException("Tag number over 30 at " + offset + " is not supported"); |
|
} |
|
int lenByte = buf[pos++]; |
|
|
|
int length; |
|
if (lenByte == (byte) 0x80) { |
|
if (!allowBER) { |
|
throw new IOException("Indefinite length encoding " + |
|
"not supported with DER"); |
|
} |
|
if (!isConstructed()) { |
|
throw new IOException("Indefinite length encoding " + |
|
"not supported with non-constructed data"); |
|
} |
|
|
|
|
|
buf = DerIndefLenConverter.convertStream( |
|
new ByteArrayInputStream(buf, pos, len - (pos - offset)), tag); |
|
offset = 0; |
|
len = buf.length; |
|
pos = 2; |
|
|
|
if (tag != buf[0]) { |
|
throw new IOException("Indefinite length encoding not supported"); |
|
} |
|
lenByte = buf[1]; |
|
if (lenByte == (byte) 0x80) { |
|
throw new IOException("Indefinite len conversion failed"); |
|
} |
|
} |
|
|
|
if ((lenByte & 0x080) == 0x00) { |
|
length = lenByte; |
|
} else { |
|
lenByte &= 0x07f; |
|
if (lenByte > 4) { |
|
throw new IOException("Invalid lenByte"); |
|
} |
|
if (len < 2 + lenByte) { |
|
throw new IOException("Not enough length bytes"); |
|
} |
|
length = 0x0ff & buf[pos++]; |
|
lenByte--; |
|
if (length == 0 && !allowBER) { |
|
|
|
throw new IOException("Redundant length bytes found"); |
|
} |
|
while (lenByte-- > 0) { |
|
length <<= 8; |
|
length += 0x0ff & buf[pos++]; |
|
} |
|
if (length < 0) { |
|
throw new IOException("Invalid length bytes"); |
|
} else if (length <= 127 && !allowBER) { |
|
throw new IOException("Should use short form for length"); |
|
} |
|
} |
|
|
|
if (len - length < pos - offset) { |
|
throw new EOFException("not enough content"); |
|
} |
|
if (len - length > pos - offset && !allowMore) { |
|
throw new IOException("extra data at the end"); |
|
} |
|
this.buffer = buf; |
|
this.start = pos; |
|
this.end = pos + length; |
|
this.allowBER = allowBER; |
|
this.data = data(); |
|
} |
|
|
|
// Get an ASN1/DER encoded datum from an input stream w/ additional |
|
|
|
DerValue(InputStream in, boolean allowBER) throws IOException { |
|
this.tag = (byte)in.read(); |
|
if ((tag & 0x1f) == 0x1f) { |
|
throw new IOException("Tag number over 30 is not supported"); |
|
} |
|
int length = DerInputStream.getLength(in); |
|
if (length == -1) { |
|
if (!allowBER) { |
|
throw new IOException("Indefinite length encoding " + |
|
"not supported with DER"); |
|
} |
|
if (!isConstructed()) { |
|
throw new IOException("Indefinite length encoding " + |
|
"not supported with non-constructed data"); |
|
} |
|
this.buffer = DerIndefLenConverter.convertStream(in, tag); |
|
ByteArrayInputStream bin = new ByteArrayInputStream(this.buffer); |
|
if (tag != bin.read()) { |
|
throw new IOException |
|
("Indefinite length encoding not supported"); |
|
} |
|
length = DerInputStream.getDefiniteLength(bin); |
|
this.start = this.buffer.length - bin.available(); |
|
this.end = this.start + length; |
|
// position of in is undetermined. Precisely, it might be n-bytes |
|
// after DerValue, and these n bytes are at the end of this.buffer |
|
// after this.end. |
|
} else { |
|
this.buffer = IOUtils.readExactlyNBytes(in, length); |
|
this.start = 0; |
|
this.end = length; |
|
// position of in is right after the DerValue |
|
} |
|
this.allowBER = allowBER; |
|
this.data = data(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(InputStream in) throws IOException { |
|
this(in, true); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void encode(DerOutputStream out) throws IOException { |
|
out.write(tag); |
|
out.putLength(end - start); |
|
out.write(buffer, start, end - start); |
|
data.pos = data.end; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final DerInputStream data() { |
|
return new DerInputStream(buffer, start, end - start, allowBER); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public final DerInputStream getData() { |
|
return data; |
|
} |
|
|
|
public final byte getTag() { |
|
return tag; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean getBoolean() throws IOException { |
|
if (tag != tag_Boolean) { |
|
throw new IOException("DerValue.getBoolean, not a BOOLEAN " + tag); |
|
} |
|
if (end - start != 1) { |
|
throw new IOException("DerValue.getBoolean, invalid length " |
|
+ (end - start)); |
|
} |
|
data.pos = data.end; |
|
return buffer[start] != 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ObjectIdentifier getOID() throws IOException { |
|
if (tag != tag_ObjectId) { |
|
throw new IOException("DerValue.getOID, not an OID " + tag); |
|
} |
|
data.pos = data.end; |
|
return new ObjectIdentifier(Arrays.copyOfRange(buffer, start, end)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] getOctetString() throws IOException { |
|
|
|
if (tag != tag_OctetString && !isConstructed(tag_OctetString)) { |
|
throw new IOException( |
|
"DerValue.getOctetString, not an Octet String: " + tag); |
|
} |
|
// Note: do not attempt to call buffer.read(bytes) at all. There's a |
|
|
|
if (end - start == 0) { |
|
return new byte[0]; |
|
} |
|
|
|
data.pos = data.end; |
|
if (!isConstructed()) { |
|
return Arrays.copyOfRange(buffer, start, end); |
|
} else { |
|
ByteArrayOutputStream bout = new ByteArrayOutputStream(); |
|
DerInputStream dis = data(); |
|
while (dis.available() > 0) { |
|
bout.write(dis.getDerValue().getOctetString()); |
|
} |
|
return bout.toByteArray(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getInteger() throws IOException { |
|
return getIntegerInternal(tag_Integer); |
|
} |
|
|
|
private int getIntegerInternal(byte expectedTag) throws IOException { |
|
BigInteger result = getBigIntegerInternal(expectedTag, 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 BigInteger getBigInteger() throws IOException { |
|
return getBigIntegerInternal(tag_Integer, false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BigInteger getPositiveBigInteger() throws IOException { |
|
return getBigIntegerInternal(tag_Integer, true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private BigInteger getBigIntegerInternal(byte expectedTag, boolean makePositive) throws IOException { |
|
if (tag != expectedTag) { |
|
throw new IOException("DerValue.getBigIntegerInternal, not expected " + tag); |
|
} |
|
if (end == start) { |
|
throw new IOException("Invalid encoding: zero length Int value"); |
|
} |
|
data.pos = data.end; |
|
if (!allowBER && (end - start >= 2 && (buffer[start] == 0) && (buffer[start + 1] >= 0))) { |
|
throw new IOException("Invalid encoding: redundant leading 0s"); |
|
} |
|
return makePositive |
|
? new BigInteger(1, buffer, start, end - start) |
|
: new BigInteger(buffer, start, end - start); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getEnumerated() throws IOException { |
|
return getIntegerInternal(tag_Enumerated); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] getBitString() throws IOException { |
|
return getBitString(false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BitArray getUnalignedBitString() throws IOException { |
|
return getUnalignedBitString(false); |
|
} |
|
|
|
/** |
|
* Returns the name component as a Java string, regardless of its |
|
* encoding restrictions (ASCII, T61, Printable, IA5, BMP, UTF8). |
|
*/ |
|
|
|
public String getAsString() throws IOException { |
|
return switch (tag) { |
|
case tag_UTF8String -> getUTF8String(); |
|
case tag_PrintableString -> getPrintableString(); |
|
case tag_T61String -> getT61String(); |
|
case tag_IA5String -> getIA5String(); |
|
case tag_UniversalString -> getUniversalString(); |
|
case tag_BMPString -> getBMPString(); |
|
case tag_GeneralString -> getGeneralString(); |
|
default -> null; |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] getBitString(boolean tagImplicit) throws IOException { |
|
if (!tagImplicit) { |
|
if (tag != tag_BitString) { |
|
throw new IOException("DerValue.getBitString, not a bit string " |
|
+ tag); |
|
} |
|
} |
|
if (end == start) { |
|
throw new IOException("Invalid encoding: zero length bit string"); |
|
} |
|
int numOfPadBits = buffer[start]; |
|
if ((numOfPadBits < 0) || (numOfPadBits > 7)) { |
|
throw new IOException("Invalid number of padding bits"); |
|
} |
|
|
|
byte[] retval = Arrays.copyOfRange(buffer, start + 1, end); |
|
if (numOfPadBits != 0) { |
|
|
|
retval[end - start - 2] &= (0xff << numOfPadBits); |
|
} |
|
data.pos = data.end; |
|
return retval; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BitArray getUnalignedBitString(boolean tagImplicit) |
|
throws IOException { |
|
if (!tagImplicit) { |
|
if (tag != tag_BitString) { |
|
throw new IOException("DerValue.getBitString, not a bit string " |
|
+ tag); |
|
} |
|
} |
|
if (end == start) { |
|
throw new IOException("Invalid encoding: zero length bit string"); |
|
} |
|
data.pos = data.end; |
|
int numOfPadBits = buffer[start]; |
|
if ((numOfPadBits < 0) || (numOfPadBits > 7)) { |
|
throw new IOException("Invalid number of padding bits"); |
|
} |
|
if (end == start + 1) { |
|
return new BitArray(0); |
|
} else { |
|
return new BitArray(((end - start - 1) << 3) - numOfPadBits, |
|
Arrays.copyOfRange(buffer, start + 1, end)); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] getDataBytes() throws IOException { |
|
data.pos = data.end; |
|
return Arrays.copyOfRange(buffer, start, end); |
|
} |
|
|
|
private String readStringInternal(byte expectedTag, Charset cs) throws IOException { |
|
if (tag != expectedTag) { |
|
throw new IOException("Incorrect string type " + tag + " is not " + expectedTag); |
|
} |
|
data.pos = data.end; |
|
return new String(buffer, start, end - start, cs); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getPrintableString() throws IOException { |
|
return readStringInternal(tag_PrintableString, US_ASCII); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getT61String() throws IOException { |
|
return readStringInternal(tag_T61String, ISO_8859_1); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getIA5String() throws IOException { |
|
return readStringInternal(tag_IA5String, US_ASCII); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getBMPString() throws IOException { |
|
|
|
return readStringInternal(tag_BMPString, UTF_16BE); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getUTF8String() throws IOException { |
|
return readStringInternal(tag_UTF8String, UTF_8); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getGeneralString() throws IOException { |
|
return readStringInternal(tag_GeneralString, US_ASCII); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getUniversalString() throws IOException { |
|
return readStringInternal(tag_UniversalString, new UTF_32BE()); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void getNull() throws IOException { |
|
if (tag != tag_Null) { |
|
throw new IOException("DerValue.getNull, not NULL: " + tag); |
|
} |
|
if (end != start) { |
|
throw new IOException("NULL should contain no data"); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private Date getTimeInternal(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; |
|
int pos = start; |
|
int len = end - start; |
|
|
|
if (generalized) { |
|
type = "Generalized"; |
|
year = 1000 * toDigit(buffer[pos++], type); |
|
year += 100 * toDigit(buffer[pos++], type); |
|
year += 10 * toDigit(buffer[pos++], type); |
|
year += toDigit(buffer[pos++], type); |
|
len -= 2; |
|
} else { |
|
type = "UTC"; |
|
year = 10 * toDigit(buffer[pos++], type); |
|
year += toDigit(buffer[pos++], type); |
|
|
|
if (year < 50) { |
|
year += 2000; |
|
} else { |
|
year += 1900; |
|
} |
|
} |
|
|
|
month = 10 * toDigit(buffer[pos++], type); |
|
month += toDigit(buffer[pos++], type); |
|
|
|
day = 10 * toDigit(buffer[pos++], type); |
|
day += toDigit(buffer[pos++], type); |
|
|
|
hour = 10 * toDigit(buffer[pos++], type); |
|
hour += toDigit(buffer[pos++], type); |
|
|
|
minute = 10 * toDigit(buffer[pos++], type); |
|
minute += toDigit(buffer[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(buffer[pos++], type); |
|
second += toDigit(buffer[pos++], type); |
|
len -= 2; |
|
|
|
if (generalized && (buffer[pos] == '.' || buffer[pos] == ',')) { |
|
len --; |
|
if (len == 0) { |
|
throw new IOException("Parse " + type + |
|
" time, empty fractional part"); |
|
} |
|
pos++; |
|
int precision = 0; |
|
while (buffer[pos] != 'Z' && |
|
buffer[pos] != '+' && |
|
buffer[pos] != '-') { |
|
// Validate all digits in the fractional part but |
|
|
|
int thisDigit = toDigit(buffer[pos], type); |
|
precision++; |
|
len--; |
|
if (len == 0) { |
|
throw new IOException("Parse " + type + |
|
" time, invalid fractional part"); |
|
} |
|
pos++; |
|
switch (precision) { |
|
case 1 -> millis += 100 * thisDigit; |
|
case 2 -> millis += 10 * thisDigit; |
|
case 3 -> millis += thisDigit; |
|
} |
|
} |
|
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 (buffer[pos++]) { |
|
case '+': |
|
if (len != 5) { |
|
throw new IOException("Parse " + type + " time, invalid offset"); |
|
} |
|
hr = 10 * toDigit(buffer[pos++], type); |
|
hr += toDigit(buffer[pos++], type); |
|
min = 10 * toDigit(buffer[pos++], type); |
|
min += toDigit(buffer[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(buffer[pos++], type); |
|
hr += toDigit(buffer[pos++], type); |
|
min = 10 * toDigit(buffer[pos++], type); |
|
min += toDigit(buffer[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'; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date getUTCTime() throws IOException { |
|
if (tag != tag_UtcTime) { |
|
throw new IOException("DerValue.getUTCTime, not a UtcTime: " + tag); |
|
} |
|
if (end - start < 11 || end - start > 17) |
|
throw new IOException("DER UTC Time length error"); |
|
|
|
data.pos = data.end; |
|
return getTimeInternal(false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date getGeneralizedTime() throws IOException { |
|
if (tag != tag_GeneralizedTime) { |
|
throw new IOException( |
|
"DerValue.getGeneralizedTime, not a GeneralizedTime: " + tag); |
|
} |
|
if (end - start < 13) |
|
throw new IOException("DER Generalized Time length error"); |
|
|
|
data.pos = data.end; |
|
return getTimeInternal(true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public boolean equals(Object o) { |
|
if (this == o) { |
|
return true; |
|
} |
|
if (!(o instanceof DerValue)) { |
|
return false; |
|
} |
|
DerValue other = (DerValue) o; |
|
if (tag != other.tag) { |
|
return false; |
|
} |
|
if (buffer == other.buffer && start == other.start && end == other.end) { |
|
return true; |
|
} |
|
return Arrays.equals(buffer, start, end, other.buffer, other.start, other.end); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public String toString() { |
|
return String.format("DerValue(%02x, %s, %d, %d)", |
|
0xff & tag, buffer, start, end); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] toByteArray() throws IOException { |
|
data.pos = data.start; |
|
|
|
DerOutputStream out = new DerOutputStream(); |
|
out.write(tag); |
|
out.putLength(end - start); |
|
int headLen = out.size(); |
|
byte[] result = Arrays.copyOf(out.buf(), end - start + headLen); |
|
System.arraycopy(buffer, start, result, headLen, end - start); |
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerInputStream toDerInputStream() throws IOException { |
|
if (tag == tag_Sequence || tag == tag_Set) |
|
return data; |
|
throw new IOException("toDerInputStream rejects tag type " + tag); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public int length() { |
|
return end - start; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static boolean isPrintableStringChar(char ch) { |
|
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || |
|
(ch >= '0' && ch <= '9')) { |
|
return true; |
|
} else { |
|
switch (ch) { |
|
case ' ': |
|
case '\'': |
|
case '(': |
|
case ')': |
|
case '+': |
|
case ',': |
|
case '-': |
|
case '.': |
|
case '/': |
|
case ':': |
|
case '=': |
|
case '?': |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public static byte createTag(byte tagClass, boolean form, byte val) { |
|
if (val < 0 || val > 30) { |
|
throw new IllegalArgumentException("Tag number over 30 is not supported"); |
|
} |
|
byte tag = (byte)(tagClass | val); |
|
if (form) { |
|
tag |= (byte)0x20; |
|
} |
|
return (tag); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void resetTag(byte tag) { |
|
this.tag = tag; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue withTag(byte newTag) { |
|
return new DerValue(newTag, buffer, start, end, allowBER); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int hashCode() { |
|
int result = tag; |
|
for (int i = start; i < end; i++) { |
|
result = 31 * result + buffer[i]; |
|
} |
|
return result; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
DerValue[] subs(byte expectedTag, int startLen) throws IOException { |
|
if (expectedTag != 0 && expectedTag != tag) { |
|
throw new IOException("Not the correct tag"); |
|
} |
|
List<DerValue> result = new ArrayList<>(startLen); |
|
DerInputStream dis = data(); |
|
while (dis.available() > 0) { |
|
result.add(dis.getDerValue()); |
|
} |
|
return result.toArray(new DerValue[0]); |
|
} |
|
|
|
public void clear() { |
|
Arrays.fill(buffer, start, end, (byte)0); |
|
} |
|
} |