package org.bouncycastle.asn1; import java.io.ByteArrayOutputStream; import java.io.IOException; import org.bouncycastle.util.Arrays; /** * Base class for an application specific object */ public class DERApplicationSpecific extends ASN1Object { private final boolean isConstructed; private final int tag; private final byte[] octets; DERApplicationSpecific( boolean isConstructed, int tag, byte[] octets) { this.isConstructed = isConstructed; this.tag = tag; this.octets = octets; } public DERApplicationSpecific( int tag, byte[] octets) { this(false, tag, octets); } public DERApplicationSpecific( int tag, DEREncodable object) throws IOException { this(true, tag, object); } public DERApplicationSpecific( boolean explicit, int tag, DEREncodable object) throws IOException { byte[] data = object.getDERObject().getDEREncoded(); this.isConstructed = explicit; this.tag = tag; if (explicit) { this.octets = data; } else { int lenBytes = getLengthOfLength(data); byte[] tmp = new byte[data.length - lenBytes]; System.arraycopy(data, lenBytes, tmp, 0, tmp.length); this.octets = tmp; } } public DERApplicationSpecific(int tagNo, ASN1EncodableVector vec) { this.tag = tagNo; this.isConstructed = true; ByteArrayOutputStream bOut = new ByteArrayOutputStream(); for (int i = 0; i != vec.size(); i++) { try { bOut.write(((ASN1Encodable)vec.get(i)).getEncoded()); } catch (IOException e) { throw new ASN1ParsingException("malformed object: " + e, e); } } this.octets = bOut.toByteArray(); } private int getLengthOfLength(byte[] data) { int count = 2; // TODO: assumes only a 1 byte tag number while((data[count - 1] & 0x80) != 0) { count++; } return count; } public boolean isConstructed() { return isConstructed; } public byte[] getContents() { return octets; } public int getApplicationTag() { return tag; } /** * Return the enclosed object assuming explicit tagging. * * @return the resulting object * @throws IOException if reconstruction fails. */ public DERObject getObject() throws IOException { return new ASN1InputStream(getContents()).readObject(); } /** * Return the enclosed object assuming implicit tagging. * * @param derTagNo the type tag that should be applied to the object's contents. * @return the resulting object * @throws IOException if reconstruction fails. */ public DERObject getObject(int derTagNo) throws IOException { if (derTagNo >= 0x1f) { throw new IOException("unsupported tag number"); } byte[] orig = this.getEncoded(); byte[] tmp = replaceTagNumber(derTagNo, orig); if ((orig[0] & DERTags.CONSTRUCTED) != 0) { tmp[0] |= DERTags.CONSTRUCTED; } return new ASN1InputStream(tmp).readObject(); } /* (non-Javadoc) * @see org.bouncycastle.asn1.DERObject#encode(org.bouncycastle.asn1.DEROutputStream) */ void encode(DEROutputStream out) throws IOException { int classBits = DERTags.APPLICATION; if (isConstructed) { classBits |= DERTags.CONSTRUCTED; } out.writeEncoded(classBits, tag, octets); } boolean asn1Equals( DERObject o) { if (!(o instanceof DERApplicationSpecific)) { return false; } DERApplicationSpecific other = (DERApplicationSpecific)o; return isConstructed == other.isConstructed && tag == other.tag && Arrays.areEqual(octets, other.octets); } public int hashCode() { return (isConstructed ? 1 : 0) ^ tag ^ Arrays.hashCode(octets); } private byte[] replaceTagNumber(int newTag, byte[] input) throws IOException { int tagNo = input[0] & 0x1f; int index = 1; // // with tagged object tag number is bottom 5 bits, or stored at the start of the content // if (tagNo == 0x1f) { tagNo = 0; int b = input[index++] & 0xff; // X.690-0207 8.1.2.4.2 // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." if ((b & 0x7f) == 0) // Note: -1 will pass { throw new ASN1ParsingException("corrupted stream - invalid high tag number found"); } while ((b >= 0) && ((b & 0x80) != 0)) { tagNo |= (b & 0x7f); tagNo <<= 7; b = input[index++] & 0xff; } tagNo |= (b & 0x7f); } byte[] tmp = new byte[input.length - index + 1]; System.arraycopy(input, index, tmp, 1, tmp.length - 1); tmp[0] = (byte)newTag; return tmp; } }