/*
*
*
* Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 only, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License version 2 for more details (a copy is
* included at /legal/license.txt).
*
* You should have received a copy of the GNU General Public License
* version 2 along with this work; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
* Clara, CA 95054 or visit www.sun.com if you need additional
* information or have any questions.
*/
package wim_data;
import java.io.UnsupportedEncodingException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Calendar;
import java.lang.RuntimeException;
/**
* Used to represent Type, Length, Value structure in a DER buffer.
*/
public class TLV {
/** ASN context specific flag used in types (0x80). */
static final int CONTEXT = 0x80;
/** ASN constructed flag used in types (0x20). */
static final int CONSTRUCTED = 0x20;
/** ASN constructed flag used in types (0x20). */
static final int EXPLICIT = CONSTRUCTED;
/** ANY_STRING type used as a place holder. [UNIVERSAL 0] */
static final int ANY_STRING_TYPE = 0x00;
/** ASN BOOLEAN type used in certificate parsing. [UNIVERSAL 1] */
static final int BOOLEAN_TYPE = 1;
/** ASN INTEGER type used in certificate parsing. [UNIVERSAL 2] */
static final int INTEGER_TYPE = 2;
/** ASN BIT STRING type used in certificate parsing. [UNIVERSAL 3] */
static final int BITSTRING_TYPE = 3;
/** ASN OCTET STRING type used in certificate parsing. [UNIVERSAL 4] */
static final int OCTETSTR_TYPE = 4;
/** ASN NULL type used in certificate parsing. [UNIVERSAL 5] */
static final int NULL_TYPE = 5;
/** ASN OBJECT ID type used in certificate parsing. [UNIVERSAL 6] */
static final int OID_TYPE = 6;
/** ASN ENUMERATED type. [UNIVERSAL 10] */
static final int ENUMERATED_TYPE = 10;
/** ASN UTF8String type used in certificate parsing. [UNIVERSAL 12] */
static final int UTF8STR_TYPE = 12;
/**
* ASN SEQUENCE type used in certificate parsing.
* [UNIVERSAL CONSTRUCTED 16]
*/
static final int SEQUENCE_TYPE = CONSTRUCTED + 16;
/**
* ASN SET type used in certificate parsing.
* [UNIVERSAL CONSTRUCTED 17]
*/
static final int SET_TYPE = CONSTRUCTED + 17;
/** ASN PrintableString type used in certificate parsing. [UNIVERSAL 19] */
static final int PRINTSTR_TYPE = 19;
/** ASN TELETEX STRING type used in certificate parsing. [UNIVERSAL 20] */
static final int TELETEXSTR_TYPE = 20;
/** ASN IA5 STRING type used in certificate parsing. [UNIVERSAL 22] */
static final int IA5STR_TYPE = 22;
/** ASN UCT time type used in certificate parsing [UNIVERSAL 23] */
static final int UCT_TIME_TYPE = 23;
/**
* ASN Generalized time type used in certificate parsing.
* [UNIVERSAL 24]
*/
static final int GEN_TIME_TYPE = 24;
/**
* ASN UniversalString type used in certificate parsing.
* [UNIVERSAL 28].
*/
static final int UNIVSTR_TYPE = 28;
/** ASN BIT STRING type used in certificate parsing. [UNIVERSAL 30] */
static final int BMPSTR_TYPE = 30;
/**
* Context specific explicit type for certificate version.
* [CONTEXT EXPLICIT 0]
*/
static final int VERSION_TYPE = CONTEXT + EXPLICIT + 0;
/**
* Context specific explicit type for certificate extensions.
* [CONTEXT EXPLICIT 3]
*/
static final int EXTENSIONS_TYPE = CONTEXT + EXPLICIT + 3;
/** Raw DER type. */
int type;
/** Number of bytes that make up the value. */
int length;
/** Offset of the value. */
int valueOffset;
/** Non-null for constructed types, the first child TLV. */
TLV child;
/** The next TLV in the parent sequence. */
TLV next;
/** Buffer that contains the DER encoded TLV. */
byte[] data;
/**
* Constructs a TLV structure, recursing down for constructed types.
* @param buffer DER buffer
* @param offset where to start parsing
* @exception java.lang.IndexOutOfBoundsException if the DER is corrupt
*/
public TLV(byte[] buffer, int offset) {
boolean constructed;
int size;
int start = offset;
data = buffer;
type = buffer[offset++] & 0xff;
// recurse for constructed types, bit 6 = 1
constructed = (type & 0x20) == 0x20;
if ((type & 0x1f) == 0x1f) {
// multi byte type, 7 bits per byte, only last byte bit 8 as zero
throw new RuntimeException("Invalid tag");
}
size = buffer[offset++] & 0xff;
if (size >= 128) {
int sizeLen = size - 128;
// NOTE: for now, all sizes must fit int 3 bytes
if (sizeLen > 3) {
throw new RuntimeException("TLV size to large");
}
size = 0;
while (sizeLen > 0) {
size = (size << 8) + (buffer[offset++] & 0xff);
sizeLen--;
}
}
length = size;
valueOffset = offset;
if (constructed && length != 0) {
int end = offset + length;
child = new TLV(buffer, offset);
TLV temp = child;
for (;;) {
offset = temp.valueOffset + temp.length;
if (offset == end) {
break;
}
if (offset > end) {
throw new RuntimeException("incorrect structure");
}
temp.next = new TLV(buffer, offset);
temp = temp.next;
}
}
}
/**
* Constructs a TLV structure.
* @param tag tag of new TLV
*/
TLV(int tag) {
type = tag;
}
/**
* Constructs a TLV structure.
* @param tag tag of new TLV
* @param bytes value of new TLV
*/
public TLV(int tag, byte[] bytes) {
type = tag;
length = bytes.length;
data = new byte[length + 4];
int i = putHeader(data, 0);
valueOffset = i;
System.arraycopy(bytes, 0, data, i, bytes.length);
}
/**
* Creates UTCTime TLV structure for given date.
* @param time date
* @return new TLV object
*/
public static TLV createUTCTime(Calendar time) {
byte[] data = new byte[13];
putDigits(data, 0, time.get(Calendar.YEAR));
putDigits(data, 2, time.get(Calendar.MONTH) + 1);
putDigits(data, 4, time.get(Calendar.DAY_OF_MONTH));
putDigits(data, 6, time.get(Calendar.HOUR_OF_DAY));
putDigits(data, 8, time.get(Calendar.MINUTE));
putDigits(data, 10, time.get(Calendar.SECOND));
data[12] = 0x5a;
return new TLV(UCT_TIME_TYPE, data);
}
/**
* Creates new sequence.
* @return new TLV object
*/
public static TLV createSequence() {
return new TLV(SEQUENCE_TYPE);
}
/**
* Creates new integer.
* @param data buffer containing the value
* @return new TLV object
*/
public static TLV createInteger(byte[] data) {
return new TLV(INTEGER_TYPE, data);
}
/**
* Creates new octet string.
* @param data buffer containing the value
* @return new TLV object
*/
public static TLV createOctetString(byte[] data) {
return new TLV(OCTETSTR_TYPE, data);
}
/**
* Creates new OID object.
* @param oid string representation of OID.
* @return new TLV object
*/
public static TLV createOID(String oid) {
return new TLV(TLV.OID_TYPE, Utils.StringToOID(oid));
}
/**
* Creates new UTF8 string.
* @param s string value
* @return new TLV object
*/
public static TLV createUTF8String(String s) {
try {
return new TLV(TLV.UTF8STR_TYPE, s.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {}
return null;
}
/**
* Creates new integer.
* @param value the value of new integer
* @return new TLV object
*/
public static TLV createInteger(long value) {
int check = (value < 0) ? -1 : 0;
int i = 1;
while (true) {
if (value >> (i * 8) == check) {
byte v = (byte) (value >> ((i - 1) * 8));
if (value < 0 ? v > 0 : v < 0) {
i++;
}
break;
}
i++;
}
byte[] data = new byte[i];
while (i > 0) {
i--;
data[i] = (byte) value;
value = value >> 8;
}
return new TLV(TLV.INTEGER_TYPE, data);
}
/**
* Creates a copy of this TLV. The value of field next of the new TLV is
* null.
* @return a copy of this TLV
*/
public TLV copy() {
return new TLV(getDERData(), 0);
}
/**
* Sets the next DER entry for this object.
* @param next TLV object
* @return the value of next to allow call chaining
*/
public TLV setNext(TLV next) {
this.next = next;
return next;
}
/**
* Sets the child DER entry for this object.
* @param child TLV object
* @return the value of child field to allow call chaining
*/
public TLV setChild(TLV child) {
this.child = child;
return child;
}
/**
* Sets the (implicit) tag value for this object.
* @param tag tag value
* @return <code>this</code> value to allow call chaining
*/
public TLV setTag(int tag) {
this.type = tag;
return this;
}
/**
* Print the a TLV structure, recursing down for constructed types.
*/
public void print() {
print(System.out, 0);
}
/**
* Print the a TLV structure, recursing down for constructed types.
* @param out output stream
*/
public void print(PrintStream out) {
print(out, 0);
}
/**
* Returns string representation of OID represented by this TLV.
* @return string representation of OID represented by this TLV
* @throws java.io.IOException if TLV doesn't contain OID
*/
public String getOID() throws IOException {
if (type != OID_TYPE) {
throw new IOException("OID expected");
}
return Utils.OIDtoString(data, valueOffset, length);
}
/**
* Returns the value field of this TLV.
* @return the value field of this TLV
*/
public byte[] getValue() {
if (data == null) {
getDERSize();
}
byte[] x = new byte[length];
getValue_(x, 0);
return x;
}
/**
* Places the value field of this TLV into the buffer.
* @param buffer destination buffer
* @param offset offset in the buffer
* @return TLV size in bytes
*/
public int getValue(byte[] buffer, int offset) {
if (data == null) {
getDERSize();
}
return getValue_(buffer, offset);
}
/**
* Returns DER encoded TLV.
* @return DER encoded TLV
*/
public byte[] getDERData() {
byte[] x = new byte[getDERSize()];
getDERData_(x, 0);
return x;
}
/**
* Returns DER encoded TLV.
* @param buffer destination buffer
* @param offset offset in the buffer
* @return DER encoded TLV
*/
public int getDERData(byte[] buffer, int offset) {
getDERSize();
return getDERData_(buffer, offset);
}
/**
* Returns the size of DER encoded TLV.
* @return the size of DER encoded TLV
*/
public int getDERSize() {
if (data == null) {
length = 0;
TLV c = child;
while (c != null) {
length += c.getDERSize();
c = c.next;
}
}
return length + getTLSize();
}
/**
* Places two ASCII encoded decimal digits into byte array.
* @param data byte aray
* @param offset the index of the first byte
* @param value the value to be placed into the buffer
*/
private static void putDigits(byte[] data, int offset, int value) {
value = value % 100;
data[offset++] = (byte) (0x30 | (value / 10));
data[offset++] = (byte) (0x30 | (value % 10));
}
/**
* Prints the a TLV structure, recursing down for constructed types.
* @param out output stream
* @param level what level this TLV is at
*/
private void print(PrintStream out, int level) {
for (int i = 0; i < level; i++) {
out.print(" ");
}
byte[] buffer;
if (data != null) {
buffer = data;
} else {
buffer = getDERData();
}
if (child == null) {
out.print("Type: 0x" + Integer.toHexString(type) +
" length: " + length + " value: ");
if (type == PRINTSTR_TYPE ||
type == TELETEXSTR_TYPE ||
type == UTF8STR_TYPE ||
type == IA5STR_TYPE ||
type == UNIVSTR_TYPE) {
try {
out.print(new String(buffer, valueOffset, length,
"UTF-8"));
} catch (UnsupportedEncodingException e) {
// ignore
}
} else if (type == OID_TYPE) {
out.print(Utils.OIDtoString(buffer, valueOffset, length));
} else {
out.print(Utils.hexEncode(buffer, valueOffset, length));
}
out.println("");
} else {
if (type == SET_TYPE) {
out.print("Set:");
} else {
out.print("Sequence:");
}
out.println(" (0x" + Integer.toHexString(type) +
" " + length + ")");
child.print(out, level + 1);
}
if (next != null) {
next.print(out, level);
}
}
/**
* Places the value field of this TLV into the buffer.
* @param buffer destination buffer
* @param offset offset in the buffer
* @return TLV size in bytes
*/
private int getValue_(byte[] buffer, int offset) {
if (data == null) {
TLV c = child;
while (c != null) {
offset += c.getDERData_(buffer, offset);
c = c.next;
}
} else {
System.arraycopy(data, valueOffset, buffer, offset, length);
}
return length;
}
/**
* Places tag and length values into the buffer.
* @param x byte buffer
* @param i offset
* @return value offset in the buffer
*/
private int putHeader(byte[] x, int i) {
x[i++] = (byte) type;
if (length < 128) {
x[i++] = (byte) length;
} else
if (length < 256) {
x[i++] = (byte) 0x81;
x[i++] = (byte) length;
} else {
x[i++] = (byte) 0x82;
x[i++] = (byte) (length >> 8);
x[i++] = (byte) length;
}
return i;
}
/**
* Places DER encoded TLV into the buffer.
* @param buffer destination buffer
* @param offset offset in the buffer
* @return TLV size
*/
private int getDERData_(byte[] buffer, int offset) {
int initialOffset = offset;
offset = putHeader(buffer, offset);
if (data == null) {
TLV c = child;
while (c != null) {
offset += c.getDERData_(buffer, offset);
c = c.next;
}
} else {
System.arraycopy(data, valueOffset, buffer, offset, length);
offset += length;
}
return (offset - initialOffset);
}
/**
* Returns size in bytes of tag and length.
* @return size in bytes of tag and length
*/
private int getTLSize() {
int TLSize = 2;
if (length >= 128) {
int i = length;
while (i != 0) {
TLSize++;
i = i >> 8;
}
}
return TLSize;
}
}