org.apache.sshd.common.util.io.der.ASN1Object Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sshd.common.util.io.der;
import java.io.EOFException;
import java.io.IOException;
import java.io.Serializable;
import java.io.StreamCorruptedException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.buffer.BufferUtils;
/**
* @author Apache MINA SSHD Project
*/
public class ASN1Object implements Serializable, Cloneable {
// Constructed Flag
public static final byte CONSTRUCTED = 0x20;
private static final long serialVersionUID = 4687581744706127265L;
private ASN1Class objClass;
private ASN1Type objType;
private boolean constructed;
private int length;
private byte[] value;
public ASN1Object() {
super();
}
/*
* The first byte in DER encoding is made of following fields
* ------------------------------------------------- |Bit 8|Bit 7|Bit 6|Bit 5|Bit 4|Bit 3|Bit 2|Bit 1|
* ------------------------------------------------- | Class | CF | Type |
* -------------------------------------------------
*/
public ASN1Object(byte tag, int len, byte... data) {
this(ASN1Class.fromDERValue(tag), ASN1Type.fromDERValue(tag), (tag & CONSTRUCTED) == CONSTRUCTED, len, data);
}
public ASN1Object(ASN1Class c, ASN1Type t, boolean ctored, int len, byte... data) {
objClass = c;
objType = t;
constructed = ctored;
length = len;
value = data;
}
public ASN1Class getObjClass() {
return objClass;
}
public void setObjClass(ASN1Class c) {
objClass = c;
}
public ASN1Type getObjType() {
return objType;
}
public void setObjType(ASN1Type y) {
objType = y;
}
public boolean isConstructed() {
return constructed;
}
public void setConstructed(boolean c) {
constructed = c;
}
public int getLength() {
return length;
}
public void setLength(int l) {
length = l;
}
public byte[] getValue() {
return value;
}
// if length is less than value.length then returns copy of it
public byte[] getPureValueBytes() {
byte[] bytes = getValue();
int available = getLength();
int numBytes = NumberUtils.length(bytes);
if (numBytes == available) {
return bytes;
}
if (available == 0) {
return GenericUtils.EMPTY_BYTE_ARRAY;
}
byte[] pure = new byte[available];
System.arraycopy(bytes, 0, pure, 0, available);
return pure;
}
public void setValue(byte[] v) {
value = v;
}
public DERParser createParser() {
return new DERParser(getValue(), 0, getLength());
}
public Object asObject() throws IOException {
ASN1Type type = getObjType();
if (type == null) {
throw new IOException("No type set");
}
switch (type) {
case INTEGER:
return asInteger();
case NUMERIC_STRING:
case PRINTABLE_STRING:
case VIDEOTEX_STRING:
case IA5_STRING:
case GRAPHIC_STRING:
case ISO646_STRING:
case GENERAL_STRING:
case BMP_STRING:
case UTF8_STRING:
return asString();
case OBJECT_IDENTIFIER:
return asOID();
case SEQUENCE:
return getValue();
default:
throw new IOException("Invalid DER: unsupported type: " + type);
}
}
/**
* Get the value as {@link BigInteger}
*
* @return BigInteger
* @throws IOException if type not an {@link ASN1Type#INTEGER}
*/
public BigInteger asInteger() throws IOException {
ASN1Type typeValue = getObjType();
if (ASN1Type.INTEGER.equals(typeValue)) {
return toInteger();
} else {
throw new IOException("Invalid DER: object is not integer: " + typeValue);
}
}
// does not check if this is an integer
public BigInteger toInteger() {
return new BigInteger(getPureValueBytes());
}
/**
* Get value as string. Most strings are treated as Latin-1.
*
* @return Java string
* @throws IOException if
*/
public String asString() throws IOException {
ASN1Type type = getObjType();
if (type == null) {
throw new IOException("No type set");
}
final String encoding;
switch (type) {
// Not all are Latin-1 but it's the closest thing
case NUMERIC_STRING:
case PRINTABLE_STRING:
case VIDEOTEX_STRING:
case IA5_STRING:
case GRAPHIC_STRING:
case ISO646_STRING:
case GENERAL_STRING:
encoding = "ISO-8859-1";
break;
case BMP_STRING:
encoding = "UTF-16BE";
break;
case UTF8_STRING:
encoding = "UTF-8";
break;
case UNIVERSAL_STRING:
throw new IOException("Invalid DER: can't handle UCS-4 string");
default:
throw new IOException("Invalid DER: object is not a string: " + type);
}
return new String(getValue(), 0, getLength(), encoding);
}
public List asOID() throws IOException {
ASN1Type typeValue = getObjType();
if (ASN1Type.OBJECT_IDENTIFIER.equals(typeValue)) {
return toOID();
} else {
throw new StreamCorruptedException("Invalid DER: object is not an OID: " + typeValue);
}
}
// Does not check that type is OID
public List toOID() throws IOException {
int vLen = getLength();
if (vLen <= 0) {
throw new EOFException("Not enough data for an OID");
}
List oid = new ArrayList<>(vLen + 1);
byte[] bytes = getValue();
int val1 = bytes[0] & 0xFF;
oid.add(Integer.valueOf(val1 / 40));
oid.add(Integer.valueOf(val1 % 40));
for (int curPos = 1; curPos < vLen; curPos++) {
int v = bytes[curPos] & 0xFF;
if (v <= 0x7F) { // short form
oid.add(Integer.valueOf(v));
continue;
}
long curVal = v & 0x7F;
curPos++;
for (int subLen = 1;; subLen++, curPos++) {
if (curPos >= vLen) {
throw new EOFException("Incomplete OID value");
}
if (subLen > 5) { // 32 bit values can span at most 5 octets
throw new StreamCorruptedException("OID component encoding beyond 5 bytes");
}
v = bytes[curPos] & 0xFF;
curVal = ((curVal << 7) & 0xFFFFFFFF80L) | (v & 0x7FL);
if (curVal > Integer.MAX_VALUE) {
throw new StreamCorruptedException("OID value exceeds 32 bits: " + curVal);
}
if (v <= 0x7F) { // found last octet ?
break;
}
}
oid.add(Integer.valueOf((int) (curVal & 0x7FFFFFFFL)));
}
return oid;
}
@Override
public int hashCode() {
return Objects.hash(getObjClass(), getObjType())
+ Boolean.hashCode(isConstructed())
+ getLength()
+ NumberUtils.hashCode(getValue(), 0, getLength());
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
if (getClass() != obj.getClass()) {
return false;
}
ASN1Object other = (ASN1Object) obj;
return Objects.equals(this.getObjClass(), other.getObjClass())
&& Objects.equals(this.getObjType(), other.getObjType())
&& (this.isConstructed() == other.isConstructed())
&& (this.getLength() == other.getLength())
&& (NumberUtils.diffOffset(this.getValue(), 0, other.getValue(), 0, this.getLength()) < 0);
}
@Override
public ASN1Object clone() {
try {
ASN1Object cpy = getClass().cast(super.clone());
byte[] data = cpy.getValue();
if (data != null) {
cpy.setValue(data.clone());
}
return cpy;
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("Unexpected clone failure: " + e.getMessage(), e);
}
}
@Override
public String toString() {
return Objects.toString(getObjClass())
+ "/" + getObjType()
+ "/" + isConstructed()
+ "[" + getLength() + "]"
+ ": " + BufferUtils.toHex(getValue(), 0, getLength(), ':');
}
}