mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			440 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			440 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Java
		
	
	
	
/* DERReader.java -- parses ASN.1 DER sequences
 | 
						|
   Copyright (C) 2003 Free Software Foundation, Inc.
 | 
						|
 | 
						|
This file is part of GNU Classpath.
 | 
						|
 | 
						|
GNU Classpath is free software; you can redistribute it and/or modify
 | 
						|
it under the terms of the GNU General Public License as published by
 | 
						|
the Free Software Foundation; either version 2, or (at your option)
 | 
						|
any later version.
 | 
						|
 | 
						|
GNU Classpath is distributed in the hope that it will be useful, but
 | 
						|
WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
General Public License for more details.
 | 
						|
 | 
						|
You should have received a copy of the GNU General Public License
 | 
						|
along with GNU Classpath; see the file COPYING.  If not, write to the
 | 
						|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | 
						|
02110-1301 USA.
 | 
						|
 | 
						|
Linking this library statically or dynamically with other modules is
 | 
						|
making a combined work based on this library.  Thus, the terms and
 | 
						|
conditions of the GNU General Public License cover the whole
 | 
						|
combination.
 | 
						|
 | 
						|
As a special exception, the copyright holders of this library give you
 | 
						|
permission to link this library with independent modules to produce an
 | 
						|
executable, regardless of the license terms of these independent
 | 
						|
modules, and to copy and distribute the resulting executable under
 | 
						|
terms of your choice, provided that you also meet, for each linked
 | 
						|
independent module, the terms and conditions of the license of that
 | 
						|
module.  An independent module is a module which is not derived from
 | 
						|
or based on this library.  If you modify this library, you may extend
 | 
						|
this exception to your version of the library, but you are not
 | 
						|
obligated to do so.  If you do not wish to do so, delete this
 | 
						|
exception statement from your version. */
 | 
						|
 | 
						|
 | 
						|
package gnu.java.security.der;
 | 
						|
 | 
						|
import gnu.java.lang.CPStringBuilder;
 | 
						|
 | 
						|
import gnu.java.security.OID;
 | 
						|
 | 
						|
import java.io.BufferedInputStream;
 | 
						|
import java.io.ByteArrayInputStream;
 | 
						|
import java.io.ByteArrayOutputStream;
 | 
						|
import java.io.EOFException;
 | 
						|
import java.io.IOException;
 | 
						|
import java.io.InputStream;
 | 
						|
import java.math.BigInteger;
 | 
						|
import java.util.Calendar;
 | 
						|
import java.util.Date;
 | 
						|
import java.util.TimeZone;
 | 
						|
 | 
						|
/**
 | 
						|
 * This class decodes DER sequences into Java objects. The methods of
 | 
						|
 * this class do not have knowledge of higher-levels of structure in the
 | 
						|
 * DER stream -- such as ASN.1 constructions -- and it is therefore up
 | 
						|
 * to the calling application to determine if the data are structured
 | 
						|
 * properly by inspecting the {@link DERValue} that is returned.
 | 
						|
 *
 | 
						|
 * @author Casey Marshall (csm@gnu.org)
 | 
						|
 */
 | 
						|
