001package org.apache.commons.ssl.org.bouncycastle.asn1;
002
003import java.io.ByteArrayOutputStream;
004import java.io.IOException;
005
006import org.bouncycastle.util.Arrays;
007
008/**
009 * Base class for an application specific object
010 */
011public class DERApplicationSpecific 
012    extends ASN1Primitive
013{
014    private final boolean   isConstructed;
015    private final int       tag;
016    private final byte[]    octets;
017
018    DERApplicationSpecific(
019        boolean isConstructed,
020        int     tag,
021        byte[]  octets)
022    {
023        this.isConstructed = isConstructed;
024        this.tag = tag;
025        this.octets = octets;
026    }
027
028    public DERApplicationSpecific(
029        int    tag,
030        byte[] octets)
031    {
032        this(false, tag, octets);
033    }
034
035    public DERApplicationSpecific(
036        int                  tag, 
037        ASN1Encodable object)
038        throws IOException 
039    {
040        this(true, tag, object);
041    }
042
043    public DERApplicationSpecific(
044        boolean      explicit,
045        int          tag,
046        ASN1Encodable object)
047        throws IOException
048    {
049        ASN1Primitive primitive = object.toASN1Primitive();
050
051        byte[] data = primitive.getEncoded(ASN1Encoding.DER);
052
053        this.isConstructed = explicit || (primitive instanceof ASN1Set || primitive instanceof ASN1Sequence);
054        this.tag = tag;
055
056        if (explicit)
057        {
058            this.octets = data;
059        }
060        else
061        {
062            int lenBytes = getLengthOfHeader(data);
063            byte[] tmp = new byte[data.length - lenBytes];
064            System.arraycopy(data, lenBytes, tmp, 0, tmp.length);
065            this.octets = tmp;
066        }
067    }
068
069    public DERApplicationSpecific(int tagNo, ASN1EncodableVector vec)
070    {
071        this.tag = tagNo;
072        this.isConstructed = true;
073        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
074
075        for (int i = 0; i != vec.size(); i++)
076        {
077            try
078            {
079                bOut.write(((ASN1Object)vec.get(i)).getEncoded(ASN1Encoding.DER));
080            }
081            catch (IOException e)
082            {
083                throw new ASN1ParsingException("malformed object: " + e, e);
084            }
085        }
086        this.octets = bOut.toByteArray();
087    }
088
089    public static DERApplicationSpecific getInstance(Object obj)
090    {
091        if (obj == null || obj instanceof DERApplicationSpecific)
092        {
093            return (DERApplicationSpecific)obj;
094        }
095        else if (obj instanceof byte[])
096        {
097            try
098            {
099                return DERApplicationSpecific.getInstance(ASN1Primitive.fromByteArray((byte[])obj));
100            }
101            catch (IOException e)
102            {
103                throw new IllegalArgumentException("failed to construct object from byte[]: " + e.getMessage());
104            }
105        }
106
107        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
108    }
109
110    private int getLengthOfHeader(byte[] data)
111    {
112        int length = data[1] & 0xff; // TODO: assumes 1 byte tag
113
114        if (length == 0x80)
115        {
116            return 2;      // indefinite-length encoding
117        }
118
119        if (length > 127)
120        {
121            int size = length & 0x7f;
122
123            // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
124            if (size > 4)
125            {
126                throw new IllegalStateException("DER length more than 4 bytes: " + size);
127            }
128
129            return size + 2;
130        }
131
132        return 2;
133    }
134
135    public boolean isConstructed()
136    {
137        return isConstructed;
138    }
139    
140    public byte[] getContents()
141    {
142        return octets;
143    }
144    
145    public int getApplicationTag() 
146    {
147        return tag;
148    }
149
150    /**
151     * Return the enclosed object assuming explicit tagging.
152     *
153     * @return  the resulting object
154     * @throws IOException if reconstruction fails.
155     */
156    public ASN1Primitive getObject()
157        throws IOException 
158    {
159        return new ASN1InputStream(getContents()).readObject();
160    }
161
162    /**
163     * Return the enclosed object assuming implicit tagging.
164     *
165     * @param derTagNo the type tag that should be applied to the object's contents.
166     * @return  the resulting object
167     * @throws IOException if reconstruction fails.
168     */
169    public ASN1Primitive getObject(int derTagNo)
170        throws IOException
171    {
172        if (derTagNo >= 0x1f)
173        {
174            throw new IOException("unsupported tag number");
175        }
176
177        byte[] orig = this.getEncoded();
178        byte[] tmp = replaceTagNumber(derTagNo, orig);
179
180        if ((orig[0] & BERTags.CONSTRUCTED) != 0)
181        {
182            tmp[0] |= BERTags.CONSTRUCTED;
183        }
184
185        return new ASN1InputStream(tmp).readObject();
186    }
187
188    int encodedLength()
189        throws IOException
190    {
191        return StreamUtil.calculateTagLength(tag) + StreamUtil.calculateBodyLength(octets.length) + octets.length;
192    }
193
194    /* (non-Javadoc)
195     * @see org.bouncycastle.asn1.ASN1Primitive#encode(org.bouncycastle.asn1.DEROutputStream)
196     */
197    void encode(ASN1OutputStream out) throws IOException
198    {
199        int classBits = BERTags.APPLICATION;
200        if (isConstructed)
201        {
202            classBits |= BERTags.CONSTRUCTED;
203        }
204
205        out.writeEncoded(classBits, tag, octets);
206    }
207    
208    boolean asn1Equals(
209        ASN1Primitive o)
210    {
211        if (!(o instanceof DERApplicationSpecific))
212        {
213            return false;
214        }
215
216        DERApplicationSpecific other = (DERApplicationSpecific)o;
217
218        return isConstructed == other.isConstructed
219            && tag == other.tag
220            && Arrays.areEqual(octets, other.octets);
221    }
222
223    public int hashCode()
224    {
225        return (isConstructed ? 1 : 0) ^ tag ^ Arrays.hashCode(octets);
226    }
227
228    private byte[] replaceTagNumber(int newTag, byte[] input)
229        throws IOException
230    {
231        int tagNo = input[0] & 0x1f;
232        int index = 1;
233        //
234        // with tagged object tag number is bottom 5 bits, or stored at the start of the content
235        //
236        if (tagNo == 0x1f)
237        {
238            tagNo = 0;
239
240            int b = input[index++] & 0xff;
241
242            // X.690-0207 8.1.2.4.2
243            // "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
244            if ((b & 0x7f) == 0) // Note: -1 will pass
245            {
246                throw new ASN1ParsingException("corrupted stream - invalid high tag number found");
247            }
248
249            while ((b >= 0) && ((b & 0x80) != 0))
250            {
251                tagNo |= (b & 0x7f);
252                tagNo <<= 7;
253                b = input[index++] & 0xff;
254            }
255
256            tagNo |= (b & 0x7f);
257        }
258
259        byte[] tmp = new byte[input.length - index + 1];
260
261        System.arraycopy(input, index, tmp, 1, tmp.length - 1);
262
263        tmp[0] = (byte)newTag;
264
265        return tmp;
266    }
267}