/*
* Copyright (C) 2006-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* 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 for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.jlan.server.auth.asn;
import java.io.IOException;
import java.util.List;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.util.DataPacker;
/**
* DER Buffer Class
*
* <p>Pack/unpack objects from a ASN.1 DER encoded blob.
*
* @author gkspencer
*/
public class DERBuffer {
// Constants
private static final int DefaultBufferSize = 256;
// Debug enable
private static final boolean DebugEnable = false;
// Data buffer, current position and offset
private byte[] m_data;
private int m_pos;
private int m_endpos;
private int m_offset;
/**
* Default constructor
*/
public DERBuffer() {
m_data = new byte[DefaultBufferSize];
m_pos = 0;
m_offset = 0;
}
/**
* Create a data buffer to write data to
*
* @param siz int
*/
public DERBuffer(int siz) {
m_data = new byte[siz];
m_pos = 0;
m_offset = 0;
}
/**
* Create a data buffer to read data from
*
* @param buf byte[]
* @param off int
* @param len int
*/
public DERBuffer(byte[] buf, int off, int len) {
m_data = buf;
m_offset = off;
m_pos = off;
m_endpos = off + len;
}
/**
* Create a data buffer to read data from
*
* @param buf byte[]
*/
public DERBuffer(byte[] buf) {
m_data = buf;
m_offset = 0;
m_pos = 0;
m_endpos = buf.length;
}
/**
* Return the data buffer
*
* @return byte[]
*/
public final byte[] getBuffer() {
return m_data;
}
/**
* Return the data length
*
* @return int
*/
public final int getLength() {
if ( m_endpos != 0)
return m_endpos - m_offset;
return m_pos - m_offset;
}
/**
* Return the data length in words
*
* @return int
*/
public final int getLengthInWords() {
return getLength()/2;
}
/**
* Return the available data length
*
* @return int
*/
public final int getAvailableLength() {
if ( m_endpos == 0)
return -1;
return m_endpos - m_pos;
}
/**
* Return the data position
*
* @return int
*/
public final int getPosition() {
return m_pos;
}
/**
* Return the used buffer as a byte array
*
* @return byte[]
*/
public final byte[] getBytes() {
if ( getLength() == 0)
return null;
byte[] byts = new byte[ getLength()];
System.arraycopy( m_data, m_offset, byts, 0, getLength());
return byts;
}
/**
* Unpack a data byte from the buffer
*
* @return int
*/
public final int unpackByte() {
// Check if there is enough data in the buffer
if ( m_data.length - m_pos < 1)
throw new ArrayIndexOutOfBoundsException("End of data buffer");
// Unpack the data byte value
return (m_data[m_pos++] & 0xFF);
}
/**
* Unpack a block of bytes
*
* @param len int
* @return byte[]
*/
public final byte[] unpackBytes(int len) {
// Check if there is enough data in the buffer
if ( m_data.length - m_pos < len)
throw new ArrayIndexOutOfBoundsException("End of data buffer");
// Create the return buffer and copy the data
byte[] byts = new byte[len];
System.arraycopy(m_data, m_pos, byts, 0, len);
// Update the buffer position
m_pos += len;
// Return the bytes
return byts;
}
/**
* Peek at the next type in the buffer
*
* @return int
*/
public final int peekType() {
// Check if there is enough data in the buffer
if ( m_data.length - m_pos < 1)
throw new ArrayIndexOutOfBoundsException("End of data buffer");
// Unpack the type byte value
return (m_data[m_pos] & 0xFF);
}
/**
* Unpack a data type from the buffer
*
* @return int
*/
public final int unpackType() {
return unpackByte();
}
/**
* Unpack a data length value
*
* @return int
*/
public final int unpackLength() {
// Check if there is enough data in the buffer
if ( m_data.length - m_pos < 1)
throw new ArrayIndexOutOfBoundsException("End of data buffer");
// Unpack the data length byte(s)
int dlen = (m_data[m_pos++] & 0xFF);
// Check if this is a multi-byte length
if (( dlen & 0x80) != 0) {
// Unpack the length bytes
int numByts = dlen & 0x7F;
dlen = 0;
while ( numByts-- > 0) {
dlen = (dlen << 8) + ( m_data[m_pos++] & 0xFF);
}
}
// Return the length
return dlen;
}
/**
* Unpack an object from the buffer
*
* @return DERObject
* @throws IOException
*/
public final DERObject unpackObject()
throws IOException {
// Check the object type
int objTyp = peekType();
int tagNo = -1;
if (( objTyp & DER.Tagged) != 0) {
// Save the tag number
tagNo = objTyp & 0x1F;
// Skip the type and outer length
unpackType();
unpackLength();
// Get the main object type
objTyp = peekType();
}
// Create the required object type
DERObject derObj = null;
if (( objTyp & DER.Application) != 0) {
// Create an applicatin sepcific object
derObj = new DERApplicationSpecific();
// DEBUG
if ( DebugEnable)
Debug.println("DER unpackObject typ=Applicationspecific objTyp=0x" + Integer.toHexString(objTyp));
}
else {
// DEBUG
if ( DebugEnable)
Debug.println("DER unpackObject typ=" + DER.isTypeString( DER.isType( objTyp)) + " objTyp=0x" + Integer.toHexString(objTyp));
// Create the appropriate object type
switch ( DER.isType( objTyp)) {
case DER.BitString:
derObj = new DERBitString();
break;
case DER.Boolean:
derObj = new DERBoolean();
break;
case DER.Enumerated:
derObj = new DEREnumerated();
break;
case DER.GeneralString:
derObj = new DERGeneralString();
break;
case DER.Integer:
derObj = new DERInteger();
break;
case DER.NumericString:
break;
case DER.ObjectIdentifier:
derObj = new DEROid();
break;
case DER.OctetString:
derObj = new DEROctetString();
break;
case DER.PrintableString:
break;
case DER.Sequence:
derObj = new DERSequence();
break;
case DER.UniversalString:
break;
case DER.UTF8String:
break;
case DER.GeneralizedTime:
derObj = new DERGeneralizedTime();
break;
}
}
// Decode the object, if valid
if ( derObj != null)
derObj.derDecode( this);
else
throw new IOException("ASN.1 type 0x" + Integer.toHexString( objTyp) + " decode not supported");
// Set the tag number, if tagged
if ( tagNo != -1)
derObj.setTagNo( tagNo);
// Return the new object
return derObj;
}
/**
* Unpack an integer value
*
* @param len int
* @return int
*/
public final int unpackInt(int len) {
// Check if there is enough data in the buffer
if ( m_data.length - m_pos < len)
throw new ArrayIndexOutOfBoundsException("End of data buffer");
// Unpack the integer value
int ival = 0;
for ( int i = 0; i < len; i++) {
ival <<= 8;
ival += unpackByte();
}
return ival;
}
/**
* Unpack a long (64 bit) value
*
* @param len int
* @return long
*/
public final long unpackLong(int len) {
// Check if there is enough data in the buffer
if ( m_data.length - m_pos < len)
throw new ArrayIndexOutOfBoundsException("End of data buffer");
// Unpack the integer value
int lval = 0;
for ( int i = 0; i < len; i++) {
lval <<= 8;
lval += unpackByte();
}
return lval;
}
/**
* Append a byte value to the buffer
*
* @param bval int
*/
public final void packByte(int bval) {
// Check if there is enough space in the buffer
if ( m_data.length - m_pos < 1)
extendBuffer();
// Pack the byte value
m_data[m_pos++] = (byte) (bval & 0xFF);
}
/**
* Pack a byte at the specified position
*
* @param pos int
* @param bval int
*/
public final void packByteAt(int pos, int bval) {
// Pack the byte value
m_data[pos] = (byte) (bval & 0xFF);
}
/**
* Pack bytes into the buffer
*
* @param buf byte[]
* @param off int
* @param len int
*/
public final void packBytes(byte[] buf, int off, int len) {
// Check if there is enough space in the buffer
if ( m_data.length - m_pos < len)
extendBuffer(len);
// Copy the data to the buffer and update the current write position
System.arraycopy(buf,off,m_data,m_pos,len);
m_pos += len;
}
/**
* Pack bytes from the specified DER buffer into this buffer
*
* @param buf DERBuffer
*/
public final void packBytes( DERBuffer buf) {
// Check if there is enough space in the buffer
if ( m_data.length - m_pos < buf.getLength())
extendBuffer( buf.getLength());
// Copy the data to the buffer and update the current write position
System.arraycopy( buf.getBuffer(), 0, m_data, m_pos, buf.getLength());
m_pos += buf.getLength();
}
/**
* Pack a data length
*
* @param len int
*/
public final void packLength( int len) {
// Check if there is enough space in the buffer
if ( m_data.length - m_pos < 4)
extendBuffer();
// Check if the length will fit in a single byte
if ( len < 128) {
// Pack a short length
packByte( len);
}
else {
// Calculate the number of bytes required to store the length and pack
int sizByts = calculateLengthBytes( len);
packByte( sizByts + 0x80);
// Pack the length bytes
int shift = (sizByts - 1) * 8;
while ( shift >= 0) {
packByte( len >> shift);
shift -= 8;
}
}
}
/**
* Pack an integer value
*
* @param ival int
*/
public final void packInt(int ival) {
// Check if there is enough space in the buffer
if ( m_data.length - m_pos < 4)
extendBuffer();
// Pack the integer value
DataPacker.putIntelInt( ival, m_data, m_pos);
m_pos += 4;
}
/**
* Pack a long value
*
* @param lval long
*/
public final void packLong(long lval) {
// Check if there is enough space in the buffer
if ( m_data.length - m_pos < 8)
extendBuffer();
// Pack the long value
DataPacker.putIntelLong( lval, m_data, m_pos);
m_pos += 8;
}
/**
* Pack an object
*
* @param derObj DERObject
* @throws IOException
*/
public final void packObject( DERObject derObj)
throws IOException {
// If the object is tagged we need to pack to a seperate buffer first to get the object length
if ( derObj.isTagged()) {
// Encode the object to a seperate buffer
DERBuffer derBuf = new DERBuffer();
derObj.derEncode( derBuf);
// Pack the tagged type
packByte( DER.Tagged + DER.Constructed + derObj.getTagNo());
packLength( derBuf.getLength());
packBytes( derBuf);
}
else {
// Encode the object into this DER buffer
derObj.derEncode( this);
}
}
/**
* Pack an application specific object
*
* @param derObj DERObject
* @exception IOException
*/
public final void packApplicationSpecific(DERObject derObj)
throws IOException {
// Pack the object
packApplicationSpecific(0, derObj);
}
/**
* Pack an application specific object
*
* @param tagId int
* @param derObj DERObject
* @exception IOException
*/
public final void packApplicationSpecific(int tagId, DERObject derObj)
throws IOException {
// Pack the header
packByte( DER.Application + DER.Constructed + (tagId & 0x1F));
// Pack the object to a seperate buffer to get the length
DERBuffer derBuf = new DERBuffer();
derBuf.packObject( derObj);
// Pack the length and object
packLength( derBuf.getLength());
packBytes( derBuf);
}
/**
* Pack an application specific list of objects
*
* @param derList List
* @exception IOException
*/
public final void packApplicationSpecific(List derList)
throws IOException {
// Pack the list of objects
packApplicationSpecific( 0, derList);
}
/**
* Pack an application specific list of objects
*
* @param tagId int
* @param derList List
* @exception IOException
*/
public final void packApplicationSpecific(int tagId, List derList)
throws IOException {
// Pack the header
packByte( DER.Application + DER.Constructed + (tagId & 0x1F));
// Pack the objects to a seperate buffer to get the length
DERBuffer derBuf = null;
if ( derList != null && derList.size() > 0) {
// Allocate the buffer
derBuf = new DERBuffer();
// Pack the objects
for ( int i = 0; i < derList.size(); i++) {
// Pack the current object
DERObject derObj = (DERObject) derList.get( i);
derBuf.packObject( derObj);
}
// Pack the length and objects
packLength( derBuf.getLength());
packBytes( derBuf);
}
else
packLength( 0);
}
/**
* Pack an application specific object
*
* @param tagId int
* @param byts byte[]
* @exception IOException
*/
public final void packApplicationSpecific(int tagId, byte[] byts)
throws IOException {
// Pack the header
packByte( DER.Application + DER.Constructed + (tagId & 0x1F));
// Pack the length and object
packLength( byts.length);
packBytes( byts, 0, byts.length);
}
/**
* Pack an application specific object
*
* @param byts byte[]
* @exception IOException
*/
public final void packApplicationSpecific(byte[] byts)
throws IOException {
// Pack the bytes
packApplicationSpecific( 0, byts);
}
/**
* Unpack an application specific object
*
* @return DERObject
* @exception IOException
*/
public final DERObject unpackApplicationSpecific()
throws IOException {
// Get the first entry from the stream
DERObject derObj = null;
int typ = unpackType();
if ( DER.isApplicationSpecific(typ)) {
// Get the object length
unpackLength();
// Unpack the top level object, this will cause all objects to be loaded
derObj = unpackObject();
}
else
throw new IOException("Wrong DER type, expected Application or Context");
// Return the decoded object
return derObj;
}
/**
* Unpack an application specific blob
*
* @return byte[]
* @exception IOException
*/
public final byte[] unpackApplicationSpecificBytes()
throws IOException {
// Get the first entry from the stream
int typ = unpackType();
byte[] objByts = null;
if ( DER.isApplicationSpecific(typ)) {
// Get the object length
int len = unpackLength();
// Unpack the top level object bytes
objByts = unpackBytes( len);
}
else
throw new IOException("Wrong DER type, expected Application or Context");
// Return the object bytes
return objByts;
}
/**
* Calculate the number of length bytes required for a data length
*
* @param len
* @return int
*/
public final int calculateLengthBytes(int len) {
// Check if the length will fit in a single byte
int sizByts = 1;
if ( len > 0x00FF) {
// Calculate the number of bytes required to store the length
sizByts = 2;
if ( len > 0x00FFFFFF)
sizByts = 4;
else if ( len > 0x0000FFFF)
sizByts = 3;
}
return sizByts;
}
/**
* Set the read/write buffer position
*
* @param pos int
*/
public final void setPosition(int pos) {
m_pos = pos;
}
/**
* Set the end of buffer position, and reset the read position to the beginning of the buffer
*/
public final void setEndOfBuffer() {
m_endpos = m_pos;
m_pos = m_offset;
}
/**
* Set the data length
*
* @param len int
*/
public final void setLength(int len) {
m_pos = m_offset + len;
}
/**
* Extend the data buffer by the specified amount
*
* @param ext int
*/
private final void extendBuffer(int ext) {
// Create a new buffer of the required size
byte[] newBuf = new byte[m_data.length + ext];
// Copy the data from the current buffer to the new buffer
System.arraycopy(m_data, 0, newBuf, 0, m_data.length);
// Set the new buffer to be the main buffer
m_data = newBuf;
}
/**
* Extend the data buffer, double the currently allocated buffer size
*/
private final void extendBuffer() {
extendBuffer(m_data.length * 2);
}
}