public class DERReader implements DER
 | 
						|
{
 | 
						|
 | 
						|
  // Fields.
 | 
						|
  // ------------------------------------------------------------------------
 | 
						|
 | 
						|
  protected InputStream in;
 | 
						|
 | 
						|
  protected final ByteArrayOutputStream encBuf;
 | 
						|
 | 
						|
  // Constructor.
 | 
						|
  // ------------------------------------------------------------------------
 | 
						|
 | 
						|
  /**
 | 
						|
   * Create a new DER reader from a byte array.
 | 
						|
   *
 | 
						|
   * @param in The encoded bytes.
 | 
						|
   */
 | 
						|
  public DERReader(byte[] in)
 | 
						|
  {
 | 
						|
    this(new ByteArrayInputStream(in));
 | 
						|
  }
 | 
						|
 | 
						|
  public DERReader (byte[] in, int off, int len)
 | 
						|
  {
 | 
						|
    this (new ByteArrayInputStream (in, off, len));
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Create a new DER readed from an input stream.
 | 
						|
   *
 | 
						|
   * @param in The encoded bytes.
 | 
						|
   */
 | 
						|
  public DERReader(InputStream in)
 | 
						|
  {
 | 
						|
    if (!in.markSupported())
 | 
						|
      this.in = new BufferedInputStream(in, 16384);
 | 
						|
    else
 | 
						|
      this.in = in;
 | 
						|
    encBuf = new ByteArrayOutputStream(2048);
 | 
						|
  }
 | 
						|
 | 
						|
  // Class methods.
 | 
						|
  // ------------------------------------------------------------------------
 | 
						|
 | 
						|
  /**
 | 
						|
   * Convenience method for reading a single primitive value from the
 | 
						|
   * given byte array.
 | 
						|
   *
 | 
						|
   * @param encoded The encoded bytes.
 | 
						|
   * @throws IOException If the bytes do not represent an encoded
 | 
						|
   * object.
 | 
						|
   */
 | 
						|
  public static DERValue read(byte[] encoded) throws IOException
 | 
						|
  {
 | 
						|
    return new DERReader(encoded).read();
 | 
						|
  }
 | 
						|
 | 
						|
  // Instance methods.
 | 
						|
  // ------------------------------------------------------------------------
 | 
						|
 | 
						|
  public void skip (int bytes) throws IOException
 | 
						|
  {
 | 
						|
    in.skip (bytes);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Decode a single value from the input stream, returning it in a new
 | 
						|
   * {@link DERValue}. By "single value" we mean any single type in its
 | 
						|
   * entirety -- including constructed types such as SEQUENCE and all
 | 
						|
   * the values they contain. Usually it is sufficient to call this
 | 
						|
   * method once to parse and return the top-level structure, then to
 | 
						|
   * inspect the returned value for the proper contents.
 | 
						|
   *
 | 
						|
   * @return The parsed DER structure.
 | 
						|
   * @throws IOException If an error occurs reading from the input
 | 
						|
   * stream.
 | 
						|
   * @throws DEREncodingException If the input does not represent a
 | 
						|
   * valid DER stream.
 | 
						|
   */
 | 
						|
  public DERValue read() throws IOException
 | 
						|
  {
 | 
						|
    int tag = in.read();
 | 
						|
    if (tag == -1)
 | 
						|
      throw new EOFException();
 | 
						|
    encBuf.write(tag);
 | 
						|
    int len = readLength();
 | 
						|
    DERValue value = null;
 | 
						|
    if ((tag & CONSTRUCTED) == CONSTRUCTED)
 | 
						|
      {
 | 
						|
        in.mark(2048);
 | 
						|
        byte[] encoded = new byte[len];
 | 
						|
        in.read(encoded);
 | 
						|
        encBuf.write(encoded);
 | 
						|
        value = new DERValue(tag, len, CONSTRUCTED_VALUE, encBuf.toByteArray());
 | 
						|
        in.reset();
 | 
						|
        encBuf.reset();
 | 
						|
        return value;
 | 
						|
      }
 | 
						|
    switch (tag & 0xC0)
 | 
						|
      {
 | 
						|
        case UNIVERSAL:
 | 
						|
          value = new DERValue(tag, len, readUniversal(tag, len),
 | 
						|
            encBuf.toByteArray());
 | 
						|
          encBuf.reset();
 | 
						|
          break;
 | 
						|
        case CONTEXT:
 | 
						|
          byte[] encoded = new byte[len];
 | 
						|
          in.read(encoded);
 | 
						|
          encBuf.write(encoded);
 | 
						|
          value = new DERValue(tag, len, encoded, encBuf.toByteArray());
 | 
						|
          encBuf.reset();
 | 
						|
          break;
 | 
						|
        case APPLICATION:
 | 
						|
          // This should not be reached, since (I think) APPLICATION is
 | 
						|
          // always constructed.
 | 
						|
          throw new DEREncodingException("non-constructed APPLICATION data");
 | 
						|
        default:
 | 
						|
          throw new DEREncodingException("PRIVATE class not supported");
 | 
						|
      }
 | 
						|
    return value;
 | 
						|
  }
 | 
						|
 | 
						|
  protected int readLength() throws IOException
 | 
						|
  {
 | 
						|
    int i = in.read();
 | 
						|
    if (i == -1)
 | 
						|
      throw new EOFException();
 | 
						|
    encBuf.write(i);
 | 
						|
    if ((i & ~0x7F) == 0)
 | 
						|
      {
 | 
						|
        return i;
 | 
						|
      }
 | 
						|
    else if (i < 0xFF)
 | 
						|
      {
 | 
						|
        byte[] octets = new byte[i & 0x7F];
 | 
						|
        in.read(octets);
 | 
						|
        encBuf.write(octets);
 | 
						|
        return new BigInteger(1, octets).intValue();
 | 
						|
      }
 | 
						|
    throw new DEREncodingException();
 | 
						|
  }
 | 
						|
 | 
						|
  // Own methods.
 | 
						|
  // ------------------------------------------------------------------------
 | 
						|
 | 
						|
  private Object readUniversal(int tag, int len) throws IOException
 | 
						|
  {
 | 
						|
    byte[] value = new byte[len];
 | 
						|
    in.read(value);
 | 
						|
    encBuf.write(value);
 | 
						|
    switch (tag & 0x1F)
 | 
						|
      {
 | 
						|
        case BOOLEAN:
 | 
						|
          if (value.length != 1)
 | 
						|
            throw new DEREncodingException();
 | 
						|
          return Boolean.valueOf(value[0] != 0);
 | 
						|
        case NULL:
 | 
						|
          if (len != 0)
 | 
						|
            throw new DEREncodingException();
 | 
						|
          return null;
 | 
						|
        case INTEGER:
 | 
						|
        case ENUMERATED:
 | 
						|
          return new BigInteger(value);
 | 
						|
        case BIT_STRING:
 | 
						|
          byte[] bits = new byte[len - 1];
 | 
						|
          System.arraycopy(value, 1, bits, 0, bits.length);
 | 
						|
          return new BitString(bits, value[0] & 0xFF);
 | 
						|
        case OCTET_STRING:
 | 
						|
          return value;
 | 
						|
        case NUMERIC_STRING:
 | 
						|
        case PRINTABLE_STRING:
 | 
						|
        case T61_STRING:
 | 
						|
        case VIDEOTEX_STRING:
 | 
						|
        case IA5_STRING:
 | 
						|
        case GRAPHIC_STRING:
 | 
						|
        case ISO646_STRING:
 | 
						|
        case GENERAL_STRING:
 | 
						|
        case UNIVERSAL_STRING:
 | 
						|
        case BMP_STRING:
 | 
						|
        case UTF8_STRING:
 | 
						|
          return makeString(tag, value);
 | 
						|
        case UTC_TIME:
 | 
						|
        case GENERALIZED_TIME:
 | 
						|
          return makeTime(tag, value);
 | 
						|
        case OBJECT_IDENTIFIER:
 | 
						|
          return new OID(value);
 | 
						|
        case RELATIVE_OID:
 | 
						|
          return new OID(value, true);
 | 
						|
        default:
 | 
						|
          throw new DEREncodingException("unknown tag " + tag);
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  private static String makeString(int tag, byte[] value)
 | 
						|
    throws IOException
 | 
						|
  {
 | 
						|
    switch (tag & 0x1F)
 | 
						|
      {
 | 
						|
        case NUMERIC_STRING:
 | 
						|
        case PRINTABLE_STRING:
 | 
						|
        case T61_STRING:
 | 
						|
        case VIDEOTEX_STRING:
 | 
						|
        case IA5_STRING:
 | 
						|
        case GRAPHIC_STRING:
 | 
						|
        case ISO646_STRING:
 | 
						|
        case GENERAL_STRING:
 | 
						|
          return fromIso88591(value);
 | 
						|
 | 
						|
        case UNIVERSAL_STRING:
 | 
						|
          // XXX The docs say UniversalString is encoded in four bytes
 | 
						|
          // per character, but Java has no support (yet) for UTF-32.
 | 
						|
          //return new String(buf, "UTF-32");
 | 
						|
        case BMP_STRING:
 | 
						|
          return fromUtf16Be(value);
 | 
						|
 | 
						|
        case UTF8_STRING:
 | 
						|
          return fromUtf8(value);
 | 
						|
 | 
						|
        default:
 | 
						|
          throw new DEREncodingException("unknown string tag");
 | 
						|
      }
 | 
						|
  }
 | 
						|
 | 
						|
  private static String fromIso88591(byte[] bytes)
 | 
						|
  {
 | 
						|
    CPStringBuilder str = new CPStringBuilder(bytes.length);
 | 
						|
    for (int i = 0; i < bytes.length; i++)
 | 
						|
      str.append((char) (bytes[i] & 0xFF));
 | 
						|
    return str.toString();
 | 
						|
  }
 | 
						|
 | 
						|
  private static String fromUtf16Be(byte[] bytes) throws IOException
 | 
						|
  {
 | 
						|
    if ((bytes.length & 0x01) != 0)
 | 
						|
      throw new IOException("UTF-16 bytes are odd in length");
 | 
						|
    CPStringBuilder str = new CPStringBuilder(bytes.length / 2);
 | 
						|
    for (int i = 0; i < bytes.length; i += 2)
 | 
						|
      {
 | 
						|
        char c = (char) ((bytes[i] << 8) & 0xFF);
 | 
						|
        c |= (char) (bytes[i+1] & 0xFF);
 | 
						|
        str.append(c);
 | 
						|
      }
 | 
						|
    return str.toString();
 | 
						|
  }
 | 
						|
 | 
						|
  private static String fromUtf8(byte[] bytes) throws IOException
 | 
						|
  {
 | 
						|
    CPStringBuilder str = new CPStringBuilder((int)(bytes.length / 1.5));
 | 
						|
    for (int i = 0; i < bytes.length; )
 | 
						|
      {
 | 
						|
        char c = 0;
 | 
						|
        if ((bytes[i] & 0xE0) == 0xE0)
 | 
						|
          {
 | 
						|
            if ((i + 2) >= bytes.length)
 | 
						|
              throw new IOException("short UTF-8 input");
 | 
						|
            c = (char) ((bytes[i++] & 0x0F) << 12);
 | 
						|
            if ((bytes[i] & 0x80) != 0x80)
 | 
						|
              throw new IOException("malformed UTF-8 input");
 | 
						|
            c |= (char) ((bytes[i++] & 0x3F) << 6);
 | 
						|
            if ((bytes[i] & 0x80) != 0x80)
 | 
						|
              throw new IOException("malformed UTF-8 input");
 | 
						|
            c |= (char) (bytes[i++] & 0x3F);
 | 
						|
          }
 | 
						|
        else if ((bytes[i] & 0xC0) == 0xC0)
 | 
						|
          {
 | 
						|
            if ((i + 1) >= bytes.length)
 | 
						|
              throw new IOException("short input");
 | 
						|
            c = (char) ((bytes[i++] & 0x1F) << 6);
 | 
						|
            if ((bytes[i] & 0x80) != 0x80)
 | 
						|
              throw new IOException("malformed UTF-8 input");
 | 
						|
            c |= (char) (bytes[i++] & 0x3F);
 | 
						|
          }
 | 
						|
        else if ((bytes[i] & 0xFF) < 0x80)
 | 
						|
          {
 | 
						|
            c = (char) (bytes[i++] & 0xFF);
 | 
						|
          }
 | 
						|
        else
 | 
						|
          throw new IOException("badly formed UTF-8 sequence");
 | 
						|
        str.append(c);
 | 
						|
      }
 | 
						|
    return str.toString();
 | 
						|
  }
 | 
						|
 | 
						|
  private Date makeTime(int tag, byte[] value) throws IOException
 | 
						|
  {
 | 
						|
    Calendar calendar = Calendar.getInstance();
 | 
						|
    String str = makeString(PRINTABLE_STRING, value);
 | 
						|
 | 
						|
    // Classpath's SimpleDateFormat does not work for parsing these
 | 
						|
    // types of times, so we do this by hand.
 | 
						|
    String date = str;
 | 
						|
    String tz = "";
 | 
						|
    if (str.indexOf("+") > 0)
 | 
						|
      {
 | 
						|
        date = str.substring(0, str.indexOf("+"));
 | 
						|
        tz = str.substring(str.indexOf("+"));
 | 
						|
      }
 | 
						|
    else if (str.indexOf("-") > 0)
 | 
						|
      {
 | 
						|
        date = str.substring(0, str.indexOf("-"));
 | 
						|
        tz = str.substring(str.indexOf("-"));
 | 
						|
      }
 | 
						|
    else if (str.endsWith("Z"))
 | 
						|
      {
 | 
						|
        date = str.substring(0, str.length()-2);
 | 
						|
        tz = "Z";
 | 
						|
      }
 | 
						|
    if (!tz.equals("Z") && tz.length() > 0)
 | 
						|
      calendar.setTimeZone(TimeZone.getTimeZone(tz));
 | 
						|
    else
 | 
						|
      calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
 | 
						|
    if ((tag & 0x1F) == UTC_TIME)
 | 
						|
      {
 | 
						|
        if (date.length() < 10)  // must be at least 10 chars long
 | 
						|
          throw new DEREncodingException("cannot parse date");
 | 
						|
        // UTCTime is of the form "yyMMddHHmm[ss](Z|(+|-)hhmm)"
 | 
						|
        try
 | 
						|
          {
 | 
						|
            int year = Integer.parseInt(str.substring(0, 2));
 | 
						|
            if (year < 50)
 | 
						|
              year += 2000;
 | 
						|
            else
 | 
						|
              year += 1900;
 | 
						|
            calendar.set(year,
 | 
						|
              Integer.parseInt(str.substring( 2,  4))-1,  // month
 | 
						|
              Integer.parseInt(str.substring( 4,  6)),    // day
 | 
						|
              Integer.parseInt(str.substring( 6,  8)),    // hour
 | 
						|
              Integer.parseInt(str.substring( 8, 10)));   // minute
 | 
						|
            if (date.length() == 12)
 | 
						|
              calendar.set(Calendar.SECOND,
 | 
						|
                Integer.parseInt(str.substring(10, 12)));
 | 
						|
          }
 | 
						|
        catch (NumberFormatException nfe)
 | 
						|
          {
 | 
						|
            throw new DEREncodingException("cannot parse date");
 | 
						|
          }
 | 
						|
      }
 | 
						|
    else
 | 
						|
      {
 | 
						|
        if (date.length() < 10)  // must be at least 10 chars long
 | 
						|
          throw new DEREncodingException("cannot parse date");
 | 
						|
        // GeneralTime is of the form "yyyyMMddHH[mm[ss[(.|,)SSSS]]]"
 | 
						|
        // followed by "Z" or "(+|-)hh[mm]"
 | 
						|
        try
 | 
						|
          {
 | 
						|
            calendar.set(
 | 
						|
              Integer.parseInt(date.substring(0, 4)),      // year
 | 
						|
              Integer.parseInt(date.substring(4, 6))-1,    // month
 | 
						|
              Integer.parseInt(date.substring(6, 8)),      // day
 | 
						|
              Integer.parseInt(date.substring(8, 10)), 0); // hour, min
 | 
						|
            switch (date.length())
 | 
						|
              {
 | 
						|
                case 19:
 | 
						|
                case 18:
 | 
						|
                case 17:
 | 
						|
                case 16:
 | 
						|
                  calendar.set(Calendar.MILLISECOND,
 | 
						|
                    Integer.parseInt(date.substring(15)));
 | 
						|
                case 14:
 | 
						|
                  calendar.set(Calendar.SECOND,
 | 
						|
                    Integer.parseInt(date.substring(12, 14)));
 | 
						|
                case 12:
 | 
						|
                  calendar.set(Calendar.MINUTE,
 | 
						|
                    Integer.parseInt(date.substring(10, 12)));
 | 
						|
              }
 | 
						|
          }
 | 
						|
        catch (NumberFormatException nfe)
 | 
						|
          {
 | 
						|
            throw new DEREncodingException("cannot parse date");
 | 
						|
          }
 | 
						|
      }
 | 
						|
    return calendar.getTime();
 | 
						|
  }
 | 
						|
}
 |