All Downloads are FREE. Search and download functionalities are using the official Maven repository.

common.crypto.DER Maven / Gradle / Ivy

package com.unbound.common.crypto;

import com.unbound.common.HEX;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Stack;
import java.util.TimeZone;

public final class DER
{
  private DER() {}

  public final static byte TAG_BOOLEAN = 0x01;
  public final static byte TAG_INTEGER = 0x02;
  public final static byte TAG_BIT_STRING = 0x03;
  public final static byte TAG_OCTET_STRING = 0x04;
  public final static byte TAG_NULL = 0x05;
  public final static byte TAG_OID = 0x06;
  public final static byte TAG_EXTERNAL = 0x08;
  public final static byte TAG_ENUMERATED = 0x0a;
  public final static byte TAG_SEQUENCE = 0x30;
  public final static byte TAG_SET = 0x31;

  public final static byte TAG_UTF8_STRING = 0x0c; // 0
  public final static byte TAG_NUMERIC_STRING = 0x12; // 1
  public final static byte TAG_PRINTABLE_STRING = 0x13; // 1
  public final static byte TAG_T61_STRING = 0x14; // 1
  public final static byte TAG_VIDEOTEX_STRING = 0x15;
  public final static byte TAG_IA5_STRING = 0x16; // 1
  public final static byte TAG_UTC_TIME = 0x17; // 1
  public final static byte TAG_GENERALIZED_TIME = 0x18; // 1
  public final static byte TAG_GRAPHIC_STRING = 0x19;
  public final static byte TAG_VISIBLE_STRING = 0x1a; // 1
  public final static byte TAG_GENERAL_STRING = 0x1b;
  public final static byte TAG_UNIVERSAL_STRING = 0x1c; // 4
  public final static byte TAG_BMP_STRING = 0x1e; // 2

  private static void checkLength(boolean ok)
  {
    if (!ok) throw new IllegalArgumentException("DER length error");
  }

  private static void checkTag(byte expected, byte tag)
  {
    if (expected!=tag) throw new IllegalArgumentException("Unexpected DER tag " + HEX.toString(tag));
  }

  private static int lengthOfInt(int length)
  {
    if (length <= 0x7f) return 1;
    if (length <= 0x7fff) return 2;
    if (length <= 0x7fffff) return 3;
    if (length > 0) return 4;
    return 5;
  }

  private static int lengthOfLength(int length)
  {
    if (length < 0x7f) return 1;
    if (length <= 0xff) return 2;
    if (length <= 0xffff) return 3;
    if (length <= 0xffffff) return 4;
    return 5;
  }

  public final static class Parser
  {
    private byte[] bytes;
    private int offset;
    private Stack stack = new Stack<>();
    private int blockEnd = 0;

    public Parser(byte[] bytes)
    {
      this.bytes = bytes;
      blockEnd = bytes.length;
    }

    public boolean isTag(byte expected)
    {
      if (offset >= blockEnd) return false;
      return bytes[offset]==expected;
    }

    private byte getTag()
    {
      checkLength(offset < blockEnd);
      return bytes[offset];
    }

    private int parseLength()
    {
      checkLength(offset < blockEnd);
      int length = bytes[offset++] & 0xff;
      if (length>=0x80)
      {
        int l = length & 0x7f;
        checkLength(l <= 4);
        checkLength(offset + l <= blockEnd);
        length = 0;
        for (int i = 0; i < l; i++) length = (length << 8) | (bytes[offset++] & 0xff);
      }
      checkLength(offset + length <= blockEnd);
      return length;
    }

    private int parseTag(byte tag)
    {
      checkLength(offset+2 <= blockEnd);
      byte t = bytes[offset++];
      if (tag!=0) checkTag(tag, t);
      return parseLength();
    }

    public void begin(byte tag)
    {
      int length = parseTag(tag);
      stack.push(blockEnd);
      blockEnd = offset + length;
    }

    public void beginSequence()
    {
      begin(TAG_SEQUENCE);
    }
    public void beginOctetString() { begin(TAG_OCTET_STRING); }

    public void beginSet()
    {
      begin(TAG_SET);
    }

    public void end()
    {
      checkLength(offset==blockEnd);
      blockEnd = stack.pop();
    }

    public boolean endOfBlock()
    {
      return offset == blockEnd;
    }

    public BigInteger getBigInteger()
    {
      int length = parseTag(TAG_INTEGER);
      offset += length;
      return new BigInteger(Arrays.copyOfRange(bytes, offset - length, offset));
    }

    public void skipNull()
    {
      parseTag(TAG_NULL);
    }

    public byte[] getFullTag()
    {
      int start = offset;
      int length = parseTag((byte)0);
      offset += length;
      return Arrays.copyOfRange(bytes, start, offset);
    }

