1 package org.bouncycastle.asn1; 2 3 import java.io.IOException; 4 5 import org.bouncycastle.util.Arrays; 6 import org.bouncycastle.util.encoders.Hex; 7 8 /** 9 * Base class for an ASN.1 ApplicationSpecific object 10 */ 11 public abstract class ASN1ApplicationSpecific 12 extends ASN1Primitive 13 { 14 protected final boolean isConstructed; 15 protected final int tag; 16 protected final byte[] octets; 17 ASN1ApplicationSpecific( boolean isConstructed, int tag, byte[] octets)18 ASN1ApplicationSpecific( 19 boolean isConstructed, 20 int tag, 21 byte[] octets) 22 { 23 this.isConstructed = isConstructed; 24 this.tag = tag; 25 this.octets = Arrays.clone(octets); 26 } 27 28 /** 29 * Return an ASN1ApplicationSpecific from the passed in object, which may be a byte array, or null. 30 * 31 * @param obj the object to be converted. 32 * @return obj's representation as an ASN1ApplicationSpecific object. 33 */ getInstance(Object obj)34 public static ASN1ApplicationSpecific getInstance(Object obj) 35 { 36 if (obj == null || obj instanceof ASN1ApplicationSpecific) 37 { 38 return (ASN1ApplicationSpecific)obj; 39 } 40 else if (obj instanceof byte[]) 41 { 42 try 43 { 44 return ASN1ApplicationSpecific.getInstance(ASN1Primitive.fromByteArray((byte[])obj)); 45 } 46 catch (IOException e) 47 { 48 throw new IllegalArgumentException("Failed to construct object from byte[]: " + e.getMessage()); 49 } 50 } 51 52 throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName()); 53 } 54 getLengthOfHeader(byte[] data)55 protected static int getLengthOfHeader(byte[] data) 56 { 57 int length = data[1] & 0xff; // TODO: assumes 1 byte tag 58 59 if (length == 0x80) 60 { 61 return 2; // indefinite-length encoding 62 } 63 64 if (length > 127) 65 { 66 int size = length & 0x7f; 67 68 // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here 69 if (size > 4) 70 { 71 throw new IllegalStateException("DER length more than 4 bytes: " + size); 72 } 73 74 return size + 2; 75 } 76 77 return 2; 78 } 79 80 /** 81 * Return true if the object is marked as constructed, false otherwise. 82 * 83 * @return true if constructed, otherwise false. 84 */ isConstructed()85 public boolean isConstructed() 86 { 87 return isConstructed; 88 } 89 90 /** 91 * Return the contents of this object as a byte[] 92 * 93 * @return the encoded contents of the object. 94 */ getContents()95 public byte[] getContents() 96 { 97 return Arrays.clone(octets); 98 } 99 100 /** 101 * Return the tag number associated with this object, 102 * 103 * @return the application tag number. 104 */ getApplicationTag()105 public int getApplicationTag() 106 { 107 return tag; 108 } 109 110 /** 111 * Return the enclosed object assuming explicit tagging. 112 * 113 * @return the resulting object 114 * @throws IOException if reconstruction fails. 115 */ getObject()116 public ASN1Primitive getObject() 117 throws IOException 118 { 119 return ASN1Primitive.fromByteArray(getContents()); 120 } 121 122 /** 123 * Return the enclosed object assuming implicit tagging. 124 * 125 * @param derTagNo the type tag that should be applied to the object's contents. 126 * @return the resulting object 127 * @throws IOException if reconstruction fails. 128 */ getObject(int derTagNo)129 public ASN1Primitive getObject(int derTagNo) 130 throws IOException 131 { 132 if (derTagNo >= 0x1f) 133 { 134 throw new IOException("unsupported tag number"); 135 } 136 137 byte[] orig = this.getEncoded(); 138 byte[] tmp = replaceTagNumber(derTagNo, orig); 139 140 if ((orig[0] & BERTags.CONSTRUCTED) != 0) 141 { 142 tmp[0] |= BERTags.CONSTRUCTED; 143 } 144 145 return ASN1Primitive.fromByteArray(tmp); 146 } 147 encodedLength()148 int encodedLength() 149 throws IOException 150 { 151 return StreamUtil.calculateTagLength(tag) + StreamUtil.calculateBodyLength(octets.length) + octets.length; 152 } 153 154 /* (non-Javadoc) 155 * @see org.bouncycastle.asn1.ASN1Primitive#encode(org.bouncycastle.asn1.DEROutputStream) 156 */ encode(ASN1OutputStream out, boolean withTag)157 void encode(ASN1OutputStream out, boolean withTag) throws IOException 158 { 159 int flags = BERTags.APPLICATION; 160 if (isConstructed) 161 { 162 flags |= BERTags.CONSTRUCTED; 163 } 164 165 out.writeEncoded(withTag, flags, tag, octets); 166 } 167 asn1Equals( ASN1Primitive o)168 boolean asn1Equals( 169 ASN1Primitive o) 170 { 171 if (!(o instanceof ASN1ApplicationSpecific)) 172 { 173 return false; 174 } 175 176 ASN1ApplicationSpecific other = (ASN1ApplicationSpecific)o; 177 178 return isConstructed == other.isConstructed 179 && tag == other.tag 180 && Arrays.areEqual(octets, other.octets); 181 } 182 hashCode()183 public int hashCode() 184 { 185 return (isConstructed ? 1 : 0) ^ tag ^ Arrays.hashCode(octets); 186 } 187 replaceTagNumber(int newTag, byte[] input)188 private byte[] replaceTagNumber(int newTag, byte[] input) 189 throws IOException 190 { 191 int tagNo = input[0] & 0x1f; 192 int index = 1; 193 // 194 // with tagged object tag number is bottom 5 bits, or stored at the start of the content 195 // 196 if (tagNo == 0x1f) 197 { 198 int b = input[index++] & 0xff; 199 200 // X.690-0207 8.1.2.4.2 201 // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." 202 if ((b & 0x7f) == 0) // Note: -1 will pass 203 { 204 throw new IOException("corrupted stream - invalid high tag number found"); 205 } 206 207 while ((b & 0x80) != 0) 208 { 209 b = input[index++] & 0xff; 210 } 211 } 212 213 byte[] tmp = new byte[input.length - index + 1]; 214 215 System.arraycopy(input, index, tmp, 1, tmp.length - 1); 216 217 tmp[0] = (byte)newTag; 218 219 return tmp; 220 } 221 toString()222 public String toString() 223 { 224 StringBuffer sb = new StringBuffer(); 225 sb.append("["); 226 if (isConstructed()) 227 { 228 sb.append("CONSTRUCTED "); 229 } 230 sb.append("APPLICATION "); 231 sb.append(Integer.toString(getApplicationTag())); 232 sb.append("]"); 233 // @todo content encoding somehow? 234 if (this.octets != null) 235 { 236 sb.append(" #"); 237 sb.append(Hex.toHexString(this.octets)); 238 } 239 else 240 { 241 sb.append(" #null"); 242 } 243 sb.append(" "); 244 return sb.toString(); 245 } 246 } 247