/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Vladimir N. Molotkov, Stepan M. Mishura
* @version $Revision$
*/
package org.apache.harmony.security.asn1;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
/**
* Decodes ASN.1 types encoded with BER (X.690)
*
* @see ASN.1
*/
public class BerInputStream {
private final InputStream in;
protected byte[] buffer;
/**
* The position in the buffer.
* Next read must place data into the buffer from this offset
*/
protected int offset = 0;
/**
* The buffer increment size.
* Must be reasonable big to reallocate memory not to often.
* Primary is used for decoding indefinite length encoding
*/
private static final int BUF_INCREASE_SIZE = 1024 * 16;
/** Indicates indefinite length of the current type */
protected static final int INDEFINIT_LENGTH = -1;
/** Current decoded tag */
public int tag;
/** Current decoded length */
protected int length;
/** Current decoded content */
public Object content;
/** Current decoded tag offset */
protected int tagOffset;
/** Current decoded content offset */
protected int contentOffset;
/**
* Creates stream for decoding.
*/
public BerInputStream(byte[] encoded) throws IOException {
this(encoded, 0, encoded.length);
}
/**
* Creates stream for decoding.
*
* @param encoded bytes array to be decoded
* @param offset the encoding offset
* @param expectedLength expected length of full encoding, this includes
* identifier, length an content octets
*/
public BerInputStream(byte[] encoded, int offset, int expectedLength) throws IOException {
this.in = null;
this.buffer = encoded;
this.offset = offset;
next();
// compare expected and decoded length
if (length != INDEFINIT_LENGTH
&& (offset + expectedLength) != (this.offset + this.length)) {
throw new ASN1Exception("Wrong content length");
}
}
/**
* Creates stream for decoding.
*
* Allocates initial buffer of default size
*/
public BerInputStream(InputStream in) throws IOException {
this(in, BUF_INCREASE_SIZE);
}
/**
* Creates stream for decoding.
*
* @param initialSize the internal buffer initial size
*/
public BerInputStream(InputStream in, int initialSize) throws IOException {
this.in = in;
buffer = new byte[initialSize];
next();
if (length != INDEFINIT_LENGTH) {
// input stream has definite length encoding
// check allocated length to avoid further reallocations
if (buffer.length < (length + offset)) {
byte[] newBuffer = new byte[length + offset];
System.arraycopy(buffer, 0, newBuffer, 0, offset);
buffer = newBuffer;
}
} else {
isIndefinedLength = true;
throw new ASN1Exception("Decoding indefinite length encoding is not supported");
}
}
/**
* Resets this stream to initial state.
*
* @param encoded a new bytes array to be decoded
* @throws IOException if an error occurs
*/
public final void reset(byte[] encoded) throws IOException {
buffer = encoded;
next();
}
/**
* Decodes next encoded type.
* Initializes tag, length, tagOffset and contentOffset variables
*
* @return next decoded tag
* @throws IOException if error occured
*/
public int next() throws IOException {
tagOffset = offset;
// read tag
tag = read();
// read length
length = read();
if (length != 0x80) { // definite form
// long or short length form
if ((length & 0x80) != 0) { // long form
int numOctets = length & 0x7F;
if (numOctets > 5) {
throw new ASN1Exception("Too long encoding at [" + tagOffset + "]"); //FIXME message
}
// collect this value length
length = read();
for (int i = 1; i < numOctets; i++) {
int ch = read();
length = (length << 8) + ch;//read();
}
if (length > 0xFFFFFF) {
throw new ASN1Exception("Too long encoding at [" + tagOffset + "]"); //FIXME message
}
}
} else { //indefinite form
length = INDEFINIT_LENGTH;
}
contentOffset = offset;
return tag;
}
/**
* Returns the length of the encoding
*/
public static int getLength(byte[] encoding) {
int length = encoding[1] & 0xFF;
int numOctets = 0;
if ((length & 0x80) != 0) { // long form
numOctets = length & 0x7F;
// collect this value length
length = encoding[2] & 0xFF;
for (int i = 3; i < numOctets + 2; i++) {
length = (length << 8) + (encoding[i] & 0xFF);
}
}
// tag length long_form content
return 1 + 1 + numOctets + length;
}
/**
* Decodes ASN.1 bitstring type
*/
public void readBitString() throws IOException {
if (tag == ASN1Constants.TAG_BITSTRING) {
if (length == 0) {
throw new ASN1Exception("ASN.1 Bitstring: wrong length. Tag at [" + tagOffset + "]");
}
readContent();
// content: check unused bits
if (buffer[contentOffset] > 7) {
throw new ASN1Exception("ASN.1 Bitstring: wrong content at [" + contentOffset
+ "]. A number of unused bits MUST be in range 0 to 7");
}
if (length == 1 && buffer[contentOffset] != 0) {
throw new ASN1Exception("ASN.1 Bitstring: wrong content at [" + contentOffset
+ "]. For empty string unused bits MUST be 0");
}
} else if (tag == ASN1Constants.TAG_C_BITSTRING) {
throw new ASN1Exception("Decoding constructed ASN.1 bitstring type is not provided");
} else {
throw expected("bitstring");
}
}
/**
* Decodes ASN.1 Enumerated type
*/
public void readEnumerated() throws IOException {
if (tag != ASN1Constants.TAG_ENUM) {
throw expected("enumerated");
}
// check encoded length
if (length == 0) {
throw new ASN1Exception("ASN.1 enumerated: wrong length for identifier at ["
+ tagOffset + "]");
}
readContent();
// check encoded content
if (length > 1) {
int bits = buffer[contentOffset] & 0xFF;
if (buffer[contentOffset + 1] < 0) {
bits += 0x100;
}
if (bits == 0 || bits == 0x1FF) {
throw new ASN1Exception("ASN.1 enumerated: wrong content at [" + contentOffset
+ "]. An integer MUST be encoded in minimum number of octets");
}
}
}
/**
* Decodes ASN.1 boolean type
*/
public void readBoolean() throws IOException {
if (tag != ASN1Constants.TAG_BOOLEAN) {
throw expected("boolean");
}
// check encoded length
if (length != 1) {
throw new ASN1Exception("Wrong length for ASN.1 boolean at [" + tagOffset + "]");
}
readContent();
}
/** The last choice index */
public int choiceIndex;
/** Keeps last decoded: year, month, day, hour, minute, second, millisecond */
public int[] times;
/**
* Decodes ASN.1 GeneralizedTime type
*
* @throws IOException if error occured
*/
public void readGeneralizedTime() throws IOException {
if (tag == ASN1Constants.TAG_GENERALIZEDTIME) {
// FIXME: any other optimizations?
readContent();
// FIXME store string somewhere to allow a custom time type perform
// additional checks
// check syntax: the last char MUST be Z
if (buffer[offset - 1] != 'Z') {
// FIXME support only format that is acceptable for DER
throw new ASN1Exception("ASN.1 GeneralizedTime: encoded format is not implemented");
}
// check syntax: MUST be YYYYMMDDHHMMSS[(./,)DDD]'Z'
if (length != 15 && (length < 17 || length > 19)) {
throw new ASN1Exception("ASN.1 GeneralizedTime wrongly encoded at ["
+ contentOffset + "]");
}
// check content: milliseconds
if (length > 16) {
byte char14 = buffer[contentOffset + 14];
if (char14 != '.' && char14 != ',') {
throw new ASN1Exception("ASN.1 GeneralizedTime wrongly encoded at ["
+ contentOffset + "]");
}
}
if (times == null) {
times = new int[7];
}
times[0] = strToInt(contentOffset, 4); // year
times[1] = strToInt(contentOffset + 4, 2); // month
times[2] = strToInt(contentOffset + 6, 2); // day
times[3] = strToInt(contentOffset + 8, 2); // hour
times[4] = strToInt(contentOffset + 10, 2); // minute
times[5] = strToInt(contentOffset + 12, 2); // second
if (length > 16) {
// FIXME optimize me
times[6] = strToInt(contentOffset + 15, length - 16);
if (length == 17) {
times[6] = times[6] * 100;
} else if (length == 18) {
times[6] = times[6] * 10;
}
}
// FIXME check all values for valid numbers!!!
} else if (tag == ASN1Constants.TAG_C_GENERALIZEDTIME) {
throw new ASN1Exception("Decoding constructed ASN.1 GeneralizedTime type is not supported");
} else {
throw expected("GeneralizedTime");
}
}
/**
* Decodes ASN.1 UTCTime type
*
* @throws IOException if an I/O error occurs or the end of the stream is reached
*/
public void readUTCTime() throws IOException {
if (tag == ASN1Constants.TAG_UTCTIME) {
switch (length) {
case ASN1UTCTime.UTC_HM:
case ASN1UTCTime.UTC_HMS:
break;
case ASN1UTCTime.UTC_LOCAL_HM:
case ASN1UTCTime.UTC_LOCAL_HMS:
// FIXME only coordinated universal time formats are supported
throw new ASN1Exception("ASN.1 UTCTime: local time format is not supported");
default:
throw new ASN1Exception("ASN.1 UTCTime: wrong length, identifier at " + tagOffset);
}
// FIXME: any other optimizations?
readContent();
// FIXME store string somewhere to allow a custom time type perform
// additional checks
// check syntax: the last char MUST be Z
if (buffer[offset - 1] != 'Z') {
throw new ASN1Exception("ASN.1 UTCTime wrongly encoded at ["
+ contentOffset + ']');
}
if (times == null) {
times = new int[7];
}
times[0] = strToInt(contentOffset, 2); // year
if (times[0] > 49) {
times[0] += 1900;
} else {
times[0] += 2000;
}
times[1] = strToInt(contentOffset + 2, 2); // month
times[2] = strToInt(contentOffset + 4, 2); // day
times[3] = strToInt(contentOffset + 6, 2); // hour
times[4] = strToInt(contentOffset + 8, 2); // minute
if (length == ASN1UTCTime.UTC_HMS) {
times[5] = strToInt(contentOffset + 10, 2); // second
}
// FIXME check all time values for valid numbers!!!
} else if (tag == ASN1Constants.TAG_C_UTCTIME) {
throw new ASN1Exception("Decoding constructed ASN.1 UTCTime type is not supported");
} else {
throw expected("UTCTime");
}
}
private int strToInt(int off, int count) throws ASN1Exception {
int result = 0;
for (int i = off, end = off + count; i < end; i++) {
int c = buffer[i] - 48;
if (c < 0 || c > 9) {
throw new ASN1Exception("Time encoding has invalid char");
}
result = result * 10 + c;
}
return result;
}
/**
* Decodes ASN.1 Integer type
*/
public void readInteger() throws IOException {
if (tag != ASN1Constants.TAG_INTEGER) {
throw expected("integer");
}
// check encoded length
if (length < 1) {
throw new ASN1Exception("Wrong length for ASN.1 integer at [" + tagOffset + "]");
}
readContent();
// check encoded content
if (length > 1) {
byte firstByte = buffer[offset - length];
byte secondByte = (byte) (buffer[offset - length + 1] & 0x80);
if (firstByte == 0 && secondByte == 0 || firstByte == (byte) 0xFF
&& secondByte == (byte) 0x80) {
throw new ASN1Exception("Wrong content for ASN.1 integer at [" + (offset - length) + "]. An integer MUST be encoded in minimum number of octets");
}
}
}
/**
* Decodes ASN.1 Octetstring type
*/
public void readOctetString() throws IOException {
if (tag == ASN1Constants.TAG_OCTETSTRING) {
readContent();
} else if (tag == ASN1Constants.TAG_C_OCTETSTRING) {
throw new ASN1Exception("Decoding constructed ASN.1 octet string type is not supported");
} else {
throw expected("octetstring");
}
}
private ASN1Exception expected(String what) throws ASN1Exception {
throw new ASN1Exception("ASN.1 " + what + " identifier expected at [" + tagOffset + "], got " + Integer.toHexString(tag));
}
public int oidElement;
/**
* Decodes ASN.1 ObjectIdentifier type
*/
public void readOID() throws IOException {
if (tag != ASN1Constants.TAG_OID) {
throw expected("OID");
}
// check encoded length
if (length < 1) {
throw new ASN1Exception("Wrong length for ASN.1 object identifier at [" + tagOffset + "]");
}
readContent();
// check content: last encoded byte (8th bit MUST be zero)
if ((buffer[offset - 1] & 0x80) != 0) {
throw new ASN1Exception("Wrong encoding at [" + (offset - 1) + "]");
}
oidElement = 1;
for (int i = 0; i < length; i++, ++oidElement) {
while ((buffer[contentOffset + i] & 0x80) == 0x80) {
i++;
}
}
}
/**
* Decodes ASN.1 Sequence type
*/
public void readSequence(ASN1Sequence sequence) throws IOException {
if (tag != ASN1Constants.TAG_C_SEQUENCE) {
throw expected("sequence");
}
int begOffset = offset;
int endOffset = begOffset + length;
ASN1Type[] type = sequence.type;
int i = 0;
if (isVerify) {
for (; (offset < endOffset) && (i < type.length); i++) {
next();
while (!type[i].checkTag(tag)) {
// check whether it is optional component or not
if (!sequence.OPTIONAL[i] || (i == type.length - 1)) {
throw new ASN1Exception("ASN.1 Sequence: mandatory value is missing at [" + tagOffset + "]");
}
i++;
}
type[i].decode(this);
}
// check the rest of components
for (; i < type.length; i++) {
if (!sequence.OPTIONAL[i]) {
throw new ASN1Exception("ASN.1 Sequence: mandatory value is missing at [" + tagOffset + "]");
}
}
} else {
int seqTagOffset = tagOffset; //store tag offset
Object[] values = new Object[type.length];
for (; (offset < endOffset) && (i < type.length); i++) {
next();
while (!type[i].checkTag(tag)) {
// check whether it is optional component or not
if (!sequence.OPTIONAL[i] || (i == type.length - 1)) {
throw new ASN1Exception("ASN.1 Sequence: mandatory value is missing at [" + tagOffset + "]");
}
// sets default value
if (sequence.DEFAULT[i] != null) {
values[i] = sequence.DEFAULT[i];
}
i++;
}
values[i] = type[i].decode(this);
}
// check the rest of components
for (; i < type.length; i++) {
if (!sequence.OPTIONAL[i]) {
throw new ASN1Exception("ASN.1 Sequence: mandatory value is missing at [" + tagOffset + "]");
}
if (sequence.DEFAULT[i] != null) {
values[i] = sequence.DEFAULT[i];
}
}
content = values;
tagOffset = seqTagOffset; //retrieve tag offset
}
if (offset != endOffset) {
throw new ASN1Exception("Wrong encoding at [" + begOffset + "]. Content's length and encoded length are not the same");
}
}
/**
* Decodes ASN.1 SequenceOf type
*/
public void readSequenceOf(ASN1SequenceOf sequenceOf) throws IOException {
if (tag != ASN1Constants.TAG_C_SEQUENCEOF) {
throw expected("sequenceOf");
}
decodeValueCollection(sequenceOf);
}
/**
* Decodes ASN.1 Set type
*/
public void readSet(ASN1Set set) throws IOException {
if (tag != ASN1Constants.TAG_C_SET) {
throw expected("set");
}
throw new ASN1Exception("Decoding ASN.1 Set type is not supported");
}
/**
* Decodes ASN.1 SetOf type
*/
public void readSetOf(ASN1SetOf setOf) throws IOException {
if (tag != ASN1Constants.TAG_C_SETOF) {
throw expected("setOf");
}
decodeValueCollection(setOf);
}
private void decodeValueCollection(ASN1ValueCollection collection) throws IOException {
int begOffset = offset;
int endOffset = begOffset + length;
ASN1Type type = collection.type;
if (isVerify) {
while (endOffset > offset) {
next();
type.decode(this);
}
} else {
int seqTagOffset = tagOffset; //store tag offset
ArrayList