| /* |
| * Copyright (c) 2000 - 2006 The Legion Of The Bouncy Castle (http://www.bouncycastle.org) |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy of this |
| * software and associated documentation files (the "Software"), to deal in the Software |
| * without restriction, including without limitation the rights to use, copy, modify, |
| * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to the following |
| * conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in all copies |
| * or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
| * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
| * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
| * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
| * DEALINGS IN THE SOFTWARE. |
| * |
| */ |
| |
| package org.apache.directory.shared.asn1.der; |
| |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.EOFException; |
| import java.io.FilterInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.nio.ByteBuffer; |
| import java.util.Vector; |
| |
| |
| /** |
| * General purpose ASN.1 decoder. |
| */ |
| public class ASN1InputStream extends FilterInputStream |
| { |
| private boolean EOF_FOUND = false; |
| |
| private DERObject END_OF_STREAM = new DERObject( 0, null ) |
| { |
| public void encode( ASN1OutputStream out ) throws IOException |
| { |
| throw new IOException( "End of stream." ); |
| } |
| |
| |
| /** |
| * Compute the instance hash code |
| * @return the instance's hashcode |
| */ |
| public int hashCode() |
| { |
| return 0; |
| } |
| |
| |
| public boolean equals( Object o ) |
| { |
| return o == this; |
| } |
| }; |
| |
| |
| public ASN1InputStream(ByteBuffer in) |
| { |
| super( newInputStream( in ) ); |
| } |
| |
| |
| public ASN1InputStream(byte[] input) |
| { |
| super( new ByteArrayInputStream( input ) ); |
| } |
| |
| |
| private static InputStream newInputStream( final ByteBuffer buf ) |
| { |
| return new InputStream() |
| { |
| public synchronized int read() throws IOException |
| { |
| if ( !buf.hasRemaining() ) |
| { |
| return -1; |
| } |
| |
| int result = buf.get() & 0x000000FF; |
| |
| return result; |
| } |
| |
| |
| public synchronized int read( byte[] bytes, int off, int len ) throws IOException |
| { |
| // Read only what's left |
| len = Math.min( len, buf.remaining() ); |
| buf.get( bytes, off, len ); |
| return len; |
| } |
| }; |
| } |
| |
| |
| protected int readLength() throws IOException |
| { |
| int length = read(); |
| if ( length < 0 ) |
| { |
| throw new IOException( "EOF found when length expected." ); |
| } |
| |
| // Indefinite-length encoding. |
| if ( length == 0x80 ) |
| { |
| return -1; |
| } |
| |
| if ( length > 127 ) |
| { |
| int size = length & 0x7f; |
| |
| if ( size > 4 ) |
| { |
| throw new IOException( "DER length more than 4 bytes." ); |
| } |
| |
| length = 0; |
| for ( int i = 0; i < size; i++ ) |
| { |
| int next = read(); |
| |
| if ( next < 0 ) |
| { |
| throw new IOException( "EOF found reading length." ); |
| } |
| |
| length = ( length << 8 ) + next; |
| } |
| |
| if ( length < 0 ) |
| { |
| throw new IOException( "Corrupted steam - negative length found." ); |
| } |
| } |
| |
| return length; |
| } |
| |
| |
| protected void readFully( byte[] bytes ) throws IOException |
| { |
| int left = bytes.length; |
| int len; |
| |
| if ( left == 0 ) |
| { |
| return; |
| } |
| |
| while ( ( len = read( bytes, bytes.length - left, left ) ) > 0 ) |
| { |
| if ( ( left -= len ) == 0 ) |
| { |
| return; |
| } |
| } |
| |
| if ( left != 0 ) |
| { |
| throw new EOFException( "EOF encountered in middle of object." ); |
| } |
| } |
| |
| |
| /** |
| * Build an object given its tag and a byte stream. |
| */ |
| protected DEREncodable buildObject( int tag, byte[] bytes ) throws IOException |
| { |
| if ( ( tag & DERObject.APPLICATION ) != 0 ) |
| { |
| return new DERApplicationSpecific( tag, bytes ); |
| } |
| |
| switch ( tag ) |
| { |
| case DERObject.NULL: |
| return new DERNull(); |
| case DERObject.SEQUENCE | DERObject.CONSTRUCTED: |
| ASN1InputStream ais = new ASN1InputStream( bytes ); |
| DEREncodable obj = null; |
| DERSequence sequence = new DERSequence(); |
| |
| try |
| { |
| obj = ais.readObject(); |
| |
| while ( obj != null ) |
| { |
| sequence.add( obj ); |
| obj = ais.readObject(); |
| } |
| } |
| finally |
| { |
| ais.close(); |
| } |
| |
| return sequence; |
| case DERObject.SET | DERObject.CONSTRUCTED: |
| ais = new ASN1InputStream( bytes ); |
| DERSet set = new DERSet(); |
| |
| try |
| { |
| obj = ais.readObject(); |
| |
| while ( obj != null ) |
| { |
| set.add( obj ); |
| obj = ais.readObject(); |
| } |
| } |
| finally |
| { |
| ais.close(); |
| } |
| |
| return set; |
| case DERObject.BOOLEAN: |
| return new DERBoolean( bytes ); |
| case DERObject.INTEGER: |
| return new DERInteger( bytes ); |
| case DERObject.ENUMERATED: |
| return new DEREnumerated( bytes ); |
| case DERObject.OBJECT_IDENTIFIER: |
| return new DERObjectIdentifier( bytes ); |
| case DERObject.BIT_STRING: |
| return new DERBitString( bytes ); |
| case DERObject.NUMERIC_STRING: |
| return new DERNumericString( bytes ); |
| case DERObject.UTF8_STRING: |
| return new DERUTF8String( bytes ); |
| case DERObject.PRINTABLE_STRING: |
| return new DERPrintableString( bytes ); |
| case DERObject.IA5_STRING: |
| return new DERIA5String( bytes ); |
| case DERObject.T61_STRING: |
| return new DERTeletexString( bytes ); |
| case DERObject.VISIBLE_STRING: |
| return new DERVisibleString( bytes ); |
| case DERObject.GENERAL_STRING: |
| return new DERGeneralString( bytes ); |
| case DERObject.UNIVERSAL_STRING: |
| return new DERUniversalString( bytes ); |
| case DERObject.BMP_STRING: |
| return new DERBMPString( bytes ); |
| case DERObject.OCTET_STRING: |
| return new DEROctetString( bytes ); |
| case DERObject.UTC_TIME: |
| return new DERUTCTime( bytes ); |
| case DERObject.GENERALIZED_TIME: |
| return new DERGeneralizedTime( bytes ); |
| default: |
| // Tag number is bottom 5 bits. |
| if ( ( tag & DERObject.TAGGED ) != 0 ) |
| { |
| int tagNo = tag & 0x1f; |
| |
| if ( tagNo == 0x1f ) |
| { |
| int idx = 0; |
| |
| tagNo = 0; |
| |
| while ( ( bytes[idx] & 0x80 ) != 0 ) |
| { |
| tagNo |= ( bytes[idx++] & 0x7f ); |
| tagNo <<= 7; |
| } |
| |
| tagNo |= ( bytes[idx] & 0x7f ); |
| |
| byte[] tmp = bytes; |
| |
| bytes = new byte[tmp.length - ( idx + 1 )]; |
| System.arraycopy( tmp, idx + 1, bytes, 0, bytes.length ); |
| } |
| |
| // Empty tag. |
| if ( bytes.length == 0 ) |
| { |
| if ( ( tag & DERObject.CONSTRUCTED ) == 0 ) |
| { |
| return new DERTaggedObject( tagNo, new DERNull() ); |
| } |
| |
| return new DERTaggedObject( false, tagNo, new DERSequence() ); |
| } |
| |
| // Simple type - implicit, return an octet string. |
| if ( ( tag & DERObject.CONSTRUCTED ) == 0 ) |
| { |
| return new DERTaggedObject( false, tagNo, new DEROctetString( bytes ) ); |
| } |
| |
| ais = new ASN1InputStream( bytes ); |
| |
| try |
| { |
| DEREncodable encodable = ais.readObject(); |
| |
| // Explicitly tagged - if it isn't we'd have to tell from |
| // the context. |
| if ( ais.available() == 0 ) |
| { |
| return new DERTaggedObject( true, tagNo, encodable, bytes ); |
| } |
| |
| // Another implicit object, create a sequence. |
| DERSequence derSequence = new DERSequence(); |
| |
| while ( encodable != null ) |
| { |
| derSequence.add( encodable ); |
| encodable = ais.readObject(); |
| } |
| |
| return new DERTaggedObject( false, tagNo, derSequence ); |
| } |
| finally |
| { |
| ais.close(); |
| } |
| } |
| |
| return new DERUnknownTag( tag, bytes ); |
| } |
| } |
| |
| |
| /** |
| * Read a string of bytes representing an indefinite length object. |
| */ |
| private byte[] readIndefiniteLengthFully() throws IOException |
| { |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| int b, b1; |
| |
| b1 = read(); |
| |
| while ( ( b = read() ) >= 0 ) |
| { |
| if ( b1 == 0 && b == 0 ) |
| { |
| break; |
| } |
| |
| baos.write( b1 ); |
| b1 = b; |
| } |
| |
| return baos.toByteArray(); |
| } |
| |
| |
| private BERConstructedOctetString buildConstructedOctetString() throws IOException |
| { |
| Vector<DEREncodable> octets = new Vector<DEREncodable>(); |
| |
| for ( ;; ) |
| { |
| DEREncodable encodable = readObject(); |
| |
| if ( encodable == END_OF_STREAM ) |
| { |
| break; |
| } |
| |
| octets.addElement( encodable ); |
| } |
| |
| return new BERConstructedOctetString( octets ); |
| } |
| |
| |
| public DEREncodable readObject() throws IOException |
| { |
| int tag = read(); |
| if ( tag == -1 ) |
| { |
| if ( EOF_FOUND ) |
| { |
| throw new EOFException( "Attempt to read past end of file." ); |
| } |
| |
| EOF_FOUND = true; |
| |
| return null; |
| } |
| |
| int length = readLength(); |
| |
| // Indefinite length method. |
| if ( length < 0 ) |
| { |
| switch ( tag ) |
| { |
| case DERObject.NULL: |
| return new BERNull(); |
| case DERObject.SEQUENCE | DERObject.CONSTRUCTED: |
| BERSequence sequence = new BERSequence(); |
| |
| for ( ;; ) |
| { |
| DEREncodable obj = readObject(); |
| |
| if ( obj == END_OF_STREAM ) |
| { |
| break; |
| } |
| |
| sequence.add( obj ); |
| } |
| return sequence; |
| case DERObject.SET | DERObject.CONSTRUCTED: |
| BERSet set = new BERSet(); |
| |
| for ( ;; ) |
| { |
| DEREncodable obj = readObject(); |
| |
| if ( obj == END_OF_STREAM ) |
| { |
| break; |
| } |
| |
| set.add( obj ); |
| } |
| return set; |
| case DERObject.OCTET_STRING | DERObject.CONSTRUCTED: |
| return buildConstructedOctetString(); |
| default: |
| // Tag number is bottom 5 bits. |
| if ( ( tag & DERObject.TAGGED ) != 0 ) |
| { |
| int tagNo = tag & 0x1f; |
| |
| if ( tagNo == 0x1f ) |
| { |
| int b = read(); |
| |
| tagNo = 0; |
| |
| while ( ( b >= 0 ) && ( ( b & 0x80 ) != 0 ) ) |
| { |
| tagNo |= ( b & 0x7f ); |
| tagNo <<= 7; |
| b = read(); |
| } |
| |
| tagNo |= ( b & 0x7f ); |
| } |
| |
| // Simple type - implicit, return an octet string. |
| if ( ( tag & DERObject.CONSTRUCTED ) == 0 ) |
| { |
| byte[] bytes = readIndefiniteLengthFully(); |
| |
| return new BERTaggedObject( false, tagNo, new DEROctetString( bytes ) ); |
| } |
| |
| // Either constructed or explicitly tagged |
| DEREncodable dObj = readObject(); |
| |
| // Empty tag! |
| if ( dObj == END_OF_STREAM ) |
| { |
| return new DERTaggedObject( tagNo ); |
| } |
| |
| DEREncodable next = readObject(); |
| |
| // Explicitly tagged. |
| if ( next == END_OF_STREAM ) |
| { |
| return new BERTaggedObject( tagNo, dObj ); |
| } |
| |
| // Another implicit object, create a sequence. |
| BERSequence berSequence = new BERSequence(); |
| |
| berSequence.add( dObj ); |
| |
| do |
| { |
| berSequence.add( next ); |
| next = readObject(); |
| } |
| while ( next != END_OF_STREAM ); |
| |
| return new BERTaggedObject( false, tagNo, berSequence ); |
| } |
| |
| throw new IOException( "Unknown BER object encountered." ); |
| } |
| } |
| |
| // End of contents marker. |
| if ( tag == 0 && length == 0 ) |
| { |
| return END_OF_STREAM; |
| } |
| |
| byte[] bytes = new byte[length]; |
| |
| readFully( bytes ); |
| |
| return buildObject( tag, bytes ); |
| } |
| } |