    public byte[] getBitString()
    {
      int length = parseTag(TAG_BIT_STRING);
      offset += length;
      return Arrays.copyOfRange(bytes, offset - length+1, offset);
    }

    public byte[] getTagBytes(byte tag)
    {
      int length = parseTag(tag);
      offset += length;
      return Arrays.copyOfRange(bytes, offset - length, offset);
    }

    public void checkOid(String oid)
    {
      String s = getOid();
      if (!oid.equalsIgnoreCase(s)) throw new IllegalArgumentException("Unexpected OID " + s + " instead of " + oid);
    }

    public String getOid()
    {
      int length = parseTag(TAG_OID);
      int end = offset + length;

      int item = bytes[offset++] & 0xff;
      StringBuilder sb = new StringBuilder();
      sb.append(item / 40);
      sb.append('.');
      sb.append(item % 40);

      while (offset < end)
      {
        sb.append('.');
        long value = 0;
        for (;;)
        {
          item  = bytes[offset++] & 0xff;
          value = (value << 7) + (item & 0x7f);
          if ((item & 0x80)==0) break;
        }
        sb.append(value);
      }
      return sb.toString();
    }

    public String getString(byte tag)
    {
      int length = parseTag(tag);
      offset += length;
      return new String(bytes, offset-length, length, StandardCharsets.UTF_8);
    }

    public String getString()
    {
      byte tag = getTag();
      Charset charset = null;
      switch (tag)
      {
        case TAG_NUMERIC_STRING:
        case TAG_PRINTABLE_STRING:
        case TAG_IA5_STRING:
        case TAG_UTC_TIME:
        case TAG_GENERALIZED_TIME:
        case TAG_VISIBLE_STRING:
        case TAG_T61_STRING: charset = StandardCharsets.US_ASCII; break;
        case TAG_BMP_STRING: charset = StandardCharsets.ISO_8859_1; break;
        case TAG_UNIVERSAL_STRING: charset = Charset.forName("UTF-32"); break;
        case TAG_UTF8_STRING: charset = StandardCharsets.UTF_8; break;
        default: checkTag(TAG_UTF8_STRING, tag);
      }
      int length = parseTag(tag);
      offset += length;
      return new String(bytes, offset-length, length, charset);
    }
  }

  public final static class Builder
  {
    private byte[] bytes = new byte[128];
    private int length = 0;
    private Stack stack = new Stack<>();

    public Builder begin(byte tag)
    {
      int padding = (tag == TAG_BIT_STRING) ? 1 : 0;
      ensureCapacity(length + 2 + padding);
      stack.push(length);
      bytes[length++] = tag;
      length += 1 + padding; // block length place holder
      return this;
    }

    public byte[] toByteArray()
    {
      if (length == bytes.length) return bytes;
      return Arrays.copyOfRange(bytes, 0, length);
    }

    private void ensureCapacity(int capacity)
    {
      if (capacity <= bytes.length) return;
      if (capacity < bytes.length * 2) capacity = bytes.length * 2;
      byte[] newBytes = new byte[capacity];
      System.arraycopy(bytes, 0, newBytes, 0, length);
      bytes = newBytes;
    }

    private void encodeLength(int offset, int length)
    {
      if (length < 0x80) bytes[offset] = (byte) length;
      else
      {
        int l = lengthOfLength(length)-1;
        bytes[offset++] = (byte) (0x80 | l);
        offset += l;
        while (length != 0)
        {
          bytes[--offset] = (byte) length;
          length >>>= 8;
        }
      }
    }

    private void encodeInt(int offset, int value)
    {
      if (value < 0) value = 0;
      int l = lengthOfInt(value);
      offset += l;
      while (value != 0)
      {
        bytes[--offset] = (byte) value;
        value >>>= 8;
      }
    }

    public Builder add(byte tag, byte[] data)
    {
      return add(tag, data, 0, data==null ? 0 : data.length);
    }

    public Builder add(byte[] data)
    {
      ensureCapacity(length + data.length);
      System.arraycopy(data, 0, bytes, length, data.length);
      length += data.length;
      return this;
    }

    private Builder add(byte tag, byte[] in, int inOffset, int inLength)
    {
      int padding = (tag == TAG_BIT_STRING) ? 1 : 0;
      int l = lengthOfLength(padding + inLength);
      ensureCapacity(length + 1 + l + padding + inLength);
      bytes[length++] = tag;
      encodeLength(length, padding + inLength); length += l;
      if (padding!=0) bytes[length++] = 0;
      if (inLength>0) System.arraycopy(in, inOffset, bytes, length, inLength); length += inLength;
      return this;
    }

