|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package sun.security.util; |
|
|
|
import java.io.*; |
|
import java.math.BigInteger; |
|
import java.util.Date; |
|
import sun.misc.IOUtils; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
|
|
|
|
public byte tag; |
|
|
|
protected DerInputBuffer buffer; |
|
|
|
|
|
|
|
*/ |
|
public final DerInputStream data; |
|
|
|
private int length; |
|
|
|
/* |
|
* 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 final static byte tag_Boolean = 0x01; |
|
|
|
|
|
public final static byte tag_Integer = 0x02; |
|
|
|
|
|
public final static byte tag_BitString = 0x03; |
|
|
|
|
|
public final static byte tag_OctetString = 0x04; |
|
|
|
|
|
public final static byte tag_Null = 0x05; |
|
|
|
|
|
public final static byte tag_ObjectId = 0x06; |
|
|
|
|
|
public final static byte tag_Enumerated = 0x0A; |
|
|
|
|
|
public final static byte tag_UTF8String = 0x0C; |
|
|
|
|
|
public final static byte tag_PrintableString = 0x13; |
|
|
|
|
|
public final static byte tag_T61String = 0x14; |
|
|
|
|
|
public final static byte tag_IA5String = 0x16; |
|
|
|
|
|
public final static byte tag_UtcTime = 0x17; |
|
|
|
|
|
public final static byte tag_GeneralizedTime = 0x18; |
|
|
|
|
|
public final static byte tag_GeneralString = 0x1B; |
|
|
|
|
|
public final static byte tag_UniversalString = 0x1C; |
|
|
|
|
|
public final static byte tag_BMPString = 0x1E; |
|
|
|
// CONSTRUCTED seq/set |
|
|
|
|
|
|
|
|
|
*/ |
|
public final static byte tag_Sequence = 0x30; |
|
|
|
|
|
|
|
|
|
*/ |
|
public final static byte tag_SequenceOf = 0x30; |
|
|
|
|
|
|
|
|
|
*/ |
|
public final static byte tag_Set = 0x31; |
|
|
|
|
|
|
|
|
|
*/ |
|
public final static byte tag_SetOf = 0x31; |
|
|
|
/* |
|
* 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); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public DerValue(String value) throws IOException { |
|
boolean isPrintableString = true; |
|
for (int i = 0; i < value.length(); i++) { |
|
if (!isPrintableStringChar(value.charAt(i))) { |
|
isPrintableString = false; |
|
break; |
|
} |
|
} |
|
|
|
data = init(isPrintableString ? tag_PrintableString : tag_UTF8String, value); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(byte stringTag, String value) throws IOException { |
|
data = init(stringTag, value); |
|
} |
|
|
|
// Creates a DerValue from a tag and some DER-encoded data w/ additional |
|
|
|
DerValue(byte tag, byte[] data, boolean allowBER) { |
|
this.tag = tag; |
|
buffer = new DerInputBuffer(data.clone(), allowBER); |
|
length = data.length; |
|
this.data = new DerInputStream(buffer); |
|
this.data.mark(Integer.MAX_VALUE); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(byte tag, byte[] data) { |
|
this(tag, data, true); |
|
} |
|
|
|
|
|
|
|
*/ |
|
DerValue(DerInputBuffer in) throws IOException { |
|
|
|
// XXX must also parse BER-encoded constructed |
|
|
|
tag = (byte)in.read(); |
|
byte lenByte = (byte)in.read(); |
|
length = DerInputStream.getLength(lenByte, in); |
|
if (length == -1) { |
|
DerInputBuffer inbuf = in.dup(); |
|
int readLen = inbuf.available(); |
|
int offset = 2; |
|
byte[] indefData = new byte[readLen + offset]; |
|
indefData[0] = tag; |
|
indefData[1] = lenByte; |
|
DataInputStream dis = new DataInputStream(inbuf); |
|
dis.readFully(indefData, offset, readLen); |
|
dis.close(); |
|
DerIndefLenConverter derIn = new DerIndefLenConverter(); |
|
inbuf = new DerInputBuffer(derIn.convert(indefData), in.allowBER); |
|
if (tag != inbuf.read()) |
|
throw new IOException |
|
("Indefinite length encoding not supported"); |
|
length = DerInputStream.getDefiniteLength(inbuf); |
|
buffer = inbuf.dup(); |
|
buffer.truncate(length); |
|
data = new DerInputStream(buffer); |
|
// indefinite form is encoded by sending a length field with a |
|
// length of 0. - i.e. [1000|0000]. |
|
|
|
in.skip(length + offset); |
|
} else { |
|
|
|
buffer = in.dup(); |
|
buffer.truncate(length); |
|
data = new DerInputStream(buffer); |
|
|
|
in.skip(length); |
|
} |
|
} |
|
|
|
// Get an ASN.1/DER encoded datum from a buffer w/ additional |
|
|
|
DerValue(byte[] buf, boolean allowBER) throws IOException { |
|
data = init(true, new ByteArrayInputStream(buf), allowBER); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(byte[] buf) throws IOException { |
|
this(buf, true); |
|
} |
|
|
|
// Get an ASN.1/DER encoded datum from part of a buffer w/ additional |
|
|
|
DerValue(byte[] buf, int offset, int len, boolean allowBER) |
|
throws IOException { |
|
data = init(true, new ByteArrayInputStream(buf, offset, len), allowBER); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(byte[] buf, int offset, int len) throws IOException { |
|
this(buf, offset, len, true); |
|
} |
|
|
|
// Get an ASN1/DER encoded datum from an input stream w/ additional |
|
|
|
DerValue(InputStream in, boolean allowBER) throws IOException { |
|
data = init(false, in, allowBER); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerValue(InputStream in) throws IOException { |
|
this(in, true); |
|
} |
|
|
|
private DerInputStream init(byte stringTag, String value) |
|
throws IOException { |
|
String enc = null; |
|
|
|
tag = stringTag; |
|
|
|
switch (stringTag) { |
|
case tag_PrintableString: |
|
case tag_IA5String: |
|
case tag_GeneralString: |
|
enc = "ASCII"; |
|
break; |
|
case tag_T61String: |
|
enc = "ISO-8859-1"; |
|
break; |
|
case tag_BMPString: |
|
enc = "UnicodeBigUnmarked"; |
|
break; |
|
case tag_UTF8String: |
|
enc = "UTF8"; |
|
break; |
|
// TBD: Need encoder for UniversalString before it can |
|
|
|
default: |
|
throw new IllegalArgumentException("Unsupported DER string type"); |
|
} |
|
|
|
byte[] buf = value.getBytes(enc); |
|
length = buf.length; |
|
buffer = new DerInputBuffer(buf, true); |
|
DerInputStream result = new DerInputStream(buffer); |
|
result.mark(Integer.MAX_VALUE); |
|
return result; |
|
} |
|
|
|
|
|
|
|
*/ |
|
private DerInputStream init(boolean fullyBuffered, InputStream in, |
|
boolean allowBER) throws IOException { |
|
|
|
tag = (byte)in.read(); |
|
byte lenByte = (byte)in.read(); |
|
length = DerInputStream.getLength(lenByte, in); |
|
if (length == -1) { |
|
int readLen = in.available(); |
|
int offset = 2; |
|
byte[] indefData = new byte[readLen + offset]; |
|
indefData[0] = tag; |
|
indefData[1] = lenByte; |
|
DataInputStream dis = new DataInputStream(in); |
|
dis.readFully(indefData, offset, readLen); |
|
dis.close(); |
|
DerIndefLenConverter derIn = new DerIndefLenConverter(); |
|
in = new ByteArrayInputStream(derIn.convert(indefData)); |
|
if (tag != in.read()) |
|
throw new IOException |
|
("Indefinite length encoding not supported"); |
|
length = DerInputStream.getDefiniteLength(in); |
|
} |
|
|
|
if (fullyBuffered && in.available() != length) |
|
throw new IOException("extra data given to DerValue constructor"); |
|
|
|
byte[] bytes = IOUtils.readExactlyNBytes(in, length); |
|
|
|
buffer = new DerInputBuffer(bytes, allowBER); |
|
return new DerInputStream(buffer); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void encode(DerOutputStream out) |
|
throws IOException { |
|
out.write(tag); |
|
out.putLength(length); |
|
|
|
if (length > 0) { |
|
byte[] value = new byte[length]; |
|
|
|
synchronized (data) { |
|
buffer.reset(); |
|
if (buffer.read(value) != length) { |
|
throw new IOException("short DER value read (encode)"); |
|
} |
|
out.write(value); |
|
} |
|
} |
|
} |
|
|
|
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 (length != 1) { |
|
throw new IOException("DerValue.getBoolean, invalid length " |
|
+ length); |
|
} |
|
if (buffer.read() != 0) { |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ObjectIdentifier getOID() throws IOException { |
|
if (tag != tag_ObjectId) |
|
throw new IOException("DerValue.getOID, not an OID " + tag); |
|
return new ObjectIdentifier(buffer); |
|
} |
|
|
|
private byte[] append(byte[] a, byte[] b) { |
|
if (a == null) |
|
return b; |
|
|
|
byte[] ret = new byte[a.length + b.length]; |
|
System.arraycopy(a, 0, ret, 0, a.length); |
|
System.arraycopy(b, 0, ret, a.length, b.length); |
|
|
|
return ret; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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 (length == 0) { |
|
return new byte[0]; |
|
} |
|
|
|
// Only allocate the array if there are enough bytes available. |
|
// This only works for ByteArrayInputStream. |
|
|
|
ByteArrayInputStream arrayInput = buffer; |
|
if (arrayInput.available() < length) { |
|
throw new IOException("short read on DerValue buffer"); |
|
} |
|
byte[] bytes = new byte[length]; |
|
arrayInput.read(bytes); |
|
|
|
if (isConstructed()) { |
|
DerInputStream in = new DerInputStream(bytes, 0, bytes.length, |
|
buffer.allowBER); |
|
bytes = null; |
|
while (in.available() != 0) { |
|
bytes = append(bytes, in.getOctetString()); |
|
} |
|
} |
|
return bytes; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getInteger() throws IOException { |
|
if (tag != tag_Integer) { |
|
throw new IOException("DerValue.getInteger, not an int " + tag); |
|
} |
|
return buffer.getInteger(data.available()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BigInteger getBigInteger() throws IOException { |
|
if (tag != tag_Integer) |
|
throw new IOException("DerValue.getBigInteger, not an int " + tag); |
|
return buffer.getBigInteger(data.available(), false); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BigInteger getPositiveBigInteger() throws IOException { |
|
if (tag != tag_Integer) |
|
throw new IOException("DerValue.getBigInteger, not an int " + tag); |
|
return buffer.getBigInteger(data.available(), true); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public int getEnumerated() throws IOException { |
|
if (tag != tag_Enumerated) { |
|
throw new IOException("DerValue.getEnumerated, incorrect tag: " |
|
+ tag); |
|
} |
|
return buffer.getInteger(data.available()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] getBitString() throws IOException { |
|
if (tag != tag_BitString) |
|
throw new IOException( |
|
"DerValue.getBitString, not a bit string " + tag); |
|
|
|
return buffer.getBitString(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BitArray getUnalignedBitString() throws IOException { |
|
if (tag != tag_BitString) |
|
throw new IOException( |
|
"DerValue.getBitString, not a bit string " + tag); |
|
|
|
return buffer.getUnalignedBitString(); |
|
} |
|
|
|
/** |
|
* Returns the name component as a Java string, regardless of its |
|
* encoding restrictions (ASCII, T61, Printable, IA5, BMP, UTF8). |
|
*/ |
|
|
|
public String getAsString() throws IOException { |
|
if (tag == tag_UTF8String) |
|
return getUTF8String(); |
|
else if (tag == tag_PrintableString) |
|
return getPrintableString(); |
|
else if (tag == tag_T61String) |
|
return getT61String(); |
|
else if (tag == tag_IA5String) |
|
return getIA5String(); |
|
|
|
|
|
|
|
*/ |
|
else if (tag == tag_BMPString) |
|
return getBMPString(); |
|
else if (tag == tag_GeneralString) |
|
return getGeneralString(); |
|
else |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] getBitString(boolean tagImplicit) throws IOException { |
|
if (!tagImplicit) { |
|
if (tag != tag_BitString) |
|
throw new IOException("DerValue.getBitString, not a bit string " |
|
+ tag); |
|
} |
|
return buffer.getBitString(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public BitArray getUnalignedBitString(boolean tagImplicit) |
|
throws IOException { |
|
if (!tagImplicit) { |
|
if (tag != tag_BitString) |
|
throw new IOException("DerValue.getBitString, not a bit string " |
|
+ tag); |
|
} |
|
return buffer.getUnalignedBitString(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] getDataBytes() throws IOException { |
|
byte[] retVal = new byte[length]; |
|
synchronized (data) { |
|
data.reset(); |
|
data.getBytes(retVal); |
|
} |
|
return retVal; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getPrintableString() |
|
throws IOException { |
|
if (tag != tag_PrintableString) |
|
throw new IOException( |
|
"DerValue.getPrintableString, not a string " + tag); |
|
|
|
return new String(getDataBytes(), "ASCII"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getT61String() throws IOException { |
|
if (tag != tag_T61String) |
|
throw new IOException( |
|
"DerValue.getT61String, not T61 " + tag); |
|
|
|
return new String(getDataBytes(), "ISO-8859-1"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getIA5String() throws IOException { |
|
if (tag != tag_IA5String) |
|
throw new IOException( |
|
"DerValue.getIA5String, not IA5 " + tag); |
|
|
|
return new String(getDataBytes(), "ASCII"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getBMPString() throws IOException { |
|
if (tag != tag_BMPString) |
|
throw new IOException( |
|
"DerValue.getBMPString, not BMP " + tag); |
|
|
|
// BMPString is the same as Unicode in big endian, unmarked |
|
|
|
return new String(getDataBytes(), "UnicodeBigUnmarked"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getUTF8String() throws IOException { |
|
if (tag != tag_UTF8String) |
|
throw new IOException( |
|
"DerValue.getUTF8String, not UTF-8 " + tag); |
|
|
|
return new String(getDataBytes(), "UTF8"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public String getGeneralString() throws IOException { |
|
if (tag != tag_GeneralString) |
|
throw new IOException( |
|
"DerValue.getGeneralString, not GeneralString " + tag); |
|
|
|
return new String(getDataBytes(), "ASCII"); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date getUTCTime() throws IOException { |
|
if (tag != tag_UtcTime) { |
|
throw new IOException("DerValue.getUTCTime, not a UtcTime: " + tag); |
|
} |
|
return buffer.getUTCTime(data.available()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Date getGeneralizedTime() throws IOException { |
|
if (tag != tag_GeneralizedTime) { |
|
throw new IOException( |
|
"DerValue.getGeneralizedTime, not a GeneralizedTime: " + tag); |
|
} |
|
return buffer.getGeneralizedTime(data.available()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@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 (data == other.data) { |
|
return true; |
|
} |
|
|
|
|
|
return (System.identityHashCode(this.data) |
|
> System.identityHashCode(other.data)) ? |
|
doEquals(this, other): |
|
doEquals(other, this); |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static boolean doEquals(DerValue d1, DerValue d2) { |
|
synchronized (d1.data) { |
|
synchronized (d2.data) { |
|
d1.data.reset(); |
|
d2.data.reset(); |
|
return d1.buffer.equals(d2.buffer); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public String toString() { |
|
try { |
|
|
|
String str = getAsString(); |
|
if (str != null) |
|
return "\"" + str + "\""; |
|
if (tag == tag_Null) |
|
return "[DerValue, null]"; |
|
if (tag == tag_ObjectId) |
|
return "OID." + getOID(); |
|
|
|
// integers |
|
else |
|
return "[DerValue, tag = " + tag |
|
+ ", length = " + length + "]"; |
|
} catch (IOException e) { |
|
throw new IllegalArgumentException("misformatted DER value"); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public byte[] toByteArray() throws IOException { |
|
DerOutputStream out = new DerOutputStream(); |
|
|
|
encode(out); |
|
data.reset(); |
|
return out.toByteArray(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public DerInputStream toDerInputStream() throws IOException { |
|
if (tag == tag_Sequence || tag == tag_Set) |
|
return new DerInputStream(buffer); |
|
throw new IOException("toDerInputStream rejects tag type " + tag); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public int length() { |
|
return length; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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) { |
|
byte tag = (byte)(tagClass | val); |
|
if (form) { |
|
tag |= (byte)0x20; |
|
} |
|
return (tag); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public void resetTag(byte tag) { |
|
this.tag = tag; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int hashCode() { |
|
return toString().hashCode(); |
|
} |
|
} |