![JAR search and dependency download from the Maven repository](/logo.png)
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