www.pudn.com > cryptix-asn1-0.1.11.zip > DerDecoder.java


/* $Id: DerDecoder.java,v 1.8 2001/05/26 07:58:43 raif Exp $ 
 * 
 * Copyright (C) 1997-2001 The Cryptix Foundation Limited. All rights reserved. 
 * 
 * Use, modification, copying and distribution of this software is subject to 
 * the terms and conditions of the Cryptix General Licence. You should have 
 * received a copy of the Cryptix General Licence along with this library; if 
 * not, you can download a copy from http://www.cryptix.org/ 
 */ 
package cryptix.asn1.encoding; 
 
import cryptix.asn1.io.*; 
import cryptix.asn1.lang.*; 
 
import org.apache.log4j.Category; 
 
import java.io.BufferedInputStream; 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.EOFException; 
import java.io.InputStream; 
import java.io.IOException; 
import java.math.BigInteger; 
import java.util.ArrayList; 
import java.util.Calendar; 
import java.util.Date; 
import java.util.Iterator; 
import java.util.TimeZone; 
 
/** 
 * A class to decode ASN.1 specifications according to the Distinguished 
 * Encoding Rules.

* * @version $Revision: 1.8 $ * @author Raif S. Naffah */ public class DerDecoder extends ASNReader { // Constants and vars // ........................................................................ static Category cat = Category.getInstance(DerDecoder.class.getName()); /** * The underlying input stream that supports mark() and reset(). */ BufferedInputStream in; // Constructor(s) // ........................................................................ /** * Trivial constructor for use by the Factory. */ public DerDecoder() { super(); } /** * Private constructor for internal use. * * @param ba a byte array to become the underlying stream of the decoder. */ private DerDecoder(byte[] ba) { this.in = new BufferedInputStream(new ByteArrayInputStream(ba), 10240); } // Class method(s) // ........................................................................ /** * A class method to compare similarity between 2 Tag instances. The * comparison works on the Tag's class and value fields. * * @param tClass the tag's actual (read) class field. * @param tValue the tag's actual (read) value field. * @param xClass the expected tag's class field. * @param xValue the expected tag's value field. */ private static final boolean eval(int tClass, int xClass, int tValue, int xValue) { cat.info("Comparing ["+tClass+", "+tValue+"] to ["+xClass+", "+xValue+"]"); if (tClass != xClass) return (false); if (tClass == Tag.APPLICATION || tClass == Tag.PRIVATE) return (tValue == xValue); if (tValue == xValue) return (true); if (xValue > 0x20) // compare unstructured values xValue -= 0x20; // equate PrintableString, IA5String and T61_STRING if (xValue == Tag.PRINTABLE_STRING || xValue == Tag.IA5_STRING || xValue == Tag.T61_STRING) return (tValue == Tag.PRINTABLE_STRING || tValue == Tag.IA5_STRING || tValue == Tag.T61_STRING); // equate SEQUENCE, SEQUENCE OF, SET and SET OF if (xValue == Tag.SEQUENCE // || xValue == Tag.SEQUENCE_OF || xValue == Tag.SET) // || xValue == Tag.SET_OF) return (tValue == Tag.SEQUENCE // || tValue == Tag.SEQUENCE_OF || tValue == Tag.SET); // || tValue == Tag.SET_OF); return (false); } /** * Use to parse UTCTime.

* * UTCTime formats are: *

 
	 *		yymmddhhmmZ 
	 *		yymmddhhmmssZ 
	 *		yymmddhhmm+hhmm 
	 *		yymmddhhmm-hhmm 
	 *		yymmddhhmmss+hhmm 
	 *		yymmddhhmmss-hhmm 
	 * 
*/ private static final Date toDate(byte[] buffer) throws DerFormatException { int limit = buffer.length; if (limit != 11 && limit != 13 && limit != 15 && limit != 17) throw new DerFormatException(Tag.UTC_TIME); if (limit == 11 && buffer[10] != 'Z') throw new DerFormatException(Tag.UTC_TIME); if (limit == 13 && buffer[12] != 'Z') throw new DerFormatException(Tag.UTC_TIME); if (limit == 15 && (buffer[10] != '+' || buffer[10] != '-')) throw new DerFormatException(Tag.UTC_TIME); if (limit == 17 && (buffer[12] != '+' || buffer[12] != '-')) throw new DerFormatException(Tag.UTC_TIME); int YY = (buffer[0]-'0')*10 + (buffer[1]-'0'); int MM = (buffer[2]-'0')*10 + (buffer[3]-'0') - 1; int DD = (buffer[4]-'0')*10 + (buffer[5]-'0'); int hh = (buffer[6]-'0')*10 + (buffer[7]-'0'); int mm = (buffer[8]-'0')*10 + (buffer[9]-'0'); YY += YY <= 50 ? 2000 : 1900; // fails for 2051 and later Date result = null; Calendar cal = null; int ss = 0; switch (limit) { case 11: cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cal.set(YY, MM, DD, hh, mm); result = cal.getTime(); break; case 13: cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); ss = (buffer[10]-'0')*10 + (buffer[11]-'0'); cal.set(YY, MM, DD, hh, mm, ss); result = cal.getTime(); break; case 15: cal = Calendar.getInstance(); cal.set(YY, MM, DD, hh, mm); hh = (buffer[11]-'0')*10 + (buffer[12]-'0'); mm = (buffer[13]-'0')*10 + (buffer[14]-'0'); mm += hh * 60; if (buffer[10] == '+') cal.add(Calendar.MINUTE, mm); else cal.add(Calendar.MINUTE, - mm); result = cal.getTime(); break; case 17: cal = Calendar.getInstance(); ss = (buffer[10]-'0')*10 + (buffer[11]-'0'); cal.set(YY, MM, DD, hh, mm, ss); hh = (buffer[13]-'0')*10 + (buffer[14]-'0'); mm = (buffer[15]-'0')*10 + (buffer[16]-'0'); mm += hh * 60; if (buffer[12] == '+') cal.add(Calendar.MINUTE, mm); else cal.add(Calendar.MINUTE, - mm); result = cal.getTime(); } return result; } /** * Use to parse GeneralizedTime.

* * GeneralizedTime use 4-digit for the year and an arbitrary number/precision * for the seconds. Its formats are: *

 
	 *		yyyymmddhhmmZ 
	 *		yyyymmddhhmmssZ 
	 *		yyyymmddhhmmss.ss..sZ 
	 *		yyyymmddhhmm+hhmm 
	 *		yyyymmddhhmm-hhmm 
	 *		yyyymmddhhmmss+hhmm 
	 *		yyyymmddhhmmss-hhmm 
	 *		yyyymmddhhmmss.ss..s+hhmm 
	 *		yyyymmddhhmmss.ss..s-hhmm 
	 * 
* * Please note that while GeneralizedTime allows for an arbitrary precision * for the fraction of a second field (digits after the decimal point) this * implementation, relying on the Java java.util.Date and * java.util.Calendar classes, only caters for time precision up to * the millisecond --first 3 digits after the decimal point if/when * encountered. If a loss of precision is detected, a warning message is * generated in the trace/debug stream, but no exception is thrown. * * @param buffer the raw bytes from a DER stream to be interpreted as a * GeneralizedTime value. * @return a java.util.Date instance emboddying this value. */ private static final Date toFullDate(byte[] buffer) throws DerFormatException { int limit = buffer.length; if (limit < 13) throw new DerFormatException(Tag.GENERALIZED_TIME); int YY = (buffer[ 0]-'0')*1000 + (buffer[ 1]-'0')*100 + (buffer[ 2]-'0')*10 + (buffer[ 3]-'0'); int MM = (buffer[ 4]-'0')*10 + (buffer[ 5]-'0') - 1; int DD = (buffer[ 6]-'0')*10 + (buffer[ 7]-'0'); int hh = (buffer[ 8]-'0')*10 + (buffer[ 9]-'0'); int mm = (buffer[10]-'0')*10 + (buffer[11]-'0'); Date result = null; Calendar cal = null; int ss = 0; int ms = 0; int precision = 0; int b = 0; boolean millis = false; if (buffer[limit-1] == 'Z') { for (int i = 12; i < limit-1; ) { b = buffer[i++] & 0xFF; if (b == '.') millis = true; else if (!millis) ss = ss * 10 + (b - '0'); else if (precision++ < 3) ms = ms * 10 + (b - '0'); } cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); cal.set(YY, MM, DD, hh, mm, ss); cal.set(Calendar.MILLISECOND, ms); } else { int i = 12; while (buffer[i] != '+' && buffer[i] != '-' && i < limit) { b = buffer[i++] & 0xFF; if (b == '.') millis = true; else if (!millis) ss = ss * 10 + (b - '0'); else if (precision++ < 3) ms = ms * 10 + (b - '0'); } if (i == limit) throw new DerFormatException(Tag.GENERALIZED_TIME); cal = Calendar.getInstance(); cal.set(YY, MM, DD, hh, mm, ss); cal.set(Calendar.MILLISECOND, ms); boolean toSubtract = (buffer[i++] == '-'); if ((limit - i) != 4) throw new DerFormatException(Tag.GENERALIZED_TIME); hh = (buffer[i++]-'0')*10 + (buffer[i++]-'0'); mm = (buffer[i++]-'0')*10 + (buffer[i++]-'0'); mm += hh * 60; if (toSubtract) mm *= -1; cal.add(Calendar.MINUTE, mm); } result = cal.getTime(); return result; } private static final String toOID(byte[] buffer) { StringBuffer sb = new StringBuffer(); int length = buffer.length; int i = 0; if (--length >= 0) { // first byte is special int b = buffer[i++] & 0xFF; int first = (b < 40 ? 0 : (b < 80 ? 1 : 2)); int second = (b - first * 40); sb.append(first).append(".").append(second); } while (length > 0) { // handle the rest sb.append("."); int sid = 0; // subid int b; do { b = buffer[i++] & 0xFF; sid = sid << 7 | (b & 0x7F); } while (--length > 0 && (b & 0x80) == 0x80); sb.append(sid); } String result = sb.toString(); return (result); } private static final Boolean toBoolean(byte[] buffer) throws DerInvalidLengthException { int length = buffer.length; if (length != 1) throw new DerInvalidLengthException(Tag.BOOLEAN, length, 1); Boolean result = new Boolean(buffer[0] != 0x00); return (result); } private static final void toNull(byte[] buffer) throws DerInvalidLengthException { int length = buffer.length; if (length != 0) throw new DerInvalidLengthException(Tag.NULL, length, 0); } // ASN1InputStream abstract methods implementation // ........................................................................ /** * Initialises this instance to decode from the designated input stream. * * @param is the designated input stream to decode. * @exception IllegalStateException if this instance is already initialised * with an input stream. Caller should close the previous stream before * invoking this method again on a new input stream. */ public void open(InputStream is) { if (in != null) throw new IllegalStateException(); this.in = is instanceof BufferedInputStream ? (BufferedInputStream) is : new BufferedInputStream(is, 10240); } /** * Decodes an ANY from the input stream. * * @param name the optional user-defined ASN.1 type name. * @return the concrete object decoded from the underlying input stream. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public IType decodeAny(String name) throws IOException { cat.debug("==> decodeAny("+name+")"); Tag tag = null; try { tag = readTag(); } catch (EOFException x) { throw new ElementNotFoundException("???"); } int length = readLength(); byte[] buffer = new byte[length]; int actualLength = read(buffer); if (actualLength == -1) throw new EOFException(); if (actualLength != length) throw new DerLengthMismatchException(actualLength, length); IType result = null; ArrayList values; DerDecoder local; if (tag.getClazz() == Tag.UNIVERSAL) switch (tag.getValue()) { case Tag.BOOLEAN: result = new ASNBoolean(name, tag, toBoolean(buffer)); break; case Tag.INTEGER: result = new ASNInteger(name, tag, new BigInteger(1, buffer)); break; case Tag.BIT_STRING: result = new BitString(name, tag, buffer); break; case Tag.OCTET_STRING: result = new OctetString(name, tag, buffer); break; case Tag.NULL: toNull(buffer); // throws an exception if malformed result = new Null(name, tag, new Object()); break; case Tag.OBJECT_IDENTIFIER: result = new ObjectIdentifier(name, tag, toOID(buffer)); break; case Tag.NUMERIC_STRING: result = new NumericString(name, tag, new String(buffer, "ASCII")); break; case Tag.PRINTABLE_STRING: result = new PrintableString(name, tag, new String(buffer, "ASCII")); break; case Tag.T61_STRING: result = new TeletexString(name, tag, new String(buffer, "ASCII")); break; case Tag.VIDEOTEX_STRING: result = new VideotexString(name, tag, new String(buffer, "ASCII")); break; case Tag.IA5_STRING: result = new IA5String(name, tag, new String(buffer, "ASCII")); break; case Tag.GRAPHIC_STRING: result = new GraphicString(name, tag, new String(buffer, "UTF8")); break; case Tag.ISO646_STRING: result = new VisibleString(name, tag, new String(buffer, "UTF8")); break; case Tag.GENERAL_STRING: result = new GeneralString(name, tag, new String(buffer, "UTF8")); break; case Tag.UNIVERSAL_STRING: result = new UniversalString(name, tag, new String(buffer, "UTF8")); break; case Tag.BMP_STRING: result = new BMPString(name, tag, new String(buffer, "UTF8")); break; case Tag.UTC_TIME: result = new UTCTime(name, tag, toDate(buffer)); break; case Tag.GENERALIZED_TIME: result = new GeneralizedTime(name, tag, toFullDate(buffer)); break; case Tag.SEQUENCE: // case Tag.SEQUENCE_OF: case Tag.SET: // case Tag.SET_OF: values = new ArrayList(); local = new DerDecoder(buffer); try { while (true) values.add(local.decodeAny("seq")); } catch (IOException ignored) { } result = new Any(name, tag, values); break; default: cat.warn("Unable to parse an ANY; tag="+String.valueOf(tag)); result = new Any(name, tag, buffer); } else if (tag.getClazz() == Tag.CONTEXT) { if (tag.isExplicit()) { // buffer contains a DER triplet (TLV) DerDecoder embedded = new DerDecoder(buffer); result = new Any(name, tag, embedded.decodeAny("xxx")); // result = new Any(name, tag, embedded.decodeAny("xxx").getValue()); } else { // buffer contains a DER value as a byte[] result = new Any(name, tag, buffer); } } else result = new Any(name, tag, buffer); cat.debug("<== decodeAny() --> "+result); return result; } /** * Decodes an OBJECT IDENTIFIER from the input stream. * * @param obj the element to decode. * @return a String representation of the OID decoded from the stream. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public String decodeObjectIdentifier(IType obj) throws IOException { cat.debug("==> decodeObjectIdentifier()"); Tag tag = obj.tag(); byte[] buffer = readRaw(tag, Tag.OBJECT_IDENTIFIER); String result = toOID(buffer); cat.debug("<== decodeObjectIdentifier() --> "+result); return result; } /** * Decodes a NULL from the input stream. * * @param obj the element to decode. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public void decodeNull(IType obj) throws IOException { cat.debug("==> decodeNull()"); Tag tag = obj.tag(); byte[] buffer = readRaw(tag, Tag.NULL); toNull(buffer); cat.debug("<== decodeNull()"); } /** * Decodes a BOOLEAN from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public Boolean decodeBoolean(IType obj) throws IOException { cat.debug("==> decodeBoolean()"); Tag tag = obj.tag(); byte[] buffer = readRaw(tag, Tag.BOOLEAN); Boolean result = toBoolean(buffer); cat.debug("<== decodeBoolean() --> "+result); return result; } /** * Decodes an INTEGER from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public BigInteger decodeInteger(IType obj) throws IOException { cat.debug("==> decodeInteger()"); Tag tag = obj.tag(); byte[] buffer = readRaw(tag, Tag.INTEGER); BigInteger result = new BigInteger(1, buffer); cat.debug("<== decodeInteger() --> "+result); return result; } /** * Decodes a PrintableString from the input stream. * * @param tagValue the value of a Tag constant to differentiate between * the different types of strings. * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public String decodeString(int tagValue, IType obj) throws IOException { cat.debug("==> decodeString("+tagValue+")"); Tag tag = obj.tag(); byte[] buffer = readRaw(tag, tagValue); String result = new String(buffer, "UTF8"); cat.debug("<== decodeString() --> \""+result+"\""); return result; } /** * Decodes a BIT STRING from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public byte[] decodeBitString(IType obj) throws IOException { cat.debug("==> decodeBitString()"); Tag tag = obj.tag(); byte[] tmp = readRaw(tag, Tag.BIT_STRING); cat.warn("Truncating "+String.valueOf(tmp[0])+" unused leftmost bit(s) from a BIT STRING"); byte[] result = new byte[tmp.length-1]; System.arraycopy(tmp, 1, result, 0, result.length); cat.debug("<== decodeBitString() --> 0x"+(new BigInteger(1, result).toString(16))); return result; } /** * Decodes an OCTET STRING from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public byte[] decodeOctetString(IType obj) throws IOException { cat.debug("==> decodeOctetString()"); Tag tag = obj.tag(); byte[] result = readRaw(tag, Tag.OCTET_STRING); cat.debug("<== decodeOctetString() --> 0x"+(new BigInteger(1, result).toString(16))); return result; } /** * Decodes a UTCTime from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public Date decodeUTCTime(IType obj) throws IOException { cat.debug("==> decodeUTCTime()"); Tag tag = obj.tag(); byte[] buffer = readRaw(tag, Tag.UTC_TIME); Date result = toDate(buffer); cat.debug("<== decodeUTCTime() --> "+result); return result; } /** * Decodes a GeneralizedTime from the input stream. * * @param obj the element to decode. * @return the concrete value of this ASN.1 type. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerInvalidLengthException if a mismatch between the expected * and parsed size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public Date decodeGeneralizedTime(IType obj) throws IOException { cat.debug("==> decodeGeneralizedTime()"); Tag tag = obj.tag(); byte[] buffer = readRaw(tag, Tag.GENERALIZED_TIME); Date result = toFullDate(buffer); cat.debug("<== decodeGeneralizedTime() --> "+result); return result; } /** * Decodes a compound type (SEQUENCE/SET [OF]) from the input stream. * * @param obj the compound element to decode. * @return a Decoder that parses the input stream according to the same * encoding rules as this one. * @exception TagMismatchException if the element in the stream has a * different tag to the designated one. * @exception DerObjectTooLargeException if the DER value of the length part * exceeds 32-bit. * @exception DerLengthMismatchException if a mismatch between the expected * (parsed) and actual (read) size of the element's encoding is detected. * @exception EOFException if the end-of-stream was encountered while * decoding the element. * @exception IOException if any other I/O related exception has occured. */ public ASNReader decodeStructure(IType obj) throws IOException { cat.debug("==> decodeStructure()"); Tag tag = obj.tag(); byte[] buffer = readRaw(tag, Tag.SEQUENCE); DerDecoder result = new DerDecoder(buffer); cat.debug("<== decodeStructure() --> "+result); return result; } // InputStream methods implementation // ....................................................................... /** * Reads the next byte of data from the underlying input stream. The value * byte is returned as an int in the range 0 to 255. * Contrary to the normal java.io.InputStream contract, if no * byte is available because the end of the stream has been reached, this * method throws a java.io.EOFException. This method blocks until * input data is available, the end of the stream is detected, or another * exception is thrown. * * @return the next byte of data. * @exception java.io.EOFException the end-of-stream was detected. * @exception java.io.IOException if an I/O error occurs. */ public int read() throws IOException { int result = in.read(); if (result == -1) throw new EOFException(); return (result & 0xFF); } /** * Closes the underlying input stream and releases any system resources * associated with it. * * @exception java.io.IOException if an I/O error occurs. */ public void close() throws IOException { if (in != null) { try { in.close(); } catch (IOException ignored) { cat.warn("I/O exception while closing the stream: "+ignored.getMessage()); } in = null; } } /** * Marks the current position in the underlying input stream. A subsequent * call to the reset() method repositions that stream at the last * marked position so that subsequent reads re-read the same byte(s).

* * The readlimit arguments tells the underlying input stream to * allow that many bytes to be read before the mark position gets * invalidated.

* * The general contract of mark() is that, if the method * markSupported() returns true, the stream somehow * remembers all the bytes read after the call to mark() and * stands ready to supply those same bytes again if and whenever the method * reset() is called. However, the stream is not required to * remember any data at all if more than readlimit bytes are read * from the stream before reset() is called. * * @param readlimit the maximum limit of bytes that can be read before the * mark position becomes invalid. */ public void mark(int readlimit) { in.mark(readlimit); } /** * Repositions the underlying stream to the position at the time the * mark() method was last called on that input stream. * * The general contract of reset() is: *

* * @exception IOException if the underlying stream has not been marked or * if the mark has been invalidated. */ public void reset() throws IOException { in.reset(); } /** * Tests if the underlying input stream supports the mark() and * reset() methods. * * @return true if the underlying input stream supports the mark() * and reset() method; false otherwise. */ public boolean markSupported() { return in.markSupported(); } // Other instance methods // ........................................................................ private byte[] readRaw(Tag tag, int universalValue) throws IOException { byte[] buffer = readBytes(tag); // buffer may contain a TLV of a UNIVERSAL class and a universalValue // value, or just the value bytes of the type's instance depending on // (a) if the tag is explicit or implicit, and (b) if it is explicit, // whether the tag's context is UNIVERSAL or not. byte[] result; if (tag.isExplicit() && !tag.isUniversal()) { // it's a TLV DerDecoder local = new DerDecoder(buffer); result = local.readBytes(new Tag(universalValue)); } else result = buffer; return result; } private byte[] readBytes(Tag tag) throws IOException { int length = readTL(tag); byte[] result = new byte[length]; int actualLength = read(result); if (actualLength == -1) throw new EOFException(); if (actualLength != length) throw new DerLengthMismatchException(actualLength, length); return result; } // Tag-related methods // ........................................................................ private int readTL(Tag tag) throws IOException { if (!isExpectedTag(tag)) throw new TagMismatchException(String.valueOf(tag)); int result = readLength(); return result; } private boolean isExpectedTag(Tag tag) throws IOException { Tag result = getExpectedTag(tag.getClazz(), tag.getValue()); return (result != null); } private Tag getExpectedTag(int xClass, int xValue) throws IOException { int tClass = -1; int tValue = -1; Tag result = null; try { cat.debug("==> getExpectedTag("+xClass+", "+xValue+")"); int c = read(); tClass = c & 0xC0; boolean tConstructed = (c & 0x20) != 0; tValue = c & 0x1F; if (tValue == 0x1F) { // multiple bytes for tag number tValue = 0; do { c = read(); tValue += c & 0x3F; } while ((c & 0x80) != 0); } result = new Tag(tClass, tValue, true, tConstructed); cat.info("Checking for Tag's ["+xClass+" "+xValue+"] found: "+result); } finally { if (!eval(tClass, xClass, tValue, xValue)) result = null; } cat.debug("<== getExpectedTag() --> "+result+" ["+tClass+", "+tValue+"]"); return result; } private Tag readTag() throws IOException { cat.debug("==> readTag()"); Tag result = null; int c = read(); int tClass = c & 0xC0; boolean tConstructed = (c & 0x20) != 0; int tValue = c & 0x1F; if (tValue == 0x1F) { // multiple bytes for tag number c = read(); tValue = c & 0x3F; while ((c & 0x80) != 0) { c = read(); tValue += c & 0x3F; } } result = new Tag(tClass, tValue, true, tConstructed); cat.debug("<== readTag() --> "+result); return result; } // Length-related methods // ....................................................................... private int readLength() throws IOException { int result; int limit = read(); if ((limit & 0x80) == 0) result = limit; else { limit &= 0x7F; if (limit > 4) throw new DerObjectTooLargeException(); result = 0; while (limit-- > 0) result = (result << 8) | (read() & 0xFF); } cat.info("Element length = "+result); return result; } }