    public Builder end()
    {
      int offset = stack.pop();
      offset++; // tag
      int blockLength = length - offset - 1;
      int l = lengthOfLength(blockLength);
      if (l == 1)
      {
        bytes[offset] = (byte) blockLength;
      }
      else
      {
        ensureCapacity(length + l - 1);
        System.arraycopy(bytes, offset + 1, bytes, offset + l, blockLength);
        encodeLength(offset, blockLength);
        length += l - 1;
      }
      return this;
    }

    public Builder beginSet() { return begin(TAG_SET); }
    public Builder beginSequence() { return begin(TAG_SEQUENCE); }
    public Builder beginOctetString() { return begin(TAG_OCTET_STRING); }
    public Builder beginBitString() { return begin(TAG_BIT_STRING); }
    public Builder addBitString(byte[] in)
    {
      return add(TAG_BIT_STRING, in);
    }

    public Builder addNull()
    {
      return add(TAG_NULL, (byte[]) null);
    }

    public Builder add(BigInteger bn)
    {
      return add(TAG_INTEGER, bn.toByteArray());
    }
    public Builder addInteger(long value)
    {
      return add(TAG_INTEGER, BigInteger.valueOf(value).toByteArray());
    }

    public Builder add(byte tag, String value)
    {
      Charset charset = StandardCharsets.UTF_8;
      switch (tag)
      {
        case TAG_NUMERIC_STRING:
        case TAG_PRINTABLE_STRING:
        case TAG_IA5_STRING:
        case TAG_UTC_TIME:
        case TAG_GENERALIZED_TIME:
        case TAG_VISIBLE_STRING:
        case TAG_T61_STRING: charset = StandardCharsets.US_ASCII; break;
        case TAG_BMP_STRING: charset = StandardCharsets.ISO_8859_1; break;
        case TAG_UNIVERSAL_STRING: charset = Charset.forName("UTF-32"); break;
        case TAG_UTF8_STRING: charset = StandardCharsets.UTF_8; break;
        default: charset = StandardCharsets.US_ASCII;
      }
      return add(tag, value.getBytes(charset));
    }

    public Builder addTime(long time)
    {
      Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
      calendar.setTimeInMillis(time*1000);

      byte[] buf = new byte[15];
      int offset = 0;

      int x = calendar.get(Calendar.YEAR);
      buf[offset++] = (byte)('0' + x / 1000);  x %= 1000;
      buf[offset++] = (byte)('0' + x / 100);   x %= 100;
      buf[offset++] = (byte)('0' + x / 10);    x %= 10;
      buf[offset++] = (byte)('0' + x);

      x = calendar.get(Calendar.MONTH)+1;       buf[offset++] = (byte)('0' + x/10);  buf[offset++] = (byte)('0' + x%10);
      x = calendar.get(Calendar.DAY_OF_MONTH);  buf[offset++] = (byte)('0' + x/10);  buf[offset++] = (byte)('0' + x%10);
      x = calendar.get(Calendar.HOUR_OF_DAY);   buf[offset++] = (byte)('0' + x/10);  buf[offset++] = (byte)('0' + x%10);
      x = calendar.get(Calendar.MINUTE);        buf[offset++] = (byte)('0' + x/10);  buf[offset++] = (byte)('0' + x%10);
      x = calendar.get(Calendar.SECOND);        buf[offset++] = (byte)('0' + x/10);  buf[offset++] = (byte)('0' + x%10);

      buf[offset++] = 'Z';
      return add(TAG_GENERALIZED_TIME, buf);
    }

    public Builder add(String value)
    {
      return add(TAG_UTF8_STRING, value);
    }

    public Builder addOid(String oid)
    {
      ensureCapacity(length + oid.length());
      bytes[length++] = TAG_OID;
      length++; // length place holder
      int oldLength = length;
      int value = 0;
      int index = 0;
      int firstValue = 0;
      for (int i=0; i<=oid.length(); i++)
      {
        char c = i==oid.length() ? '.' : oid.charAt(i);
        if (c>='0' && c<='9')
        {
          value = value*10 + c - '0';
        }
        else if (c=='.')
        {
          if (value>=128)
          {
            int v1 = (value >> 21) & 0x7f; if (v1!=0) bytes[length++] = (byte)(v1 | 0x80);
            int v2 = (value >> 14) & 0x7f; if (v1!=0 || v2!=0) bytes[length++] = (byte)(v2 | 0x80);
            int v3 = (value >>  7) & 0x7f; if (v1!=0 || v2!=0 || v3!=0) bytes[length++] = (byte)(v3 | 0x80);
            value &= 0x7f;
          }
          if (index==0) firstValue = value;
          else if (index==1) bytes[length++] = (byte)(firstValue*40 + value);
          else bytes[length++] = (byte)value;
          value = 0;
          index++;
        }
        else throw new IllegalArgumentException("Invalid OID " + oid);
      }
      bytes[oldLength-1] = (byte)(length-oldLength);
      return this;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy