1 package org.bouncycastle.asn1; 2 3 import java.io.ByteArrayInputStream; 4 import java.io.EOFException; 5 import java.io.FilterInputStream; 6 import java.io.IOException; 7 import java.io.InputStream; 8 9 import org.bouncycastle.util.io.Streams; 10 11 /** 12 * a general purpose ASN.1 decoder - note: this class differs from the 13 * others in that it returns null after it has read the last object in 14 * the stream. If an ASN.1 NULL is encountered a DER/BER Null object is 15 * returned. 16 */ 17 public class ASN1InputStream 18 extends FilterInputStream 19 implements BERTags 20 { 21 private final int limit; 22 private final boolean lazyEvaluate; 23 24 private final byte[][] tmpBuffers; 25 ASN1InputStream( InputStream is)26 public ASN1InputStream( 27 InputStream is) 28 { 29 this(is, StreamUtil.findLimit(is)); 30 } 31 32 /** 33 * Create an ASN1InputStream based on the input byte array. The length of DER objects in 34 * the stream is automatically limited to the length of the input array. 35 * 36 * @param input array containing ASN.1 encoded data. 37 */ ASN1InputStream( byte[] input)38 public ASN1InputStream( 39 byte[] input) 40 { 41 this(new ByteArrayInputStream(input), input.length); 42 } 43 44 /** 45 * Create an ASN1InputStream based on the input byte array. The length of DER objects in 46 * the stream is automatically limited to the length of the input array. 47 * 48 * @param input array containing ASN.1 encoded data. 49 * @param lazyEvaluate true if parsing inside constructed objects can be delayed. 50 */ ASN1InputStream( byte[] input, boolean lazyEvaluate)51 public ASN1InputStream( 52 byte[] input, 53 boolean lazyEvaluate) 54 { 55 this(new ByteArrayInputStream(input), input.length, lazyEvaluate); 56 } 57 58 /** 59 * Create an ASN1InputStream where no DER object will be longer than limit. 60 * 61 * @param input stream containing ASN.1 encoded data. 62 * @param limit maximum size of a DER encoded object. 63 */ ASN1InputStream( InputStream input, int limit)64 public ASN1InputStream( 65 InputStream input, 66 int limit) 67 { 68 this(input, limit, false); 69 } 70 71 /** 72 * Create an ASN1InputStream where no DER object will be longer than limit, and constructed 73 * objects such as sequences will be parsed lazily. 74 * 75 * @param input stream containing ASN.1 encoded data. 76 * @param lazyEvaluate true if parsing inside constructed objects can be delayed. 77 */ ASN1InputStream( InputStream input, boolean lazyEvaluate)78 public ASN1InputStream( 79 InputStream input, 80 boolean lazyEvaluate) 81 { 82 this(input, StreamUtil.findLimit(input), lazyEvaluate); 83 } 84 85 /** 86 * Create an ASN1InputStream where no DER object will be longer than limit, and constructed 87 * objects such as sequences will be parsed lazily. 88 * 89 * @param input stream containing ASN.1 encoded data. 90 * @param limit maximum size of a DER encoded object. 91 * @param lazyEvaluate true if parsing inside constructed objects can be delayed. 92 */ ASN1InputStream( InputStream input, int limit, boolean lazyEvaluate)93 public ASN1InputStream( 94 InputStream input, 95 int limit, 96 boolean lazyEvaluate) 97 { 98 super(input); 99 this.limit = limit; 100 this.lazyEvaluate = lazyEvaluate; 101 this.tmpBuffers = new byte[11][]; 102 } 103 getLimit()104 int getLimit() 105 { 106 return limit; 107 } 108 readLength()109 protected int readLength() 110 throws IOException 111 { 112 return readLength(this, limit); 113 } 114 readFully( byte[] bytes)115 protected void readFully( 116 byte[] bytes) 117 throws IOException 118 { 119 if (Streams.readFully(this, bytes) != bytes.length) 120 { 121 throw new EOFException("EOF encountered in middle of object"); 122 } 123 } 124 125 /** 126 * build an object given its tag and the number of bytes to construct it from. 127 */ buildObject( int tag, int tagNo, int length)128 protected ASN1Primitive buildObject( 129 int tag, 130 int tagNo, 131 int length) 132 throws IOException 133 { 134 boolean isConstructed = (tag & CONSTRUCTED) != 0; 135 136 DefiniteLengthInputStream defIn = new DefiniteLengthInputStream(this, length); 137 138 if ((tag & APPLICATION) != 0) 139 { 140 return new DERApplicationSpecific(isConstructed, tagNo, defIn.toByteArray()); 141 } 142 143 if ((tag & TAGGED) != 0) 144 { 145 return new ASN1StreamParser(defIn).readTaggedObject(isConstructed, tagNo); 146 } 147 148 if (isConstructed) 149 { 150 // TODO There are other tags that may be constructed (e.g. BIT_STRING) 151 switch (tagNo) 152 { 153 case OCTET_STRING: 154 // 155 // yes, people actually do this... 156 // 157 ASN1EncodableVector v = buildDEREncodableVector(defIn); 158 ASN1OctetString[] strings = new ASN1OctetString[v.size()]; 159 160 for (int i = 0; i != strings.length; i++) 161 { 162 strings[i] = (ASN1OctetString)v.get(i); 163 } 164 165 return new BEROctetString(strings); 166 case SEQUENCE: 167 if (lazyEvaluate) 168 { 169 return new LazyEncodedSequence(defIn.toByteArray()); 170 } 171 else 172 { 173 return DERFactory.createSequence(buildDEREncodableVector(defIn)); 174 } 175 case SET: 176 return DERFactory.createSet(buildDEREncodableVector(defIn)); 177 case EXTERNAL: 178 return new DERExternal(buildDEREncodableVector(defIn)); 179 default: 180 throw new IOException("unknown tag " + tagNo + " encountered"); 181 } 182 } 183 184 return createPrimitiveDERObject(tagNo, defIn, tmpBuffers); 185 } 186 buildEncodableVector()187 ASN1EncodableVector buildEncodableVector() 188 throws IOException 189 { 190 ASN1EncodableVector v = new ASN1EncodableVector(); 191 ASN1Primitive o; 192 193 while ((o = readObject()) != null) 194 { 195 v.add(o); 196 } 197 198 return v; 199 } 200 buildDEREncodableVector( DefiniteLengthInputStream dIn)201 ASN1EncodableVector buildDEREncodableVector( 202 DefiniteLengthInputStream dIn) throws IOException 203 { 204 return new ASN1InputStream(dIn).buildEncodableVector(); 205 } 206 readObject()207 public ASN1Primitive readObject() 208 throws IOException 209 { 210 int tag = read(); 211 if (tag <= 0) 212 { 213 if (tag == 0) 214 { 215 throw new IOException("unexpected end-of-contents marker"); 216 } 217 218 return null; 219 } 220 221 // 222 // calculate tag number 223 // 224 int tagNo = readTagNumber(this, tag); 225 226 boolean isConstructed = (tag & CONSTRUCTED) != 0; 227 228 // 229 // calculate length 230 // 231 int length = readLength(); 232 233 if (length < 0) // indefinite length method 234 { 235 if (!isConstructed) 236 { 237 throw new IOException("indefinite length primitive encoding encountered"); 238 } 239 240 IndefiniteLengthInputStream indIn = new IndefiniteLengthInputStream(this, limit); 241 ASN1StreamParser sp = new ASN1StreamParser(indIn, limit); 242 243 if ((tag & APPLICATION) != 0) 244 { 245 return new BERApplicationSpecificParser(tagNo, sp).getLoadedObject(); 246 } 247 248 if ((tag & TAGGED) != 0) 249 { 250 return new BERTaggedObjectParser(true, tagNo, sp).getLoadedObject(); 251 } 252 253 // TODO There are other tags that may be constructed (e.g. BIT_STRING) 254 switch (tagNo) 255 { 256 case OCTET_STRING: 257 return new BEROctetStringParser(sp).getLoadedObject(); 258 case SEQUENCE: 259 return new BERSequenceParser(sp).getLoadedObject(); 260 case SET: 261 return new BERSetParser(sp).getLoadedObject(); 262 case EXTERNAL: 263 return new DERExternalParser(sp).getLoadedObject(); 264 default: 265 throw new IOException("unknown BER object encountered"); 266 } 267 } 268 else 269 { 270 try 271 { 272 return buildObject(tag, tagNo, length); 273 } 274 catch (IllegalArgumentException e) 275 { 276 throw new ASN1Exception("corrupted stream detected", e); 277 } 278 } 279 } 280 readTagNumber(InputStream s, int tag)281 static int readTagNumber(InputStream s, int tag) 282 throws IOException 283 { 284 int tagNo = tag & 0x1f; 285 286 // 287 // with tagged object tag number is bottom 5 bits, or stored at the start of the content 288 // 289 if (tagNo == 0x1f) 290 { 291 tagNo = 0; 292 293 int b = s.read(); 294 295 // X.690-0207 8.1.2.4.2 296 // "c) bits 7 to 1 of the first subsequent octet shall not all be zero." 297 if ((b & 0x7f) == 0) // Note: -1 will pass 298 { 299 throw new IOException("corrupted stream - invalid high tag number found"); 300 } 301 302 while ((b >= 0) && ((b & 0x80) != 0)) 303 { 304 tagNo |= (b & 0x7f); 305 tagNo <<= 7; 306 b = s.read(); 307 } 308 309 if (b < 0) 310 { 311 throw new EOFException("EOF found inside tag value."); 312 } 313 314 tagNo |= (b & 0x7f); 315 } 316 317 return tagNo; 318 } 319 readLength(InputStream s, int limit)320 static int readLength(InputStream s, int limit) 321 throws IOException 322 { 323 int length = s.read(); 324 if (length < 0) 325 { 326 throw new EOFException("EOF found when length expected"); 327 } 328 329 if (length == 0x80) 330 { 331 return -1; // indefinite-length encoding 332 } 333 334 if (length > 127) 335 { 336 int size = length & 0x7f; 337 338 // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here 339 if (size > 4) 340 { 341 throw new IOException("DER length more than 4 bytes: " + size); 342 } 343 344 length = 0; 345 for (int i = 0; i < size; i++) 346 { 347 int next = s.read(); 348 349 if (next < 0) 350 { 351 throw new EOFException("EOF found reading length"); 352 } 353 354 length = (length << 8) + next; 355 } 356 357 if (length < 0) 358 { 359 throw new IOException("corrupted stream - negative length found"); 360 } 361 362 if (length >= limit) // after all we must have read at least 1 byte 363 { 364 throw new IOException("corrupted stream - out of bounds length found"); 365 } 366 } 367 368 return length; 369 } 370 getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers)371 private static byte[] getBuffer(DefiniteLengthInputStream defIn, byte[][] tmpBuffers) 372 throws IOException 373 { 374 int len = defIn.getRemaining(); 375 if (defIn.getRemaining() < tmpBuffers.length) 376 { 377 byte[] buf = tmpBuffers[len]; 378 379 if (buf == null) 380 { 381 buf = tmpBuffers[len] = new byte[len]; 382 } 383 384 Streams.readFully(defIn, buf); 385 386 return buf; 387 } 388 else 389 { 390 return defIn.toByteArray(); 391 } 392 } 393 getBMPCharBuffer(DefiniteLengthInputStream defIn)394 private static char[] getBMPCharBuffer(DefiniteLengthInputStream defIn) 395 throws IOException 396 { 397 int len = defIn.getRemaining() / 2; 398 char[] buf = new char[len]; 399 int totalRead = 0; 400 while (totalRead < len) 401 { 402 int ch1 = defIn.read(); 403 if (ch1 < 0) 404 { 405 break; 406 } 407 int ch2 = defIn.read(); 408 if (ch2 < 0) 409 { 410 break; 411 } 412 buf[totalRead++] = (char)((ch1 << 8) | (ch2 & 0xff)); 413 } 414 415 return buf; 416 } 417 createPrimitiveDERObject( int tagNo, DefiniteLengthInputStream defIn, byte[][] tmpBuffers)418 static ASN1Primitive createPrimitiveDERObject( 419 int tagNo, 420 DefiniteLengthInputStream defIn, 421 byte[][] tmpBuffers) 422 throws IOException 423 { 424 switch (tagNo) 425 { 426 case BIT_STRING: 427 return DERBitString.fromInputStream(defIn.getRemaining(), defIn); 428 case BMP_STRING: 429 return new DERBMPString(getBMPCharBuffer(defIn)); 430 case BOOLEAN: 431 return ASN1Boolean.fromOctetString(getBuffer(defIn, tmpBuffers)); 432 case ENUMERATED: 433 return ASN1Enumerated.fromOctetString(getBuffer(defIn, tmpBuffers)); 434 case GENERALIZED_TIME: 435 return new ASN1GeneralizedTime(defIn.toByteArray()); 436 case GENERAL_STRING: 437 return new DERGeneralString(defIn.toByteArray()); 438 case IA5_STRING: 439 return new DERIA5String(defIn.toByteArray()); 440 case INTEGER: 441 return new ASN1Integer(defIn.toByteArray()); 442 case NULL: 443 return DERNull.INSTANCE; // actual content is ignored (enforce 0 length?) 444 case NUMERIC_STRING: 445 return new DERNumericString(defIn.toByteArray()); 446 case OBJECT_IDENTIFIER: 447 return ASN1ObjectIdentifier.fromOctetString(getBuffer(defIn, tmpBuffers)); 448 case OCTET_STRING: 449 return new DEROctetString(defIn.toByteArray()); 450 case PRINTABLE_STRING: 451 return new DERPrintableString(defIn.toByteArray()); 452 case T61_STRING: 453 return new DERT61String(defIn.toByteArray()); 454 case UNIVERSAL_STRING: 455 return new DERUniversalString(defIn.toByteArray()); 456 case UTC_TIME: 457 return new ASN1UTCTime(defIn.toByteArray()); 458 case UTF8_STRING: 459 return new DERUTF8String(defIn.toByteArray()); 460 case VISIBLE_STRING: 461 return new DERVisibleString(defIn.toByteArray()); 462 default: 463 throw new IOException("unknown tag " + tagNo + " encountered"); 464 } 465 } 466 } 467