/* * 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); } }