1package org.bouncycastle.asn1; 2 3import java.io.ByteArrayOutputStream; 4import java.io.IOException; 5 6import org.bouncycastle.util.Arrays; 7 8/** 9 * Base class for an application specific object 10 */ 11public class DERApplicationSpecific 12 extends ASN1Primitive 13{ 14 private final boolean isConstructed; 15 private final int tag; 16 private final byte[] octets; 17 18 DERApplicationSpecific( 19 boolean isConstructed, 20 int tag, 21 byte[] octets) 22 { 23 this.isConstructed = isConstructed; 24 this.tag = tag; 25 this.octets = octets; 26 } 27 28 public DERApplicationSpecific( 29 int tag, 30 byte[] octets) 31 { 32 this(false, tag, octets); 33 } 34 35 public DERApplicationSpecific( 36 int tag, 37 ASN1Encodable object) 38 throws IOException 39 { 40 this(true, tag, object); 41 } 42 43 public DERApplicationSpecific( 44 boolean explicit, 45 int tag, 46 ASN1Encodable object) 47 throws IOException 48 { 49 ASN1Primitive primitive = object.toASN1Primitive(); 50 51 byte[] data = primitive.getEncoded(ASN1Encoding.DER); 52 53 this.isConstructed = explicit || (primitive instanceof ASN1Set || primitive instanceof ASN1Sequence); 54 this.tag = tag; 55 56 if (explicit) 57 { 58 this.octets = data; 59 } 60 else 61 { 62 int lenBytes = getLengthOfHeader(data); 63 byte[] tmp = new byte[data.length - lenBytes]; 64 System.arraycopy(data, lenBytes, tmp, 0, tmp.length); 65 this.octets = tmp; 66 } 67 } 68 69 public DERApplicationSpecific(int tagNo, ASN1EncodableVector vec) 70 { 71 this.tag = tagNo; 72 this.isConstructed = true; 73 ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 74 75 for (int i = 0; i != vec.size(); i++) 76 { 77 try 78 { 79 bOut.write(((ASN1Object)vec.get(i)).getEncoded(ASN1Encoding.DER)); 80 } 81 catch (IOException e) 82 { 83 throw new ASN1ParsingException("malformed object: " + e, e); 84 } 85 } 86 this.octets = bOut.toByteArray(); 87 } 88 89 public static DERApplicationSpecific getInstance(Object obj) 90 { 91 if (obj == null || obj instanceof DERApplicationSpecific) 92 { 93 return (DERApplicationSpecific)obj; 94 } 95 else if (obj instanceof byte[]) 96 { 97 try 98 { 99 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} 268