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