Source for gnu.java.security.der.DERReader

   1: /* DERReader.java -- parses ASN.1 DER sequences
   2:    Copyright (C) 2003 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package gnu.java.security.der;
  40: 
  41: import gnu.java.security.OID;
  42: 
  43: import java.io.BufferedInputStream;
  44: import java.io.ByteArrayInputStream;
  45: import java.io.ByteArrayOutputStream;
  46: import java.io.EOFException;
  47: import java.io.IOException;
  48: import java.io.InputStream;
  49: import java.math.BigInteger;
  50: import java.util.Calendar;
  51: import java.util.Date;
  52: import java.util.TimeZone;
  53: 
  54: /**
  55:  * This class decodes DER sequences into Java objects. The methods of
  56:  * this class do not have knowledge of higher-levels of structure in the
  57:  * DER stream -- such as ASN.1 constructions -- and it is therefore up
  58:  * to the calling application to determine if the data are structured
  59:  * properly by inspecting the {@link DERValue} that is returned.
  60:  *
  61:  * @author Casey Marshall (csm@gnu.org)
  62:  */
  63: public class DERReader implements DER
  64: {
  65: 
  66:   // Fields.
  67:   // ------------------------------------------------------------------------
  68: 
  69:   protected InputStream in;
  70: 
  71:   protected final ByteArrayOutputStream encBuf;
  72: 
  73:   // Constructor.
  74:   // ------------------------------------------------------------------------
  75: 
  76:   /**
  77:    * Create a new DER reader from a byte array.
  78:    *
  79:    * @param in The encoded bytes.
  80:    */
  81:   public DERReader(byte[] in)
  82:   {
  83:     this(new ByteArrayInputStream(in));
  84:   }
  85: 
  86:   public DERReader (byte[] in, int off, int len)
  87:   {
  88:     this (new ByteArrayInputStream (in, off, len));
  89:   }
  90: 
  91:   /**
  92:    * Create a new DER readed from an input stream.
  93:    *
  94:    * @param in The encoded bytes.
  95:    */
  96:   public DERReader(InputStream in)
  97:   {
  98:     if (!in.markSupported())
  99:       this.in = new BufferedInputStream(in, 16384);
 100:     else
 101:       this.in = in;
 102:     encBuf = new ByteArrayOutputStream(2048);
 103:   }
 104: 
 105:   // Class methods.
 106:   // ------------------------------------------------------------------------
 107: 
 108:   /**
 109:    * Convenience method for reading a single primitive value from the
 110:    * given byte array.
 111:    *
 112:    * @param encoded The encoded bytes.
 113:    * @throws IOException If the bytes do not represent an encoded
 114:    * object.
 115:    */
 116:   public static DERValue read(byte[] encoded) throws IOException
 117:   {
 118:     return new DERReader(encoded).read();
 119:   }
 120: 
 121:   // Instance methods.
 122:   // ------------------------------------------------------------------------
 123: 
 124:   public void skip (int bytes) throws IOException
 125:   {
 126:     in.skip (bytes);
 127:   }
 128: 
 129:   /**
 130:    * Decode a single value from the input stream, returning it in a new
 131:    * {@link DERValue}. By "single value" we mean any single type in its
 132:    * entirety -- including constructed types such as SEQUENCE and all
 133:    * the values they contain. Usually it is sufficient to call this
 134:    * method once to parse and return the top-level structure, then to
 135:    * inspect the returned value for the proper contents.
 136:    *
 137:    * @return The parsed DER structure.
 138:    * @throws IOException If an error occurs reading from the input
 139:    * stream.
 140:    * @throws DEREncodingException If the input does not represent a
 141:    * valid DER stream.
 142:    */
 143:   public DERValue read() throws IOException
 144:   {
 145:     int tag = in.read();
 146:     if (tag == -1)
 147:       throw new EOFException();
 148:     encBuf.write(tag);
 149:     int len = readLength();
 150:     DERValue value = null;
 151:     if ((tag & CONSTRUCTED) == CONSTRUCTED)
 152:       {
 153:         in.mark(2048);
 154:         byte[] encoded = new byte[len];
 155:         in.read(encoded);
 156:         encBuf.write(encoded);
 157:         value = new DERValue(tag, len, CONSTRUCTED_VALUE, encBuf.toByteArray());
 158:         in.reset();
 159:         encBuf.reset();
 160:         return value;
 161:       }
 162:     switch (tag & 0xC0)
 163:       {
 164:         case UNIVERSAL:
 165:           value = new DERValue(tag, len, readUniversal(tag, len),
 166:             encBuf.toByteArray());
 167:           encBuf.reset();
 168:           break;
 169:         case CONTEXT:
 170:           byte[] encoded = new byte[len];
 171:           in.read(encoded);
 172:           encBuf.write(encoded);
 173:           value = new DERValue(tag, len, encoded, encBuf.toByteArray());
 174:           encBuf.reset();
 175:           break;
 176:         case APPLICATION:
 177:           // This should not be reached, since (I think) APPLICATION is
 178:           // always constructed.
 179:           throw new DEREncodingException("non-constructed APPLICATION data");
 180:         default:
 181:           throw new DEREncodingException("PRIVATE class not supported");
 182:       }
 183:     return value;
 184:   }
 185: 
 186:   protected int readLength() throws IOException
 187:   {
 188:     int i = in.read();
 189:     if (i == -1)
 190:       throw new EOFException();
 191:     encBuf.write(i);
 192:     if ((i & ~0x7F) == 0)
 193:       {
 194:         return i;
 195:       }
 196:     else if (i < 0xFF)
 197:       {
 198:         byte[] octets = new byte[i & 0x7F];
 199:         in.read(octets);
 200:         encBuf.write(octets);
 201:         return new BigInteger(1, octets).intValue();
 202:       }
 203:     throw new DEREncodingException();
 204:   }
 205: 
 206:   // Own methods.
 207:   // ------------------------------------------------------------------------
 208: 
 209:   private Object readUniversal(int tag, int len) throws IOException
 210:   {
 211:     byte[] value = new byte[len];
 212:     in.read(value);
 213:     encBuf.write(value);
 214:     switch (tag & 0x1F)
 215:       {
 216:         case BOOLEAN:
 217:           if (value.length != 1)
 218:             throw new DEREncodingException();
 219:           return Boolean.valueOf(value[0] != 0);
 220:         case NULL:
 221:           if (len != 0)
 222:             throw new DEREncodingException();
 223:           return null;
 224:         case INTEGER:
 225:         case ENUMERATED:
 226:           return new BigInteger(value);
 227:         case BIT_STRING:
 228:           byte[] bits = new byte[len - 1];
 229:           System.arraycopy(value, 1, bits, 0, bits.length);
 230:           return new BitString(bits, value[0] & 0xFF);
 231:         case OCTET_STRING:
 232:           return value;
 233:         case NUMERIC_STRING:
 234:         case PRINTABLE_STRING:
 235:         case T61_STRING:
 236:         case VIDEOTEX_STRING:
 237:         case IA5_STRING:
 238:         case GRAPHIC_STRING:
 239:         case ISO646_STRING:
 240:         case GENERAL_STRING:
 241:         case UNIVERSAL_STRING:
 242:         case BMP_STRING:
 243:         case UTF8_STRING:
 244:           return makeString(tag, value);
 245:         case UTC_TIME:
 246:         case GENERALIZED_TIME:
 247:           return makeTime(tag, value);
 248:         case OBJECT_IDENTIFIER:
 249:           return new OID(value);
 250:         case RELATIVE_OID:
 251:           return new OID(value, true);
 252:         default:
 253:           throw new DEREncodingException("unknown tag " + tag);
 254:       }
 255:   }
 256: 
 257:   private static String makeString(int tag, byte[] value)
 258:     throws IOException
 259:   {
 260:     switch (tag & 0x1F)
 261:       {
 262:         case NUMERIC_STRING:
 263:         case PRINTABLE_STRING:
 264:         case T61_STRING:
 265:         case VIDEOTEX_STRING:
 266:         case IA5_STRING:
 267:         case GRAPHIC_STRING:
 268:         case ISO646_STRING:
 269:         case GENERAL_STRING:
 270:           return fromIso88591(value);
 271: 
 272:         case UNIVERSAL_STRING:
 273:           // XXX The docs say UniversalString is encoded in four bytes
 274:           // per character, but Java has no support (yet) for UTF-32.
 275:           //return new String(buf, "UTF-32");
 276:         case BMP_STRING:
 277:           return fromUtf16Be(value);
 278: 
 279:         case UTF8_STRING:
 280:           return fromUtf8(value);
 281: 
 282:         default:
 283:           throw new DEREncodingException("unknown string tag");
 284:       }
 285:   }
 286: 
 287:   private static String fromIso88591(byte[] bytes)
 288:   {
 289:     StringBuffer str = new StringBuffer(bytes.length);
 290:     for (int i = 0; i < bytes.length; i++)
 291:       str.append((char) (bytes[i] & 0xFF));
 292:     return str.toString();
 293:   }
 294: 
 295:   private static String fromUtf16Be(byte[] bytes) throws IOException
 296:   {
 297:     if ((bytes.length & 0x01) != 0)
 298:       throw new IOException("UTF-16 bytes are odd in length");
 299:     StringBuffer str = new StringBuffer(bytes.length / 2);
 300:     for (int i = 0; i < bytes.length; i += 2)
 301:       {
 302:         char c = (char) ((bytes[i] << 8) & 0xFF);
 303:         c |= (char) (bytes[i+1] & 0xFF);
 304:         str.append(c);
 305:       }
 306:     return str.toString();
 307:   }
 308: 
 309:   private static String fromUtf8(byte[] bytes) throws IOException
 310:   {
 311:     StringBuffer str = new StringBuffer((int)(bytes.length / 1.5));
 312:     for (int i = 0; i < bytes.length; )
 313:       {
 314:         char c = 0;
 315:         if ((bytes[i] & 0xE0) == 0xE0)
 316:           {
 317:             if ((i + 2) >= bytes.length)
 318:               throw new IOException("short UTF-8 input");
 319:             c = (char) ((bytes[i++] & 0x0F) << 12);
 320:             if ((bytes[i] & 0x80) != 0x80)
 321:               throw new IOException("malformed UTF-8 input");
 322:             c |= (char) ((bytes[i++] & 0x3F) << 6);
 323:             if ((bytes[i] & 0x80) != 0x80)
 324:               throw new IOException("malformed UTF-8 input");
 325:             c |= (char) (bytes[i++] & 0x3F);
 326:           }
 327:         else if ((bytes[i] & 0xC0) == 0xC0)
 328:           {
 329:             if ((i + 1) >= bytes.length)
 330:               throw new IOException("short input");
 331:             c = (char) ((bytes[i++] & 0x1F) << 6);
 332:             if ((bytes[i] & 0x80) != 0x80)
 333:               throw new IOException("malformed UTF-8 input");
 334:             c |= (char) (bytes[i++] & 0x3F);
 335:           }
 336:         else if ((bytes[i] & 0xFF) < 0x80)
 337:           {
 338:             c = (char) (bytes[i++] & 0xFF);
 339:           }
 340:         else
 341:           throw new IOException("badly formed UTF-8 sequence");
 342:         str.append(c);
 343:       }
 344:     return str.toString();
 345:   }
 346: 
 347:   private Date makeTime(int tag, byte[] value) throws IOException
 348:   {
 349:     Calendar calendar = Calendar.getInstance();
 350:     String str = makeString(PRINTABLE_STRING, value);
 351: 
 352:     // Classpath's SimpleDateFormat does not work for parsing these
 353:     // types of times, so we do this by hand.
 354:     String date = str;
 355:     String tz = "";
 356:     if (str.indexOf("+") > 0)
 357:       {
 358:         date = str.substring(0, str.indexOf("+"));
 359:         tz = str.substring(str.indexOf("+"));
 360:       }
 361:     else if (str.indexOf("-") > 0)
 362:       {
 363:         date = str.substring(0, str.indexOf("-"));
 364:         tz = str.substring(str.indexOf("-"));
 365:       }
 366:     else if (str.endsWith("Z"))
 367:       {
 368:         date = str.substring(0, str.length()-2);
 369:         tz = "Z";
 370:       }
 371:     if (!tz.equals("Z") && tz.length() > 0)
 372:       calendar.setTimeZone(TimeZone.getTimeZone(tz));
 373:     else
 374:       calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
 375:     if ((tag & 0x1F) == UTC_TIME)
 376:       {
 377:         if (date.length() < 10)  // must be at least 10 chars long
 378:           throw new DEREncodingException("cannot parse date");
 379:         // UTCTime is of the form "yyMMddHHmm[ss](Z|(+|-)hhmm)"
 380:         try
 381:           {
 382:             int year = Integer.parseInt(str.substring(0, 2));
 383:             if (year < 50)
 384:               year += 2000;
 385:             else
 386:               year += 1900;
 387:             calendar.set(year,
 388:               Integer.parseInt(str.substring( 2,  4))-1,  // month
 389:               Integer.parseInt(str.substring( 4,  6)),    // day
 390:               Integer.parseInt(str.substring( 6,  8)),    // hour
 391:               Integer.parseInt(str.substring( 8, 10)));   // minute
 392:             if (date.length() == 12)
 393:               calendar.set(Calendar.SECOND,
 394:                 Integer.parseInt(str.substring(10, 12)));
 395:           }
 396:         catch (NumberFormatException nfe)
 397:           {
 398:             throw new DEREncodingException("cannot parse date");
 399:           }
 400:       }
 401:     else
 402:       {
 403:         if (date.length() < 10)  // must be at least 10 chars long
 404:           throw new DEREncodingException("cannot parse date");
 405:         // GeneralTime is of the form "yyyyMMddHH[mm[ss[(.|,)SSSS]]]"
 406:         // followed by "Z" or "(+|-)hh[mm]"
 407:         try
 408:           {
 409:             calendar.set(
 410:               Integer.parseInt(date.substring(0, 4)),      // year
 411:               Integer.parseInt(date.substring(4, 6))-1,    // month
 412:               Integer.parseInt(date.substring(6, 8)),      // day
 413:               Integer.parseInt(date.substring(8, 10)), 0); // hour, min
 414:             switch (date.length())
 415:               {
 416:                 case 19:
 417:                 case 18:
 418:                 case 17:
 419:                 case 16:
 420:                   calendar.set(Calendar.MILLISECOND,
 421:                     Integer.parseInt(date.substring(15)));
 422:                 case 14:
 423:                   calendar.set(Calendar.SECOND,
 424:                     Integer.parseInt(date.substring(12, 14)));
 425:                 case 12:
 426:                   calendar.set(Calendar.MINUTE,
 427:                     Integer.parseInt(date.substring(10, 12)));
 428:               }
 429:           }
 430:         catch (NumberFormatException nfe)
 431:           {
 432:             throw new DEREncodingException("cannot parse date");
 433:           }
 434:       }
 435:     return calendar.getTime();
 436:   }
 437: }